How to Inline Value Object in Symfony PHP Config


Rector uses PHP Symfony configs for many good reasons.

One of them is the possibility to have control over complex configurations with value objects. Would you like such features in your configs too? Unfortunately, Symfony does not support it out of the box.

What can we do about it?

Why are Value Objects in Configuration Priceless?

Let's say we want to rename dump() func call to Tracy\Debugger::dump().

-dump($value);
+Tracy\Debugger::dump($value);

KISS. Why not use a simple array?

Look at our previous post about why the road to hell is paved with good intentions.


Now that we know why and when we want to use value objects, the question is How can we use them in Symfony PHP Config?

1. Direct Object

First obvious idea is to just use is like we would in PHP:

// very dummy PHP approach
$funcCallToStaticCallRector = new FuncCallToStaticCallRector();

$configuration = [
    FuncCallToStaticCallRector::FUNC_CALLS_TO_STATIC_CALLS => [
         new FuncCallToStaticCall('dump', 'Tracy\Debugger', 'dump'),
    ]
];

$funcCallToStaticCallRector->configure($configuration);

This should work, right? It's just passing a bunch of scalar values:

// rector.php

$services->set(FuncCallToStaticCallRector::class)
    ->call('configure', [[
        FuncCallToStaticCallRector::FUNC_CALLS_TO_STATIC_CALLS => [
            new FuncCallToStaticCall('dump', 'Tracy\Debugger', 'dump'),
        ]
    ]]);

 [ERROR] Cannot use values of type
         "Rector\Transform\ValueObject\FuncCallToStaticCall" in service
         configuration files.


2. Native inline_service() Function

Fortunately, Symfony gives us a little trick. The inline_service() basically registers a local service, just for the configuration:

// rector.php

$services->set(FuncCallToStaticCallRector::class)
    ->call('configure', [[
        FuncCallToStaticCallRector::FUNC_CALLS_TO_STATIC_CALLS => [
            inline_service(FuncCallToStaticCall::class)
                ->args(['dump', 'Tracy\Debugger', 'dump']),
        ]
    ]]);

This works!

There is just little detail. The IDE autocomplete we want from value objects:

...is gone.

This approach shuts us back to coding in the dark with Notepad.


3. Best of Both Worlds - inline_value_objects()

What we need here?


To cover all benefits together, we added custom inline_value_objects() function:

// rector.php

$services->set(FuncCallToStaticCallRector::class)
    ->call('configure', [[
        FuncCallToStaticCallRector::FUNC_CALLS_TO_STATIC_CALLS => inline_value_objects([
            new FuncCallToStaticCall('dump', 'Tracy\Debugger', 'dump'),
            // it handles multiple items without duplicated call
            new FuncCallToStaticCall('d', 'Tracy\Debugger', 'dump'),
            new FuncCallToStaticCall('dd', 'Tracy\Debugger', 'dump'),
        ])
    ]]);


This way, anyone can configure even the most complex Rector rules without ever looking inside the Rector rule and scan for configuration values.

So which Rector uses what Value Object?

In convention over configuration principle, all you need to know the rule name.

Could you guess the value object naming pattern?

That's it!


Happy and safe coding!