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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Latest
## Changes
* [#90](https://github.com/cleverage/process-bundle/issues/90) Installation documentation: describe the Symfony Flex recipe ([symfony/recipes-contrib#2081](https://github.com/symfony/recipes-contrib/pull/2081)), which registers the bundle and creates `config/packages/clever_age_process.yaml` importing the `config/packages/process/` directory, and the manual setup without Flex.

## Fixes
* [#327](https://github.com/cleverage/process-bundle/issues/327) `ContextualOptionResolver`: quote the context keys in the pattern of the `{{ key }}` placeholders (a key containing `/` made every string option fail, a key containing a regex character matched other placeholders), and leave the options as is with an empty context (`{{ }}` matched an empty key).

v5.4
-----

Expand Down
7 changes: 5 additions & 2 deletions src/Context/ContextualOptionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ public function contextualizeOption(mixed $value, array $context): mixed
return $this->contextualizeOptions($value, $context);
}

if (\is_string($value)) {
$pattern = \sprintf('/{{[ ]*(%s){1}[ ]*}}/', implode('|', array_keys($context)));
// Without context, there is nothing to replace ("{{ }}" would match an empty key)
if (\is_string($value) && [] !== $context) {
// The keys are free (console, process_executor, UI...): quoted, they cannot alter the pattern
$keys = array_map(static fn (int|string $key): string => preg_quote((string) $key, '/'), array_keys($context));
$pattern = \sprintf('/{{[ ]*(%s){1}[ ]*}}/', implode('|', $keys));

$matches = [];
$result = preg_match($pattern, $value, $matches);
Expand Down
64 changes: 64 additions & 0 deletions tests/Context/ContextualOptionResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

/*
* This file is part of the CleverAge/ProcessBundle package.
*
* Copyright (c) Clever-Age
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CleverAge\ProcessBundle\Tests\Context;

use CleverAge\ProcessBundle\Context\ContextualOptionResolver;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(ContextualOptionResolver::class)]
class ContextualOptionResolverTest extends TestCase
{
public function testExactMatchReturnsTheContextValue(): void
{
self::assertSame(['a', 'b'], (new ContextualOptionResolver())->contextualizeOption('{{ ids }}', ['ids' => ['a', 'b']]));
}

public function testPlaceholdersAreReplacedInStrings(): void
{
self::assertSame(
['path' => 'var/data/books.csv', 'other' => 'no placeholder', 'books' => 1],
(new ContextualOptionResolver())->contextualizeOptions(
['path' => 'var/{{dir}}/{{ name }}.csv', 'other' => 'no placeholder', '{{ name }}' => 1],
['dir' => 'data', 'name' => 'books']
)
);
}

/**
* The context keys are free (console, process_executor, UI...): they must not alter the pattern.
*/
public function testContextKeysAreQuoted(): void
{
$resolver = new ContextualOptionResolver();

// A "/" made the pattern invalid, for every string option
self::assertSame('value', $resolver->contextualizeOption('{{ a/b }}', ['a/b' => 'value']));
self::assertSame('no placeholder', $resolver->contextualizeOption('no placeholder', ['a/b' => 'value']));
// "x." matched "{{ x }}"
self::assertSame('{{ x }}', $resolver->contextualizeOption('{{ x }}', ['x.' => 'value']));
self::assertSame('value', $resolver->contextualizeOption('{{ x. }}', ['x.' => 'value']));
}

public function testEmptyContext(): void
{
// "{{ }}" matched an empty key
self::assertSame('{{ }} {{ name }}', (new ContextualOptionResolver())->contextualizeOption('{{ }} {{ name }}', []));
}

public function testNonStringValuesAreKept(): void
{
self::assertSame(12, (new ContextualOptionResolver())->contextualizeOption(12, ['name' => 'books']));
}
}
Loading