All skills
jeffallan avatar

/php-pro

@efebc44
by jeffallanjeffallan/claude-skills12k stars
1,127

Use when building PHP applications with modern PHP 8.3+ features, Laravel, or Symfony frameworks. Invokes strict typing, PHPStan level 9, async patterns with Swoole, and PSR standards. Creates controllers, configures middleware, generates migrations, writes PHPUnit/Pest tests, defines typed DTOs and value objects, sets up dependency injection, and scaffolds REST/GraphQL APIs. Use when working with Eloquent, Doctrine, Composer, Psalm, ReactPHP, or any PHP API development.

Use this Skill: https://skilld.dev/gh/jeffallan/claude-skills/php-pro

This session only. Nothing lands on disk.

referencessymfony-patterns.md

≈2.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Symfony Patterns

Dependency Injection

<?php

declare(strict_types=1);

namespace App\Service;

use App\Repository\UserRepositoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\MailerInterface;

final readonly class UserService
{
    public function __construct(
        private UserRepositoryInterface $userRepository,
        private MailerInterface $mailer,
        private LoggerInterface $logger,
    ) {}

    public function createUser(string $email, string $password): User
    {
        $user = new User($email, password_hash($password, PASSWORD_ARGON2ID));

        $this->userRepository->save($user);
        $this->logger->info('User created', ['email' => $email]);

        return $user;
    }
}

Service Configuration (services.yaml)

# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true
        bind:
            string $projectDir: '%kernel.project_dir%'
            bool $isDebug: '%kernel.debug%'

    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    # Interface binding
    App\Repository\UserRepositoryInterface:
        class: App\Repository\DoctrineUserRepository

    # Service with specific configuration
    App\Service\PaymentService:
        arguments:
            $apiKey: '%env(PAYMENT_API_KEY)%'
            $timeout: 30

    # Tagged services
    App\EventSubscriber\:
        resource: '../src/EventSubscriber/'
        tags: ['kernel.event_subscriber']

Controllers with Attributes

<?php

declare(strict_types=1);

namespace App\Controller;

use App\DTO\CreateUserRequest;
use App\Entity\User;
use App\Service\UserService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/api/users', name: 'api_users_')]
final class UserController extends AbstractController
{
    public function __construct(
        private readonly UserService $userService,
    ) {}

    #[Route('', name: 'list', methods: ['GET'])]
    #[IsGranted('ROLE_USER')]
    public function list(): JsonResponse
    {
        $users = $this->userService->getAllUsers();

        return $this->json($users, Response::HTTP_OK, [], [
            'groups' => ['user:read'],
        ]);
    }

    #[Route('', name: 'create', methods: ['POST'])]
    #[IsGranted('ROLE_ADMIN')]
    public function create(
        #[MapRequestPayload] CreateUserRequest $request
    ): JsonResponse {
        $user = $this->userService->createUser(
            $request->email,
            $request->password
        );

        return $this->json($user, Response::HTTP_CREATED, [], [
            'groups' => ['user:read'],
        ]);
    }

    #[Route('/{id}', name: 'show', methods: ['GET'])]
    public function show(User $user): JsonResponse
    {
        $this->denyAccessUnlessGranted('view', $user);

        return $this->json($user, context: ['groups' => ['user:detail']]);
    }
}

DTOs with Validation

<?php

declare(strict_types=1);

namespace App\DTO;

use Symfony\Component\Validator\Constraints as Assert;

final readonly class CreateUserRequest
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\Email]
        public string $email,

        #[Assert\NotBlank]
        #[Assert\Length(min: 8, max: 100)]
        #[Assert\PasswordStrength(minScore: Assert\PasswordStrength::STRENGTH_MEDIUM)]
        public string $password,

        #[Assert\NotBlank]
        #[Assert\Length(min: 2, max: 100)]
        public string $name,

        #[Assert\Choice(choices: ['admin', 'user', 'moderator'])]
        public string $role = 'user',
    ) {}
}

