Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name PHP Ecosystem
description 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.
Provide comprehensive patterns for modern PHP (8.1+) language features, PSR standards, testing, static analysis, and package development in a framework-agnostic approach. PHP version-specific feature availability Typed class constants json_validate() function Randomizer::getFloat() and nextFloat() Deep cloning of readonly properties Override attribute Granular DateTime exceptions Readonly classes DNF types (Disjunctive Normal Form) null, false, true as standalone types Constants in traits Deprecate dynamic properties Enums (backed and unit) Readonly properties Fibers Intersection types never return type First-class callable syntax New in initializers Named arguments Attributes Constructor property promotion Union types Match expression Nullsafe operator mixed type php.ini recommended settings for development error_reporting = E_ALL display_errors = On log_errors = On opcache.enable = 1 opcache.validate_timestamps = 1 Multiple types for parameter or return function process(string|int $value): string|null { return is_string($value) ? $value : (string) $value; } Value must satisfy all types (PHP 8.1+) function process(Countable&Iterator $collection): int { return count($collection); } Combine union and intersection types (PHP 8.2+) function handle((Countable&Iterator)|null $items): void { if ($items === null) { return; } foreach ($items as $item) { // process } } Enum with scalar backing (PHP 8.1+) 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'

Enum without backing value 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',
    };
}

}

Immutable property (PHP 8.1+) class User { public function __construct( public readonly string $id, public readonly string $email, ) {} } All properties become readonly (PHP 8.2+) readonly class ValueObject { public function __construct( public string $name, public int $value, ) {} } Define and use custom attributes (PHP 8.0+) #[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' }

Declare and assign properties in constructor (PHP 8.0+) class Product { public function \_\_construct( private string $name, private float $price, private int $quantity = 0, ) {}
public function getName(): string
{
    return $this->name;
}

}

Pass arguments by name (PHP 8.0+) 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', ); Are you skipping optional parameters or improving readability? Use named arguments Use positional arguments for simple calls

Type declarations for class constants (PHP 8.3+) class Config { public const string VERSION = '1.0.0'; public const int MAX_RETRIES = 3; public const array ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'DELETE']; }
Basic coding standards for PHP files Files MUST use only <?php and <?= tags Files MUST use only UTF-8 without BOM Class names MUST be declared in StudlyCaps Class constants MUST be declared in UPPER_CASE Method names MUST be declared in camelCase Autoloading classes from file paths // 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 }

Common interface for logging libraries use Psr\Log\LoggerInterface; use Psr\Log\LogLevel;

class UserService { public function __construct( private LoggerInterface $logger, ) {}

public function create(array $data): User
{
    $this-&gt;logger-&gt;info('Creating user', ['email' =&gt; $data['email']]);

    try {
        $user = new User($data);
        $this-&gt;logger-&gt;debug('User created', ['id' =&gt; $user-&gt;getId()]);
        return $user;
    } catch (\Exception $e) {
        $this-&gt;logger-&gt;error('Failed to create user', [
            'exception' =&gt; $e,
            'data' =&gt; $data,
        ]);
        throw $e;
    }
}

}

Common interfaces for HTTP messages 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
    -&gt;withStatus(200)
    -&gt;withHeader('Content-Type', 'application/json');

}

Common interface for dependency injection containers use Psr\Container\ContainerInterface;

class ServiceLocator { public function __construct( private ContainerInterface $container, ) {}

public function getUserService(): UserService
{
    return $this-&gt;container-&gt;get(UserService::class);
}

}

Extends PSR-1 with detailed formatting rules Code MUST follow PSR-1 Code MUST use 4 spaces for indenting Lines SHOULD be 80 characters or less There MUST be one blank line after namespace declaration Opening braces for classes MUST go on next line Opening braces for methods MUST go on next line Visibility MUST be declared on all properties and methods Interfaces for HTTP server request handlers and middleware 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-&gt;validateToken($token)) {
        return new Response(401);
    }

    return $handler-&gt;handle($request);
}

}

Factory interfaces for creating PSR-7 objects 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-&gt;streamFactory-&gt;createStream($json);

    return $this-&gt;responseFactory-&gt;createResponse($status)
        -&gt;withHeader('Content-Type', 'application/json')
        -&gt;withBody($body);
}

}

Common interface for HTTP clients 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-&gt;requestFactory-&gt;createRequest('GET', $url);
    $response = $this-&gt;httpClient-&gt;sendRequest($request);

    return json_decode(
        $response-&gt;getBody()-&gt;getContents(),
        true,
        512,
        JSON_THROW_ON_ERROR
    );
}

}

Immutable objects representing a value 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-&gt;currency !== $other-&gt;currency) {
        throw new InvalidArgumentException('Currency mismatch');
    }
    return new self($this-&gt;amount + $other-&gt;amount, $this-&gt;currency);
}

