The worker¶
The Drupal rules and analyzer plugins are written in PHP. Mago starts PHP processes that run them,
called workers. drupal.mago.toml sets the worker up as an extension host:
[extension-hosts.drupal]
command = ["php", "-d", "xdebug.mode=off", "-d", "pcov.enabled=0", "vendor/drupal/mago/resources/worker.php"]
The same worker runs the linter rules for mago lint and the
analyzer plugins for mago analyze. The drupal and phpunit plugins are
on by default and don't need any configuration. The phpstan-ignores plugin is off until you turn
it on, see PHPStan ignores.
Xdebug and pcov¶
-d xdebug.mode=off turns off Xdebug, and -d pcov.enabled=0 turns off pcov, the code coverage
driver that PHPUnit can use. While either one is on, the worker takes up to twice as long, even when
it isn't being debugged or measured for coverage. PHP ignores each option when its extension isn't
installed.
An XDEBUG_MODE environment variable wins over xdebug.mode, so
XDEBUG_MODE=debug vendor/bin/mago analyze turns Xdebug back on, for example to step through a
rule or an analyzer check.
Arguments¶
The worker takes its arguments from the command of its extension host. Add each one as another
string after the path of worker.php. A project that sets its own command replaces the whole
list.
Finding the Drupal root¶
The analyzer reads services, config schema and hook documentation from the Drupal root. Without
--root, the worker looks for core/lib/Drupal.php in these directories, in order, and takes the
first one that holds it:
- the
web-rootof the Composer scaffold; - the project directory;
web/,docroot/,html/andpublic/, in that order;vendor/drupal, where core is installed by Composer as a plain package in a module's own repository. In that layout the worker also reads the project directory and the other packages undervendor/drupal;- the nearest directory above the project directory, for a module cloned into a site, such as
modules/fooin a core checkout orweb/modules/contrib/foo. The module's files are read with the rest of the site.
When none of them holds it, the worker uses the project directory as the root. Add
"--root=PATH" when the root is somewhere else.
Step 5 only applies to a project in one of the directories scanned by Drupal for extensions:
modules/, themes/, profiles/, the same three under sites/*/, and core's. It doesn't apply
under a vendor/ directory. A module linked into the site from somewhere else, such as a Composer
path repository, starts the worker in the link's target, so it needs --root.
A config file outside the project¶
Mago starts the worker in the directory of mago.toml, and some checks read files from there. When
the config file is kept outside the project, set working-directory to the project:
[extension-hosts.drupal]
command = ["php", "-d", "xdebug.mode=off", "-d", "pcov.enabled=0", "/path/to/project/vendor/drupal/mago/resources/worker.php", "--root=/path/to/drupal"]
working-directory = "/path/to/project"
Deprecation target¶
Add "--deprecations=12" to report only the Drupal deprecations that are removed in Drupal 12 or
earlier. See Deprecation target.
phpcs and PHPStan comments¶
Add "--phpcs-ignores" to let phpcs suppression comments, such as phpcs:ignore, drop the issues
of the rules and analyzer checks that port the Coder codes they ignore. See
phpcs ignores.
Add "--phpstan-ignores" to let @phpstan-ignore comments drop the issues of the linter rules that
port a PHPStan check, such as drupal/render-callback. The analyzer's own issues need the
phpstan-ignores plugin instead. See PHPStan ignores.
Both are off by default. drupal-core.mago.toml turns both on, since core's own phpcs and PHPStan
runs rely on these comments:
[extension-hosts.drupal]
command = ["php", "-d", "xdebug.mode=off", "-d", "pcov.enabled=0", "vendor/drupal/mago/resources/worker.php", "--phpcs-ignores", "--phpstan-ignores"]
A project that extends drupal-core.mago.toml and sets its own command repeats them, since the
command is replaced as a whole.
Your own worker file¶
worker.php turns its arguments into a call to DrupalExtension::create(). A project can make that
call in its own worker file instead, and run other Mago extensions in the same worker. They then
share one pool of PHP processes. With worker.php, each extension needs its own extension host,
and each host starts its own pool.
Save the file as .mago/extensions.php in the project:
<?php
declare(strict_types=1);
use DrupalMago\DrupalExtension;
use Mago\Sdk\Worker;
// Mago reads the worker's stdout as its protocol stream, so PHP errors and
// text that the autoloader prints go to stderr.
ini_set('display_errors', 'stderr');
ob_start(static function (string $output): string {
fwrite(STDERR, $output);
return '';
}, 1);
require dirname(__DIR__) . '/vendor/autoload.php';
(new Worker(
DrupalExtension::create(),
// Other extensions go here.
))->run();
Then point the command at it:
[extension-hosts.drupal]
command = ["php", "-d", "xdebug.mode=off", "-d", "pcov.enabled=0", ".mago/extensions.php"]
Each worker argument has a named argument of create():
| Worker argument | create() argument |
|---|---|
--root=PATH |
root: 'PATH' |
--deprecations=12 |
deprecations: 12 |
--phpcs-ignores |
phpcsIgnores: true |
--phpstan-ignores |
phpstanIgnores: true |
For example, DrupalExtension::create(root: '../drupal', deprecations: 12) does the same as
"--root=../drupal", "--deprecations=12".
Index cache¶
The analyzer caches what it reads from disk under the system temporary directory, in
mago-drupal-<uid>/. An entry is keyed by the modification times and sizes of its files, so the
next run picks up an edit. The key also holds the extension's code, the PHP version and the
symfony/yaml version, so an update of any of them doesn't read older entries.
MAGO_DRUPAL_CACHE=/dir moves the cache, and MAGO_DRUPAL_CACHE=0 turns it off.
A watch or editor session keeps some lists until its workers restart: the @internal and
@deprecated classes, the classes that use DependencySerializationTrait or declare a
#[TrustedCallback] method, and the properties that each trait reads without declaring them.
Restart the session after changing one of those.