irahardianto / php-idioms
Modern PHP 8.x idioms: strict types, typed properties, enums, readonly classes, match expressions, and PSR standards. Use when writing, refactoring, or reviewing pure PHP code, domain services, or libraries. Pair with laravel-idioms for Laravel applications.
PHP Idioms and Patterns
Modern PHP (8.x) rewards type safety, immutability, and framework-agnostic design. Lean into typed properties, enums, and readonly classes. Idiomatic PHP = strict types, PSR-compliant, well-tested.
Scope: PHP coding idioms. Test naming: .agents/rules/testing-strategy.md. Logging:
@.agents/skills/logging-implementation/SKILL.md.
Modern PHP Features (8.x)
declare(strict_types=1)— always, every file.Enums for domain constants:
`php
enum Priority: string {
case Low = 'low';
case Medium = 'medium';
case High = 'high';
}`Readonly classes for immutable DTOs:
`php
readonly class CreateTaskRequest {
public function __construct(
public string $title,
public Priority $priority,
) {}
}`Named arguments for clarity:
`php
$task = new Task(title: 'Deploy fix', priority: Priority::High);`Match expressions over switch:
`php
$score = match($priority) {
Priority::Low => 1,
Priority::Medium => 5,
Priority::High => 10,
};`
Error Handling
Domain exception hierarchies:
`php
abstract class DomainException extends \RuntimeException {}class NotFoundException extends DomainException {
public function __construct(
public readonly string $resource,
public readonly string $resourceId,
) {
parent::__construct("{$resource} '{$resourceId}' not found");
}
}`- Never
catch (\Exception)without re-throw or specific handling. - Type-safe return types — never mixed without justification.
Interfaces and DI
Define interface where consumed, implement where provided:
`php
// In task feature
interface TaskStorage {
public function getById(string $id): Task;
public function save(Task $task): void;
}// Constructor injection
class TaskService {
public function __construct(
private readonly TaskStorage $storage,
) {}
}`
Naming (PSR-12)
- PascalCase for classes, interfaces, traits, enums.
- camelCase for methods, properties.
- UPPERSNAKECASE for constants.
- PSR-4 autoloading — namespace = directory path.
Testing
PHPUnit or Pest:
`php
test('calculate discount returns zero for no items', function () {
$result = $this->calculator->calculateDiscount([], $this->coupon);
expect($result)->toBe(0.0);
});`- Data providers for parameterized tests.
- Mockery or PHPUnit mocks — never test implementation details.
Formatting and Static Analysis
| Tool | Purpose | Command |
|---|---|---|
| PHP CS Fixer | PSR-12 formatting | php-cs-fixer fix . |
| PHPStan (level 9) | Static analysis | phpstan analyse src/ --level 9 |
| Psalm | Type checking | psalm --show-info=true |
composer audit | CVE scanning | composer audit |
Related
- Code Idioms and Conventions .agents/rules/code-idioms-and-conventions.md
- Testing Strategy .agents/rules/testing-strategy.md
- Error Handling Principles .agents/rules/error-handling-principles.md