本文へ移動
cccskills
無料GitHub で公開

symfony-conventions

Consolidated Symfony conventions (Symfony 6.4 / 7.x): attribute routing, controllers as services, autowiring/DI, Form types, Validation constraints, Voters for authorization, the Serializer/DTO contract, and Messenger. Apply when writing or reviewing Symfony backend code. Activated automatically by symfony-plugin/stack.md as a convention skill for the development phase. Apply when: writing or reviewing Symfony controllers, services, forms, validators, voters, and serialization. Do NOT use this skill for: - PHP language idioms (strict_types, enums, readonly) — see php-foundation:php-conventions. - Doctrine queries / entities — see symfony-plugin:doctrine-patterns. - Testing — injected by the QA phase (WebTestCase/KernelTestCase) + php-foundation:php-testing.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md8.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Symfony Conventions (6.4 / 7.x)

This skill encodes the conventions used across modern Symfony projects. Apply alongside php-foundation:php-conventions (language idioms) when implementing features.

1. Attribute routing

Routes are declared with #[Route] attributes on controller actions — not YAML/XML for new code.

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/subscriptions', name: 'subscription_')]
final class SubscriptionController extends AbstractController
{
    #[Route('/{id}', name: 'show', methods: ['GET'], requirements: ['id' => '\d+'])]
    public function show(int $id): Response
    {
        // ...
    }
}

A class-level #[Route] sets a path prefix and name prefix. Always set methods: explicitly. Use requirements: to constrain route params.

2. Controllers as services + thin actions

  • Extend AbstractController (gives render(), json(), denyAccessUnlessGranted(), getUser()).
  • A controller with #[Route] is automatically registered as a service with autowired action arguments.
  • Keep actions thin: authorize → map/validate input → call a service → return a response. Business logic lives in services.
public function __construct(
    private readonly SubscriptionManager $subscriptions,
) {
}

#[Route('', name: 'store', methods: ['POST'])]
public function store(#[MapRequestPayload] CreateSubscriptionRequest $request): Response
{
    $this->denyAccessUnlessGranted('SUBSCRIPTION_CREATE');

    $subscription = $this->subscriptions->create($this->getUser(), $request);

    return $this->redirectToRoute('subscription_show', ['id' => $subscription->getId()]);
}

#[MapRequestPayload] deserializes + validates the request body into a DTO automatically.

3. Dependency injection / autowiring

  • Constructor injection only. Never property injection or @required setters for new code.
  • Services are autowired + autoconfigured by default (config/services.yaml _defaults: autowire: true, autoconfigure: true).
  • Inject scalars/parameters with #[Autowire]:
public function __construct(
    private readonly HttpClientInterface $http,
    #[Autowire('%env(STRIPE_SECRET)%')] private readonly string $stripeSecret,
) {
}
  • Bind/alias an interface to an implementation in services.yaml only when autowiring can't resolve it. Avoid manual service definitions otherwise.

4. Form types

Use Form types for HTML form workflows; constructor-inject collaborators.

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class SubscriptionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('plan', ChoiceType::class, ['choices' => Plan::cases()])
            ->add('startsAt', DateType::class, ['required' => false]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults(['data_class' => CreateSubscriptionRequest::class]);
    }
}

For JSON APIs prefer #[MapRequestPayload] DTOs over Form types.

5. Validation — constraints on DTOs/entities

Always declare validation with Constraint attributes; never validate inline in the controller.

namespace App\Dto;

use Symfony\Component\Validator\Constraints as Assert;

final class CreateSubscriptionRequest
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\Choice(['basic', 'pro', 'enterprise'])]
        public readonly string $plan,

        #[Assert\GreaterThanOrEqual('today')]
        public readonly ?\DateTimeImmutable $startsAt = null,
    ) {
    }
}

#[MapRequestPayload] runs validation automatically and returns 422 on failure. Otherwise call the ValidatorInterface in the service and use #[Valid] for nested objects.

6. Authorization — Voters

Centralize authorization in Voters; never inline role checks.

namespace App\Security\Voter;

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

final class SubscriptionVoter extends Voter
{
    public const VIEW = 'SUBSCRIPTION_VIEW';
    public const EDIT = 'SUBSCRIPTION_EDIT';

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

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        if (!$user instanceof User) {
            return false;
        }

        return $subject->getOwner() === $user || $user->isAdmin();
    }
}

Use via #[IsGranted('SUBSCRIPTION_EDIT', subject: 'subscription')] on the action, or $this->denyAccessUnlessGranted(...). Coarse, path-based rules go in security.yaml access_control.

Never inline if (in_array('ROLE_ADMIN', $user->getRoles())) — that bypasses the authorization layer and is untestable.

7. Serializer / DTO contract

Never serialize a raw entity with all fields. Use serialization groups or dedicated DTOs.

use Symfony\Component\Serializer\Attribute\Groups;

class Subscription
{
    #[Groups(['subscription:read'])]
    private int $id;

    #[Groups(['subscription:read'])]
    private string $plan;

    // no #[Groups] on stripeCustomerId → never serialized
}

// in the controller:
return $this->json($subscription, context: ['groups' => 'subscription:read']);

Document the exposed shape (DTO/group) so a SPA frontend agent can consume it.

8. Messenger — async work

Offload slow/side-effectful work to the message bus.

use Symfony\Component\Messenger\Attribute\AsMessageHandler;

final readonly class SendSubscriptionReceipt
{
    public function __construct(public int $subscriptionId) {}
}

#[AsMessageHandler]
final class SendSubscriptionReceiptHandler
{
    public function __construct(private readonly Mailer $mailer) {}

    public function __invoke(SendSubscriptionReceipt $message): void
    {
        // ...
    }
}

Dispatch with $this->bus->dispatch(new SendSubscriptionReceipt($id)). Configure transports (async) in config/packages/messenger.yaml.

9. Configuration

  • Service config in config/services.yaml; package config in config/packages/*.yaml.
  • Env values via %env(...)% (typed: %env(int:...)%, %env(bool:...)%). Secrets via the Secrets Vault (bin/console secrets:set). Never hardcode.
  • Bind app parameters under parameters: and inject with #[Autowire('%param%')].

10. Anti-patterns to avoid

Don'tDo
YAML/XML routing for new controllers#[Route] attributes
Property/setter injectionConstructor injection
Inline $validator->validate() in controllersConstraint attributes + #[MapRequestPayload] / #[Valid]
in_array('ROLE_X', $user->getRoles())Voter + #[IsGranted]
return $this->json($entity) (all fields)Serialization groups or a DTO
Business logic in the controllerService class
Hardcoded secrets in config%env(...)% / Secrets Vault

11. Checklist before completing development phase

  • All new routes use #[Route] with explicit methods:
  • Controllers extend AbstractController and are thin; logic in services
  • All collaborators injected via constructor
  • All input validated via Constraint attributes (no inline validation)
  • Authorization via Voters / access_control (no inline role checks)
  • Serialized output uses groups/DTOs (no raw-entity exposure)
  • No hardcoded secrets — %env(...)% only
  • php bin/console lint:container passes
  • vendor/bin/php-cs-fixer fix clean

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する