Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Latest
------

## Changes
* [#269](https://github.com/cleverage/process-bundle/issues/269) Composer: `symfony/process`, `symfony/stopwatch`, `symfony/var-dumper` and `symfony/monolog-bundle` are no longer required, they are suggested (applications using them must require them). Without `symfony/process` (`CommandRunnerTask`, `ProcessLauncherTask`) or `symfony/stopwatch` (`StopwatchTask`), the container still compiles and these tasks fail with an explicit message (`UnavailableServicesCompilerPass`); without `symfony/var-dumper`, nothing is dumped (as before); without `symfony/monolog-bundle`, the `cleverage_process*` channels and processors are not registered and the default logger is used. `monolog/monolog` stays required (`Logger\*Processor`). Remove the unused `symfony/monolog-bridge` dependency. `ProcessConfiguration` no longer uses `array_any()` (PHP 8.4), whose polyfill was only installed by the removed packages. Add a `conflict` with `cleverage/doctrine-process-bundle`, `cleverage/soap-process-bundle` and `cleverage/ui-process-bundle` `<3.3`, which use the MonologBundle without requiring it. Update documentation, add tests.
* [#91](https://github.com/cleverage/process-bundle/issues/91) Reference a task by a code in the process configurations: new `task` key (`task: transformer`), next to its `options`. The code of a task service is declared with the new `code` attribute of the `cleverage.task` tag or the new `#[AsTask(code: '...')]` attribute (autoconfiguration); every task of the bundle has a code, given on its reference page (snake case of its class name without `Task`, e.g. `transformer`, `constant_output`, `csv_reader`). The codes are resolved when the container is compiled (`TaskLocatorCompilerPass`): an unknown or duplicate code fails the compilation. Referencing a task by its `service` (e.g. `service: '@CleverAge\ProcessBundle\Task\TransformerTask'`) is deprecated (configuration deprecation), it will be removed in v6.0: use `task: <code>`. The documentation examples use the codes. Update documentation, add tests.
* [#276](https://github.com/cleverage/process-bundle/issues/276) TransformerRegistry: the transformers are loaded lazily from a service locator indexed by code (new internal `TransformerLocatorCompilerPass`, new optional `TransformerRegistry` constructor argument): a transformer is only instantiated when a process uses it. The code is given by the new `code` attribute of the `cleverage.transformer` tag, or by the new `#[AsTransformer(code: '...')]` attribute (autoconfiguration); it must be the one returned by `getCode()`. All the bundle transformers and the generic transformers have their code. Duplicate codes fail the container compilation. A transformer tagged without `code` is still registered eagerly, with `TransformerRegistry::addTransformer()`, which is deprecated (the `code` will be required in v6.0, #280). `RegistryCompilerPass` is no longer used and deprecated. `TransformerInterface::getCode()` is deprecated (`@deprecated` PHPDoc: no runtime notice is possible for an interface method), it will be removed from the interface in v6.0 (#280): keep implementing it, returning the code of the tag until then. Update documentation, add tests.
* [#149](https://github.com/cleverage/process-bundle/issues/149) Apply Symfony best practices to the tasks and transformers services: they are defined explicitly (no more autowiring, autoconfiguration nor bindings in the bundle), keeping their class name as service id. Same arguments, tags and `shared` settings as before (generated from the previously autowired definitions). They stay public, but fetching them directly from the service container (`$container->get(<class>)`) is deprecated (`container.private` tag): they will be private in v6.0, use dependency injection (autowiring by class and decoration keep working). Update documentation.
Expand Down
19 changes: 13 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,11 @@
"symfony/finder": "^6.4|^7.4|^8",
"symfony/framework-bundle": "^6.4|^7.4|^8",
"symfony/http-kernel": "^6.4|^7.4|^8",
"symfony/monolog-bridge": "^6.4|^7.4|^8",
"symfony/monolog-bundle": "^3.11|^4",
"symfony/options-resolver": "^6.4|^7.4|^8",
"symfony/process": "^6.4|^7.4|^8",
"symfony/property-access": "^6.4|^7.4|^8",
"symfony/serializer": "^6.4|^7.4|^8",
"symfony/service-contracts": "^2.5|^3",
"symfony/stopwatch": "^6.4|^7.4|^8",
"symfony/validator": "^6.4|^7.4|^8",
"symfony/var-dumper": "^6.4|^7.4|^8",
"symfony/yaml": "^6.4|^7.4|^8"
},
"require-dev": {
Expand All @@ -96,9 +91,21 @@
"rector/rector": "*",
"roave/security-advisories": "dev-latest",
"symfony/cache": "^6.4|^7.4|^8",
"symfony/test-pack": "^1.1"
"symfony/process": "^6.4|^7.4|^8",
"symfony/stopwatch": "^6.4|^7.4|^8",
"symfony/test-pack": "^1.1",
"symfony/var-dumper": "^6.4|^7.4|^8"
},
"conflict": {
"cleverage/doctrine-process-bundle": "<3.3",
"cleverage/soap-process-bundle": "<3.3",
"cleverage/ui-process-bundle": "<3.3"
},
"suggest": {
"symfony/monolog-bundle": "To log in the dedicated cleverage_process, cleverage_process_task and cleverage_process_transformer channels, with the process and task data in the extra of the records",
"symfony/process": "Required by the CommandRunnerTask and the ProcessLauncherTask",
"symfony/stopwatch": "Required by the StopwatchTask",
"symfony/var-dumper": "To dump the values with the DebugTask, the DebugTransformer and the --output-format=dump option of cleverage:process:execute",
"cleverage/archive-process-bundle": "Dedicated bundle for Archive dependencies for the Process Bundle",
"cleverage/cache-process-bundle": "Dedicated bundle for Cache dependencies for the Process Bundle",
"cleverage/doctrine-process-bundle": "Dedicated bundle for Doctrine dependencies for the process bundle",
Expand Down
12 changes: 12 additions & 0 deletions docs/01-quick_start.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ framework:
enabled: true
```

Some features rely on optional packages, suggested by the bundle:

| Package | Needed by |
| ------- | --------- |
| `symfony/process` | the [CommandRunnerTask](reference/tasks/command_runner_task.md) and the [ProcessLauncherTask](reference/tasks/process_launcher_task.md) |
| `symfony/stopwatch` | the [StopwatchTask](reference/tasks/stopwatch_task.md) |
| `symfony/var-dumper` | the dumps of the [DebugTask](reference/tasks/debug_task.md), of the `debug` transformer and of `cleverage:process:execute --output-format=dump` (nothing is dumped without it) |
| `symfony/monolog-bundle` | the `cleverage_process`, `cleverage_process_task` and `cleverage_process_transformer` log channels, with the process and task data in the `extra` of the records (see [logging](03-custom_tasks.md#logging)); without it, the default Symfony logger is used |

Without `symfony/process` or `symfony/stopwatch`, the container still compiles, and a process using one of these tasks
fails with an explicit message giving the package to install.

## Global configuration

You can use `./bin/console config:dump-reference clever_age_process` to have a summary of current configuration.
Expand Down
4 changes: 3 additions & 1 deletion docs/03-custom_tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,9 @@ transform:

## Logging

The bundle declares three [Monolog channels](https://symfony.com/doc/current/logging/channels_handlers.html):
The bundle declares three [Monolog channels](https://symfony.com/doc/current/logging/channels_handlers.html), created
when the MonologBundle is installed (`composer require symfony/monolog-bundle`, otherwise every log goes to the default
Symfony logger, without the `extra` values below):

| Channel | Used by |
| ------- | ------- |
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/tasks/command_runner_task.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ CommandRunnerTask
Runs a system command (with the Symfony Process component) for each input. The input is passed to the command as
stdin, and the command standard output becomes the task output.

Requires `symfony/process` (`composer require symfony/process`): without it, the task fails with an explicit message.

Task reference
--------------

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/tasks/process_launcher_task.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Launches a process in a separate system process (`bin/console cleverage:process:
allowing parallelization. The task keeps a pool of at most `max_processes` running sub-processes and waits for a free
slot before launching a new one.

Requires `symfony/process` (`composer require symfony/process`): without it, the task fails with an explicit message.

Task reference
--------------

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/tasks/stopwatch_task.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ StopwatchTask
Logs (at `info` level, on the `cleverage_process_task` channel) every event of the `__root__` section of the Symfony
[Stopwatch component](https://symfony.com/doc/current/components/stopwatch.html). Useful to profile a process.

Requires `symfony/stopwatch` (`composer require symfony/stopwatch`): without it, the task fails with an explicit message.

Task reference
--------------

Expand Down
8 changes: 7 additions & 1 deletion src/Configuration/ProcessConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,13 @@ public function getDependencyGroups(): array
if (null === $this->dependencyGroups) {
$this->dependencyGroups = [];
foreach ($this->getTaskConfigurations() as $taskConfiguration) {
$isInBranch = array_any($this->dependencyGroups, static fn ($branch) => \in_array($taskConfiguration->getCode(), $branch, true));
$isInBranch = false;
foreach ($this->dependencyGroups as $branch) {
if (\in_array($taskConfiguration->getCode(), $branch, true)) {
$isInBranch = true;
break;
}
}
if (!$isInBranch) {
$dependencies = $this->buildDependencies($taskConfiguration);
$dependencies = $this->sortDependencies($dependencies);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

namespace CleverAge\ProcessBundle\DependencyInjection\Compiler;

use CleverAge\ProcessBundle\Task\Debug\StopwatchTask;
use CleverAge\ProcessBundle\Task\Process\CommandRunnerTask;
use CleverAge\ProcessBundle\Task\Process\ProcessLauncherTask;
use CleverAge\ProcessBundle\Task\Serialization\DenormalizerTask;
use CleverAge\ProcessBundle\Task\Serialization\DeserializerTask;
use CleverAge\ProcessBundle\Task\Serialization\NormalizerTask;
Expand All @@ -26,6 +29,7 @@
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Process\Process;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

Expand All @@ -35,12 +39,24 @@
*
* @internal
*/
final class UnavailableServicesCompilerPass implements CompilerPassInterface
final readonly class UnavailableServicesCompilerPass implements CompilerPassInterface
{
public const SERIALIZER_MESSAGE = 'The "%s" service requires the Symfony serializer, which does not seem enabled: consider enabling framework.serializer (see https://symfony.com/doc/current/reference/configuration/framework.html#reference-serializer-enabled).';

public const VALIDATOR_MESSAGE = 'The "%s" service requires the Symfony validator, which does not seem enabled: consider enabling framework.validation (see https://symfony.com/doc/current/reference/configuration/framework.html#reference-validation-enabled).';

public const STOPWATCH_MESSAGE = 'The "%s" service requires the Symfony Stopwatch component, which does not seem installed: try "composer require symfony/stopwatch".';

public const PROCESS_MESSAGE = 'The "%s" service requires the Symfony Process component, which does not seem installed: try "composer require symfony/process".';

/**
* @param (\Closure(class-string): bool)|null $classExists defaults to class_exists() (the tests check the missing packages)
*/
public function __construct(
private ?\Closure $classExists = null,
) {
}

public function process(ContainerBuilder $container): void
{
if (!$container->has('serializer') && !$container->has(DenormalizerInterface::class)) {
Expand All @@ -57,6 +73,15 @@ public function process(ContainerBuilder $container): void
if (!$container->has('validator') && !$container->has(ValidatorInterface::class)) {
$this->replace($container, self::VALIDATOR_MESSAGE, [ValidatorTask::class]);
}

// The debug.stopwatch service is registered by the FrameworkBundle when symfony/stopwatch is installed
if (!$container->has('debug.stopwatch')) {
$this->replace($container, self::STOPWATCH_MESSAGE, [StopwatchTask::class]);
}

if (!($this->classExists ?? class_exists(...))(Process::class)) {
$this->replace($container, self::PROCESS_MESSAGE, [CommandRunnerTask::class, ProcessLauncherTask::class]);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
use CleverAge\ProcessBundle\DependencyInjection\CleverAgeProcessExtension;
use CleverAge\ProcessBundle\DependencyInjection\Compiler\UnavailableServicesCompilerPass;
use CleverAge\ProcessBundle\DependencyInjection\Configuration;
use CleverAge\ProcessBundle\Task\Debug\StopwatchTask;
use CleverAge\ProcessBundle\Task\Process\CommandRunnerTask;
use CleverAge\ProcessBundle\Task\Process\ProcessLauncherTask;
use CleverAge\ProcessBundle\Task\Serialization\DenormalizerTask;
use CleverAge\ProcessBundle\Task\Serialization\DeserializerTask;
use CleverAge\ProcessBundle\Task\Serialization\NormalizerTask;
Expand All @@ -31,6 +34,7 @@
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Process\Process;

#[CoversClass(UnavailableServicesCompilerPass::class)]
#[UsesClass(CleverAgeProcessExtension::class)]
Expand All @@ -45,7 +49,7 @@ public function testServicesAreKeptWhenTheirDependenciesAreAvailable(): void
{
$container = $this->createContainer(serializer: true, validator: true);

foreach ([...self::SERIALIZER_TASKS, NormalizeTransformer::class, DenormalizeTransformer::class, ValidatorTask::class] as $id) {
foreach ([...self::SERIALIZER_TASKS, NormalizeTransformer::class, DenormalizeTransformer::class, ValidatorTask::class, StopwatchTask::class, CommandRunnerTask::class, ProcessLauncherTask::class] as $id) {
self::assertSame($id, $container->findDefinition($id)->getClass());
}
}
Expand Down Expand Up @@ -98,7 +102,33 @@ public function testValidatorTaskIsReplacedWithoutValidator(): void
self::assertSame(NormalizerTask::class, $container->findDefinition(NormalizerTask::class)->getClass());
}

private function createContainer(bool $serializer, bool $validator): ContainerBuilder
public function testStopwatchTaskIsReplacedWithoutStopwatch(): void
{
$container = $this->createContainer(serializer: true, validator: true, stopwatch: false);

$definition = $container->findDefinition(StopwatchTask::class);
self::assertSame(UnavailableTask::class, $definition->getClass());
self::assertSame([\sprintf(UnavailableServicesCompilerPass::STOPWATCH_MESSAGE, StopwatchTask::class)], $definition->getArguments());
self::assertTrue($definition->isPublic());
self::assertFalse($definition->isShared());
self::assertSame(CommandRunnerTask::class, $container->findDefinition(CommandRunnerTask::class)->getClass());
}

public function testProcessTasksAreReplacedWithoutProcessComponent(): void
{
$container = $this->createContainer(serializer: true, validator: true, process: false);

foreach ([CommandRunnerTask::class, ProcessLauncherTask::class] as $id) {
$definition = $container->findDefinition($id);
self::assertSame(UnavailableTask::class, $definition->getClass(), $id);
self::assertSame([\sprintf(UnavailableServicesCompilerPass::PROCESS_MESSAGE, $id)], $definition->getArguments());
self::assertTrue($definition->isPublic());
self::assertFalse($definition->isShared());
}
self::assertSame(StopwatchTask::class, $container->findDefinition(StopwatchTask::class)->getClass());
}

private function createContainer(bool $serializer, bool $validator, bool $stopwatch = true, bool $process = true): ContainerBuilder
{
$container = new ContainerBuilder();
(new CleverAgeProcessExtension())->load([], $container);
Expand All @@ -110,7 +140,11 @@ private function createContainer(bool $serializer, bool $validator): ContainerBu
if ($validator) {
$container->setDefinition('validator', new Definition(\stdClass::class));
}
(new UnavailableServicesCompilerPass())->process($container);
if ($stopwatch) {
$container->setDefinition('debug.stopwatch', new Definition(\stdClass::class));
}
$classExists = static fn (string $class): bool => (Process::class !== $class || $process) && class_exists($class);
(new UnavailableServicesCompilerPass($classExists))->process($container);

return $container;
}
Expand Down
Loading