--- name: php-modernization description: PHP 8.x modernization patterns. Use when upgrading to PHP 8.2/8.3/8.4, implementing type safety, or achieving PHPStan level 10. version: 1.0.0 triggers: - php - modernization - phpstan - rector - type safety - dto - enum --- # PHP Modernization Skill Modernize PHP applications to PHP 8.x with type safety, PSR compliance, and static analysis. ## Expertise Areas - **PHP 8.x**: Constructor promotion, readonly, enums, match, attributes, union types - **PSR/PER Compliance**: Active PHP-FIG standards - **Static Analysis**: PHPStan (level 9+), PHPat, Rector, PHP-CS-Fixer - **Type Safety**: DTOs/VOs over arrays, generics via PHPDoc ## PHP 8.x Features ### Constructor Property Promotion (PHP 8.0+) ```php // ❌ OLD class UserService { private UserRepository $userRepository; private LoggerInterface $logger; public function __construct( UserRepository $userRepository, LoggerInterface $logger ) { $this->userRepository = $userRepository; $this->logger = $logger; } } // ✅ NEW final class UserService { public function __construct( private readonly UserRepository $userRepository, private readonly LoggerInterface $logger, ) {} } ``` ### Readonly Classes (PHP 8.2+) ```php // ✅ All properties are implicitly readonly final readonly class UserDTO { public function __construct( public int $id, public string $name, public string $email, ) {} } ``` ### Enums (PHP 8.1+) ```php // ❌ OLD - String constants class Status { public const DRAFT = 'draft'; public const PUBLISHED = 'published'; public const ARCHIVED = 'archived'; } // ✅ NEW - Backed enum enum Status: string { case Draft = 'draft'; case Published = 'published'; case Archived = 'archived'; public function label(): string { return match($this) { self::Draft => 'Draft', self::Published => 'Published', self::Archived => 'Archived', }; } } // Usage public function setStatus(Status $status): void { $this->status = $status; } $item->setStatus(Status::Published); ``` ### Match Expression (PHP 8.0+) ```php // ❌ OLD - Switch switch ($type) { case 'a': $result = 'Type A'; break; case 'b': $result = 'Type B'; break; default: $result = 'Unknown'; } // ✅ NEW - Match $result = match($type) { 'a' => 'Type A', 'b' => 'Type B', default => 'Unknown', }; ``` ### Named Arguments (PHP 8.0+) ```php // ✅ Clearer and order-independent $this->doSomething( name: 'value', options: ['key' => 'value'], enabled: true, ); ``` ### Null Safe Operator (PHP 8.0+) ```php // ❌ OLD $country = null; if ($user !== null && $user->getAddress() !== null) { $country = $user->getAddress()->getCountry(); } // ✅ NEW $country = $user?->getAddress()?->getCountry(); ``` ### Union Types (PHP 8.0+) ```php public function process(string|int $value): string|null { // ... } ``` ### Intersection Types (PHP 8.1+) ```php public function handle(Countable&Iterator $collection): void { // $collection must implement both interfaces } ``` ### Attributes (PHP 8.0+) ```php use TYPO3\CMS\Core\Attribute\AsEventListener; #[AsEventListener(identifier: 'myext/my-listener')] final class MyListener { public function __invoke(SomeEvent $event): void { // Handle event } } ``` ## DTOs and Value Objects ### Never Use Arrays for Structured Data ```php // ❌ BAD - Array passing public function createUser(array $data): array { // What fields are expected? What types? } // ✅ GOOD - DTO pattern public function createUser(CreateUserDTO $dto): UserDTO { // Type-safe, documented, IDE-friendly } ``` ### Data Transfer Object ```php value === $other->value; } public function __toString(): string { return $this->value; } } ``` ## PSR/PER Compliance ### Active Standards | Standard | Purpose | Status | |----------|---------|--------| | PSR-1 | Basic Coding | Required | | PSR-4 | Autoloading | Required | | PER CS | Coding Style | Required (supersedes PSR-12) | | PSR-3 | Logger Interface | Use for logging | | PSR-6/16 | Cache | Use for caching | | PSR-7/17/18 | HTTP | Use for HTTP clients | | PSR-11 | Container | Use for DI | | PSR-14 | Events | Use for event dispatching | | PSR-15 | Middleware | Use for HTTP middleware | | PSR-20 | Clock | Use for time-dependent code | ### PER Coding Style ```php $param1, 2 => $param1 . $param1, default => '', }; } } ``` ## Static Analysis Tools ### PHPStan (Level 9+) ```neon # phpstan.neon includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/saschaegerer/phpstan-typo3/extension.neon parameters: level: 10 paths: - Classes - Tests excludePaths: - Classes/Domain/Model/* ``` **Level Guide:** - Level 0-5: Basic checks - Level 6-8: Type checking - Level 9: Strict mixed handling - Level 10: Maximum strictness (recommended) ### PHP-CS-Fixer ```php setRules([ '@PER-CS' => true, '@PER-CS:risky' => true, 'declare_strict_types' => true, 'no_unused_imports' => true, 'ordered_imports' => ['sort_algorithm' => 'alpha'], 'single_line_empty_body' => true, 'trailing_comma_in_multiline' => [ 'elements' => ['arguments', 'arrays', 'match', 'parameters'], ], ]) ->setRiskyAllowed(true) ->setFinder( PhpCsFixer\Finder::create() ->in(__DIR__ . '/Classes') ->in(__DIR__ . '/Tests') ); ``` ### Rector ```php withPaths([ __DIR__ . '/Classes', __DIR__ . '/Tests', ]) ->withSets([ LevelSetList::UP_TO_PHP_83, SetList::CODE_QUALITY, SetList::TYPE_DECLARATION, SetList::DEAD_CODE, ]); ``` ### PHPat (Architecture Testing) ```php classes(Selector::inNamespace('Vendor\MyExtension\Domain')) ->shouldNotDependOn() ->classes(Selector::inNamespace('Vendor\MyExtension\Infrastructure')); } } ``` ## Type Safety Patterns ### Typed Arrays with PHPDoc Generics ```php /** * @return array */ public function getUsers(): array { return $this->users; } /** * @param array $config */ public function configure(array $config): void { // ... } /** * @return \Generator */ public function iterateItems(): \Generator { foreach ($this->items as $item) { yield $item; } } ``` ### Strict Comparison ```php // ❌ Loose comparison if ($value == '1') {} // ✅ Strict comparison if ($value === '1') {} if ($value === 1) {} ``` ### Early Returns ```php // ❌ Nested conditions public function process(?User $user): ?Result { if ($user !== null) { if ($user->isActive()) { return $this->doProcess($user); } } return null; } // ✅ Early returns public function process(?User $user): ?Result { if ($user === null) { return null; } if (!$user->isActive()) { return null; } return $this->doProcess($user); } ``` ## Migration Checklist - [ ] `declare(strict_types=1)` in all files - [ ] PSR-4 autoloading in composer.json - [ ] PER Coding Style enforced via PHP-CS-Fixer - [ ] PHPStan level 9+ (level 10 for new projects) - [ ] All methods have return types - [ ] All parameters have type declarations - [ ] All properties have type declarations - [ ] **DTOs for data transfer**, Value Objects for domain concepts - [ ] **Enums** for fixed sets of values (not string constants) - [ ] Constructor property promotion used - [ ] `final` on classes not designed for inheritance - [ ] `readonly` on immutable classes - [ ] No `@var` annotations when type is declared - [ ] PHPat architecture tests for layer dependencies ## Resources - **PHP-FIG**: https://www.php-fig.org/ - **PHPStan**: https://phpstan.org/ - **Rector**: https://getrector.com/ - **PHP-CS-Fixer**: https://cs.symfony.com/ - **PHPat**: https://www.phpat.dev/ --- ## Credits & Attribution This skill is based on the excellent work by **[Netresearch DTT GmbH](https://www.netresearch.de/)**. Original repository: https://github.com/netresearch/php-modernization-skill **Copyright (c) Netresearch DTT GmbH** - Methodology and best practices Adapted by webconsulting.at for this skill collection