This skill should be used when the user asks to "write php", "php 8", "composer", "phpunit", "pest", "phpstan", "psalm", "psr", or works with modern PHP language patterns and configuration. Provides comprehensive modern PHP ecosystem patterns and best practices.
<php_version> <version_mapping> <description>PHP version-specific feature availability</description> <version php="8.4" released="2024-11"> <feature>Property hooks (get/set)</feature> <feature>Asymmetric visibility (public private(set))</feature> <feature>#[Deprecated] attribute</feature> <feature>Lazy objects</feature> <feature>new MyClass()->method() without parentheses</feature> <feature>array_find(), array_find_key(), array_any(), array_all()</feature> <feature>mb_trim(), mb_ltrim(), mb_rtrim()</feature> </version> <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>
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>
<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>
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>
<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>
<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>
<property_hooks> <pattern name="basic-hooks"> <description>Property hooks for validation and transformation (PHP 8.4+)</description> <example> class User { public string $email { get => $this->email; set { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } $this->email = strtolower($value); } } } </example> <decision_tree name="when_to_use"> <question>Do you need validation or transformation on property access?</question> <if_yes>Use property hooks instead of getters/setters</if_yes> <if_no>Use simple public properties or readonly</if_no> </decision_tree> </pattern>
<pattern name="virtual-property">
<description>Computed property without backing storage (PHP 8.4+)</description>
<example>
class User
{
public string $fullName {
get => $this->firstName . ' ' . $this->lastName;
}
public function __construct(
public string $firstName,
public string $lastName,
) {}
}
</example>
</pattern>
<pattern name="old_vs_modern">
<description>Migration from getters/setters to property hooks</description>
<old_way>
// Before PHP 8.4: Boilerplate getters/setters
class Product
{
private int $price;
public function getPrice(): int
{
return $this->price;
}
public function setPrice(int $price): void
{
if ($price < 0) {
throw new InvalidArgumentException('Price cannot be negative');
}
$this->price = $price;
}
}
// Usage
$product->setPrice(100);
echo $product->getPrice();
</old_way>
<modern_way>
// PHP 8.4+: Property hooks
class Product
{
public int $price {
set {
if ($value < 0) {
throw new InvalidArgumentException('Price cannot be negative');
}
$this->price = $value;
}
}
}
// Usage - direct property access
$product->price = 100;
echo $product->price;
</modern_way>
</pattern>
</property_hooks>
<asymmetric_visibility> <pattern name="public-read-private-write"> <description>Public read access with private write (PHP 8.4+)</description> <example> class Order { public private(set) string $status = 'pending'; public private(set) DateTimeImmutable $createdAt; public private(set) ?DateTimeImmutable $processedAt = null;
public function __construct()
{
$this->createdAt = new DateTimeImmutable();
}
public function process(): void
{
$this->status = 'processed';
$this->processedAt = new DateTimeImmutable();
}
}
// Usage
$order = new Order();
echo $order->status; // OK: public read
$order->status = 'failed'; // Error: private write
</example>
<decision_tree name="when_to_use">
<question>Do you need public read but controlled write access?</question>
<if_yes>Use asymmetric visibility (public private(set))</if_yes>
<if_no>Use readonly for immutable or regular visibility for mutable</if_no>
</decision_tree>
</pattern>
</asymmetric_visibility>
<deprecated_attribute> <pattern name="basic"> <description>Mark functions/methods/constants as deprecated (PHP 8.4+)</description> <example> class ApiService { #[Deprecated( message: 'Use fetchV2() instead', since: '2.0.0' )] public function fetch(): array { return $this->fetchV2(); }
public function fetchV2(): array
{
// New implementation
}
}
// Usage triggers E_USER_DEPRECATED automatically
$service->fetch();
</example>
</pattern>
</deprecated_attribute>
<lazy_objects> <pattern name="lazy_ghost"> <description>Create lazy-initialized objects (PHP 8.4+)</description> <example> class ExpensiveService { public function __construct() { // Heavy initialization... } }
// Create lazy ghost - initializes only when accessed
$reflector = new ReflectionClass(ExpensiveService::class);
$service = $reflector->newLazyGhost(
function (ExpensiveService $instance) {
// Called when first property/method accessed
$instance->__construct();
}
);
// $service not initialized yet
$service->doSomething(); // Triggers initialization
</example>
<decision_tree name="when_to_use">
<question>Do you need to defer expensive object creation?</question>
<if_yes>Use lazy ghost for same-class or lazy proxy for interface wrapping</if_yes>
<if_no>Use regular instantiation</if_no>
</decision_tree>
</pattern>
</lazy_objects> </type_system>
<context7_integration> <library_id>/php/doc-en</library_id> <trust_score>9.4</trust_score>
<usage_pattern> <step>For PHP core documentation, use library ID /php/doc-en</step> <step>Fetch specific topic documentation with get-library-docs</step> </usage_pattern>
<common_queries> <query topic="attributes">PHP attribute syntax and usage</query> <query topic="enums">Enumeration types and patterns</query> <query topic="readonly">Readonly properties and classes</query> <query topic="property-hooks">Property hooks syntax (PHP 8.4)</query> </common_queries> </context7_integration>
<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>
// File: src/Domain/User/Entity/User.php
namespace App\Domain\User\Entity;
class User
{
// Fully qualified: App\Domain\User\Entity\User
}
</example>
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>
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>
class ServiceLocator
{
public function __construct(
private ContainerInterface $container,
) {}
public function getUserService(): UserService
{
return $this->container->get(UserService::class);
}
}
</example>
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>
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>
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>
<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>
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>
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>
// 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 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.3",
"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>
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>
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>
<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="level-9-10-config">
<description>Maximum strictness configuration for new projects</description>
<example>
# phpstan.neon
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
parameters:
level: 10 # PHPStan 2.0+ (use 9 for PHPStan 1.x)
paths:
- src
- tests
checkUninitializedProperties: true
checkBenevolentUnionTypes: true
reportPossiblyNonexistentGeneralArrayOffset: true
</example>
<note>Level 10 treats implicit mixed (missing types) as strictly as explicit mixed. Use phpstan-strict-rules for additional checks like === enforcement.</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>
<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>
<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-CS' => true,
'@PHP84Migration' => 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>
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->withSets([
SetList::CODE_QUALITY,
SetList::DEAD_CODE,
SetList::TYPE_DECLARATION,
])
->withPhpSets(php84: true);
</example>
<note>LevelSetList (e.g., UP_TO_PHP_84) deprecated since Rector 0.19.2. Use ->withPhpSets() instead.</note>
</pattern>
$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>
// 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>
<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>
/** @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>
<array_functions> <php84_functions> <function name="array_find"> <description>Find first element matching predicate (PHP 8.4+)</description> <example> $users = [ ['id' => 1, 'active' => false], ['id' => 2, 'active' => true], ['id' => 3, 'active' => true], ];
$firstActive = array_find($users, fn($user) => $user['active']);
// ['id' => 2, 'active' => true]
</example>
</function>
<function name="array_find_key">
<description>Find key of first element matching predicate (PHP 8.4+)</description>
<example>
$users = [
'john' => ['active' => false],
'jane' => ['active' => true],
];
$firstActiveKey = array_find_key($users, fn($user) => $user['active']);
// 'jane'
</example>
</function>
<function name="array_any">
<description>Check if any element matches predicate (PHP 8.4+)</description>
<example>
$hasAdmin = array_any($users, fn($u) => $u['role'] === 'admin');
</example>
</function>
<function name="array_all">
<description>Check if all elements match predicate (PHP 8.4+)</description>
<example>
$allActive = array_all($users, fn($u) => $u['active']);
</example>
</function>
</php84_functions> </array_functions>
<anti_patterns> <avoid name="god_class"> <description>Classes that do too much</description> <instead>Split into focused single-responsibility classes</instead> </avoid>
// Good
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
</example>
<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 9+ 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>
<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>
<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>