Aller au contenu principal
Retour au blog

Claude Craft #4 : Symfony 8.1, 126 commandes et 3 agents dédiés

Flavien Métivier28 juillet 20268 min

Symfony 8.1.0 est sorti le 29 mai 2026 avec un positionnement qui change la donne pour les équipes PHP : les applications sans cycle HTTP — workers asynchrones, CLI tools, pipelines de données — y sont désormais des cibles de première classe du framework. Pour les équipes qui utilisent déjà Claude Code au quotidien, cette évolution arrive au bon moment. Claude Craft v8.18.1 embarque depuis quelques mois une stack Symfony complète parmi ses 11 stacks officielles, avec 126 slash commands et 3 agents dédiés. Ce quatrième volet de la série installe Claude Craft sur un projet Symfony 8.1 réel et illustre trois workflows concrets — génération d'entité Doctrine, tests PHPUnit et déploiement Docker — du début à la fin.

Symfony 8.1 : quand le framework s'affranchit du cycle requête-réponse

Symfony a toujours su gérer les workers et les commandes Console, mais Symfony 8.1 pousse ce modèle plus loin. La configuration du framework distingue désormais les applications orientées HTTP des applications HTTP-less, avec des kernels allégés et des pipelines de boot distincts. Concrètement, un worker Symfony Messenger tournant en PHP 8.5 sous un Kubernetes Job n'a plus à charger Twig, le router ou le firewall — le gain en mémoire est immédiat sur les processus longue durée.

  • Workers Messenger — traitement de files RabbitMQ ou SQS sans bottleneck HTTP, avec retry configurable et supervision native
  • CLI tools Console — pipelines de données orchestrés par cron ou Kubernetes CronJob, avec input/output typés et exit codes exploitables
  • Consumers asynchrones — ingestion de webhooks avec idempotency key, transformation et persistence sans passer par une requête HTTP
  • Data pipelines — ETL en PHP 8.5 avec les property hooks pour des Value Objects expressifs et sans boilerplate

C'est précisément cette cible — des projets Symfony qui vont au-delà du classique controller → service → repository — que la stack Symfony de Claude Craft adresse avec ses agents dédiés. L'outillage suit la complexité du projet, pas l'inverse.

Claude Craft v8.18.1 et la stack Symfony : 126 commandes, 3 agents

Claude Craft est un gestionnaire de profils, de slash commands et d'agents spécialisés pour Claude Code. En version 8.18.1, il propose 11 stacks officiellement supportées — Laravel, Rails, FastAPI, Next.js, Django et Symfony parmi elles — chacune avec son ensemble de commandes contextualisées, ses agents préconfigurés et son CLAUDE.md intégré. La documentation complète est disponible sur thebeardedbearsas.github.io/claude-craft.

  • 126 slash commands couvrant Doctrine ORM, Symfony Messenger, Console, API Platform, Security, DI Container et Cache
  • Agent tech-lead — génère des entités, Value Objects, services et repositories en respectant les patterns architecturaux de ton CLAUDE.md
  • Agent accessibility-expert — crée les tests PHPUnit (unit + intégration) avec couverture totale et validation du contrat d'interface public
  • Agent devops-engineer — audite et génère Dockerfile multi-stage, docker-compose et workflows GitHub Actions alignés best practices 2026
  • CLAUDE.md préconfigurés — PSR-12, PHPStan niveau 9, architecture hexagonale et hooks pre-commit PHP CS Fixer inclus

Installation via Makefile : 3 cibles, projet prêt en 5 minutes

L'approche recommandée est d'encapsuler Claude Craft dans le Makefile du projet, de sorte que chaque développeur de l'équipe puisse bootstrapper son environnement avec une seule commande, quel que soit son OS. On passe systématiquement par Docker pour s'abstraire de l'environnement local.

# Makefile — ajoute ces cibles à la racine de ton projet Symfony 8.1
APP_NAME   ?= my-symfony-app
CC_VERSION ?= 8.18.1

.PHONY: cc-install cc-init cc-status

## Installe Claude Craft CLI dans le conteneur de dev
cc-install:
	docker compose run --rm app sh -c \
	  'command -v claude-craft >/dev/null 2>&1 || npm install -g claude-craft@$(CC_VERSION)'
	@echo "Claude Craft $(CC_VERSION) ready"

