๐ŸŒ Global Mirror โ€” Visit original CN site โ†’
Back to skills

Php Patterns

ASecurity

PHP development patterns and best practices. Covers Laravel, Symfony, modern PHP 8+, PSR standards, Composer, and testing with PHPUnit/Pest.

18 stars
0 votes
0 copies
3 views
Added 2/11/2026
developmentrustgophpsqlexpresstestingapisecurity

Works with

api

Security Analysis

A100/100

Scanned 2/12/2026

Install via CLI

$openskills install lee-to/ai-factory
Download Zip
SKILL.md
---
name: php-patterns
description: PHP development patterns and best practices. Covers Laravel, Symfony, modern PHP 8+, PSR standards, Composer, and testing with PHPUnit/Pest.
argument-hint: [topic: laravel|symfony|php8|psr|testing|security]
---

# PHP Patterns Guide

Modern PHP patterns and best practices for building robust applications.

## Topics

### Laravel (`/php-patterns laravel`)

**Project Structure:**
```
app/
โ”œโ”€โ”€ Console/Commands/        # Artisan commands
โ”œโ”€โ”€ Exceptions/              # Exception handlers
โ”œโ”€โ”€ Http/
โ”‚   โ”œโ”€โ”€ Controllers/         # Request handlers
โ”‚   โ”œโ”€โ”€ Middleware/          # Request/response filters
โ”‚   โ”œโ”€โ”€ Requests/            # Form requests (validation)
โ”‚   โ””โ”€โ”€ Resources/           # API resources
โ”œโ”€โ”€ Models/                  # Eloquent models
โ”œโ”€โ”€ Policies/                # Authorization policies
โ”œโ”€โ”€ Providers/               # Service providers
โ””โ”€โ”€ Services/                # Business logic
```

**Eloquent Best Practices:**
```php
// โœ… Good: Eager loading to prevent N+1
$users = User::with(['posts', 'profile'])->get();

// โŒ Bad: N+1 query problem
$users = User::all();
foreach ($users as $user) {
    echo $user->posts->count(); // Query per user!
}

// โœ… Good: Query scopes for reusable queries
class User extends Model
{
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('status', 'active');
    }

    public function scopeVerified(Builder $query): Builder
    {
        return $query->whereNotNull('email_verified_at');
    }
}

// Usage
$users = User::active()->verified()->get();
```

**Form Requests:**
```php
class StoreUserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'email' => ['required', 'email', 'unique:users'],
            'password' => ['required', 'min:12', 'confirmed'],
            'name' => ['required', 'string', 'max:255'],
        ];
    }
}

// Controller stays clean
public function store(StoreUserRequest $request): JsonResponse
{
    $user = User::create($request->validated());
    return response()->json($user, 201);
}
```

**API Resources:**
```php
class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toISOString(),
            'posts' => PostResource::collection($this->whenLoaded('posts')),
        ];
    }
}
```

### Symfony (`/php-patterns symfony`)

**Service Architecture:**
```php
// src/Service/UserService.php
#[AsService]
class UserService
{
    public function __construct(
        private UserRepository $userRepository,
        private PasswordHasherInterface $passwordHasher,
        private EventDispatcherInterface $dispatcher,
    ) {}

    public function createUser(CreateUserDTO $dto): User
    {
        $user = new User();
        $user->setEmail($dto->email);
        $user->setPassword(
            $this->passwordHasher->hashPassword($user, $dto->password)
        );

        $this->userRepository->save($user, flush: true);
        $this->dispatcher->dispatch(new UserCreatedEvent($user));

        return $user;
    }
}
```

**Repository Pattern:**
```php
// src/Repository/UserRepository.php
class UserRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, User::class);
    }

    public function findActiveByEmail(string $email): ?User
    {
        return $this->createQueryBuilder('u')
            ->andWhere('u.email = :email')
            ->andWhere('u.status = :status')
            ->setParameter('email', $email)
            ->setParameter('status', 'active')
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function save(User $entity, bool $flush = false): void
    {
        $this->getEntityManager()->persist($entity);
        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }
}
```

**DTOs with Validation:**
```php
// src/DTO/CreateUserDTO.php
class CreateUserDTO
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\Email]
        public readonly string $email,

        #[Assert\NotBlank]
        #[Assert\Length(min: 12)]
        public readonly string $password,

        #[Assert\NotBlank]
        #[Assert\Length(max: 255)]
        public readonly string $name,
    ) {}
}
```

### Modern PHP 8+ (`/php-patterns php8`)

**Constructor Property Promotion:**
```php
// โœ… PHP 8+ concise
class User
{
    public function __construct(
        public readonly int $id,
        public string $name,
        public string $email,
        private ?string $password = null,
    ) {}
}

// โŒ Old verbose style
class User
{
    public int $id;
    public string $name;

    public function __construct(int $id, string $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}
```

**Named Arguments:**
```php
// Clear intent
$user = new User(
    id: 1,
    name: 'John',
    email: 'john@example.com',
);

// Skip optional params
sendEmail(
    to: $user->email,
    subject: 'Welcome',
    // body uses default
);
```

