Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ bin/configlet.exe

/vendor/
composer.lock
CLAUDE.local.md

# IDE Files
.idea
59 changes: 59 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this repo is

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.

## Common commands

```shell
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 configlet
```

`composer 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.

## Repository layout

- `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 by `configlet sync`; controls which canonical test cases are included/excluded (`include = false`) and lets you attach a `comment` explaining a deviation. Hand edits other than `include`/`comment` get 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.php` instead of `example.php`
- additional `.docs/hints.md` and `.meta/design.md` explaining pedagogical intent
- `.docs/introduction.md.tpl` may exist as the templated source for the generated `introduction.md`. A template pulls in one or more concepts' own `introduction.md` via `%{concept:<concept-slug>}` placeholders (see `concepts/<concept-name>/introduction.md`), and can add exercise-specific prose around those placeholders. **When a `.docs/introduction.md.tpl` exists, never hand-edit `.docs/introduction.md` directly — edit the `.tpl` and regenerate with `bin/configlet generate` (not `configlet fmt`, which will fail/overwrite it).**
- `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.json` lists 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.) `configlet` uses to generate per-exercise scaffolding.
- `bin/configlet` — the official Exercism tool (fetched via `bin/fetch-configlet`, run through `composer` scripts) that validates `config.json` against the exercise directories and formats metadata (`configlet fmt`, `configlet sync`, `configlet create`).
- `src/Exercism/Sniffs/` — a custom PHP_CodeSniffer sniff (`ExplainStrictTypesSniff`) enforced by `phpcs.xml` on 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.

## Coding standard specifics (phpcs.xml)

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/*.php` reference solutions, all `concept/*` exercises, and `hello-world`.
- The custom `ExplainStrictTypesSniff` requires strict-types declarations to carry an explanatory comment; it's excluded on test files, `.meta/*.php`, `src/*`, and `contribution/*.php`.
- `Squiz.Scope.MethodScope.Missing` is excluded for `concept/city-office` and `concept/windowing-system` (these intentionally use non-method function scope for teaching purposes).
- `use` statements must be alphabetically sorted.

## Adding/modifying exercises

1. Scaffold a new practice exercise: `bin/configlet create --practice-exercise <slug>` (creates `exercises/practice/<slug>/`).
2. Write/edit `.meta/example.php` (or `exemplar.php` for concept exercises) and the test file; mark canonical test cases you deliberately skip in `.meta/tests.toml` with `include = false` (and a `comment` explaining why).
3. There is a WIP test generator under `contribution/generator` (Symfony console app) usable via `composer -d contribution/generator install && contribution/generator/bin/console app:create-tests '<slug>'`, followed by `composer lint:fix`.
4. If you change an exercise's difficulty or add a practice exercise, run `bin/order-exercises.sh` to reorder `config.json` accordingly (requires `jq`).
5. If you add a new practice exercise that should stay in sync with `problem-specifications`, add its slug to `bin/auto-sync.txt` — `bin/auto-sync.sh` reads that list and runs `configlet sync` (update mode) only for the exercises named in it, so exercises left off the list are never auto-synced.
6. Run `composer ci` before opening a PR — this is what GitHub Actions enforces (PHP 8.2–8.4 across Linux/Windows/macOS).
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The `lint:check` is included in `composer ci` to run the CI checks locally.
- Follow the [PSR-12] coding style (Exercisms PHP track uses a slightly [modified][local-file-phpcs-config] version of [PSR-12]).
- Run `composer ci` to run CI checks locally before pushing.
- CI is run on all pull requests, it must pass the required checks for merge.
- CI is running all tests on PHP 8.1 to PHP 8.4 for Linux, Windows and MacOS.
- CI is running all tests on PHP 8.2 to PHP 8.4 for Linux, Windows and MacOS.

## Generating new practice exercises

Expand Down
29 changes: 29 additions & 0 deletions contribution/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this directory.

## Scope

This applies to `contribution/` only. It's a grab-bag of standalone maintainer tooling for the Exercism PHP track (see the parent `../CLAUDE.md` for the track itself) — not part of the track content shipped to students, not covered by the root `composer.json`/`phpcs.xml`, and not exercised by `composer ci`.

- `generator/` — a proof-of-concept Symfony console app that auto-generates exercise test files from `problem-specifications` canonical data, using `nikic/php-parser`. Has its own `composer.json` (PHP >=8.2, Symfony 7.0, PHPUnit 11) independent of the root project's dependencies.
- `checkDeprecatedExercises.php` — a standalone PHP script (no dependencies) with a hardcoded list of exercise slugs; it queries GitHub for a `.deprecated` marker on each in `exercism/problem-specifications` and reports which ones should be marked deprecated in the root `config.json` and then removed from the script's own list.

If a directory in here isn't listed above but shows up on disk anyway, check for a `CLAUDE.local.md` in this folder (gitignored, machine-specific) before assuming it's part of the project.

## Commands

```shell
composer -d contribution/generator install # install the generator's own dependencies
contribution/generator/bin/console app:create-tests '<slug>' # generate a test file for an exercise
composer lint:fix # fix style on the generated file, from repo root
```

```shell
php contribution/checkDeprecatedExercises.php # list exercises that problem-specifications has deprecated
```

## Notes

- The generator is explicitly a PoC ("Let me know what you think" in `generator/README.md`) — treat it as unmaintained/experimental, not a polished tool with guaranteed correctness.
- Generated test files still need to go through the normal track workflow (write/adjust `.meta/example.php`, `.meta/tests.toml`, `composer lint:fix`, `composer test:run -- <slug>`) described in the root `CLAUDE.md`.
Loading