final readonly class UpdateUserRequest
{
    public function __construct(
        #[Assert\Email]
        public ?string $email = null,

        #[Assert\Length(min: 2, max: 100)]
        public ?string $name = null,

        #[Assert\Type('bool')]
        public ?bool $isActive = null,
    ) {}
}

Event Subscribers

<?php

declare(strict_types=1);

namespace App\EventSubscriber;

use App\Event\UserRegisteredEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\MailerInterface;

final readonly class UserSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private MailerInterface $mailer,
        private LoggerInterface $logger,
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [
            UserRegisteredEvent::class => [
                ['sendWelcomeEmail', 10],
                ['logRegistration', 5],
            ],
        ];
    }

    public function sendWelcomeEmail(UserRegisteredEvent $event): void
    {
        $user = $event->getUser();
        // Send email logic
        $this->logger->info('Welcome email sent', ['user_id' => $user->getId()]);
    }

    public function logRegistration(UserRegisteredEvent $event): void
    {
        $this->logger->info('User registered', [
            'user_id' => $event->getUser()->getId(),
            'email' => $event->getUser()->getEmail(),
        ]);
    }
}

Custom Events

<?php

declare(strict_types=1);

namespace App\Event;

use App\Entity\User;
use Symfony\Contracts\EventDispatcher\Event;

final class UserRegisteredEvent extends Event
{
    public function __construct(
        private readonly User $user,
        private readonly \DateTimeImmutable $occurredAt = new \DateTimeImmutable(),
    ) {}

    public function getUser(): User
    {
        return $this->user;
    }

    public function getOccurredAt(): \DateTimeImmutable
    {
        return $this->occurredAt;
    }
}

// Dispatching events
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

final readonly class UserService
{
    public function __construct(
        private EventDispatcherInterface $eventDispatcher,
    ) {}

    public function registerUser(string $email, string $password): User
    {
        $user = new User($email, $password);
        // ... save user

        $this->eventDispatcher->dispatch(new UserRegisteredEvent($user));

        return $user;
    }
}

Console Commands

<?php

declare(strict_types=1);

namespace App\Command;

use App\Service\UserService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
    name: 'app:user:create',
    description: 'Create a new user',
)]
final class CreateUserCommand extends Command
{
    public function __construct(
        private readonly UserService $userService,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addArgument('email', InputArgument::REQUIRED, 'User email')
            ->addArgument('password', InputArgument::REQUIRED, 'User password')
            ->addOption('admin', 'a', InputOption::VALUE_NONE, 'Make user admin');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        $email = $input->getArgument('email');
        $password = $input->getArgument('password');
        $isAdmin = $input->getOption('admin');

        $user = $this->userService->createUser($email, $password, $isAdmin);

        $io->success(sprintf('User created with ID: %d', $user->getId()));

        return Command::SUCCESS;
    }
}

Voters (Authorization)

<?php

declare(strict_types=1);

namespace App\Security\Voter;

use App\Entity\Post;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

final class PostVoter extends Voter
{
    public const VIEW = 'view';
    public const EDIT = 'edit';
    public const DELETE = 'delete';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return in_array($attribute, [self::VIEW, self::EDIT, self::DELETE])
            && $subject instanceof Post;
    }

    protected function voteOnAttribute(
        string $attribute,
        mixed $subject,
        TokenInterface $token
    ): bool {
        $user = $token->getUser();

        if (!$user instanceof User) {
            return false;
        }

        /** @var Post $post */
        $post = $subject;

        return match ($attribute) {
            self::VIEW => $this->canView($post, $user),
            self::EDIT => $this->canEdit($post, $user),
            self::DELETE => $this->canDelete($post, $user),
            default => false,
        };
    }

    private function canView(Post $post, User $user): bool
    {
        return $post->isPublished() || $this->isOwner($post, $user);
    }

    private function canEdit(Post $post, User $user): bool
    {
        return $this->isOwner($post, $user);
    }

    private function canDelete(Post $post, User $user): bool
    {
        return $this->isOwner($post, $user) || $user->hasRole('ROLE_ADMIN');
    }

    private function isOwner(Post $post, User $user): bool
    {
        return $post->getAuthor()->getId() === $user->getId();
    }
}

