High-level services should depend on interfaces that can be implemented by databases, queues or test doubles.
What It Does and When to Use It
A high-level use case depends on an interface rather than a concrete transport. A minimum platform requirement controls what syntax can be parsed; target-version tests find runtime and extension incompatibilities.
Requirements, Inputs and Expected Behavior
Confirm the runtime, extension, environment variables, and permissions before running the examples in production. Pass interfaces through constructors instead of instantiating concrete infrastructure in methods.
Example 1: Basic Operation
A high-level use case depends on an interface rather than a concrete transport.
<?php
interface Notifier { public function send(string $message): void; }
final class EchoNotifier implements Notifier {
public function send(string $message): void { echo $message; }
}
final class RegisterUser {
public function __construct(private Notifier $notifier) {}
public function run(): void { $this->notifier->send('Welcome'); }
}
(new RegisterUser(new EchoNotifier()))->run();
Example 2: Behavior and Validation
Inject a clock to make time-dependent behavior testable.
<?php
interface Clock { public function now(): DateTimeImmutable; }
final class FixedClock implements Clock {
public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-01-01'); }
}
function expiresAt(Clock $clock): string { return $clock->now()->modify('+1 day')->format('Y-m-d'); }
echo expiresAt(new FixedClock());
Practical Example: Application Use
Use an in-memory substitute without changing application code.
<?php
interface PriceReader { public function price(int $id): int; }
final class InMemoryPrices implements PriceReader { public function price(int $id): int { return 50; } }
final class QuoteService {
public function __construct(private PriceReader $prices) {}
public function quote(int $id, int $quantity): int { return $this->prices->price($id) * $quantity; }
}
echo (new QuoteService(new InMemoryPrices()))->quote(1, 3);
Important Behavior and Edge Cases
The second example highlights: Inject a clock to make time-dependent behavior testable. The practical example adds: Use an in-memory substitute without changing application code.
Comparison with Related Approaches
| Approach | What changes |
|---|---|
| Minimum PHP version and target PHP version | A minimum platform requirement controls what syntax can be parsed; target-version tests find runtime and extension incompatibilities. |
| Choosing safely | Do not introduce syntax from a newer minor version before every production host supports it. |
Common Beginner Mistakes
| Mistake | Correct approach |
|---|---|
| Assuming newer syntax parses on older PHP | Lint with the oldest supported runtime; gate or delay newer syntax. |
| Upgrading CLI PHP but not the web worker | Check the FPM binary, all extensions and the Composer platform in staging. |
| Skipping operational constraints | Pass interfaces through constructors instead of instantiating concrete infrastructure in methods. |
Key Takeaways
- High-level services should depend on interfaces that can be implemented by databases, queues or test doubles.
- Use an in-memory substitute without changing application code.
- Do not introduce syntax from a newer minor version before every production host supports it.