# 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](<https://project.pages.drupalcode.org/mago/analyzer/index.md>) 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](<https://project.pages.drupalcode.org/mago/analyzer/phpstan-ignores/index.md>).

## 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:

1. the `web-root` of the Composer scaffold;
2. the project directory;
3. `web/`, `docroot/`, `html/` and `public/`, in that order;
4. `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 under `vendor/drupal`;
5. the nearest directory above the project directory, for a module cloned into a site, such as `modules/foo` in a core checkout or `web/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](<https://project.pages.drupalcode.org/mago/analyzer/deprecations/index.md#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](<https://project.pages.drupalcode.org/mago/coder/phpcs-ignores/index.md>).

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](<https://project.pages.drupalcode.org/mago/analyzer/phpstan-ignores/index.md#linter-rules>).

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.