public function equals(Money $other): bool
{
    return $this-&gt;amount === $other-&gt;amount
        &amp;&amp; $this-&gt;currency === $other-&gt;currency;
}

}

Abstract data persistence behind an interface 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-&gt;pdo-&gt;prepare(
        'SELECT * FROM users WHERE id = :id'
    );
    $stmt-&gt;execute(['id' =&gt; $id-&gt;toString()]);
    $row = $stmt-&gt;fetch(PDO::FETCH_ASSOC);

    return $row ? $this-&gt;hydrate($row) : null;
}

public function save(User $user): void
{
    $stmt = $this-&gt;pdo-&gt;prepare(
        'INSERT INTO users (id, email, name)
         VALUES (:id, :email, :name)
         ON DUPLICATE KEY UPDATE email = :email, name = :name'
    );
    $stmt-&gt;execute([
        'id' =&gt; $user-&gt;getId()-&gt;toString(),
        'email' =&gt; $user-&gt;getEmail()-&gt;toString(),
        'name' =&gt; $user-&gt;getName(),
    ]);
}

} Do you need to abstract persistence details from domain logic? Use Repository pattern Direct database access may be sufficient for simple CRUD

Coordinate use cases and transactions class CreateUserHandler { public function __construct( private UserRepositoryInterface $userRepository, private PasswordHasherInterface $passwordHasher, private EventDispatcherInterface $eventDispatcher, ) {}
public function handle(CreateUserCommand $command): UserId
{
    $email = new Email($command-&gt;email);

    if ($this-&gt;userRepository-&gt;findByEmail($email) !== null) {
        throw new UserAlreadyExistsException($email);
    }

    $user = User::create(
        UserId::generate(),
        $email,
        $command-&gt;name,
        $this-&gt;passwordHasher-&gt;hash($command-&gt;password),
    );

    $this-&gt;userRepository-&gt;save($user);
    $this-&gt;eventDispatcher-&gt;dispatch(new UserCreatedEvent($user));

    return $user-&gt;getId();
}

}

Inject dependencies through constructor // 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-&gt;redis-&gt;get($key);
    return $value !== false ? unserialize($value) : null;
}

public function set(string $key, mixed $value, int $ttl = 3600): void
{
    $this-&gt;redis-&gt;setex($key, $ttl, serialize($value));
}

}

// Service depending on abstraction class ProductService { public function __construct( private ProductRepositoryInterface $repository, private CacheInterface $cache, ) {} }

Add production dependencies composer require psr/log composer require guzzlehttp/guzzle composer require symfony/http-foundation Add development dependencies composer require --dev phpunit/phpunit composer require --dev phpstan/phpstan composer require --dev friendsofphp/php-cs-fixer Specify version requirements { "require": { "php": "^8.2", "psr/log": "^3.0", "guzzlehttp/guzzle": "^7.0" }, "require-dev": { "phpunit/phpunit": "^10.0 || ^11.0", "phpstan/phpstan": "^1.10" } } ^ allows minor version updates, ~ allows patch updates only Standard library package structure my-package/ ├── src/ │ └── MyClass.php ├── tests/ │ └── MyClassTest.php ├── composer.json ├── phpunit.xml.dist ├── phpstan.neon ├── .php-cs-fixer.dist.php ├── LICENSE └── README.md Complete composer.json for library { "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 } } Automate common tasks with Composer scripts { "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" ] } } Basic PHPUnit test structure 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-&gt;calculator = new Calculator();
}

#[Test]
public function itAddsNumbers(): void
{
    $result = $this-&gt;calculator-&gt;add(2, 3);

    $this-&gt;assertSame(5, $result);
}

#[Test]
#[DataProvider('additionProvider')]
public function itAddsVariousNumbers(int $a, int $b, int $expected): void
{
    $this-&gt;assertSame($expected, $this-&gt;calculator-&gt;add($a, $b));
}

public static function additionProvider(): array
{
    return [
        'positive numbers' =&gt; [1, 2, 3],
        'negative numbers' =&gt; [-1, -2, -3],
        'mixed numbers' =&gt; [-1, 2, 1],
        'zeros' =&gt; [0, 0, 0],
    ];
}

}

Create test doubles with PHPUnit 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-&gt;createMock(PasswordHasherInterface::class);
    $hasher
        -&gt;method('hash')
        -&gt;willReturn('hashed_password');

    $service = new UserService($repository, $hasher);

    // Act
    $userId = $service-&gt;create('test@example.com', 'password');

    // Assert
    $this-&gt;assertInstanceOf(UserId::class, $userId);
}

}

PHPUnit configuration file <!-- 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>
Pest PHP test syntax // 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);

Pest datasets for parameterized tests 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');

Pest expectation API test('user properties', function () { $user = new User('john@example.com', 'John Doe');
expect($user)
    -&gt;toBeInstanceOf(User::class)
    -&gt;email-&gt;toBe('john@example.com')
    -&gt;name-&gt;toBe('John Doe')
    -&gt;isActive()-&gt;toBeTrue();

});

