diff --git a/README.md b/README.md index e4fec945a..b850d12b9 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ potentially very difficult to debug due to dissimilar or unsupported package ver - [Autoload aliases](docs/further-reading.md#autoload-aliases) - [Class aliases](docs/further-reading.md#class-aliases) - [Function aliases](docs/further-reading.md#function-aliases) + - [Symfony support](docs/further-reading.md#symfony-support) - [Limitations](docs/limitations.md#limitations) - [Dynamic symbols](docs/limitations.md#dynamic-symbols) - [Date symbols](docs/limitations.md#date-symbols) diff --git a/docs/further-reading.md b/docs/further-reading.md index 33ab9a6be..da9f29154 100644 --- a/docs/further-reading.md +++ b/docs/further-reading.md @@ -4,6 +4,7 @@ - [Autoload aliases](#autoload-aliases) - [Class aliases](#class-aliases) - [Function aliases](#function-aliases) +- [Symfony support](#symfony-support) ### How to deal with unknown third-party symbols @@ -65,6 +66,51 @@ When [exposing a function] or when a globally declared [excluded function] declaration is found (see [#706]), an alias will be registered. +### Symfony Support + +When using [PHP configuration][symfony-php-config] files for your services, some elements may not be prefixed correctly +due to being strings. For example (taken directly from the Symfony docs): + +```php +services() + ->defaults() + ->autowire() // Automatically injects dependencies in your services. + ->autoconfigure() // Automatically registers your services as commands, event subscribers, etc. + ; + + // makes classes in src/ available to be used as services + // this creates a service per class whose id is the fully-qualified class name + $services->load('App\\', '../src/') + ->exclude('../src/{DependencyInjection,Entity,Kernel.php}'); + + // order is important in this file because service definitions + // always *replace* previous ones; add your own service configuration below +}; +``` + +The string `'App\\'` from `$services->load()` will not be made into `'Prefix\\App\\'`. To address this +you need to use [patchers]. Alternatively, PHP-Scoper provides one which should should handle such cases: + +```php + [$symfonyPatcher], + // ... +]; +``` + +Note that the path is the "regular path(s)" that can be passed to patchers. + +

@@ -76,3 +122,5 @@ declaration is found (see [#706]), an alias will be registered. [exposing a class]: configuration.md#exposing-classes [exposing a function]: configuration.md#exposing-functions [#706]: https://github.com/humbug/php-scoper/pull/706 +[patchers]: ./configuration.md#patchers +[symfony-php-config]: https://symfony.com/doc/current/service_container.html#explicitly-configuring-services-and-arguments diff --git a/res/create-symfony-php-services-patcher.php b/res/create-symfony-php-services-patcher.php new file mode 100644 index 000000000..7828109ed --- /dev/null +++ b/res/create-symfony-php-services-patcher.php @@ -0,0 +1,34 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Creates a patcher able to fix the paths of the Symfony PHP configuration files. + * + * @param string|array $filesPath + */ +return static function (array|string $fileOrFilesPath): Closure { + $filesPath = (array) $fileOrFilesPath; + + return static function (string $filePath, string $prefix, string $contents) use ($filesPath): string { + if (!in_array($filePath, $filesPath, true)) { + return $contents; + } + + return preg_replace( + '/(.*->load\((?:\n\s+)?\')(.+?\\\\)(\',.*)/', + '$1'.$prefix.'\\\\$2$3', + $contents, + ); + }; +}; diff --git a/tests/Patcher/SymfonyPhpServicesPatcherTest.php b/tests/Patcher/SymfonyPhpServicesPatcherTest.php new file mode 100644 index 000000000..a552c9046 --- /dev/null +++ b/tests/Patcher/SymfonyPhpServicesPatcherTest.php @@ -0,0 +1,109 @@ +, + * Pádraic Brady + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Humbug\PhpScoper\Patcher; + +use Closure; +use PHPUnit\Framework\TestCase; + +/** + * @internal + */ +final class SymfonyPhpServicesPatcherTest extends TestCase +{ + private const FILE_PATH = 'path/to/config.php'; + + private Closure $patcher; + + protected function setUp(): void + { + $this->patcher = (require __DIR__.'/../../res/create-symfony-php-services-patcher.php')([self::FILE_PATH]); + } + + /** + * @dataProvider symfonyConfigFileProvider + */ + public function test_it_can_patch_a_symfony_service_file( + string $prefix, + string $contents, + string $expected, + ): void { + $actual = ($this->patcher)(self::FILE_PATH, $prefix, $contents); + + self::assertSame($expected, $actual); + } + + public static function symfonyConfigFileProvider(): iterable + { + $prefix = 'Prefix'; + + yield 'load statement' => [ + $prefix, + <<<'PHP' + use SomeNamespace\SomeClass; + + return static function (ContainerConfigurator $containerConfigurator) { + $services = $containerConfigurator->services(); + + $services->load('SomeNamespace\ConsoleColorDiff\\', __DIR__ . '/../src'); + + $services->set(SomeClass::class); + } + PHP, + <<<'PHP' + use SomeNamespace\SomeClass; + + return static function (ContainerConfigurator $containerConfigurator) { + $services = $containerConfigurator->services(); + + $services->load('Prefix\SomeNamespace\ConsoleColorDiff\\', __DIR__ . '/../src'); + + $services->set(SomeClass::class); + } + PHP, + ]; + + yield 'multiline load statement' => [ + $prefix, + <<<'PHP' + use SomeNamespace\SomeClass; + + return static function (ContainerConfigurator $containerConfigurator) { + $services = $containerConfigurator->services(); + + $services->load( + 'SomeNamespace\ConsoleColorDiff\\', + __DIR__ . '/../src', + ); + + $services->set(SomeClass::class); + } + PHP, + <<<'PHP' + use SomeNamespace\SomeClass; + + return static function (ContainerConfigurator $containerConfigurator) { + $services = $containerConfigurator->services(); + + $services->load( + 'Prefix\SomeNamespace\ConsoleColorDiff\\', + __DIR__ . '/../src', + ); + + $services->set(SomeClass::class); + } + PHP, + ]; + } +}