Message Handler (Messenger)

<?php

declare(strict_types=1);

namespace App\Message;

final readonly class SendWelcomeEmail
{
    public function __construct(
        public int $userId,
    ) {}
}

namespace App\MessageHandler;

use App\Message\SendWelcomeEmail;
use App\Repository\UserRepositoryInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
final readonly class SendWelcomeEmailHandler
{
    public function __construct(
        private UserRepositoryInterface $userRepository,
        private MailerInterface $mailer,
    ) {}

    public function __invoke(SendWelcomeEmail $message): void
    {
        $user = $this->userRepository->find($message->userId);

        if (!$user) {
            return;
        }

        // Send email logic
    }
}

// Dispatching messages
use Symfony\Component\Messenger\MessageBusInterface;

$this->messageBus->dispatch(new SendWelcomeEmail($user->getId()));

Quick Reference

Component Purpose File Location
Controller HTTP handlers src/Controller/
Service Business logic src/Service/
Repository Data access src/Repository/
Event Domain events src/Event/
EventSubscriber Event handlers src/EventSubscriber/
Command CLI commands src/Command/
Voter Authorization src/Security/Voter/
Message Async messages src/Message/
MessageHandler Message handlers src/MessageHandler/
DTO Data transfer src/DTO/

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk CRITICAL
  • Gen Agent Trust Hub16d

    This skill provides a comprehensive environment for senior PHP development, including patterns for Laravel, Symfony, and asynchronous programming with Swoole and ReactPHP. While automated scanners flagged the documentation link and SKILL.md file, manual analysis indicates the content consists of standard enterprise PHP development patterns and references to the author's own GitHub documentation pages.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    6 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at efebc44. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 5 months ago
Other metadata
metadata
{
  "author": "https://github.com/Jeffallan",
  "version": "1.1.0",
  "domain": "language",
  "triggers": "PHP, Laravel, Symfony, Composer, PHPStan, PSR, PHP API, Eloquent, Doctrine",
  "role": "specialist",
  "scope": "implementation",
  "output-format": "code",
  "related-skills": "fullstack-guardian, fastapi-expert"
}
  • Testing
  • php
  • laravel
  • symfony
  • phpstan
  • orm
  • dependency-injection
  • rest-api
  • graphql
  • typed-dto

README badge

README badge for jeffallan/claude-skills/php-pro

Writes PHP 8.3+ code with strict typing, PHPStan level 9 validation, and enterprise patterns for Laravel or Symfony. Generates typed DTOs, services with dependency injection, controllers, migrations, and PHPUnit/Pest tests enforcing 80%+ coverage before delivery.

Generated from the current SKILL.md.

Does this skill support PHP versions before 8.3?
No. PHP Pro requires PHP 8.3+ and enforces modern language features like readonly properties, enums, and strict typing.
What frameworks does this skill work with?
Laravel and Symfony are the primary targets. The skill includes reference guides for both frameworks' patterns (services, repositories, DI, events, commands).
Does this skill enforce static analysis?
Yes. PHPStan level 9 is mandatory before delivery, and all code must pass with zero errors. Tests must achieve 80%+ coverage.
Can this skill set up async code?
Yes. The skill includes patterns for Swoole, ReactPHP, and fibers for async PHP workflows.
What testing frameworks does this skill use?
PHPUnit and Pest. The skill includes test structure templates and enforces 80%+ code coverage before delivery.

Generated from the current SKILL.md. These answers refresh after source changes.