php-spellcheck/spellcheck-symfony-bundle
A Symfony bundle that integrates php-spellcheck/spellcheck with Symfony’s service container and configuration. It provides ready-to-use spellcheck services so you can validate text, forms, and custom workflows in your applications.
Install as a dev dependency — it is a build/CI tool, not a runtime library:
composer require --dev php-spellcheck/spellcheck-symfony-bundle
Before writing any configuration, run the diagnostic command. It is the single most useful entry point because it tells you which backends actually work on the machine, which dictionaries are discoverable, and how locales map to them:
bin/console spellcheck:doctor
First use case worth wiring: checking the translation catalogues. That is the highest-signal, lowest-noise target — every catalogue is spell-checked against the dictionary of its own locale, so a messages.fr.yaml typo is caught with a French dictionary rather than being flagged by an English one. Add the code check (class names, methods, properties, parameters, constants, docblocks, comments) against the code dictionary second, since that generates more initial noise on a legacy codebase.
The one thing to internalise up front: this is a Symfony bundle, not a Laravel package. There is no package discovery, no config/spellcheck.php, and no php artisan spellcheck:* commands. Under the hood it is a thin integration over php-spellcheck/spellcheck-core ^1.0, which is framework-agnostic. In a Laravel app you have two realistic paths:
If you are on Symfony for a service and Laravel elsewhere, the bundle is the natural fit on the Symfony side and the core library is the natural fit on the Laravel side.
CI-first, baseline-second. The intended workflow is to run the check in CI on pull requests and fail the build on new typos only. For an existing project, generate a baseline on day one so the current state is grandfathered in, commit it, and let the check fail only on additions. Keep the baseline as small as you can — every entry you fix is one fewer thing hiding in the file.
Pick the backend deliberately per environment.
wordlist is pure PHP and needs no system binary — ideal for slim CI images, containers, and ephemeral build agents.hunspell / aspell / ext-pspell give better dictionaries but require the binary and dictionary packs installed in the image. Pin this in your Dockerfile or CI setup step, not in a developer's local machine only, or CI and local results will diverge.Report format follows the consumer. Human-readable output for local runs; GitHub Actions annotations when running on GitHub so typos land as inline PR comments; GitLab Code Quality when the report is ingested by the merge request widget. Emit the format matching the CI system rather than parsing console text.
Local pre-commit hook. Run the same command as CI over changed files before committing. This catches most typos before they ever reach a pipeline and keeps the "fix typo, push, wait for CI" loop out of the workflow.
Locale mapping is configuration, not magic. The bundle checks each catalogue with its own locale's dictionary, which only works if your locale codes resolve to installed dictionaries. Verify the mapping via spellcheck:doctor and pin it explicitly rather than relying on fallback behaviour.
Lean on the built-in stripping. Placeholders, HTML, markdown code spans, URLs, e-mails and paths are stripped before checking while preserving the column position in the original message. This is what makes the tool usable against real translation files and docblocks — do not reimplement it, and do not disable it to "see more".
In Laravel, wire the core behind an Artisan command. A thin command that resolves the core checker from the container, points it at lang/ and app/, and returns a non-zero exit code on failure gives you the same workflow with idiomatic php artisan spellcheck. Add it to a Composer script or CI step. The six extension points the bundle autoconfigures in Symfony have direct equivalents in the core — you just bind them yourself.
No Laravel autodiscovery. Composer will install it, but nothing registers. Do not expect spellcheck:doctor to exist in a Laravel app. Plan for the core-library route from the start instead of discovering this after wiring config that is never read.
Keep it out of production. It is a --dev requirement. Make sure your deploy pipeline runs composer install --no-dev (or otherwise excludes dev dependencies) so the bundle and its dictionaries do not ship to production.
PHP and Symfony version floors. PHP 8.1 through 8.5; Symfony 5.4 LTS, 6.4 LTS, 7.x, or 8.x — and Symfony 8 itself requires PHP >= 8.4. If your app spans both, check the matrix before assuming one lockfile covers everything. The bundle deliberately avoids AbstractBundle, 6.1+ attributes, and autowiring, so it works under _defaults: autowire: false — do not reintroduce autowiring assumptions in your own wrapper services.
The wordlist backend is a trade-off, not a free lunch. It removes the binary dependency at the cost of dictionary quality. Expect more false negatives than hunspell. If typo detection quality matters more than image size, install hunspell and its dictionary packs.
ICU messages are expanded, and only textual branches are checked. Plural and select messages are unwrapped so each textual branch gets checked. A string that only ever appears in a non-textual branch will not be checked — do not read a clean run as "every byte of this catalogue was verified".
Baselines rot. A baseline entry that no longer corresponds to a real typo will silently suppress a new typo on the same line later. Regenerate or prune the baseline when files move, and review baseline diffs in code review the same way you review the code.
Column preservation depends on the stripping being intact. Inline-ignore directives, editor integrations, and annotation output all key off line/column in the original file. If you post-process messages before checking, or the message spans lines unexpectedly, expect off-by-N column reports.
Debugging checklist, in order: spellcheck:doctor for backend/dictionary/locale state; bump verbosity on the check command to see which files and dictionaries were used; confirm the locale code resolves (regional codes like en_US vs en are a common cause of "everything is a typo" or "nothing is checked"); confirm the binary is on PATH inside the container, not just on the host.
Prefer word lists over disabling checks. Project-specific jargon, product names and domain terms belong in a custom dictionary or ignore list. Disabling a check or widening the baseline to silence jargon is how the tool stops being useful.
Scope the paths on large codebases. Full catalogue plus full source scanning is not free. Restrict to changed files in pre-commit and full scans to CI, and cache dictionaries between runs where the CI system allows it.
How can I help you explore Laravel packages today?