<php_version> <version_mapping> <description>PHP version-specific feature availability</description> <version php="8.3" released="2023-11"> <feature>Typed class constants</feature> <feature>json_validate() 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> </version_mapping>
<recommended_config> <description>php.ini recommended settings for development</description> <config> error_reporting = E_ALL display_errors = On log_errors = On opcache.enable = 1 opcache.validate_timestamps = 1 </config> </recommended_config> </php_version>
<type_system> <union_types> <pattern name="basic"> <description>Multiple types for parameter or return</description> <example> function process(string|int $value): string|null { return is_string($value) ? $value : (string) $value; } </example> </pattern> </union_types>
<intersection_types> <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> </intersection_types>
<dnf_types> <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> </dnf_types>
<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::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
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: 'user@example.com',
name: 'John Doe',
role: 'admin',
);
</example>
<decision_tree name="when_to_use">
<question>Are you skipping optional parameters or improving readability?</question>
<if_yes>Use named arguments</if_yes>
<if_no>Use positional arguments for simple calls</if_no>
</decision_tree>
</pattern>
</named_arguments>
<typed_class_constants> <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 MAX_RETRIES = 3; public const array ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'DELETE']; } </example> </pattern> </typed_class_constants> </type_system>
<psr_standards> <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 UPPER_CASE</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 = json_encode($data, JSON_THROW_ON_ERROR);
$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 json_decode(
$response->getBody()->getContents(),
true,
512,
JSON_THROW_ON_ERROR
);
}
}
</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>
<decision_tree name="when_to_use">
<question>Do you need to abstract persistence details from domain logic?</question>
<if_yes>Use Repository pattern</if_yes>
<if_no>Direct database access may be sufficient for simple CRUD</if_no>
</decision_tree>
</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": "you@example.com"
}
],
"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('test@example.com', '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('john@example.com', 'John Doe');
expect($user)
->toBeInstanceOf(User::class)
->email->toBe('john@example.com')
->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>
<php_cs_fixer> <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,
'strict_types' => true,
'declare_strict_types' => true,
'array_syntax' => ['syntax' => 'short'],
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'trailing_comma_in_multiline' => true,
])
->setFinder($finder)
->setRiskyAllowed(true);
</example>
</pattern>
</php_cs_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::CODE_QUALITY,
SetList::DEAD_CODE,
SetList::TYPE_DECLARATION,
])
->withPhpSets(php83: true);
</example>
<note>LevelSetList (e.g., UP_TO_PHP_83) 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::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => 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.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.save_comments=1
opcache.enable_file_override=1
</example>
<note>Set validate_timestamps=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.jit_buffer_size=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) {
class_exists($class);
}
</example>
<config>
; php.ini
opcache.preload=/path/to/preload.php
opcache.preload_user=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>
<anti_patterns> <avoid name="god_class"> <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="array_shape_everywhere"> <description>Using arrays instead of typed objects</description> <instead>Create value objects or DTOs with typed properties</instead> </avoid> <avoid name="mixed_type_abuse"> <description>Using mixed to avoid proper typing</description> <instead>Use union types, generics, or proper type narrowing</instead> </avoid> <avoid name="static_method_abuse"> <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>
<best_practices> <practice priority="critical">Enable strict_types 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><error_escalation> <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> </error_escalation>
<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><related_agents> <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> </related_agents>
<related_skills> <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> </related_skills>
Scan to join WeChat group