Journal — Symfony
Illustration : Fæ — PDM 1.0 · source
A Doctrine migration that cannot be generated, a login form that rejects everything, stylesheets served as HTML. Three blockers, three causes you cannot guess.
Three blockers hit while setting up a fresh project on Symfony 8. None of them is documented where you look for it, and all three cost me time. Here they are, with their cause and their fix.
The setSchema() method requires the DBAL Schema::edit() API
which is not available in the current DBAL version.
This feature requires doctrine/dbal ^4.5 or higher.
A crystal-clear message, except that doctrine/dbal 4.5 only exists as a development
version: impossible to install on a project kept at stable. You end up with a tool demanding a
dependency you cannot satisfy.
The cause is not DBAL but what calls it: doctrine/orm 3.6.8 and
doctrine/doctrine-bundle 3.3 use that API. Dropping back to ORM 3.6.7 and bundle
3.2 unblocks everything, and loses nothing.
The lesson goes beyond the case: when a neighbouring project works, its composer.lock is a
more reliable source than any search. I found the combination by reading the one from an application
already in production.
Right username, right password, systematic rejection. Looking at the HTML:
<input type="hidden" name="_csrf_token" value="csrf-token">
The token value is literally the string csrf-token. This is not a bug: it is the new
stateless token mechanism, where the value is only a marker that a Stimulus controller is
meant to replace in the browser.
The consequence: with no front-end build, no form gets through, login included. On a project that deliberately has no application JavaScript, that is a wall. Going back to session tokens takes three lines:
framework:
csrf_protection:
stateless_token_ids: []
The page renders unstyled, and the requested stylesheet returns HTML. The culprit: how PHP's built-in server was started.
php -S 127.0.0.1:8000 -t public public/index.php
Passing public/index.php as the router sends every request to the front controller,
including those aiming at a file that exists. You need a router that hands real files back:
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ('/' !== $path && is_file(__DIR__.'/../public'.$path)) {
return false;
}
require __DIR__.'/../public/index.php';
In passing, I had pinned the asset version to v1 to force a refresh. Bad idea: a hard-coded
value never changes, so the browser serves the same stylesheet forever. A versioning strategy based on
the file's modification date settles it for good, with no build and no manifest.
The three traps have one thing in common: the error message describes the symptom, never the cause. That is normal — but it is also why it helps to have met them before.
Migration Symfony jumps from 4.4 to 6.4 and FrameworkBundleAdminController disappears. An inventory of what breaks, module by module, and of what doesn't move.
Security Three patterns come back in almost every audit of a made-to-order module. None of them is exotic, all of them are avoidable, and reading the code is enough to spot them.
Takeover No access, no repository, no documentation. The audit method I apply before saying whether to repair or rebuild.