# PHP Code Examples

Reference examples for each rule area. Read the relevant section when generating code for that area.

---

## General

```php
// ✅ Good
declare(strict_types=1);

class UserService
{
    public function __construct(
        private readonly UserRepository $userRepository,
    ) {}

    public function findById(int $id): UserDto
    {
        $user = $this->userRepository->findById($id);
        if ($user === null) {
            throw new NotFoundException("User $id not found");
        }
        return UserDto::fromArray($user);
    }
}

// ❌ Bad — no strict types, logic in global scope
$pdo = new PDO(...);
$user = $pdo->query("SELECT * FROM users WHERE id = $_GET[id]")->fetch();
echo $user['name'];
```

---

## Security

```php
// ✅ Good — prepared statement
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

// ✅ Good — safe HTML output
echo htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8');

// ❌ Bad — SQL injection
$result = $pdo->query("SELECT * FROM users WHERE email = '$email'");
```

---

## Database / Repository

```php
// ✅ Good
class UserRepository
{
    public function __construct(private readonly \PDO $pdo) {}

    public function findByEmail(string $email): ?array
    {
        $stmt = $this->pdo->prepare(
            'SELECT id, email, full_name FROM users WHERE email = :email AND deleted = 0'
        );
        $stmt->execute([':email' => $email]);
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    public function create(string $email, string $fullName, string $passwordHash): int
    {
        $stmt = $this->pdo->prepare(
            'INSERT INTO users (email, full_name, password_hash) VALUES (:email, :full_name, :password_hash)'
        );
        $stmt->execute([
            ':email'         => $email,
            ':full_name'     => $fullName,
            ':password_hash' => $passwordHash,
        ]);
        return (int) $this->pdo->lastInsertId();
    }
}
```

---

## Controller

```php
// ✅ Good
declare(strict_types=1);

class UserController
{
    public function __construct(private readonly UserService $userService) {}

    public function create(): void
    {
        $body = json_decode(file_get_contents('php://input'), true) ?? [];
        $email    = trim($body['email'] ?? '');
        $fullName = trim($body['full_name'] ?? '');
        $password = $body['password'] ?? '';

        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            http_response_code(400);
            echo json_encode(['error' => 'Invalid email']);
            return;
        }

        $user = $this->userService->create($email, $fullName, $password);
        http_response_code(201);
        echo json_encode($user);
    }
}
```

---

## Error Handling

```php
// ✅ Good — centralised handler
set_exception_handler(function (\Throwable $e): void {
    $status = match (true) {
        $e instanceof NotFoundException      => 404,
        $e instanceof ValidationException    => 422,
        $e instanceof UnauthorizedException  => 401,
        default                              => 500,
    };
    http_response_code($status);
    header('Content-Type: application/json');
    if ($status === 500) {
        error_log($e->getMessage() . ' ' . $e->getTraceAsString());
        echo json_encode(['error' => 'Internal server error']);
    } else {
        echo json_encode(['error' => $e->getMessage()]);
    }
});
```

---

## Testing

```php
class UserServiceTest extends TestCase
{
    public function testCreateThrowsOnDuplicateEmail(): void
    {
        $repo = $this->createMock(UserRepository::class);
        $repo->method('findByEmail')->willReturn(['id' => 1]);

        $service = new UserService($repo);

        $this->expectException(ValidationException::class);
        $service->create('dup@example.com', 'Test', 'password');
    }
}
```
