Global Rank · of 601 Skills
php-modernization AI Agent Skill
View Source: dirnbauer/webconsulting-skills
SafeInstallation
npx skills add dirnbauer/webconsulting-skills --skill php-modernization 65
Installs
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+)
// ❌ 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+)
// ✅ 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+)
// ❌ 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+)
// ❌ 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+)
// ✅ Clearer and order-independent
$this->doSomething(
name: 'value',
options: ['key' => 'value'],
enabled: true,
);Null Safe Operator (PHP 8.0+)
// ❌ OLD
$country = null;
if ($user !== null && $user->getAddress() !== null) {
$country = $user->getAddress()->getCountry();
}
// ✅ NEW
$country = $user?->getAddress()?->getCountry();Union Types (PHP 8.0+)
public function process(string|int $value): string|null
{
// ...
}Intersection Types (PHP 8.1+)
public function handle(Countable&Iterator $collection): void
{
// $collection must implement both interfaces
}Attributes (PHP 8.0+)
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
// ❌ 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
declare(strict_types=1);
namespace Vendor\MyExtension\DTO;
final readonly class CreateUserDTO
{
public function __construct(
public string $name,
public string $email,
public ?string $phone = null,
) {}
public static function fromArray(array $data): self
{
return new self(
name: $data['name'] ?? throw new \InvalidArgumentException('Name required'),
email: $data['email'] ?? throw new \InvalidArgumentException('Email required'),
phone: $data['phone'] ?? null,
);
}
}Value Object
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\ValueObject;
final readonly class EmailAddress
{
private function __construct(
public string $value,
) {}
public static function fromString(string $email): self
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Invalid email address');
}
return new self($email);
}
public function equals(self $other): bool
{
return $this->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
declare(strict_types=1);
namespace Vendor\Package;
use Vendor\Package\SomeClass;
final class MyClass
{
public function __construct(
private readonly SomeClass $dependency,
) {}
public function doSomething(
string $param1,
int $param2,
): string {
return match ($param2) {
1 => $param1,
2 => $param1 . $param1,
default => '',
};
}
}Static Analysis Tools
PHPStan (Level 9+)
# 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
// .php-cs-fixer.dist.php
$config = new PhpCsFixer\Config();
return $config
->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
// rector.php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/Classes',
__DIR__ . '/Tests',
])
->withSets([
LevelSetList::UP_TO_PHP_83,
SetList::CODE_QUALITY,
SetList::TYPE_DECLARATION,
SetList::DEAD_CODE,
]);PHPat (Architecture Testing)
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Architecture;
use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;
final class ArchitectureTest
{
public function testDomainDoesNotDependOnInfrastructure(): Rule
{
return PHPat::rule()
->classes(Selector::inNamespace('Vendor\MyExtension\Domain'))
->shouldNotDependOn()
->classes(Selector::inNamespace('Vendor\MyExtension\Infrastructure'));
}
}Type Safety Patterns
Typed Arrays with PHPDoc Generics
/**
* @return array<int, User>
*/
public function getUsers(): array
{
return $this->users;
}
/**
* @param array<string, mixed> $config
*/
public function configure(array $config): void
{
// ...
}
/**
* @return \Generator<int, Item, mixed, void>
*/
public function iterateItems(): \Generator
{
foreach ($this->items as $item) {
yield $item;
}
}Strict Comparison
// ❌ Loose comparison
if ($value == '1') {}
// ✅ Strict comparison
if ($value === '1') {}
if ($value === 1) {}Early Returns
// ❌ 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);
}Appendix
For the migration checklist, tooling links, and attribution details, see references/additional-resources.md.
Credits & Attribution
This skill is based on the excellent work by
Netresearch DTT GmbH.
Original repository: https://github.com/netresearch/php-modernization-skill
Copyright (c) Netresearch DTT GmbH — Methodology and best practices (MIT / CC-BY-SA-4.0)
Adapted by webconsulting.at for this skill collection
Installs
Security Audit
Power your AI Agents with
the best open-source models.
Drop-in OpenAI-compatible API. No data leaves Europe.
Explore Inference APIGLM
GLM 5
$1.00 / $3.20
per M tokens
Kimi
Kimi K2.5
$0.60 / $2.80
per M tokens
MiniMax
MiniMax M2.5
$0.30 / $1.20
per M tokens
Qwen
Qwen3.5 122B
$0.40 / $3.00
per M tokens
How to use this skill
Install php-modernization by running npx skills add dirnbauer/webconsulting-skills --skill php-modernization in your project directory. Run the install command above in your project directory. The skill file will be downloaded from GitHub and placed in your project.
No configuration needed. Your AI agent (Claude Code, Cursor, Windsurf, etc.) automatically detects installed skills and uses them as context when generating code.
The skill enhances your agent's understanding of php-modernization, helping it follow established patterns, avoid common mistakes, and produce production-ready output.
What you get
Skills are plain-text instruction files — not executable code. They encode expert knowledge about frameworks, languages, or tools that your AI agent reads to improve its output. This means zero runtime overhead, no dependency conflicts, and full transparency: you can read and review every instruction before installing.
Compatibility
This skill works with any AI coding agent that supports the skills.sh format, including Claude Code (Anthropic), Cursor, Windsurf, Cline, Aider, and other tools that read project-level context files. Skills are framework-agnostic at the transport level — the content inside determines which language or framework it applies to.
Chat with 100+ AI Models in one App.
Use Claude, ChatGPT, Gemini alongside with EU-Hosted Models like Deepseek, GLM-5, Kimi K2.5 and many more.