NYC
skills/smithery/ai/php-guidelines

php-guidelines

SKILL.md

PHP Guidelines

Overview

Modern PHP guidelines based on the PHP Manual covering PHP 8.x idioms. PHP has evolved dramatically — write modern, type-safe PHP, not legacy PHP 5 patterns.

Core Principles

  • Always use declare(strict_types=1) — first line of every file
  • Type everything — parameters, returns, properties, constants
  • Use === not == — loose comparison causes security bugs
  • Errors are exceptions — catch them, don't suppress with @
  • Composition over inheritance — interfaces + traits over deep hierarchies

Strict Types

<?php
declare(strict_types=1);  // MUST be first statement

function add(int $a, int $b): int {
    return $a + $b;
}
add("1", "2"); // TypeError in strict mode, silently coerces without it
Mode Behavior
Coercive (default) "123" silently becomes 123, truncates floats
Strict (strict_types=1) TypeError on any mismatch (except int to float)
Scope Per-file — each file must declare independently

Type System

Scalar Types

Type Falsy values Gotchas
bool false -1 is truthy; "0" is falsy but "false" is truthy
int 0 Overflow silently becomes float; use PHP_INT_MAX
float 0.0, -0.0 Never compare for equality — use epsilon; NAN != NAN
string "", "0" No native Unicode — use mbstring; "0" is falsy
null null isset() returns false for null; use ?Type or Type|null

Type Declarations

// Union types (PHP 8.0+)
function handle(int|string $value): string|false { }

// Intersection types (PHP 8.1+)
function process(Countable&ArrayAccess $data): void { }

// DNF types (PHP 8.2+)
function route((Logger&Handler)|NullHandler $h): void { }

// Nullable shorthand
function get(?int $id): ?string { }  // same as int|null

// Special return types
function loop(): never { while(true) {} }   // never returns
function fire(): void { }                    // returns nothing
Type Use for
mixed Accepts anything (avoid — be specific)
void Function returns nothing visible
never Function never returns (exit, throw, infinite loop)
iterable array|Traversable
callable Parameters/returns only — cannot type properties
self, static, parent Class context references

Type Juggling Pitfalls

// BAD: loose comparison — security holes
0 == "a"       // false in PHP 8 (was TRUE in PHP 7!)
"0" == false   // true — "0" is falsy
"" == null     // true
"0" == null    // false (inconsistent!)
"123" == "123.0" // true — numeric string comparison

// GOOD: always strict
0 === "a"      // false
"0" === false  // false

// Float precision trap
0.1 + 0.7 == 0.8   // FALSE — binary representation
floor((0.1 + 0.7) * 10) // 7, not 8!
// Use: abs($a - $b) < PHP_FLOAT_EPSILON

OOP

Rules Summary

Rule Detail
Always declare property types Untyped properties are error-prone
Use readonly for immutable data Can only be set once (PHP 8.1+)
Constructor promotion Reduces boilerplate — use for simple DTOs
private(set) Read public, write private (PHP 8.4+)
Dynamic properties deprecated PHP 8.2+ — declare all properties explicitly
Reading uninitialized typed property Throws Error
Interface methods must be public All of them
Small interfaces 1-3 methods — compose larger ones
Abstract for shared behavior Interfaces for contracts
final prevents extension Use when inheritance isn't intended
final class constants (8.1+) Prevent override in children
Use enums over class constants Type-safe, exhaustive matching (PHP 8.1+)
from() throws on invalid tryFrom() returns null
Enums can have methods and interfaces But no state (properties)

Detailed OOP code examples (classes, interfaces, traits, enums, visibility, magic methods): see resources/oop-patterns.md

Dependency Injection & SOLID

Principle Rule
S — Single Responsibility One class = one reason to change
O — Open/Closed Open for extension, closed for modification — use interfaces
L — Liskov Substitution Subtypes must be substitutable for their base types
I — Interface Segregation Many small interfaces > one large interface
D — Dependency Inversion Depend on abstractions (interfaces), not concretions
Rule Detail
Constructor injection Preferred — makes dependencies explicit
Type-hint interfaces Not concrete classes — enables swapping
DI container ≠ DI Containers are optional convenience; DI is the pattern
Avoid Service Locator Hiding dependencies inside a container = anti-pattern
final by default Mark classes final unless designed for extension

