SKILL.md
<purpose> Provide comprehensive patterns for modern PHP (8.1+) language features, PSR standards, testing, static analysis, and package development in a framework-agnostic approach. </purpose>
<phpversion> <versionmapping> <description>PHP version-specific feature availability</description> <version php="8.3" released="2023-11"> <feature>Typed class constants</feature> <feature>jsonvalidate() function</feature> <feature>Randomizer::getFloat() and nextFloat()</feature> <feature>Deep cloning of readonly properties</feature> <feature>Override attribute</feature> <feature>Granular DateTime exceptions</feature> </version> <version php="8.2" released="2022-12"> <feature>Readonly classes</feature> <feature>DNF types (Disjunctive Normal Form)</feature> <feature>null, false, true as standalone types</feature> <feature>Constants in traits</feature> <feature>Deprecate dynamic properties</feature> </version> <version php="8.1" released="2021-11"> <feature>Enums (backed and unit)</feature> <feature>Readonly properties</feature> <feature>Fibers</feature> <feature>Intersection types</feature> <feature>never return type</feature> <feature>First-class callable syntax</feature> <feature>New in initializers</feature> </version> <version php="8.0" released="2020-11"> <feature>Named arguments</feature> <feature>Attributes</feature> <feature>Constructor property promotion</feature> <feature>Union types</feature> <feature>Match expression</feature> <feature>Nullsafe operator</feature> <feature>mixed type</feature> </version> </versionmapping>
<recommendedconfig> <description>php.ini recommended settings for development</description> <config> errorreporting = EALL displayerrors = On logerrors = On opcache.enable = 1 opcache.validatetimestamps = 1 </config> </recommendedconfig> </phpversion>
<typesystem> <uniontypes> <pattern name="basic"> <description>Multiple types for parameter or return</description> <example> function process(string|int $value): string|null { return isstring($value) ? $value : (string) $value; } </example> </pattern> </uniontypes>
<intersectiontypes> <pattern name="basic"> <description>Value must satisfy all types (PHP 8.1+)</description> <example> function process(Countable&Iterator $collection): int { return count($collection); } </example> </pattern> </intersectiontypes>
<dnftypes> <pattern name="disjunctive-normal-form"> <description>Combine union and intersection types (PHP 8.2+)</description> <example> function handle((Countable&Iterator)|null $items): void { if ($items === null) { return; } foreach ($items as $item) { // process } } </example> </pattern> </dnftypes>
<enums> <pattern name="backed-enum"> <description>Enum with scalar backing (PHP 8.1+)</description> <example> 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 $status = Status::from('published'); $value = $status->value; // 'published' </example> </pattern>
<pattern name="unit-enum"> <description>Enum without backing value</description> <example> enum Suit { case Hearts; case Diamonds; case Clubs; case Spades;
public function color(): string { return match($this) { self::Hearts, self::Diamonds => 'red', self::Clubs, self::Spades => 'black', }; } } </example> </pattern> </enums>
<readonly> <pattern name="readonly-property"> <description>Immutable property (PHP 8.1+)</description> <example> class User { public function __construct( public readonly string $id, public readonly string $email, ) {} } </example> </pattern>
<pattern name="readonly-class"> <description>All properties become readonly (PHP 8.2+)</description> <example> readonly class ValueObject { public function __construct( public string $name, public int $value, ) {} } </example> </pattern> </readonly>
<attributes> <pattern name="custom-attribute"> <description>Define and use custom attributes (PHP 8.0+)</description> <example> #[Attribute(Attribute::TARGETMETHOD | Attribute::TARGETFUNCTION)] class Route { public function __construct( public string $path, public string $method = 'GET', ) {} }
class UserController { #[Route('/users', 'GET')] public function index(): array { return []; } }
// Reading attributes via reflection $method = new ReflectionMethod(UserController::class, 'index'); $attributes = $method->getAttributes(Route::class); foreach ($attributes as $attribute) { $route = $attribute->newInstance(); echo $route->path; // '/users' } </example> </pattern> </attributes>
<constructor_promotion> <pattern name="basic"> <description>Declare and assign properties in constructor (PHP 8.0+)</description> <example> class Product { public function __construct( private string $name, private float $price, private int $quantity = 0, ) {}
public function getName(): string { return $this->name; } } </example> </pattern> </constructor_promotion>
<named_arguments> <pattern name="basic"> <description>Pass arguments by name (PHP 8.0+)</description> <example> function createUser( string $name, string $email, bool $active = true, ?string $role = null, ): User { // ... }
// Usage with named arguments $user = createUser( email: '[email protected]', name: 'John Doe', role: 'admin', ); </example> <decisiontree name="whentouse"> <question>Are you skipping optional parameters or improving readability?</question> <ifyes>Use named arguments</ifyes> <ifno>Use positional arguments for simple calls</ifno> </decisiontree> </pattern> </named_arguments>
<typedclassconstants> <pattern name="basic"> <description>Type declarations for class constants (PHP 8.3+)</description> <example> class Config { public const string VERSION = '1.0.0'; public const int MAXRETRIES = 3; public const array ALLOWEDMETHODS = ['GET', 'POST', 'PUT', 'DELETE']; } </example> </pattern> </typedclassconstants> </type_system>
<psrstandards> <psr name="PSR-1" title="Basic Coding Standard"> <description>Basic coding standards for PHP files</description> <rules> <rule>Files MUST use only <?php and <?= tags</rule> <rule>Files MUST use only UTF-8 without BOM</rule> <rule>Class names MUST be declared in StudlyCaps</rule> <rule>Class constants MUST be declared in UPPERCASE</rule> <rule>Method names MUST be declared in camelCase</rule> </rules> </psr>
<psr name="PSR-4" title="Autoloading Standard"> <description>Autoloading classes from file paths</description> <example> // composer.json { "autoload": { "psr-4": { "App\\": "src/", "App\\Tests\\": "tests/" } } }
// File: src/Domain/User/Entity/User.php namespace App\Domain\User\Entity;
class User { // Fully qualified: App\Domain\User\Entity\User } </example> </psr>
<psr name="PSR-3" title="Logger Interface"> <description>Common interface for logging libraries</description> <example> use Psr\Log\LoggerInterface; use Psr\Log\LogLevel;
class UserService { public function __construct( private LoggerInterface $logger, ) {}
public function create(array $data): User { $this->logger->info('Creating user', ['email' => $data['email']]);
try { $user = new User($data); $this->logger->debug('User created', ['id' => $user->getId()]); return $user; } catch (\Exception $e) { $this->logger->error('Failed to create user', [ 'exception' => $e, 'data' => $data, ]); throw $e; } } } </example> </psr>
<psr name="PSR-7" title="HTTP Message Interface"> <description>Common interfaces for HTTP messages</description> <example> use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface;
function handleRequest(ServerRequestInterface $request): ResponseInterface { $method = $request->getMethod(); $uri = $request->getUri(); $body = $request->getParsedBody(); $query = $request->getQueryParams();
// PSR-7 messages are immutable $response = new Response(); return $response ->withStatus(200) ->withHeader('Content-Type', 'application/json'); } </example> </psr>
<psr name="PSR-11" title="Container Interface"> <description>Common interface for dependency injection containers</description> <example> use Psr\Container\ContainerInterface;
class ServiceLocator { public function __construct( private ContainerInterface $container, ) {}
public function getUserService(): UserService { return $this->container->get(UserService::class); } } </example> </psr>
<psr name="PSR-12" title="Extended Coding Style"> <description>Extends PSR-1 with detailed formatting rules</description> <rules> <rule>Code MUST follow PSR-1</rule> <rule>Code MUST use 4 spaces for indenting</rule> <rule>Lines SHOULD be 80 characters or less</rule> <rule>There MUST be one blank line after namespace declaration</rule> <rule>Opening braces for classes MUST go on next line</rule> <rule>Opening braces for methods MUST go on next line</rule> <rule>Visibility MUST be declared on all properties and methods</rule> </rules> </psr>
<psr name="PSR-15" title="HTTP Server Request Handlers"> <description>Interfaces for HTTP server request handlers and middleware</description> <example> use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\MiddlewareInterface;
class AuthMiddleware implements MiddlewareInterface { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { $token = $request->getHeaderLine('Authorization');
if (!$this->validateToken($token)) { return new Response(401); }
return $handler->handle($request); } } </example> </psr>
<psr name="PSR-17" title="HTTP Factories"> <description>Factory interfaces for creating PSR-7 objects</description> <example> use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\StreamFactoryInterface;
class JsonResponder { public function __construct( private ResponseFactoryInterface $responseFactory, private StreamFactoryInterface $streamFactory, ) {}
public function respond(array $data, int $status = 200): ResponseInterface { $json = jsonencode($data, JSONTHROWONERROR); $body = $this->streamFactory->createStream($json);
return $this->responseFactory->createResponse($status) ->withHeader('Content-Type', 'application/json') ->withBody($body); } } </example> </psr>
<psr name="PSR-18" title="HTTP Client"> <description>Common interface for HTTP clients</description> <example> use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface;
class ApiClient { public function __construct( private ClientInterface $httpClient, private RequestFactoryInterface $requestFactory, ) {}
public function get(string $url): array { $request = $this->requestFactory->createRequest('GET', $url); $response = $this->httpClient->sendRequest($request);
return jsondecode( $response->getBody()->getContents(), true, 512, JSONTHROWONERROR ); } } </example> </psr> </psr_standards>
<design_patterns> <pattern name="value-object"> <description>Immutable objects representing a value</description> <example> readonly class Money { public function __construct( public int $amount, public string $currency, ) { if ($amount < 0) { throw new InvalidArgumentException('Amount cannot be negative'); } }
public function add(Money $other): self { if ($this->currency !== $other->currency) { throw new InvalidArgumentException('Currency mismatch'); } return new self($this->amount + $other->amount, $this->currency); }
public function equals(Money $other): bool { return $this->amount === $other->amount && $this->currency === $other->currency; } } </example> </pattern>
<pattern name="repository"> <description>Abstract data persistence behind an interface</description> <example> interface UserRepositoryInterface { public function find(UserId $id): ?User; public function findByEmail(Email $email): ?User; public function save(User $user): void; public function remove(User $user): void; }
class PdoUserRepository implements UserRepositoryInterface { public function __construct( private PDO $pdo, ) {}
public function find(UserId $id): ?User { $stmt = $this->pdo->prepare( 'SELECT * FROM users WHERE id = :id' ); $stmt->execute(['id' => $id->toString()]); $row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->hydrate($row) : null; }
public function save(User $user): void { $stmt = $this->pdo->prepare( 'INSERT INTO users (id, email, name) VALUES (:id, :email, :name) ON DUPLICATE KEY UPDATE email = :email, name = :name' ); $stmt->execute([ 'id' => $user->getId()->toString(), 'email' => $user->getEmail()->toString(), 'name' => $user->getName(), ]); } } </example> <decisiontree name="whentouse"> <question>Do you need to abstract persistence details from domain logic?</question> <ifyes>Use Repository pattern</ifyes> <ifno>Direct database access may be sufficient for simple CRUD</ifno> </decisiontree> </pattern>
<pattern name="service-layer"> <description>Coordinate use cases and transactions</description> <example> class CreateUserHandler { public function __construct( private UserRepositoryInterface $userRepository, private PasswordHasherInterface $passwordHasher, private EventDispatcherInterface $eventDispatcher, ) {}
public function handle(CreateUserCommand $command): UserId { $email = new Email($command->email);
if ($this->userRepository->findByEmail($email) !== null) { throw new UserAlreadyExistsException($email); }
$user = User::create( UserId::generate(), $email, $command->name, $this->passwordHasher->hash($command->password), );
$this->userRepository->save($user); $this->eventDispatcher->dispatch(new UserCreatedEvent($user));
return $user->getId(); } } </example> </pattern>
<pattern name="dependency-injection"> <description>Inject dependencies through constructor</description> <example> // Interface for abstraction interface CacheInterface { public function get(string $key): mixed; public function set(string $key, mixed $value, int $ttl = 3600): void; }
// Concrete implementation class RedisCache implements CacheInterface { public function __construct( private \Redis $redis, ) {}
public function get(string $key): mixed { $value = $this->redis->get($key); return $value !== false ? unserialize($value) : null; }
public function set(string $key, mixed $value, int $ttl = 3600): void { $this->redis->setex($key, $ttl, serialize($value)); } }
// Service depending on abstraction class ProductService { public function __construct( private ProductRepositoryInterface $repository, private CacheInterface $cache, ) {} } </example> </pattern> </design_patterns>
<composer> <package_management> <pattern name="require"> <description>Add production dependencies</description> <example> composer require psr/log composer require guzzlehttp/guzzle composer require symfony/http-foundation </example> </pattern>
<pattern name="require-dev"> <description>Add development dependencies</description> <example> composer require --dev phpunit/phpunit composer require --dev phpstan/phpstan composer require --dev friendsofphp/php-cs-fixer </example> </pattern>
<pattern name="version-constraints"> <description>Specify version requirements</description> <example> { "require": { "php": "^8.2", "psr/log": "^3.0", "guzzlehttp/guzzle": "^7.0" }, "require-dev": { "phpunit/phpunit": "^10.0 || ^11.0", "phpstan/phpstan": "^1.10" } } </example> <note>^ allows minor version updates, ~ allows patch updates only</note> </pattern> </package_management>
<package_development> <pattern name="library-structure"> <description>Standard library package structure</description> <example> my-package/ ├── src/ │ └── MyClass.php ├── tests/ │ └── MyClassTest.php ├── composer.json ├── phpunit.xml.dist ├── phpstan.neon ├── .php-cs-fixer.dist.php ├── LICENSE └── README.md </example> </pattern>
<pattern name="composer-json"> <description>Complete composer.json for library</description> <example> { "name": "vendor/my-package", "description": "My awesome PHP package", "type": "library", "license": "MIT", "authors": [ { "name": "Your Name", "email": "[email protected]" } ], "require": { "php": "^8.2" }, "require-dev": { "phpunit/phpunit": "^11.0", "phpstan/phpstan": "^1.10" }, "autoload": { "psr-4": { "Vendor\\MyPackage\\": "src/" } }, "autoload-dev": { "psr-4": { "Vendor\\MyPackage\\Tests\\": "tests/" } }, "scripts": { "test": "phpunit", "analyse": "phpstan analyse", "cs-fix": "php-cs-fixer fix" }, "config": { "sort-packages": true } } </example> </pattern>
<pattern name="scripts"> <description>Automate common tasks with Composer scripts</description> <example> { "scripts": { "test": "phpunit --colors=always", "test:coverage": "phpunit --coverage-html coverage", "analyse": "phpstan analyse --memory-limit=512M", "cs-check": "php-cs-fixer fix --dry-run --diff", "cs-fix": "php-cs-fixer fix", "ci": [ "@cs-check", "@analyse", "@test" ] } } </example> </pattern> </package_development> </composer>
<testing> <phpunit> <pattern name="test-case"> <description>Basic PHPUnit test structure</description> <example> use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\DataProvider;
class CalculatorTest extends TestCase { private Calculator $calculator;
protected function setUp(): void { $this->calculator = new Calculator(); }
#[Test] public function itAddsNumbers(): void { $result = $this->calculator->add(2, 3);
$this->assertSame(5, $result); }
#[Test] #[DataProvider('additionProvider')] public function itAddsVariousNumbers(int $a, int $b, int $expected): void { $this->assertSame($expected, $this->calculator->add($a, $b)); }
public static function additionProvider(): array { return [ 'positive numbers' => [1, 2, 3], 'negative numbers' => [-1, -2, -3], 'mixed numbers' => [-1, 2, 1], 'zeros' => [0, 0, 0], ]; } } </example> </pattern>
<pattern name="mocking"> <description>Create test doubles with PHPUnit</description> <example> use PHPUnit\Framework\TestCase;
class UserServiceTest extends TestCase { #[Test] public function itCreatesUser(): void { // Arrange $repository = $this->createMock(UserRepositoryInterface::class); $repository ->expects($this->once()) ->method('save') ->with($this->isInstanceOf(User::class));
$hasher = $this->createMock(PasswordHasherInterface::class); $hasher ->method('hash') ->willReturn('hashed_password');
$service = new UserService($repository, $hasher);
// Act $userId = $service->create('[email protected]', 'password');
// Assert $this->assertInstanceOf(UserId::class, $userId); } } </example> </pattern>
<pattern name="config"> <description>PHPUnit configuration file</description> <example> <!-- phpunit.xml.dist --> <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" cacheDirectory=".phpunit.cache"> <testsuites> <testsuite name="Unit"> <directory>tests/Unit</directory> </testsuite> <testsuite name="Integration"> <directory>tests/Integration</directory> </testsuite> </testsuites> <source> <include> <directory>src</directory> </include> </source> </phpunit> </example> </pattern> </phpunit>
<pest> <pattern name="basic"> <description>Pest PHP test syntax</description> <example> // tests/Unit/CalculatorTest.php use App\Calculator;
beforeEach(function () { $this->calculator = new Calculator(); });
test('it adds numbers', function () { expect($this->calculator->add(2, 3))->toBe(5); });
test('it subtracts numbers', function () { expect($this->calculator->subtract(5, 3))->toBe(2); });
it('throws on division by zero', function () { $this->calculator->divide(10, 0); })->throws(DivisionByZeroError::class); </example> </pattern>
<pattern name="datasets"> <description>Pest datasets for parameterized tests</description> <example> dataset('addition', [ 'positive' => [1, 2, 3], 'negative' => [-1, -2, -3], 'mixed' => [-1, 2, 1], ]);
test('it adds numbers correctly', function (int $a, int $b, int $expected) { expect($this->calculator->add($a, $b))->toBe($expected); })->with('addition'); </example> </pattern>
<pattern name="expectations"> <description>Pest expectation API</description> <example> test('user properties', function () { $user = new User('[email protected]', 'John Doe');
expect($user) ->toBeInstanceOf(User::class) ->email->toBe('[email protected]') ->name->toBe('John Doe') ->isActive()->toBeTrue(); }); </example> </pattern> </pest> </testing>
<static_analysis> <phpstan> <pattern name="config"> <description>PHPStan configuration</description> <example> # phpstan.neon parameters: level: 8 paths: - src - tests excludePaths: - vendor checkMissingIterableValueType: true checkGenericClassInNonGenericObjectType: true reportUnmatchedIgnoredErrors: true </example> </pattern>
<pattern name="levels"> <description>PHPStan strictness levels (0-9)</description> <levels> <level number="0">Basic checks</level> <level number="1">Possibly undefined variables</level> <level number="2">Unknown methods on $this</level> <level number="3">Wrong return types</level> <level number="4">Dead code</level> <level number="5">Argument types</level> <level number="6">Missing type hints</level> <level number="7">Partial union types</level> <level number="8">No mixed types</level> <level number="9">Mixed type operations</level> <level number="10">Stricter implicit mixed (PHPStan 2.0+)</level> </levels> <note>Start at level 5-6 for existing projects, level 9-10 for new projects. Use --level max for highest available.</note> </pattern>
<pattern name="generics"> <description>Generic types with PHPStan annotations</description> <example> /** @template T @param class-string<T> $class @return T / public function create(string $class): object { return new $class(); }
/** @template T of object @param T $entity @return T / public function save(object $entity): object { // persist return $entity; } </example> </pattern> </phpstan>
<psalm> <pattern name="config"> <description>Psalm configuration</description> <example> <!-- psalm.xml --> <?xml version="1.0"?> <psalm errorLevel="1" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > <projectFiles> <directory name="src" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> </projectFiles> </psalm> </example> </pattern>
<pattern name="annotations"> <description>Psalm-specific annotations</description> <example> /** @psalm-immutable / readonly class ImmutableValue { public function __construct( public string $value, ) {} }
/** @psalm-assert-if-true User $user / function isActiveUser(?User $user): bool { return $user !== null && $user->isActive(); } </example> </pattern> </psalm>
<phpcsfixer> <pattern name="config"> <description>PHP CS Fixer configuration</description> <example> <?php // .php-cs-fixer.dist.php $finder = PhpCsFixer\Finder::create() ->in(DIR . '/src') ->in(DIR . '/tests');
return (new PhpCsFixer\Config()) ->setRules([ '@PER-CS2.0' => true, '@PHP82Migration' => true, 'stricttypes' => true, 'declarestricttypes' => true, 'arraysyntax' => ['syntax' => 'short'], 'nounusedimports' => true, 'orderedimports' => ['sortalgorithm' => 'alpha'], 'trailingcommainmultiline' => true, ]) ->setFinder($finder) ->setRiskyAllowed(true); </example> </pattern> </phpcs_fixer>
<rector> <pattern name="config"> <description>Rector automated refactoring configuration</description> <example> <?php // rector.php use Rector\Config\RectorConfig; use Rector\Set\ValueObject\SetList;
return RectorConfig::configure() ->withPaths([ DIR . '/src', DIR . '/tests', ]) ->withSets([ SetList::CODEQUALITY, SetList::DEADCODE, SetList::TYPEDECLARATION, ]) ->withPhpSets(php83: true); </example> <note>LevelSetList (e.g., UPTOPHP83) deprecated since Rector 0.19.2. Use ->withPhpSets() instead.</note> </pattern> </rector> </static_analysis>
<database> <pdo> <pattern name="connection"> <description>PDO database connection</description> <example> $dsn = 'mysql:host=localhost;dbname=myapp;charset=utf8mb4'; $options = [ PDO::ATTRERRMODE => PDO::ERRMODEEXCEPTION, PDO::ATTRDEFAULTFETCHMODE => PDO::FETCHASSOC, PDO::ATTREMULATEPREPARES => false, ];
$pdo = new PDO($dsn, $username, $password, $options); </example> </pattern>
<pattern name="prepared-statements"> <description>Secure parameterized queries</description> <example> // Named parameters $stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email'); $stmt->execute(['email' => $email]); $user = $stmt->fetch();
// Positional parameters $stmt = $pdo->prepare('INSERT INTO users (email, name) VALUES (?, ?)'); $stmt->execute([$email, $name]); $id = $pdo->lastInsertId(); </example> <warning>Never concatenate user input into SQL queries</warning> </pattern>
<pattern name="transactions"> <description>Database transactions with PDO</description> <example> try { $pdo->beginTransaction();
$stmt = $pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?'); $stmt->execute([$amount, $fromAccount]);
$stmt = $pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?'); $stmt->execute([$amount, $toAccount]);
$pdo->commit(); } catch (\Exception $e) { $pdo->rollBack(); throw $e; } </example> </pattern> </pdo> </database>
<performance> <opcache> <pattern name="production-config"> <description>OPcache settings for production</description> <example> ; php.ini production settings opcache.enable=1 opcache.memoryconsumption=256 opcache.internedstringsbuffer=16 opcache.maxacceleratedfiles=10000 opcache.validatetimestamps=0 opcache.savecomments=1 opcache.enablefileoverride=1 </example> <note>Set validatetimestamps=0 in production, clear cache on deploy</note> </pattern> </opcache>
<jit> <pattern name="jit-config"> <description>JIT compiler settings (PHP 8.0+)</description> <example> ; php.ini JIT settings opcache.jit=1255 opcache.jitbuffersize=128M </example> <note>JIT provides most benefit for CPU-intensive tasks, less for I/O-bound web apps</note> </pattern> </jit>
<preloading> <pattern name="preload-script"> <description>Preload classes at startup (PHP 7.4+)</description> <example> <?php // preload.php require DIR . '/vendor/autoload.php';
// Preload commonly used classes $classesToPreload = [ App\Domain\User\User::class, App\Domain\Order\Order::class, ];
foreach ($classesToPreload as $class) { classexists($class); } </example> <config> ; php.ini opcache.preload=/path/to/preload.php opcache.preloaduser=www-data </config> </pattern> </preloading> </performance>
<error_handling> <pattern name="exception-hierarchy"> <description>Custom exception hierarchy</description> <example> // Base domain exception abstract class DomainException extends \Exception {}
// Specific exceptions class EntityNotFoundException extends DomainException { public static function forClass(string $class, string $id): self { return new self(sprintf('%s with id "%s" not found', $class, $id)); } }
class ValidationException extends DomainException { public function construct( string $message, public readonly array $errors = [], ) { parent::construct($message); } }
// Usage throw EntityNotFoundException::forClass(User::class, $userId); </example> </pattern>
<pattern name="result-type"> <description>Result type for error handling without exceptions</description> <example> /** @template T @template E */ readonly class Result { private function __construct( private bool $success, private mixed $value, ) {}
/** @return self<T, never> */ public static function ok(mixed $value): self { return new self(true, $value); }
/** @return self<never, E> */ public static function error(mixed $error): self { return new self(false, $error); }
public function isSuccess(): bool { return $this->success; } public function isError(): bool { return !$this->success; } public function getValue(): mixed { return $this->value; } }
// Usage function divide(int $a, int $b): Result { if ($b === 0) { return Result::error('Division by zero'); } return Result::ok($a / $b); } </example> </pattern> </error_handling>
<antipatterns> <avoid name="godclass"> <description>Classes that do too much</description> <instead>Split into focused single-responsibility classes</instead> </avoid>
<avoid name="service_locator"> <description>Global service container access</description> <instead>Use constructor injection for explicit dependencies</instead> </avoid>
<avoid name="arrayshapeeverywhere"> <description>Using arrays instead of typed objects</description> <instead>Create value objects or DTOs with typed properties</instead> </avoid>
<avoid name="mixedtypeabuse"> <description>Using mixed to avoid proper typing</description> <instead>Use union types, generics, or proper type narrowing</instead> </avoid>
<avoid name="staticmethodabuse"> <description>Overusing static methods making testing difficult</description> <instead>Use instance methods with dependency injection</instead> </avoid>
<avoid name="null_returns"> <description>Returning null for error conditions</description> <instead>Throw exceptions or use Result type</instead> </avoid>
<avoid name="sql_concatenation"> <description>Building SQL with string concatenation</description> <instead>Always use prepared statements with parameters</instead> <example> // Bad $sql = "SELECT * FROM users WHERE email = '$email'";
// Good $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?'); $stmt->execute([$email]); </example> </avoid> </anti_patterns>
<bestpractices> <practice priority="critical">Enable stricttypes in all PHP files</practice> <practice priority="critical">Use prepared statements for all database queries</practice> <practice priority="critical">Use PHPStan level 6+ for type safety</practice> <practice priority="high">Use readonly classes for value objects</practice> <practice priority="high">Follow PSR-12 coding style</practice> <practice priority="high">Use enums instead of string/int constants</practice> <practice priority="high">Inject dependencies through constructor</practice> <practice priority="medium">Use named arguments for complex function calls</practice> <practice priority="medium">Create custom exceptions for domain errors</practice> <practice priority="medium">Use attributes for metadata instead of docblock annotations</practice> </best_practices>
<workflow> <phase name="analyze"> <objective>Understand PHP code requirements</objective> <step>1. Check PHP version constraints in composer.json</step> <step>2. Review existing type patterns in project</step> <step>3. Identify PSR standards in use</step> </phase> <phase name="implement"> <objective>Write type-safe PHP code</objective> <step>1. Add declare(strict_types=1) at file start</step> <step>2. Define interfaces before implementations</step> <step>3. Use constructor property promotion</step> <step>4. Add return types to all methods</step> </phase> <phase name="validate"> <objective>Verify PHP correctness</objective> <step>1. Run PHPStan for type checking</step> <step>2. Run PHP CS Fixer for style</step> <step>3. Run PHPUnit/Pest for tests</step> </phase> </workflow>
<errorescalation> <level severity="low"> <example>Minor coding style issue</example> <action>Auto-fix with PHP CS Fixer</action> </level> <level severity="medium"> <example>PHPStan error or missing type</example> <action>Fix type, verify with static analysis</action> </level> <level severity="high"> <example>Breaking API change or security issue</example> <action>Stop, present options to user</action> </level> <level severity="critical"> <example>SQL injection or authentication bypass</example> <action>Block operation, require immediate fix</action> </level> </errorescalation>
<constraints> <must>Add declare(strict_types=1) to all PHP files</must> <must>Use prepared statements for database queries</must> <must>Define explicit return types on all methods</must> <must>Follow PSR-12 coding style</must> <avoid>Using mixed type without justification</avoid> <avoid>Suppressing PHPStan errors without documentation</avoid> <avoid>Using @ error suppression operator</avoid> </constraints>
<relatedagents> <agent name="design">API design, architecture, and module structure planning</agent> <agent name="execute">PHP implementation with strict typing and PSR compliance</agent> <agent name="code-quality">PHPStan validation, type safety, and best practices</agent> <agent name="test">PHPUnit and Pest test creation and coverage</agent> <agent name="security">SQL injection, XSS, and authentication vulnerabilities</agent> </relatedagents>
<relatedskills> <skill name="serena-usage">Symbol-level navigation for class and interface definitions</skill> <skill name="context7-usage">Fetch latest PHP and library documentation</skill> <skill name="testing-patterns">Test strategy and coverage patterns</skill> <skill name="database">PDO patterns and query optimization</skill> </relatedskills>