Agent skill
php-guide
PHP language guardrails, patterns, and best practices for AI-assisted development. Use when working with PHP files (.php), composer.json, or when the user mentions PHP. Provides type declaration patterns, Composer conventions, PSR standards, and testing guidelines specific to this project's coding standards.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/php-guide
Metadata
Additional technical details for this skill
- author
- samuel
- version
- 1.0
- category
- language
- language
- php
- extensions
- .php
SKILL.md
PHP Guide
Applies to: PHP 8.1+, Web Applications, APIs, CLIs, Microservices
Core Principles
- Strict Types Always: Every PHP file starts with
declare(strict_types=1) - Type Declarations Everywhere: All parameters, return types, and properties must have type declarations
- PSR Standards: Follow PSR-12 coding standard, PSR-4 autoloading, PSR-7 HTTP messages
- Composition Over Inheritance: Prefer interfaces, traits, and dependency injection over deep class hierarchies
- Modern PHP First: Use PHP 8.1+ features (enums, readonly properties, fibers, named arguments, match expressions)
Guardrails
Version & Dependencies
- Target PHP 8.1+ (enums, readonly properties, fibers, intersection types)
- Define all dependencies in
composer.jsonwith version constraints - Run
composer validateandcomposer auditbefore committing - Use
composer.lockfor applications (commit it), omit for libraries - Separate
require-devfor development-only dependencies - Never use
composer updatein production (usecomposer install --no-dev)
Code Style (PSR-12)
- Run PHP-CS-Fixer or PHP_CodeSniffer before every commit
- Naming:
PascalCaseclasses/enums,camelCasemethods/properties,UPPER_SNAKEconstants - One class per file, file name matches class name
- Opening braces on same line for control structures, next line for classes/methods
declare(strict_types=1)as first statement after<?phpin every file- No closing
?>tag in pure PHP files - Imports: one
useper declaration, grouped (classes, functions, constants), alphabetized
<?php
declare(strict_types=1);
namespace App\Domain\User;
use App\Domain\Exception\ValidationException;
use App\Domain\ValueObject\Email;
final class UserService
{
public function __construct(
private readonly UserRepositoryInterface $repository,
private readonly EventDispatcherInterface $dispatcher,
) {}
public function register(string $name, string $email): User
{
$emailVO = Email::fromString($email);
if ($this->repository->existsByEmail($emailVO)) {
throw ValidationException::duplicateEmail($email);
}
$user = User::create(name: $name, email: $emailVO);
$this->repository->save($user);
$this->dispatcher->dispatch(new UserRegistered($user->id));
return $user;
}
}
Type Declarations
- All function parameters MUST have type declarations
- All methods MUST declare return types (including
void) - Use union types (
string|int) instead ofmixedwhen possible - Use intersection types (
Countable&Iterator) for combined type constraints - Use
Type|nullfor nullable parameters (prefer explicit over?Type) - Use
neverreturn type for functions that always throw or exit - Avoid
mixed-- if truly needed, document why
Error Handling
- Never use
@error suppression operator - Convert PHP errors to exceptions with
set_error_handlerat bootstrap - Create domain-specific exception hierarchies extending a base exception
- Use specific exception types (not generic
\Exceptionor\RuntimeException) - Always include context in exception messages
- Catch specific exceptions, never bare
catch (\Throwable $e)without re-throwing - Use
previousparameter to chain exceptions
<?php
declare(strict_types=1);
namespace App\Domain\Exception;
abstract class DomainException extends \RuntimeException
{
public function __construct(
string $message,
public readonly string $errorCode = 'UNKNOWN',
int $code = 0,
?\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
}
final class NotFoundException extends DomainException
{
public static function forResource(string $resource, string $id): self
{
return new self(
message: sprintf('%s with ID "%s" not found', $resource, $id),
errorCode: 'NOT_FOUND',
);
}
}
Security
- All user input validated before processing (filter functions or validation libraries)
- All SQL queries use prepared statements with bound parameters (PDO or Doctrine DBAL)
- All output escaped for context:
htmlspecialchars()for HTML, parameterized for SQL - All file operations validate paths (
realpath()+ check against allowed directories) - Never use
eval(),exec(),system(),passthru(), or backtick operator with user input - Use
password_hash()withPASSWORD_ARGON2ID(orPASSWORD_BCRYPTminimum) - Set
session.cookie_httponly,session.cookie_secure,session.cookie_samesite - Use CSRF tokens for all state-changing requests
Project Structure
myproject/
├── src/ # Application source (PSR-4: App\)
│ ├── Domain/ # Business logic, entities, value objects
│ ├── Application/ # Use cases, command/query handlers
│ ├── Infrastructure/ # Framework, database, external services
│ └── Kernel.php
├── tests/
│ ├── Unit/ # Fast, isolated unit tests
│ ├── Integration/ # Tests with real dependencies
│ └── bootstrap.php
├── config/ # Configuration files
├── public/ # Web root (index.php entry point)
├── composer.json
├── composer.lock
├── phpunit.xml
├── phpstan.neon
└── .php-cs-fixer.php
- PSR-4 autoloading:
"App\\": "src/"incomposer.json - Domain layer has zero framework dependencies
- Infrastructure implements domain interfaces
- One class per file, directory structure mirrors namespace
- Keep
public/as the web root with a singleindex.phpfront controller
Key Patterns
Use PHP 8.1+ features idiomatically:
- Enums: Backed enums with methods for labels, state machines, role-based permissions
- Readonly classes (PHP 8.2+): Value objects, DTOs, Money pattern -- all properties implicitly readonly
- Named arguments: Improve readability for constructors and functions with many parameters
- Match expressions: Prefer over
switch-- strict comparison, expression-based, no fallthrough - Fibers: Cooperative multitasking foundation for async frameworks
Enums (PHP 8.1+)
<?php
declare(strict_types=1);
enum UserRole: string
{
case Admin = 'admin';
case Editor = 'editor';
case Viewer = 'viewer';
public function label(): string
{
return match ($this) {
self::Admin => 'Administrator',
self::Editor => 'Editor',
self::Viewer => 'Viewer',
};
}
/** @return list<Permission> */
public function permissions(): array
{
return match ($this) {
self::Admin => Permission::cases(),
self::Editor => [Permission::Read, Permission::Write],
self::Viewer => [Permission::Read],
};
}
}
Readonly Classes (PHP 8.2+)
<?php
declare(strict_types=1);
readonly class Money
{
public function __construct(
public int $amount,
public string $currency,
) {}
public function add(self $other): self
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException('Cannot add different currencies');
}
return new self($this->amount + $other->amount, $this->currency);
}
}
See references/patterns.md for additional patterns: dependency injection, repository pattern, value objects, command/handler, fibers, match expressions, and tooling configurations.
Testing
Standards
- Test files:
*Test.phpintests/mirroringsrc/structure - Test methods:
test_<unit>_<scenario>_<expected>or#[Test]attribute - PHPUnit as primary framework; Pest as alternative
- Coverage: >80% business logic, >60% overall
- Use data providers for parameterized tests
- Mock external dependencies with Mockery or PHPUnit mocks
- No database or network calls in unit tests
- Each test method tests one behavior
PHPUnit with Data Providers
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\ValueObject;
use App\Domain\Exception\ValidationException;
use App\Domain\ValueObject\Email;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class EmailTest extends TestCase
{
#[Test]
#[DataProvider('validEmailProvider')]
public function it_accepts_valid_emails(string $input): void
{
$email = Email::fromString($input);
self::assertSame(strtolower($input), $email->toString());
}
/** @return iterable<string, array{string}> */
public static function validEmailProvider(): iterable
{
yield 'simple' => ['[email protected]'];
yield 'with subdomain' => ['[email protected]'];
yield 'with plus' => ['[email protected]'];
yield 'uppercase' => ['[email protected]'];
}
#[Test]
#[DataProvider('invalidEmailProvider')]
public function it_rejects_invalid_emails(string $input): void
{
$this->expectException(ValidationException::class);
Email::fromString($input);
}
/** @return iterable<string, array{string}> */
public static function invalidEmailProvider(): iterable
{
yield 'empty string' => [''];
yield 'no at sign' => ['userexample.com'];
yield 'no domain' => ['user@'];
yield 'no local part' => ['@example.com'];
yield 'spaces' => ['user @example.com'];
}
}
Tooling
Required Dev Dependencies
phpunit/phpunit^10.0 -- testingphpstan/phpstan^1.10 -- static analysis (level 8)friendsofphp/php-cs-fixer^3.0 -- code stylemockery/mockery^1.6 -- mockingvimeo/psalm^5.0 -- alternative static analysis
PHPStan: Always Level 8
# phpstan.neon
parameters:
level: 8
paths:
- src
treatPhpDocTypesAsCertain: false
checkMissingIterableValueType: true
See references/patterns.md for full composer.json, phpstan.neon, and .php-cs-fixer.php configurations.
Essential Commands
composer install # Install dependencies
composer validate # Validate composer.json
composer audit # Check for security vulnerabilities
php vendor/bin/phpunit # Run all tests
php vendor/bin/phpunit --coverage-text # With coverage summary
php vendor/bin/phpstan analyse # Static analysis (level 8)
php vendor/bin/psalm # Alternative static analysis
php vendor/bin/php-cs-fixer fix # Auto-fix code style
php vendor/bin/php-cs-fixer fix --dry-run --diff # Preview fixes
References
For detailed code examples, see:
- references/patterns.md -- Enum patterns, readonly classes, dependency injection, repository pattern, type declarations, error handling, modern PHP features, mocking, tooling configurations
External References
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?