DI code examples and namespace patterns: see resources/oop-patterns.md

PSR Standards & Composer

PSR Standards

Standard Purpose
PSR-1 Basic coding standard — <?php tag, UTF-8, class naming
PSR-4 Autoloading — namespace maps to directory structure
PSR-12 / PER Extended coding style — indentation, braces, spacing
PSR-3 Logger interface (Psr\Log\LoggerInterface)
PSR-7 HTTP message interfaces (request/response)
PSR-11 Container interface (Psr\Container\ContainerInterface)
PSR-15 HTTP handlers and middleware

Composer

# Initialize project
composer init

# Add dependency
composer require monolog/monolog

# Install from lock file (deployment — deterministic)
composer install --no-dev --optimize-autoloader

# Update to latest compatible versions (development)
composer update

# Autoload — include once at entry point
require 'vendor/autoload.php';
Rule Detail
Commit composer.lock Ensures identical versions across team/environments
composer install in production Never composer update — use lock file
--no-dev in production Exclude dev dependencies
--optimize-autoloader / -o Converts PSR-4/PSR-0 to classmap for speed
PSR-4 autoloading Namespace App\ -> directory src/
composer dump-autoload -o Regenerate optimized autoload after changes
Security auditing composer audit checks for known vulnerabilities

Modern PHP 8.x Patterns

Key features by version:

Version Key Features
8.0 Match, named args, union types, constructor promotion, nullsafe ?->, attributes
8.1 Enums, readonly, fibers, intersection types, never, first-class callables
8.2 Readonly classes, DNF types, true/false/null types, trait constants
8.3 Typed class constants, #[Override], json_validate()
8.4 Property hooks, asymmetric visibility, #[Deprecated], lazy objects
8.5 Pipe operator |>, #[NoDiscard], (void) cast

Detailed code examples for all features, functions, generators, fibers, and attributes: see resources/modern-php.md

Error Handling

try {
    $result = riskyOperation();
} catch (NotFoundException $e) {
    return defaultValue();
} catch (ValidationException | AuthException $e) {
    log($e->getMessage());
    throw $e;
} finally {
    cleanup();  // always runs
}
Rule Detail
Catch \Throwable for everything Exception + Error
Use union catches catch (TypeA | TypeB $e) (PHP 8.0+)
Catch without variable catch (SpecificException) (PHP 8.0+)
Call parent::__construct() In custom exception classes
Log exceptions, don't display Never show stack traces to users
throw is an expression (8.0+) $x ?? throw new Exception()
Never use @ suppression Hides real problems; custom handlers still fire
PHP 7+ throws Error Not Exception — use \Throwable

Error Configuration

Setting Development Production
error_reporting E_ALL E_ALL
display_errors On Off
log_errors On On
error_log stderr syslog or file

Arrays & Strings

Arrays

$map = ['key' => 'value', 'other' => 42];
$list = [1, 2, 3];

// Destructuring
['name' => $name, 'age' => $age] = $userData;
[$first, , $third] = $list;  // skip second

// Spread (PHP 7.4+ for numeric, 8.1+ for string keys)
$merged = [...$array1, ...$array2];
Rule Detail
Null coalescing $map['key'] ?? $default
Keys are int|string only Objects/arrays cannot be keys
Iteration order preserved Arrays are ordered maps
Empty array is falsy if ($arr) checks non-empty

Strings

// Interpolation — use braces for clarity
$msg = "Hello {$user->name}, you have {$count} items";

// Heredoc (indented, PHP 7.3+)
$html = <<<HTML
    <div class="card">
        <h1>{$title}</h1>
    </div>
    HTML;

// Nowdoc — no interpolation
$sql = <<<'SQL'
    SELECT * FROM users WHERE id = :id
    SQL;
Rule Detail
Use {$var} in double-quoted Not ${var} (deprecated PHP 8.2)
Single-byte encoding Use mb_strlen(), mb_substr() for Unicode
=== for string comparison == does numeric coercion on numeric strings
Negative offset (7.1+) $str[-1] for last character

JSON