PHPStan configuration

phpstan.neon

parameters: level: 8 paths: - src - tests excludePaths: - vendor checkMissingIterableValueType: true checkGenericClassInNonGenericObjectType: true reportUnmatchedIgnoredErrors: true

PHPStan strictness levels (0-9) Basic checks Possibly undefined variables Unknown methods on $this Wrong return types Dead code Argument types Missing type hints Partial union types No mixed types Mixed type operations Stricter implicit mixed (PHPStan 2.0+) Start at level 5-6 for existing projects, level 9-10 for new projects. Use --level max for highest available. Generic types with PHPStan annotations /** * @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; }
Psalm configuration <!-- 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> Psalm-specific annotations /** * @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(); }
PHP CS Fixer configuration <?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);

Rector automated refactoring configuration <?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); LevelSetList (e.g., UP_TO_PHP_83) deprecated since Rector 0.19.2. Use ->withPhpSets() instead.

PDO database connection $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);

Secure parameterized queries // 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(); Never concatenate user input into SQL queries

Database transactions with PDO try { $pdo->beginTransaction();
$stmt = $pdo-&gt;prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?');
$stmt-&gt;execute([$amount, $fromAccount]);

$stmt = $pdo-&gt;prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?');
$stmt-&gt;execute([$amount, $toAccount]);

$pdo-&gt;commit();

} catch (\Exception $e) { $pdo->rollBack(); throw $e; }

OPcache settings for production ; 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 Set validate_timestamps=0 in production, clear cache on deploy JIT compiler settings (PHP 8.0+) ; php.ini JIT settings opcache.jit=1255 opcache.jit_buffer_size=128M JIT provides most benefit for CPU-intensive tasks, less for I/O-bound web apps

Preload classes at startup (PHP 7.4+) <?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); } ; php.ini opcache.preload=/path/to/preload.php opcache.preload_user=www-data

Custom exception hierarchy // 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);

Result type for error handling without exceptions /** * @template T * @template E */ readonly class Result { private function __construct( private bool $success, private mixed $value, ) {}
/** @return self&lt;T, never&gt; */
public static function ok(mixed $value): self
{
    return new self(true, $value);
}

/** @return self&lt;never, E&gt; */
public static function error(mixed $error): self
{
    return new self(false, $error);
}

public function isSuccess(): bool { return $this-&gt;success; }
public function isError(): bool { return !$this-&gt;success; }
public function getValue(): mixed { return $this-&gt;value; }

}

// Usage function divide(int $a, int $b): Result { if ($b === 0) { return Result::error('Division by zero'); } return Result::ok($a / $b); }

Classes that do too much Split into focused single-responsibility classes Global service container access Use constructor injection for explicit dependencies Using arrays instead of typed objects Create value objects or DTOs with typed properties Using mixed to avoid proper typing Use union types, generics, or proper type narrowing Overusing static methods making testing difficult Use instance methods with dependency injection Returning null for error conditions Throw exceptions or use Result type Building SQL with string concatenation Always use prepared statements with parameters // Bad $sql = "SELECT * FROM users WHERE email = '$email'";

// Good $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?'); $stmt->execute([$email]);

Enable strict_types in all PHP files Use prepared statements for all database queries Use PHPStan level 6+ for type safety Use readonly classes for value objects Follow PSR-12 coding style Use enums instead of string/int constants Inject dependencies through constructor Use named arguments for complex function calls Create custom exceptions for domain errors Use attributes for metadata instead of docblock annotations Understand PHP code requirements 1. Check PHP version constraints in composer.json 2. Review existing type patterns in project 3. Identify PSR standards in use Write type-safe PHP code 1. Add declare(strict_types=1) at file start 2. Define interfaces before implementations 3. Use constructor property promotion 4. Add return types to all methods Verify PHP correctness 1. Run PHPStan for type checking 2. Run PHP CS Fixer for style 3. Run PHPUnit/Pest for tests Minor coding style issue Auto-fix with PHP CS Fixer PHPStan error or missing type Fix type, verify with static analysis Breaking API change or security issue Stop, present options to user SQL injection or authentication bypass Block operation, require immediate fix Add declare(strict_types=1) to all PHP files Use prepared statements for database queries Define explicit return types on all methods Follow PSR-12 coding style Using mixed type without justification Suppressing PHPStan errors without documentation Using @ error suppression operator API design, architecture, and module structure planning PHP implementation with strict typing and PSR compliance PHPStan validation, type safety, and best practices PHPUnit and Pest test creation and coverage SQL injection, XSS, and authentication vulnerabilities Symbol-level navigation for class and interface definitions Fetch latest PHP and library documentation Test strategy and coverage patterns PDO patterns and query optimization