## Initialise la stack Symfony dans le projet
cc-init: cc-install
	docker compose run --rm app claude-craft init \
	  --stack symfony \
	  --php 8.5 \
	  --project $(APP_NAME) \
	  --agents tech-lead,accessibility-expert,devops-engineer
	@echo "Stack Symfony — 126 slash commands disponibles"

## Vérifie la configuration active
cc-status:
	docker compose run --rm app claude-craft status --verbose

Après make cc-init, Claude Craft génère automatiquement trois fichiers dans le projet : .claude/settings.json (permissions et hooks), CLAUDE.md (conventions Symfony 8.1 préconfigurées) et les trois fichiers d'agent dans .claude/agents/. Rien à éditer à la main — tu commites tout ça dans Git et chaque membre de l'équipe bénéficie instantanément du même contexte.

Workflow 1 — Générer une entité Doctrine avec le tech-lead agent

On part d'un projet e-commerce Symfony 8.1. Il faut une entité Product avec les contraintes métier correctes. On utilise la slash command dédiée directement dans Claude Code — le tech-lead agent prend le relais et applique les conventions du CLAUDE.md Symfony : constructeur privé, factory statique, pas de setters publics.

# Dans Claude Code (terminal intégré ou IDE)
/symfony:entity:generate \
  --name Product \
  --fields "name:string price:float category:Category stock:int" \
  --factory \
  --agent tech-lead

Besoin d'un expert Symfony ?

Réserver un appel →
<?php
// src/Entity/Product.php — généré par le tech-lead agent
declare(strict_types=1);

namespace App\Entity;

use App\Repository\ProductRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: ProductRepository::class)]
#[ORM\Table(name: 'product')]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank]
    #[Assert\Length(max: 255)]
    private string $name;

    #[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
    #[Assert\Positive]
    private float $price;

    #[ORM\ManyToOne(targetEntity: Category::class)]
    #[ORM\JoinColumn(nullable: false)]
    private Category $category;

    #[ORM\Column]
    #[Assert\PositiveOrZero]
    private int $stock = 0;

    private function __construct(
        string $name,
        float $price,
        Category $category,
    ) {
        $this->name = $name;
        $this->price = $price;
        $this->category = $category;
    }

    public static function create(
        string $name,
        float $price,
        Category $category,
    ): self {
        return new self($name, $price, $category);
    }

    public function restock(int $quantity): void
    {
        if ($quantity < 0) {
            throw new \InvalidArgumentException(
                sprintf('Quantity must be positive, %d given.', $quantity)
            );
        }
        $this->stock += $quantity;
    }

    public function getId(): ?int { return $this->id; }
    public function getName(): string { return $this->name; }
    public function getPrice(): float { return $this->price; }
    public function getCategory(): Category { return $this->category; }
    public function getStock(): int { return $this->stock; }
}

Ce que bin/console make:entity n'aurait pas généré : le constructeur privé, la factory statique, les contraintes de validation Symfony et le guard métier dans restock(). L'agent génère aussi la migration Doctrine et le ProductRepository correspondants en une seule passe, sans aller-retour.

Workflow 2 — Tests PHPUnit complets avec l'accessibility-expert agent

L'accessibility-expert dans Claude Craft Symfony a une mission précise : garantir que chaque use case est couvert par des tests qui valident à la fois le comportement PHP et le contrat d'interface public de la classe — ce qu'un consommateur extérieur (autre service, API Platform, worker) peut attendre de façon fiable. On lui passe l'entité fraîchement générée avec l'exigence de couverture totale.

# Dans Claude Code
/symfony:test:phpunit \
  --subject Product \
  --coverage 100 \
  --types "unit,integration" \
  --agent accessibility-expert
<?php
// tests/Unit/Entity/ProductTest.php — généré par l'accessibility-expert agent
declare(strict_types=1);

namespace App\Tests\Unit\Entity;

use App\Entity\Category;
use App\Entity\Product;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

#[CoversClass(Product::class)]
final class ProductTest extends TestCase
{
    #[Test]
    public function createValidProduct(): void
    {
        $category = $this->createMock(Category::class);
        $product = Product::create('Widget Pro', 29.99, $category);

        self::assertSame('Widget Pro', $product->getName());
        self::assertSame(29.99, $product->getPrice());
        self::assertSame(0, $product->getStock());
        self::assertSame($category, $product->getCategory());
    }