// Encode — always use JSON_THROW_ON_ERROR (PHP 7.3+)
$json = json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

// Decode — assoc arrays are faster than objects for data access
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

// Validate without decoding (PHP 8.3+)
if (json_validate($json)) { /* valid */ }

// Large integers — prevent precision loss
$data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
Flag Effect
JSON_THROW_ON_ERROR Throw JsonException instead of returning false/null
JSON_UNESCAPED_UNICODE Don't escape UTF-8 chars — smaller output
JSON_UNESCAPED_SLASHES Don't escape / — cleaner URLs
JSON_PRESERVE_ZERO_FRACTION Keep 10.0 instead of 10
JSON_BIGINT_AS_STRING Decode large ints as strings (prevent precision loss)
JSON_NUMERIC_CHECK Convert numeric strings to numbers — use cautiously (phone numbers!)
JSON_INVALID_UTF8_SUBSTITUTE Replace broken UTF-8 with U+FFFD (PHP 7.2+)
JSON_PRETTY_PRINT Human-readable output — dev/debug only
Rule Detail
Always JSON_THROW_ON_ERROR Never check json_last_error() manually
Input must be UTF-8 mb_convert_encoding() first if unsure
json_validate() (8.3+) Faster than decode when you just need validity
Assoc arrays over objects json_decode($json, true) — faster property access

Testing

Rule Detail
assertSame() over assertEquals() Strict comparison (type + value)
Data providers for table-driven tests #[DataProvider('method')] attribute
expectException() before the call Not after
Test naming *Test.php, method test*
PHPStan Levels 0-9 strictness; start low, increase per sprint
Psalm Adds taint analysis for security
CI gate Fail build on any new error — never ignore regressions

Full testing examples, static analysis setup, and version migration reference: see resources/testing-migration.md


Build & Deploy

Always Use Makefile

Before running composer install or any build command, check if a Makefile exists. If it does, use it.

Situation Action
Makefile exists with relevant target make deploy, make build, make test
Makefile exists, no matching target List targets, pick closest
No Makefile composer install, php artisan, etc.

Permissions & Ownership

stat -c '%U:%G' * | sort | uniq -c | sort -rn | head -5
chown -R <user>:<group> .

Temporary Files

File type Location
Build intermediates /tmp — never the project directory
Dependencies vendor/ (Composer standard)

Test File Placement

Test type Location
Temporary /tmp
Permanent, dir exists tests/ (follow existing structure)
Permanent, no dir Create tests/ at project root

Anti-Pattern Quick Reference

Anti-Pattern Better Alternative
No strict_types declare(strict_types=1) in every file
== comparison === everywhere
@ error suppression Try-catch or proper validation
No type declarations Type params, returns, properties, constants
catch (Exception) only catch (\Throwable) for Error too
Dynamic properties Declare explicitly (#[AllowDynamicProperties] if must)
Class constants for finite sets enum (PHP 8.1+)
Deep inheritance Interfaces + traits composition
__sleep() / __wakeup() __serialize() / __unserialize()
${var} interpolation {$var}
get_class() no arg $obj::class
switch fall-through match expression
Manual null chain Nullsafe ?->
array_key_exists + access ?? null coalescing
String constants Backed enums
Float == Epsilon: abs($a - $b) < PHP_FLOAT_EPSILON
global $var Dependency injection
extract() Explicit assignment
Implicit nullable param Explicit ?Type
(boolean) cast (bool)
json_last_error() checking JSON_THROW_ON_ERROR flag

Resources

Detailed code examples and extended references are organized in resource files:

  • resources/oop-patterns.md — OOP detailed code (classes, interfaces, traits, enums, visibility, magic methods), dependency injection examples, and namespace patterns
  • resources/modern-php.md — Modern PHP 8.x feature code (match, named args, readonly, pipe operator, deprecated/nodiscard), functions (arrow functions, closures, variadic, generators, fibers), and attributes
  • resources/testing-migration.md — Testing examples with PHPUnit and data providers, static analysis setup (PHPStan, Psalm), and PHP version migration reference (8.0-8.5)
Weekly Installs
1
Repository
smithery/ai
First Seen
5 days ago
Installed on
github-copilot1