This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is the Exercism PHP track: the source of truth for every PHP exercise served on exercism.org. It is not an application — it's a curated set of independent exercise directories (practice + concept exercises), each containing a problem statement, a test suite, and a reference solution, plus tooling to keep them in sync with Exercism's cross-language problem-specifications and CI-verify that every reference solution actually passes its own tests.
composer install # install dependencies, fetches bin/configlet
composer ci # full local CI: configlet fmt + lint:check + tests:run — run before pushing
composer test:run # run every exercise's tests against its reference solution
composer test:run -- book-store # run tests for a single exercise (glob supported, e.g. "b*")
composer lint:check # phpcs — check PSR-12-derived style
composer lint:fix # phpcbf — autofix style issues
composer configlet:fmt # normalize exercise metadata files via configletcomposer test:run works by copying an exercise directory to a temp dir, overlaying the reference solution (.meta/example.php for practice exercises, .meta/exemplar.php for concept exercises) on top of the stub, stripping markTestSkipped() calls from the test file, and running PHPUnit directly (bin/test.sh). Always use composer test:run -- <exercise-slug> to test a single exercise rather than invoking phpunit directly — running the test file in place tests the stub, not the reference solution.
exercises/practice/<slug>/— one dir per practice exercise:<PascalName>.php— stub the student fills in<PascalName>Test.php— PHPUnit test suite (student-facing).docs/introduction.md,.docs/instructions.md— problem statement (often generated, see below).meta/example.php— reference solution used by CI (may instead be a.meta/example/directory when the solution needs multiple files).meta/tests.toml— auto-generated byconfiglet sync; controls which canonical test cases are included/excluded (include = false) and lets you attach acommentexplaining a deviation. Hand edits other thaninclude/commentget wiped on regeneration..meta/config.json— per-exercise metadata (authors, files, etc.)
exercises/concept/<slug>/— same idea but for concept exercises, which teach one specific language concept:- reference solution is
.meta/exemplar.phpinstead ofexample.php - additional
.docs/hints.mdand.meta/design.mdexplaining pedagogical intent .docs/introduction.md.tplmay exist as the templated source for the generatedintroduction.md. A template pulls in one or more concepts' ownintroduction.mdvia%{concept:<concept-slug>}placeholders (seeconcepts/<concept-name>/introduction.md), and can add exercise-specific prose around those placeholders. When a.docs/introduction.md.tplexists, never hand-edit.docs/introduction.mddirectly — edit the.tpland regenerate withbin/configlet generate(notconfiglet fmt, which will fail/overwrite it).
- reference solution is
concepts/<concept-name>/— the concept glossary (about.md,links.md) that concept exercises reference by slug (e.g.basic-syntax,arrays); a concept exercise's.meta/config.jsonlists which concepts it teaches and which are prerequisites.config.json(repo root) — the master Exercism track manifest: registers every exercise, its UUID, concepts/prerequisites, difficulty, and the file-role patterns (%{pascal_slug}.php, etc.)configletuses to generate per-exercise scaffolding.bin/configlet— the official Exercism tool (fetched viabin/fetch-configlet, run throughcomposerscripts) that validatesconfig.jsonagainst the exercise directories and formats metadata (configlet fmt,configlet sync,configlet create).src/Exercism/Sniffs/— a custom PHP_CodeSniffer sniff (ExplainStrictTypesSniff) enforced byphpcs.xmlon top of PSR-12.contribution/— auxiliary, not-fully-maintained tooling (e.g. a Symfony-based test generator, a deprecated-exercise checker); treat as separate from the main track content.
Style is PSR-12 with these deviations:
- Namespace/multiple-class-per-file rules are relaxed (exercises are single-file, namespace-free by convention).
declare(strict_types=1)is required on practice-exercise solution files but is explicitly excluded on.meta/*.phpreference solutions, allconcept/*exercises, andhello-world.- The custom
ExplainStrictTypesSniffrequires strict-types declarations to carry an explanatory comment; it's excluded on test files,.meta/*.php,src/*, andcontribution/*.php. Squiz.Scope.MethodScope.Missingis excluded forconcept/city-officeandconcept/windowing-system(these intentionally use non-method function scope for teaching purposes).usestatements must be alphabetically sorted.
- Scaffold a new practice exercise:
bin/configlet create --practice-exercise <slug>(createsexercises/practice/<slug>/). - Write/edit
.meta/example.php(orexemplar.phpfor concept exercises) and the test file; mark canonical test cases you deliberately skip in.meta/tests.tomlwithinclude = false(and acommentexplaining why). - There is a WIP test generator under
contribution/generator(Symfony console app) usable viacomposer -d contribution/generator install && contribution/generator/bin/console app:create-tests '<slug>', followed bycomposer lint:fix. - If you change an exercise's difficulty or add a practice exercise, run
bin/order-exercises.shto reorderconfig.jsonaccordingly (requiresjq). - If you add a new practice exercise that should stay in sync with
problem-specifications, add its slug tobin/auto-sync.txt—bin/auto-sync.shreads that list and runsconfiglet sync(update mode) only for the exercises named in it, so exercises left off the list are never auto-synced. - Run
composer cibefore opening a PR — this is what GitHub Actions enforces (PHP 8.2–8.4 across Linux/Windows/macOS).