diff --git a/README.md b/README.md index b143b62..b29b17d 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ ## Installation 📥 -1. Install via composer +1. Install via composer: ```sh composer require phphd/exception-toolkit ``` -2. In case you are using symfony, enable the bundle in the `bundles.php` +2. If you are using Symfony, enable the bundle in the `bundles.php` ```php PhPhD\ExceptionToolkit\Bundle\PhdExceptionToolkitBundle::class => ['all' => true], @@ -31,7 +31,6 @@ Allows you to unwrap composite exceptions and get the atomic errors you are inte use PhPhD\ExceptionToolkit\Unwrapper\ExceptionUnwrapper; /** @var ExceptionUnwrapper $unwrapper */ -$unwrapper = getUnwrapper(); $compositeException = new CompositeException([ new InvalidEmailException(), diff --git a/composer.json b/composer.json index c0253a2..6e15c35 100644 --- a/composer.json +++ b/composer.json @@ -14,11 +14,11 @@ "php": ">=8.1" }, "conflict": { - "php": ">=9.0", "symfony/http-kernel": "<6.0 || >8.4", "symfony/dependency-injection": "<6.2 || >8.4" , "symfony/config": "<6.0 || >8.4", "symfony/messenger": ">8.4", + "symfony/deprecation-contracts": "<2.5", "amphp/amp": ">=4.0" }, "require-dev": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a7764c4..35cd868 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,5 +21,11 @@ src + + src/*/*/Tests + src/*/*/*/Tests + src + src + diff --git a/src/Bundle/DependencyInjection/PhdExceptionToolkitExtension.php b/src/Bundle/DependencyInjection/PhdExceptionToolkitExtension.php index 9caaf72..4d12b12 100644 --- a/src/Bundle/DependencyInjection/PhdExceptionToolkitExtension.php +++ b/src/Bundle/DependencyInjection/PhdExceptionToolkitExtension.php @@ -4,43 +4,54 @@ namespace PhPhD\ExceptionToolkit\Bundle\DependencyInjection; -use Amp\CompositeException as AmpCompositeException; use Exception; -use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\Extension; -use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use Symfony\Component\Messenger\Exception\WrappedExceptionsInterface as MessengerCompositeException; +use Symfony\Component\DependencyInjection\Extension\AbstractExtension; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -use function class_exists; -use function interface_exists; +use function array_keys; +use function array_map; -final class PhdExceptionToolkitExtension extends Extension +final class PhdExceptionToolkitExtension extends AbstractExtension { public const ALIAS = 'phd_exception_toolkit'; /** - * @param array $configs - * - * @override - * - * @throws Exception + * @param array $parameters required by {@see \Symfony\Component\DependencyInjection\Extension\ExtensionTrait::executeConfiguratorCallback()}: + * - kernel.environment + * - kernel.build_dir */ - public function load(array $configs, ContainerBuilder $container): void + public static function getContainer(array $parameters): ContainerBuilder { - /** @var ?string $env */ - $env = $container->getParameter('kernel.environment'); + $container = new ContainerBuilder(); + + $container->setResourceTracking(false); + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); + + $container->registerExtension($extension = new self()); + $container->loadFromExtension($extension->getAlias()); + + $container->addCompilerPass(new DecoratorServicePass(), PassConfig::TYPE_OPTIMIZE); - $loader = new YamlFileLoader($container, new FileLocator(), $env); - $loader->load(__DIR__.'/../../Unwrapper/services.yaml'); + array_map($container->setParameter(...), array_keys($parameters), $parameters); // @phpstan-ignore argument.type - if (class_exists(AmpCompositeException::class)) { - $loader->load(__DIR__.'/../../Unwrapper/Amp/services.yaml'); - } + return $container; + } - if (interface_exists(MessengerCompositeException::class)) { - $loader->load(__DIR__.'/../../Unwrapper/Messenger/services.yaml'); - } + /** + * @param array $config + * + * @throws Exception + */ + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + $container->import(__DIR__.'/../../**/services.php'); + $container->import(__DIR__.'/../../**/services.yaml'); } /** @override */ diff --git a/src/Unwrapper/Amp/services.php b/src/Unwrapper/Amp/services.php new file mode 100644 index 0000000..2c597ae --- /dev/null +++ b/src/Unwrapper/Amp/services.php @@ -0,0 +1,29 @@ +services(); + + $services + ->set('phd_exception_toolkit.exception_unwrapper.amp', AmpExceptionUnwrapper::class) + ->decorate('phd_exception_toolkit.exception_unwrapper.stack') + ->args([ + service('.inner'), + service(ExceptionUnwrapper::class), + ]) + ; +}; diff --git a/src/Unwrapper/Amp/services.yaml b/src/Unwrapper/Amp/services.yaml deleted file mode 100644 index 903917b..0000000 --- a/src/Unwrapper/Amp/services.yaml +++ /dev/null @@ -1,7 +0,0 @@ -services: - phd_exception_toolkit.exception_unwrapper.amp: - class: PhPhD\ExceptionToolkit\Unwrapper\Amp\AmpExceptionUnwrapper - decorates: phd_exception_toolkit.exception_unwrapper.stack - arguments: - - '@.inner' - - '@phd_exception_toolkit.exception_unwrapper' diff --git a/src/Unwrapper/Messenger/services.php b/src/Unwrapper/Messenger/services.php new file mode 100644 index 0000000..015257b --- /dev/null +++ b/src/Unwrapper/Messenger/services.php @@ -0,0 +1,29 @@ +services(); + + $services + ->set('phd_exception_toolkit.exception_unwrapper.messenger', MessengerExceptionUnwrapper::class) + ->decorate('phd_exception_toolkit.exception_unwrapper.stack') + ->args([ + service('.inner'), + service(ExceptionUnwrapper::class), + ]) + ; +}; diff --git a/src/Unwrapper/Messenger/services.yaml b/src/Unwrapper/Messenger/services.yaml deleted file mode 100644 index 10c7918..0000000 --- a/src/Unwrapper/Messenger/services.yaml +++ /dev/null @@ -1,7 +0,0 @@ -services: - phd_exception_toolkit.exception_unwrapper.messenger: - class: PhPhD\ExceptionToolkit\Unwrapper\Messenger\MessengerExceptionUnwrapper - decorates: phd_exception_toolkit.exception_unwrapper.stack - arguments: - - '@.inner' - - '@phd_exception_toolkit.exception_unwrapper' diff --git a/src/Unwrapper/services.php b/src/Unwrapper/services.php new file mode 100644 index 0000000..bb5c9ea --- /dev/null +++ b/src/Unwrapper/services.php @@ -0,0 +1,21 @@ +services(); + + $services->alias('phd_exception_toolkit.exception_unwrapper', ExceptionUnwrapper::class); + + // Stack is used to create a chain of decorated services. + // When adding new unwrapper, you should decorate this service: + $services + ->set('phd_exception_toolkit.exception_unwrapper.stack', PassThroughExceptionUnwrapper::class) + ; +}; diff --git a/src/Unwrapper/services.yaml b/src/Unwrapper/services.yaml index c385cbe..d605747 100644 --- a/src/Unwrapper/services.yaml +++ b/src/Unwrapper/services.yaml @@ -1,5 +1,6 @@ services: - phd_exception_toolkit.exception_unwrapper: + PhPhD\ExceptionToolkit\Unwrapper\ExceptionUnwrapper: + public: true class: PhPhD\ExceptionToolkit\Unwrapper\ExceptionUnwrapper factory: - !service_locator @@ -7,6 +8,3 @@ services: - get arguments: [ stack ] lazy: true - - phd_exception_toolkit.exception_unwrapper.stack: - class: PhPhD\ExceptionToolkit\Unwrapper\PassThroughExceptionUnwrapper diff --git a/tests/Bundle/Compiler/TestServicesCompilerPass.php b/tests/Bundle/Compiler/TestServicesCompilerPass.php index 6af8683..12fcd55 100644 --- a/tests/Bundle/Compiler/TestServicesCompilerPass.php +++ b/tests/Bundle/Compiler/TestServicesCompilerPass.php @@ -11,6 +11,6 @@ final class TestServicesCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { - $container->getDefinition('phd_exception_toolkit.exception_unwrapper')->setPublic(true); + $container->getAlias('phd_exception_toolkit.exception_unwrapper')->setPublic(true); } } diff --git a/tests/Unwrapper/ExceptionUnwrapperUnitTest.php b/tests/Unwrapper/ExceptionUnwrapperUnitTest.php index fe28006..2a0ceab 100644 --- a/tests/Unwrapper/ExceptionUnwrapperUnitTest.php +++ b/tests/Unwrapper/ExceptionUnwrapperUnitTest.php @@ -6,17 +6,15 @@ use Amp\CompositeException; use Exception; -use PhPhD\ExceptionToolkit\Unwrapper\Amp\AmpExceptionUnwrapper; +use PhPhD\ExceptionToolkit\Bundle\DependencyInjection\PhdExceptionToolkitExtension; use PhPhD\ExceptionToolkit\Unwrapper\ExceptionUnwrapper; -use PhPhD\ExceptionToolkit\Unwrapper\Messenger\MessengerExceptionUnwrapper; -use PhPhD\ExceptionToolkit\Unwrapper\PassThroughExceptionUnwrapper; use PHPUnit\Framework\TestCase; use stdClass; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\HandlerFailedException; -use Throwable; /** + * @covers \PhPhD\ExceptionToolkit\Bundle\DependencyInjection\PhdExceptionToolkitExtension * @covers \PhPhD\ExceptionToolkit\Unwrapper\PassThroughExceptionUnwrapper * @covers \PhPhD\ExceptionToolkit\Unwrapper\Amp\AmpExceptionUnwrapper * @covers \PhPhD\ExceptionToolkit\Unwrapper\Messenger\MessengerExceptionUnwrapper @@ -31,19 +29,15 @@ protected function setUp(): void { parent::setUp(); - $this->exceptionUnwrapper = self::createStub(ExceptionUnwrapper::class); + $container = PhdExceptionToolkitExtension::getContainer([ + 'kernel.environment' => 'test', + 'kernel.build_dir' => __DIR__.'/var', + ]); + $container->compile(); - $stackUnwrapper = new MessengerExceptionUnwrapper( - new AmpExceptionUnwrapper( - new PassThroughExceptionUnwrapper(), - $this->exceptionUnwrapper, - ), - $this->exceptionUnwrapper, - ); - - $this->exceptionUnwrapper->method('unwrap') - ->willReturnCallback(static fn (Throwable $exception): array => $stackUnwrapper->unwrap($exception)) - ; + /** @var ExceptionUnwrapper $unwrapper */ + $unwrapper = $container->get(ExceptionUnwrapper::class); + $this->exceptionUnwrapper = $unwrapper; } public function testAtomicExceptionIsNotUnwrapped(): void