ACTIVATE when the user wants to build, implement, or develop any new feature, endpoint, or user story using TDD, test-first, or red-green-refactor methodology. ACTIVATE for the '/feature-dev' command. ACTIVATE whenever 'TDD', 'test first', 'red-green-refactor', or 'itérations' appears alongside building something new. Covers: breaking features into small cross-layer TDD iterations, the red-green-refactor cycle, keeping the app working after each step, bug-fix-first-test workflow. DO NOT use for: writing a single test for existing code, test naming conventions (see php-test-conventions), general PHP/Symfony questions.
From phpnpx claudepluginhub fabiensalles/claude-marketplace --plugin phpThis skill uses the workspace's default tool permissions.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Designs KPI dashboards with metrics selection (MRR, churn, LTV/CAC), visualization best practices, real-time monitoring, and hierarchy for executives, operations, and product teams.
Transforms raw data into narratives with story structures, visuals, and frameworks for executive presentations, analytics reports, and stakeholder communications.
For test writing conventions (DAMP, test doubles, factories, annotations), see
php-test-conventions.
The key difference from standard TDD: each iteration cuts across ALL layers (API, Domain, SPI) rather than completing one layer at a time.
Iteration 1: Controller stub + first Domain behavior
↓
Iteration 2: Domain logic + SPI interface
↓
Iteration 3: Back to Controller → complete happy path
↓
Iteration 4: Edge case handling
↓
...continue until feature complete
Each iteration touches whatever layer is needed next:
IsBuyerEligibleForDiscount.canApplyDiscount() returns correct value. Implement: component method.Pattern: Happy path first (Controller -> Domain -> SPI -> back to Controller), then edge cases.
After each GREEN, check for duplication in both code and tests.
Data providers: When multiple tests have the same AAA structure and only differ in input data, consolidate. Criterion: no if needed in the test body.
// Same structure, different data → data provider
/**
* @test
* @dataProvider provideValidSelections
*/
public function formIsValid(array $selection): void
{
$form = $this->factory->create(MyForm::class);
$form->submit(['type' => $selection]);
self::assertTrue($form->isValid());
}
public static function provideValidSelections(): \Generator
{
yield 'initialPaymentOnly' => ['selection' => ['initial_payment']];
yield 'scheduledPaymentOnly' => ['selection' => ['scheduled_payment']];
}
Do NOT use a data provider when assertions differ between cases or setup is specific to each case.
At the end of each GREEN iteration, the app MUST work in dev AND prod. This is the most common mistake: tests pass but the app is broken.
// WRONG: referencing a non-existent template — test passes, app crashes
return new Response($this->twig->render('order/page.html.twig'));
// CORRECT: return empty Response OR create the template (even empty)
return new Response('');
GREEN checklist: template referenced? create the file. Service injected? declare in services.yaml. Interface used? implement or create a stub. Route added? verify with debug:router.
# After each GREEN, verify the app starts
docker compose exec php bin/console cache:clear
docker compose exec php bin/console lint:container
After each phase (RED or GREEN), run the specific test:
docker compose run --rm php ./vendor/bin/phpunit path/to/YourTest.php
# For vendor/acme/* packages:
cd vendor/acme/<package>/ && docker compose run --rm php ./vendor/bin/phpunit tests/Path/To/YourTest.php
When something is broken, follow this order before any code change:
// Mocks hide real issues:
$serializer = $this->prophesize(SerializerInterface::class);
$serializer->deserialize(...)->willReturn($expectedResponse); // Always passes!
// Real dependencies expose them:
$serializer = $this->createSerializer();
$result = $httpClient->__invoke(); // Fails if deserialization is broken
| Code Created | Tests Required |
|---|---|
| Feature class | Unit test for business logic |
| Controller | Functional test for endpoint |
| Repository | Integration test with database |
| Specification | Unit test for each rule |
| DTO/Contract | Serialization test (in package) |
tests/Unit/make php/tests # All tests
docker compose exec php ./vendor/bin/phpunit path/to/Test.php # Specific file
docker compose exec php ./vendor/bin/phpunit --filter=name # Filter by name