What you'll learn
Make the same artifact run predictably in development, test, staging, and production. Configuration enters at startup, secrets remain outside source, and Composer scripts standardize how the team installs and checks the application.
By the end of this lesson, you'll be able to:
- Separate code, configuration, and secrets
- Validate environment settings at startup
- Create repeatable Composer workflows
Core mental model
Professional PHP is less about memorizing APIs and more about choosing a clear boundary for each responsibility. Use this table as a decision guide while reading the examples.
| Concept | What it protects | Decision rule |
|---|---|---|
| Environment variable | Deploy-specific input | Use for values that change between deployments. |
| Bootstrap | Single construction path | Load, validate, and freeze config before handling work. |
| Dependency constraint | Compatible upgrade range | Use the narrowest range supported by tests. |
Professional workflow
Build the feature in small, verifiable steps. Each step leaves the system in a state you can test.
- Describe the application bootstrap boundary: its inputs, outputs, invariants, and expected failures.
- Implement the smallest happy path behind an explicit contract.
- Add validation and translate low-level failures into language the caller understands.
- Exercise the boundary with realistic data, then inspect output, logs, and resource cleanup.
- Refactor only after behavior is protected by a repeatable check.
Make the boundary visible
Guided code lab
Fail fast on missing configuration
A typed configuration object prevents environment reads from spreading through domain code.
<?php
final readonly class Config
{
public function __construct(
public string $environment,
public string $databaseDsn,
public bool $debug,
) {}
public static function fromEnvironment(): self
{
$dsn = $_ENV['DB_DSN'] ?? throw new RuntimeException('DB_DSN is required');
$env = $_ENV['APP_ENV'] ?? 'production';
return new self($env, $dsn, filter_var($_ENV['APP_DEBUG'] ?? false, FILTER_VALIDATE_BOOL));
}
}Standardize team commands
Composer scripts make local and CI quality checks use the same entry points.
{
"require": { "php": "^8.3", "vlucas/phpdotenv": "^5.6" },
"require-dev": { "phpunit/phpunit": "^11.0", "phpstan/phpstan": "^2.0" },
"scripts": {
"test": "phpunit --colors=always",
"analyse": "phpstan analyse --memory-limit=1G",
"check": ["@test", "@analyse"]
}
}Production practice
Contract
Read environment at the composition root and pass typed config or constructed services inward.
Verification
Boot with each environment profile and assert required, malformed, and unsafe combinations fail immediately.
Operations
Use a secret manager, rotate credentials, and expose configuration fingerprints—not values—for diagnosis.
Common failure mode
Independent workshop
Create a production-ready bootstrap for web, CLI, worker, and test entry points sharing one validated configuration model.
Your finished workshop must include:
- Typed config loader and safe example file
- Composer install/check scripts
- Environment matrix and secret-rotation note
Definition of done
Recap & quick check
Key takeaways
- Deployments vary through configuration, not source edits.
- Configuration should be validated once.
- Secrets need managed storage and rotation.
- Composer scripts create repeatable workflows.
Quick check
1. Where should environment reads happen?
2. Should .env containing secrets be committed?
3. Why commit composer.lock for an app?
Keep the workshop: later phases deliberately build on these boundaries, so today's small example can become part of your portfolio architecture.