**Match Expression:**
```php
// โœ… PHP 8+ match
$result = match($status) {
    'pending' => 'Awaiting review',
    'approved' => 'Ready to publish',
    'rejected' => 'Please revise',
    default => 'Unknown status',
};

// โŒ Verbose switch
switch($status) {
    case 'pending':
        $result = 'Awaiting review';
        break;
    // ...
}
```

**Enums:**
```php
enum OrderStatus: string
{
    case Pending = 'pending';
    case Processing = 'processing';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';

    public function label(): string
    {
        return match($this) {
            self::Pending => 'Pending',
            self::Processing => 'Processing',
            self::Shipped => 'Shipped',
            self::Delivered => 'Delivered',
            self::Cancelled => 'Cancelled',
        };
    }

    public function canCancel(): bool
    {
        return in_array($this, [self::Pending, self::Processing]);
    }
}

// Usage
$order->status = OrderStatus::Pending;
if ($order->status->canCancel()) {
    // ...
}
```

**Attributes:**
```php
#[Route('/api/users', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
public function list(): JsonResponse
{
    // ...
}

// Custom attribute
#[Attribute(Attribute::TARGET_PROPERTY)]
class Encrypted
{
    public function __construct(
        public string $algorithm = 'aes-256-cbc'
    ) {}
}

class User
{
    #[Encrypted]
    private string $ssn;
}
```

### PSR Standards (`/php-patterns psr`)

**PSR-4 Autoloading:**
```json
// composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/",
            "Tests\\": "tests/"
        }
    }
}
```

**PSR-12 Code Style:**
```php
<?php

declare(strict_types=1);

namespace App\Service;

use App\Repository\UserRepository;
use Psr\Log\LoggerInterface;

class UserService
{
    public function __construct(
        private UserRepository $repository,
        private LoggerInterface $logger,
    ) {
    }

    public function findUser(int $id): ?User
    {
        if ($id <= 0) {
            throw new InvalidArgumentException('ID must be positive');
        }

        return $this->repository->find($id);
    }
}
```

### Testing (`/php-patterns testing`)

**PHPUnit:**
```php
class UserServiceTest extends TestCase
{
    private UserService $service;
    private MockObject $repository;

    protected function setUp(): void
    {
        $this->repository = $this->createMock(UserRepository::class);
        $this->service = new UserService($this->repository);
    }

    public function testCreateUserHashesPassword(): void
    {
        // Arrange
        $dto = new CreateUserDTO(
            email: 'test@example.com',
            password: 'plainpassword',
            name: 'Test User',
        );

        $this->repository
            ->expects($this->once())
            ->method('save')
            ->with($this->callback(fn(User $user) =>
                password_verify('plainpassword', $user->getPassword())
            ));

        // Act
        $user = $this->service->createUser($dto);

        // Assert
        $this->assertEquals('test@example.com', $user->getEmail());
    }

    /**
     * @dataProvider invalidEmailProvider
     */
    public function testRejectsInvalidEmail(string $email): void
    {
        $this->expectException(ValidationException::class);

        new CreateUserDTO($email, 'password123', 'Name');
    }

    public static function invalidEmailProvider(): array
    {
        return [
            'empty' => [''],
            'no at sign' => ['invalid'],
            'no domain' => ['test@'],
        ];
    }
}
```

**Pest (Modern alternative):**
```php
test('user can be created', function () {
    $user = User::factory()->create();

    expect($user)
        ->toBeInstanceOf(User::class)
        ->id->toBeInt()
        ->email->toContain('@');
});

test('password is hashed on create')
    ->expect(fn() => User::factory()->create(['password' => 'secret']))
    ->password->not->toBe('secret');

it('validates email format', function (string $email) {
    expect(fn() => new CreateUserDTO($email, 'pass', 'name'))
        ->toThrow(ValidationException::class);
})->with(['', 'invalid', 'test@']);
```

### Security (`/php-patterns security`)

**Password Hashing:**
```php
// โœ… Good: Use password_hash
$hash = password_hash($password, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,
    'time_cost' => 4,
    'threads' => 3,
]);

// Verify
if (password_verify($inputPassword, $storedHash)) {
    // Valid
}

// โŒ Never use: md5, sha1, sha256 for passwords
```

**SQL Injection Prevention:**
```php
// โœ… PDO prepared statements
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);

// โœ… Eloquent (auto-escaped)
User::where('email', $email)->first();

// โœ… Query Builder
DB::table('users')->where('email', '=', $email)->first();

// โŒ NEVER: String interpolation
$pdo->query("SELECT * FROM users WHERE email = '$email'");
```

**XSS Prevention:**
```php
// โœ… Blade auto-escapes
{{ $userInput }}

// โŒ Raw output - only when certain it's safe
{!! $trustedHtml !!}

// โœ… Manual escaping
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
```

## Best Practices Summary

1. **Use strict types** - `declare(strict_types=1);`
2. **Type everything** - Parameters, returns, properties
3. **Prefer readonly** - Immutable by default
4. **Use enums** - Instead of string constants
5. **Dependency injection** - Constructor injection preferred
6. **Follow PSR-12** - Code style consistency
7. **Write tests** - PHPUnit or Pest
8. **Use static analysis** - PHPStan level 8+

Comments (0)

No comments yet. Be the first to comment!

Php Patterns (Grade A) - Claude Skill | Skills Directory

Stay up to date

Get the latest news on AI tools and skills delivered to your inbox.