
Php
- 63 installs
- 19 repo stars
- Updated January 20, 2026
- miles990/claude-software-skills
Helps with ai & agent building tasks.
About
php is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- php
- AI & Agent Building
- AI-coding skill
Php by the numbers
- 63 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #6,243 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/miles990/claude-software-skills --skill phpAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 63 |
|---|---|
| repo stars | ★ 19 |
| Last updated | January 20, 2026 |
| Repository | miles990/claude-software-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
PHP
Overview
Modern PHP (7.4+/8.x) patterns including typed properties, attributes, and modern OOP features.
---
Modern PHP Features
Type System
<?php
declare(strict_types=1);
// Typed properties (PHP 7.4+)
class User
{
public string $id;
public string $email;
public ?string $name = null;
public bool $active = true;
public array $roles = [];
public DateTimeImmutable $createdAt;
// Constructor promotion (PHP 8.0+)
public function __construct(
public readonly string $email,
public readonly string $name,
private string $password
) {
$this->id = uniqid();
$this->createdAt = new DateTimeImmutable();
}
}
// Union types (PHP 8.0+)
function process(string|int $value): string|int
{
return is_string($value) ? strtoupper($value) : $value * 2;
}
// Intersection types (PHP 8.1+)
function processIterable(Countable&Iterator $items): int
{
return count($items);
}
// Return types
function findUser(string $id): ?User
{
return $this->repository->find($id);
}
function getUsers(): array
{
return $this->repository->findAll();
}
// Never return type (PHP 8.1+)
function fail(string $message): never
{
throw new RuntimeException($message);
}
// Nullable types
function setName(?string $name): void
{
$this->name = $name;
}Attributes (PHP 8.0+)
<?php
use Attribute;
// Define attribute
#[Attribute(Attribute::TARGET_PROPERTY)]
class Column
{
public function __construct(
public string $name,
public string $type = 'string',
public bool $nullable = false
) {}
}
#[Attribute(Attribute::TARGET_METHOD)]
class Route
{
public function __construct(
public string $path,
public string $method = 'GET'
) {}
}
#[Attribute(Attribute::TARGET_CLASS)]
class Entity
{
public function __construct(
public string $table
) {}
}
// Using attributes
#[Entity(table: 'users')]
class User
{
#[Column(name: 'id', type: 'uuid')]
public string $id;
#[Column(name: 'email', type: 'string')]
public string $email;
#[Column(name: 'name', nullable: true)]
public ?string $name;
}
class UserController
{
#[Route(path: '/users', method: 'GET')]
public function index(): array
{
return $this->userService->getAll();
}
#[Route(path: '/users/{id}', method: 'GET')]
public function show(string $id): User
{
return $this->userService->find($id);
}
}
// Reading attributes
$reflection = new ReflectionClass(User::class);
$attributes = $reflection->getAttributes(Entity::class);
foreach ($attributes as $attribute) {
$instance = $attribute->newInstance();
echo $instance->table; // 'users'
}Enums (PHP 8.1+)
<?php
// Basic enum
enum Status
{
case Pending;
case Active;
case Inactive;
public function label(): string
{
return match ($this) {
self::Pending => 'Pending Review',
self::Active => 'Active',
self::Inactive => 'Inactive',
};
}
}
// Backed enum (with values)
enum Role: string
{
case Admin = 'admin';
case Moderator = 'moderator';
case User = 'user';
public function permissions(): array
{
return match ($this) {
self::Admin => ['read', 'write', 'delete', 'admin'],
self::Moderator => ['read', 'write', 'delete'],
self::User => ['read', 'write'],
};
}
public static function fromString(string $value): self
{
return self::from($value);
}
public static function tryFromString(string $value): ?self
{
return self::tryFrom($value);
}
}
// Usage
$status = Status::Active;
echo $status->label(); // "Active"
$role = Role::Admin;
echo $role->value; // "admin"
$userRole = Role::from('user');
$permissions = $userRole->permissions();---
Object-Oriented Patterns
Traits
<?php
// Trait definition
trait Timestampable
{
protected ?DateTimeImmutable $createdAt = null;
protected ?DateTimeImmutable $updatedAt = null;
public function getCreatedAt(): ?DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): ?DateTimeImmutable
{
return $this->updatedAt;
}
public function touch(): void
{
$now = new DateTimeImmutable();
$this->createdAt ??= $now;
$this->updatedAt = $now;
}
}
trait SoftDeletable
{
protected ?DateTimeImmutable $deletedAt = null;
public function delete(): void
{
$this->deletedAt = new DateTimeImmutable();
}
public function restore(): void
{
$this->deletedAt = null;
}
public function isDeleted(): bool
{
return $this->deletedAt !== null;
}
}
// Using traits
class Document
{
use Timestampable;
use SoftDeletable;
public function __construct(
public string $title,
public string $content
) {
$this->touch();
}
}
// Trait conflict resolution
trait A
{
public function hello(): string
{
return 'Hello from A';
}
}
trait B
{
public function hello(): string
{
return 'Hello from B';
}
}
class MyClass
{
use A, B {
A::hello insteadof B;
B::hello as helloFromB;
}
}Interfaces and Abstract Classes
<?php
// Interface
interface Repository
{
public function find(string $id): ?object;
public function findAll(): array;
public function save(object $entity): void;
public function delete(string $id): void;
}
// Generic-style interface
interface Collection
{
public function add(mixed $item): void;
public function remove(mixed $item): bool;
public function contains(mixed $item): bool;
public function count(): int;
public function toArray(): array;
}
// Abstract class
abstract class BaseRepository implements Repository
{
public function __construct(
protected PDO $pdo,
protected string $table
) {}
abstract protected function hydrate(array $row): object;
public function find(string $id): ?object
{
$stmt = $this->pdo->prepare(
"SELECT * FROM {$this->table} WHERE id = :id"
);
$stmt->execute(['id' => $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->hydrate($row) : null;
}
public function findAll(): array
{
$stmt = $this->pdo->query("SELECT * FROM {$this->table}");
return array_map(
fn(array $row) => $this->hydrate($row),
$stmt->fetchAll(PDO::FETCH_ASSOC)
);
}
}
// Concrete implementation
class UserRepository extends BaseRepository
{
public function __construct(PDO $pdo)
{
parent::__construct($pdo, 'users');
}
protected function hydrate(array $row): User
{
return new User(
id: $row['id'],
email: $row['email'],
name: $row['name']
);
}
public function findByEmail(string $email): ?User
{
// Custom method
}
}---
Error Handling
<?php
// Custom exceptions
class AppException extends Exception
{
public function __construct(
string $message,
public readonly string $code,
public readonly array $context = [],
?Throwable $previous = null
) {
parent::__construct($message, 0, $previous);
}
}
class ValidationException extends AppException
{
public function __construct(
public readonly array $errors,
?Throwable $previous = null
) {
parent::__construct(
'Validation failed',
'VALIDATION_ERROR',
['errors' => $errors],
$previous
);
}
}
class NotFoundException extends AppException
{
public function __construct(
string $resource,
string $id,
?Throwable $previous = null
) {
parent::__construct(
"{$resource} not found: {$id}",
'NOT_FOUND',
['resource' => $resource, 'id' => $id],
$previous
);
}
}
// Result pattern
readonly class Result
{
private function __construct(
public mixed $value,
public ?string $error,
public bool $success
) {}
public static function success(mixed $value): self
{
return new self($value, null, true);
}
public static function failure(string $error): self
{
return new self(null, $error, false);
}
public function map(callable $fn): self
{
if (!$this->success) {
return $this;
}
return self::success($fn($this->value));
}
public function flatMap(callable $fn): self
{
if (!$this->success) {
return $this;
}
return $fn($this->value);
}
}
// Usage
function createUser(array $data): Result
{
if (empty($data['email'])) {
return Result::failure('Email is required');
}
try {
$user = new User($data['email'], $data['name'] ?? '');
$this->repository->save($user);
return Result::success($user);
} catch (Exception $e) {
return Result::failure($e->getMessage());
}
}---
Collections and Arrays
<?php
// Array functions
$users = [
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Charlie', 'age' => 35],
];
// Filter
$adults = array_filter($users, fn($u) => $u['age'] >= 30);
// Map
$names = array_map(fn($u) => $u['name'], $users);
// Reduce
$totalAge = array_reduce($users, fn($sum, $u) => $sum + $u['age'], 0);
// Find
$bob = array_filter($users, fn($u) => $u['name'] === 'Bob');
$bob = current($bob); // Get first match
// Sort
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
// Group by (custom)
function groupBy(array $items, string $key): array
{
return array_reduce($items, function ($groups, $item) use ($key) {
$groups[$item[$key]][] = $item;
return $groups;
}, []);
}
// Collection class
class Collection implements Countable, IteratorAggregate
{
public function __construct(private array $items = []) {}
public static function from(array $items): self
{
return new self($items);
}
public function map(callable $fn): self
{
return new self(array_map($fn, $this->items));
}
public function filter(callable $fn): self
{
return new self(array_filter($this->items, $fn));
}
public function reduce(callable $fn, mixed $initial = null): mixed
{
return array_reduce($this->items, $fn, $initial);
}
public function first(): mixed
{
return $this->items[array_key_first($this->items)] ?? null;
}
public function count(): int
{
return count($this->items);
}
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
public function toArray(): array
{
return $this->items;
}
}
// Usage
$result = Collection::from($users)
->filter(fn($u) => $u['age'] >= 30)
->map(fn($u) => $u['name'])
->toArray();---
Dependency Injection
<?php
// Interface definition
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
interface UserRepositoryInterface
{
public function find(string $id): ?User;
public function save(User $user): void;
}
// Service with constructor injection
class UserService
{
public function __construct(
private readonly UserRepositoryInterface $repository,
private readonly LoggerInterface $logger,
private readonly EventDispatcher $events
) {}
public function createUser(string $email, string $name): User
{
$this->logger->info('Creating user', ['email' => $email]);
$user = new User($email, $name);
$this->repository->save($user);
$this->events->dispatch(new UserCreated($user));
return $user;
}
}
// Simple container
class Container
{
private array $bindings = [];
private array $instances = [];
public function bind(string $abstract, callable $concrete): void
{
$this->bindings[$abstract] = $concrete;
}
public function singleton(string $abstract, callable $concrete): void
{
$this->bindings[$abstract] = function ($c) use ($abstract, $concrete) {
if (!isset($this->instances[$abstract])) {
$this->instances[$abstract] = $concrete($c);
}
return $this->instances[$abstract];
};
}
public function get(string $abstract): mixed
{
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]($this);
}
return $this->resolve($abstract);
}
private function resolve(string $class): object
{
$reflection = new ReflectionClass($class);
$constructor = $reflection->getConstructor();
if (!$constructor) {
return new $class();
}
$params = array_map(
fn(ReflectionParameter $p) => $this->get($p->getType()->getName()),
$constructor->getParameters()
);
return $reflection->newInstanceArgs($params);
}
}---
Related Skills
- [[backend]] - Laravel, Symfony
- [[database]] - Doctrine, Eloquent
- [[testing]] - PHPUnit, Pest
{
"name": "vendor/my-project",
"description": "Project description",
"type": "project",
"license": "MIT",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.2",
"ext-json": "*",
"ext-pdo": "*",
"guzzlehttp/guzzle": "^7.8",
"monolog/monolog": "^3.5",
"vlucas/phpdotenv": "^5.6",
"nesbot/carbon": "^3.0",
"ramsey/uuid": "^4.7"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
"mockery/mockery": "^1.6",
"phpstan/phpstan": "^1.10",
"squizlabs/php_codesniffer": "^3.8",
"friendsofphp/php-cs-fixer": "^3.41",
"fakerphp/faker": "^1.23"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"test:coverage": "phpunit --coverage-html coverage",
"lint": "phpcs --standard=PSR12 src/",
"lint:fix": "phpcbf --standard=PSR12 src/",
"analyze": "phpstan analyse src/ --level=8",
"cs-fix": "php-cs-fixer fix",
"check": [
"@lint",
"@analyze",
"@test"
]
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"allow-plugins": {
"php-http/discovery": true
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
PHPUnit Configuration Template
Usage: Copy to project root
-->
<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"
executionOrder="depends,defects"
failOnRisky="true"
failOnWarning="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerWarnings="true"
displayDetailsOnTestsThatTriggerNotices="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
<exclude>
<directory>src/Migrations</directory>
</exclude>
</source>
<coverage>
<report>
<html outputDirectory="coverage"/>
<clover outputFile="coverage/clover.xml"/>
<text outputFile="php://stdout"/>
</report>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<ini name="memory_limit" value="512M"/>
<ini name="error_reporting" value="-1"/>
</php>
<logging>
<junit outputFile="coverage/junit.xml"/>
</logging>
</phpunit>
PHP Templates
Project configuration templates for PHP applications.
Files
| Template | Purpose |
|---|---|
composer.json | Dependency management |
phpunit.xml | Test configuration |
Usage
Quick Start
# Copy templates
cp templates/composer.json ./composer.json
cp templates/phpunit.xml ./phpunit.xml
# Install dependencies
composer install
# Run tests
composer test
# Run full check
composer checkComposer Dependencies
Production
| Package | Purpose |
|---|---|
| guzzlehttp/guzzle | HTTP client |
| monolog/monolog | Logging |
| vlucas/phpdotenv | Environment variables |
| nesbot/carbon | Date/time handling |
| ramsey/uuid | UUID generation |
Development
| Package | Purpose |
|---|---|
| phpunit/phpunit | Testing |
| mockery/mockery | Mocking |
| phpstan/phpstan | Static analysis |
| php_codesniffer | PSR-12 linting |
| php-cs-fixer | Code formatting |
| fakerphp/faker | Test data |
Composer Scripts
composer test # Run tests
composer test:coverage # Run with coverage
composer lint # Check code style
composer lint:fix # Auto-fix style
composer analyze # Static analysis
composer cs-fix # Format code
composer check # Run all checksPHPUnit Configuration
Test Suites
| Suite | Directory |
|---|---|
| Unit | tests/Unit/ |
| Feature | tests/Feature/ |
| Integration | tests/Integration/ |
Coverage Output
| Format | Location |
|---|---|
| HTML | coverage/ |
| Clover XML | coverage/clover.xml |
| JUnit | coverage/junit.xml |
Project Structure
my-project/
├── composer.json
├── phpunit.xml
├── .env
├── .env.example
├── src/
│ ├── Controllers/
│ ├── Models/
│ ├── Services/
│ └── bootstrap.php
├── tests/
│ ├── Unit/
│ ├── Feature/
│ └── TestCase.php
├── public/
│ └── index.php
└── storage/
└── logs/Environment Setup
# Create .env
cp .env.example .env
# .env contents
APP_ENV=local
APP_DEBUG=true
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=Test Example
<?php
// tests/Unit/ExampleTest.php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
public function test_example(): void
{
$this->assertTrue(true);
}
}Customization
Add Laravel
composer require laravel/frameworkAdd Symfony
composer require symfony/framework-bundleChange PHP Version
{
"require": {
"php": ">=8.3"
}
}Change PHPStan Level
composer analyze -- --level=9Related skills
AI & Agent Buildingagents