php-modern-standards
Required Plugins
Superpowers plugin: MUST be active for all work using this skill. Use throughout the entire build pipeline — design decisions, code generation, debugging, quality checks, and any task where it offers enhanced capabilities. If superpowers provides a better way to accomplish something, prefer it over the default approach.
PHP Modern Standards
Production-grade PHP patterns for maintainable, testable, secure, high-performance applications.
Core Principle: Write type-safe, secure, performant PHP code following PSR standards with modern PHP 8+ features.
See subdirectories: references/security-patterns.md, examples/modern-php-patterns.php, examples/laravel-patterns.php
When to Use
✅ PHP 8+ applications ✅ OOP architecture ✅ Code security ✅ Testable systems ✅ Performance optimization ✅ Laravel conventions
File Structure
<?php
declare(strict_types=1);
namespace App\Domain\User;
use App\Domain\Shared\ValueObject;
final readonly class User
{
public function __construct(
private int $id,
private string $email,
) {
}
}
Rules: Always declare(strict_types=1), one class per file, namespace = directory, import all dependencies.
Cross-Platform File Naming (MANDATORY)
Code runs on Windows (dev), Ubuntu (staging), and Debian (production). Linux is case-sensitive:
- Directories: Use lowercase for config/utility dirs (
src/config/,src/lang/). Use PascalCase for module dirs matching namespace (src/HR/Services/,src/Auth/). - Class files: PascalCase matching class name (
StaffService.php,EmailService.php). - require/include: Must match EXACT case on disk.
../src/Config/database.phpwill fail on Linux if dir isconfig/. - Paths: Use
/(forward slash) in PHP code. Never hardcodeC:\...in application logic. UseDIRECTORY_SEPARATORor/which PHP handles cross-platform. - Temp files: Use
sys_get_temp_dir(), not hardcoded paths.
Type System
Strict Typing (Required)
declare(strict_types=1); // Always
function calculateTotal(int $quantity, float $price): float { }
function getUser(int $id): ?User { } // Nullable
function log(string $msg): void { } // Void
Modern Types
// Union types (PHP 8.0+)
function process(int|float $value): string|int { }
// Intersection types (PHP 8.1+)
function handle(Countable&Traversable $collection): void { }
// Never type (PHP 8.1+)
function terminate(): never { throw new RuntimeException(); }
// Short nullable (?Type not Type|null)
function getName(): ?string // ✓ CORRECT
Typed Properties (Required)
final class User
{
private int $id;
private string $email;
private ?string $nickname = null;
private array $roles = [];
}
Constructor Promotion
final readonly class User
{
public function __construct(
private int $id,
private string $email,
private ?string $nickname = null,
) {
}
}
Readonly (PHP 8.1+)
final readonly class Money
{
public function __construct(
public float $amount,
public string $currency,
) {
}
}
Modern Features
Enums (PHP 8.1+)
enum Status: string
{
case Pending = 'pending';
case Active = 'active';
public function label(): string
{
return match ($this) {
self::Pending => 'Pending',
self::Active => 'Active',
};
}
}
Match (PHP 8.0+)
$status = match ($code) {
200, 201 => 'success',
400, 422 => 'error',
default => 'unknown',
};
Named Arguments
new User(
id: 1,
email: 'user@example.com',
name: 'John',
);
Nullsafe Operator
$country = $user?->getAddress()?->getCountry();
Attributes
#[\Attribute]
final readonly class Route
{
public function __construct(
public string $path,
public string $method = 'GET',
) {
}
}
SOLID Principles
Single Responsibility
final readonly class UserValidator { }
final readonly class UserRepository { }
Open/Closed
interface PaymentGateway { }
final readonly class StripeGateway implements PaymentGateway { }
Dependency Inversion
public function __construct(
private PaymentGateway $gateway, // Interface, not concrete
) {
}
Control Flow
Happy Path Last
public function process(Order $order): void
{
if (!$order->isValid()) {
throw new InvalidOrderException();
}
// Happy path
$this->fulfillment->process($order);
}
Avoid else
if (!$user->isActive()) {
return null;
}
return $user->process();
Strict Comparison
if ($status === 'active') { } // ✓ CORRECT
if ($count !== 0) { }
if (in_array($role, $roles, true)) { }
Security
See references/security-patterns.md for complete guide.
Input Validation
final readonly class UserValidator
{
public function validate(array $data): ValidationResult
{
$errors = [];
if (!filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Invalid email';
}
$age = filter_var($data['age'] ?? null, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 13, 'max_range' => 120],
]);
if ($age === false) {
$errors['age'] = 'Invalid age';
}
return new ValidationResult($errors);
}
}
SQL Injection Prevention
// ✓ CORRECT
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
// ✗ WRONG
$query = "SELECT * FROM users WHERE email = '$email'"; // VULNERABLE!
XSS Protection
echo htmlspecialchars($userInput, ENT_QUOTES | ENT_HTML5, 'UTF-8');
echo json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP);
Password Handling
// Hash (Argon2id)
$hash = password_hash($plainPassword, PASSWORD_ARGON2ID, [
'memory_cost' => 65536,
'time_cost' => 4,
'threads' => 3,
]);
// Verify
if (password_verify($plainPassword, $hash)) {
if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
$newHash = password_hash($plainPassword, PASSWORD_ARGON2ID);
}
}
CSRF Protection
final readonly class CsrfProtection
{
public function generateToken(): string
{
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_token_time'] = time();
return $token;
}
public function validateToken(string $token): bool
{
if (!isset($_SESSION['csrf_token'])) {
return false;
}
if (time() - ($_SESSION['csrf_token_time'] ?? 0) > 7200) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
}
Performance
Generators
function readLargeFile(string $path): \Generator
{
$handle = fopen($path, 'r');
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
}
foreach (readLargeFile('large.csv') as $line) {
processLine($line);
}
SPL Data Structures
$queue = new \SplQueue();
$queue->enqueue('task');
$task = $queue->dequeue();
$pq = new \SplPriorityQueue();
$pq->insert('low', 1);
$pq->insert('high', 10);
Laravel Conventions
Routes
// URLs: kebab-case, Names: camelCase, Params: camelCase
Route::get('/open-source', [OpenSourceController::class, 'index'])
->name('openSource');
Controllers
// Plural for resources
final class PostsController extends Controller
{
public function index(): Response { }
public function show(Post $post): Response { }
public function store(StorePostRequest $request): Response { }
}
// Singular for single resources
final class ProfileController extends Controller
{
public function show(): Response { }
}
Models
final class User extends Model
{
protected $fillable = ['name', 'email'];
protected $hidden = ['password'];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'is_active' => 'boolean',
];
}
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
Class Design
Use final by Default
final readonly class User { } // Default
abstract class BaseController { } // Only when needed
Class Structure Order
final class Example
{
// 1. Constants
private const MAX = 3;
// 2. Properties (public → protected → private)
public readonly int $id;
private string $name;
// 3. Constructor
public function __construct(int $id) { }
// 4. Public methods
public function getName(): string { }
// 5. Private methods
private function helper(): void { }
}
Traits (Sparingly)
// One trait per line
final class Article
{
use Timestampable;
use Publishable;
}
Anti-Patterns (Avoid)
// ✗ No types
function process($data) { }
// ✗ Loose comparison
if ($value == 1) { }
// ✗ Switch for values
switch ($status) { }
// ✗ Globals
$GLOBALS['config'] = [];
// ✗ Redundant docblocks
/** @param string $name */
public function setName(string $name): void { }
PSR Standards
- PSR-1: Basic coding
- PSR-12: Style guide (follow this)
- PSR-4: Autoloading
- PSR-7: HTTP messages
- PSR-11: Container
- PSR-15: Request handlers
Tooling
- PHPStan: Static analysis (level 8+)
- PHP CS Fixer: PSR-12 formatting
- PHPUnit/Pest: Testing
Checklist
✅ declare(strict_types=1)
✅ Full type hints
✅ Readonly for immutable
✅ Final by default
✅ Match over switch
✅ Enums for fixed values
✅ Early returns
✅ Strict comparison (===)
✅ Input validation
✅ Prepared statements
✅ Output escaping
✅ Argon2id passwords
✅ Generators for large data
✅ PSR-12 compliant
References:
- PHP: https://www.php.net/manual/
- PSR: https://www.php-fig.org/psr/
- Modern PHP: https://phptherightway.com/