    #[Test]
    public function restockIncreasesStock(): void
    {
        $category = $this->createMock(Category::class);
        $product = Product::create('Widget', 9.99, $category);
        $product->restock(50);

        self::assertSame(50, $product->getStock());
    }

    #[Test]
    public function restockIsAdditive(): void
    {
        $product = Product::create('Widget', 9.99, $this->createMock(Category::class));
        $product->restock(30);
        $product->restock(20);

        self::assertSame(50, $product->getStock());
    }

    #[Test]
    #[DataProvider('negativeQuantities')]
    public function restockWithNegativeQuantityThrows(int $qty): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessageMatches('/must be positive/');

        $product = Product::create('Widget', 9.99, $this->createMock(Category::class));
        $product->restock($qty);
    }

    public static function negativeQuantities(): array
    {
        return [
            'minus one'      => [-1],
            'large negative' => [-999],
        ];
    }
}

Pour les tests d'intégration, l'agent génère un ProductApiTest basé sur ApiTestCase d'API Platform, avec les assertions JSON-LD, les codes HTTP attendus et la vérification des contraintes de validation via la réponse 422. La configuration phpunit.xml est mise à jour avec --coverage-min=100 pour que la CI bloque si la couverture descend sous le seuil.

Workflow 3 — Déploiement Docker supervisé par le devops-engineer

Le troisième agent intervient en fin de feature ou lors d'un audit infra. Il analyse le Dockerfile existant, l'aligne sur les best practices 2026 — PHP 8.5-fpm-alpine, build multi-stage, utilisateur non-root, séparation des stages deps/build/runner — et génère le workflow GitHub Actions correspondant.

# Dans Claude Code
/symfony:docker:review \
  --dockerfile Dockerfile \
  --compose docker-compose.prod.yml \
  --registry ghcr.io \
  --agent devops-engineer
# Dockerfile — généré/corrigé par le devops-engineer agent (Symfony 8.1 / PHP 8.5)
FROM php:8.5-fpm-alpine AS base

RUN apk add --no-cache icu-dev libpq-dev \
 && docker-php-ext-install intl pdo_pgsql opcache \
 && rm -rf /var/cache/apk/*

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

# ---- stage : dépendances ----
FROM base AS deps
WORKDIR /app
COPY composer.json composer.lock symfony.lock ./
RUN composer install \
      --no-dev \
      --optimize-autoloader \
      --no-scripts \
      --no-interaction

# ---- stage : build ----
FROM deps AS build
COPY . .
RUN APP_ENV=prod composer run-script post-install-cmd --no-dev \
 && php bin/console cache:warmup --env=prod --no-debug

# ---- stage : runner ----
FROM base AS runner
WORKDIR /app
COPY --from=build /app .

RUN addgroup -S symfony && adduser -S -G symfony symfony
USER symfony

EXPOSE 9000
CMD ["php-fpm", "--nodaemonize"]

En parallèle, le devops-engineer génère le .github/workflows/deploy.yml : build matrix PHP 8.5, scan Trivy avec exit-code: 1 sur les CVE HIGH/CRITICAL, push GHCR avec tag sha-${{ github.sha }} et déploiement conditionnel sur main. Aucun continue-on-error n'est ajouté — chaque step échoue fort dès qu'une vulnérabilité ou un échec de build est détecté, ce qui est le comportement attendu en production.

Ces trois workflows — entité Doctrine, tests PHPUnit, pipeline Docker — illustrent le principe directeur de Claude Craft : automatiser le boilerplate sans sacrifier les conventions architecturales que ton équipe a établies. Chaque agent connaît le contexte du projet via le CLAUDE.md généré à l'init, ce qui évite de répéter les règles à chaque prompt. La stack Symfony 8.1 est disponible dès maintenant dans Claude Craft v8.18.1 — documentation complète sur thebeardedbearsas.github.io/claude-craft. Le prochain volet de la série explorera les patterns Symfony Messenger — transports, middleware et workers supervisés — avec l'agent tech-lead en mode bounded context.

Cet article vous a plu ? Partagez-le !

Besoin d'un expert Symfony ?

20 ans d'expérience sur l'écosystème PHP/Symfony.