All skills
asyrafhussin avatar

/php-best-practices

@e7ea05d

PHP 8.x modern patterns, PSR standards, and SOLID principles. Use when reviewing PHP code, checking type safety, auditing code quality, or ensuring PHP best practices. Triggers on "review PHP", "check PHP code", "audit PHP", or "PHP best practices".

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/php-best-practices

This session only. Nothing lands on disk.

rulessolid-lsp.md

≈1.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types without altering correctness.

Bad Example

<?php

declare(strict_types=1);

class Rectangle
{
    protected int $width;
    protected int $height;

    public function setWidth(int $width): void
    {
        $this->width = $width;
    }

    public function setHeight(int $height): void
    {
        $this->height = $height;
    }

    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

// Violates LSP - Square changes Rectangle behavior
class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        // Breaks the contract - also sets height
        $this->width = $width;
        $this->height = $width;
    }

    public function setHeight(int $height): void
    {
        // Breaks the contract - also sets width
        $this->width = $height;
        $this->height = $height;
    }
}

// This function expects Rectangle behavior
function resizeRectangle(Rectangle $rect): int
{
    $rect->setWidth(5);
    $rect->setHeight(10);
    return $rect->getArea(); // Expects 50
}

$rectangle = new Rectangle();
echo resizeRectangle($rectangle); // 50 - correct

$square = new Square();
echo resizeRectangle($square); // 100 - unexpected! LSP violated

// Another violation example
class Bird
{
    public function fly(): void
    {
        echo "Flying...";
    }
}

class Penguin extends Bird
{
    public function fly(): void
    {
        // Penguins can't fly - throws exception
        throw new LogicException("Penguins can't fly!");
    }
}

function makeBirdFly(Bird $bird): void
{
    $bird->fly(); // Throws if penguin
}

Good Example

<?php

declare(strict_types=1);

// Use interface for common behavior
interface Shape
{
    public function getArea(): int;
}

// Rectangle and Square are separate implementations
readonly class Rectangle implements Shape
{
    public function __construct(
        private int $width,
        private int $height,
    ) {}

    public function getArea(): int
    {
        return $this->width * $this->height;
    }

    public function getWidth(): int
    {
        return $this->width;
    }

    public function getHeight(): int
    {
        return $this->height;
    }
}

readonly class Square implements Shape
{
    public function __construct(
        private int $side,
    ) {}

    public function getArea(): int
    {
        return $this->side * $this->side;
    }

    public function getSide(): int
    {
        return $this->side;
    }
}

// Both work correctly with the Shape interface
function calculateTotalArea(array $shapes): int
{
    return array_sum(
        array_map(fn(Shape $shape) => $shape->getArea(), $shapes)
    );
}

$shapes = [
    new Rectangle(5, 10),  // 50
    new Square(5),         // 25
];

echo calculateTotalArea($shapes); // 75 - correct

// Bird example fixed with proper abstraction
interface Bird
{
    public function eat(): void;
    public function sleep(): void;
}

interface FlyingBird extends Bird
{
    public function fly(): void;
}

interface SwimmingBird extends Bird
{
    public function swim(): void;
}

class Sparrow implements FlyingBird
{
    public function eat(): void
    {
        echo "Eating seeds...";
    }

    public function sleep(): void
    {
        echo "Sleeping in nest...";
    }

    public function fly(): void
    {
        echo "Flying through the air...";
    }
}

class Penguin implements SwimmingBird
{
    public function eat(): void
    {
        echo "Eating fish...";
    }

    public function sleep(): void
    {
        echo "Sleeping on ice...";
    }

    public function swim(): void
    {
        echo "Swimming in the ocean...";
    }
}

// Only flying birds are passed to this function
function makeBirdsFly(FlyingBird ...$birds): void
{
    foreach ($birds as $bird) {
        $bird->fly();
    }
}

Contract Preservation Example

<?php

declare(strict_types=1);

interface PaymentGateway
{
    /**
     * Process a payment.
     *
     * @throws InsufficientFundsException When balance is insufficient
     * @throws PaymentDeclinedException When payment is declined
     */
    public function charge(Money $amount, PaymentMethod $method): PaymentResult;

    /**
     * Refund must return the same currency as the original payment.
     */
    public function refund(PaymentId $paymentId, Money $amount): RefundResult;
}

// Correct - follows the contract
class StripeGateway implements PaymentGateway
{
    public function charge(Money $amount, PaymentMethod $method): PaymentResult
    {
        try {
            $charge = $this->stripe->charges->create([
                'amount' => $amount->cents,
                'currency' => $amount->currency,
            ]);

            return new PaymentResult(
                id: new PaymentId($charge->id),
                amount: $amount,
                status: PaymentStatus::Completed,
            );
        } catch (StripeException $e) {
            if ($e->getCode() === 'insufficient_funds') {
                throw new InsufficientFundsException($e->getMessage());
            }
            throw new PaymentDeclinedException($e->getMessage());
        }
    }

    public function refund(PaymentId $paymentId, Money $amount): RefundResult
    {
        // Returns same currency as documented
        $refund = $this->stripe->refunds->create([
            'charge' => $paymentId->value,
            'amount' => $amount->cents,
        ]);

        return new RefundResult(
            id: new RefundId($refund->id),
            amount: $amount,
        );
    }
}

// Also correct - different implementation, same contract
class PayPalGateway implements PaymentGateway
{
    public function charge(Money $amount, PaymentMethod $method): PaymentResult
    {
        // Different implementation, same exceptions for same conditions
        // Same return type with same semantics
    }

    public function refund(PaymentId $paymentId, Money $amount): RefundResult
    {
        // Same contract honored
    }
}

Why

  • Substitutability: Subclasses work in place of parent classes
  • Reliability: Code using base types works with any subtype
  • Polymorphism: True polymorphism requires LSP compliance
  • Testing: Base type tests apply to all subtypes
  • Design Quality: Violations indicate wrong inheritance hierarchy
  • Maintenance: Changing subtype implementation won't break callers

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill is a comprehensive and safe resource for PHP 8.x best practices. It provides structured guidance on type safety, modern PHP syntax, PSR standards, and secure coding practices (such as prepared statements and password hashing). The agent is instructed to use standard environment detection commands (php -v, grep) to tailor its advice. No malicious patterns or security risks were identified.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer6mo

    39 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at e7ea05d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
Other metadata
metadata
{
  "author": "php-community",
  "version": "2.1.0",
  "phpVersion": "8.0 - 8.5"
}

README badge

README badge for asyrafhussin/agent-skills/php-best-practices