From e42e350b92580f6089365517c69f149174302098 Mon Sep 17 00:00:00 2001 From: l3l0 Date: Sat, 5 Jul 2014 14:21:07 +0200 Subject: [PATCH] [DI] Add compiler pass which check extistence of service class and factory_class name (using class_exists() ) if that is possible - definition is not syntetic or is not definition via factory method. --- .../Controller/ControllerResolverTest.php | 4 +- .../Compiler/CheckServiceClassPass.php | 57 ++++++++++ .../Compiler/PassConfig.php | 1 + .../Exception/BadServiceClassException.php | 27 +++++ .../Compiler/CheckServiceClassPassTest.php | 106 ++++++++++++++++++ .../Tests/Compiler/IntegrationTest.php | 16 +++ .../Tests/Fixtures/containers/container9.php | 1 + .../Fixtures/includes/ProjectExtension.php | 13 +++ .../Tests/Fixtures/includes/classes.php | 12 ++ 9 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/CheckServiceClassPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Exception/BadServiceClassException.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckServiceClassPassTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php index 49fa22bcb2fff..f4f70c78ee397 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php @@ -109,8 +109,8 @@ public function testGetControllerOnNonUndefinedFunction($controller, $exceptionN public function getUndefinedControllers() { return array( - array('foo', '\LogicException', 'Unable to parse the controller name "foo".'), - array('foo::bar', '\InvalidArgumentException', 'Class "foo" does not exist.'), + array('myfoo', '\LogicException', 'Unable to parse the controller name "myfoo".'), + array('myfoo::bar', '\InvalidArgumentException', 'Class "myfoo" does not exist.'), array('stdClass', '\LogicException', 'Unable to parse the controller name "stdClass".'), array( 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar', diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckServiceClassPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckServiceClassPass.php new file mode 100644 index 0000000000000..1f0897e4f664d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckServiceClassPass.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\BadServiceClassException; + +/** + * Checks your service exists by checking its definition "class" and "factory_class" keys + * + * @author Leszek "l3l0" Prabucki + */ +class CheckServiceClassPass implements CompilerPassInterface +{ + /** + * Checks if ContainerBuilder services exists + * + * @param ContainerBuilder $container The ContainerBuilder instances + */ + public function process(ContainerBuilder $container) + { + $parameterBag = $container->getParameterBag(); + foreach ($container->getDefinitions() as $id => $definition) { + if ($this->allowsToCheckClassExistenceForClass($definition) && !class_exists($parameterBag->resolveValue($definition->getClass()))) { + throw new BadServiceClassException($id, $definition->getClass(), 'class'); + } + if ($this->allowsToCheckClassExistenceForFactoryClass($definition) && !class_exists($parameterBag->resolveValue($definition->getFactoryClass()))) { + throw new BadServiceClassException($id, $definition->getFactoryClass(), 'factory_class'); + } + } + } + + private function allowsToCheckClassExistenceForClass(Definition $definition) + { + return $definition->getClass() && !$this->isFactoryDefinition($definition) && !$definition->isSynthetic(); + } + + private function allowsToCheckClassExistenceForFactoryClass(Definition $definition) + { + return $definition->getFactoryClass() && !$definition->isSynthetic(); + } + + private function isFactoryDefinition(Definition $definition) + { + return $definition->getFactoryClass() || $definition->getFactoryService(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index bcef41052b9bc..27a4c0e396b8c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -67,6 +67,7 @@ public function __construct() new RemoveUnusedDefinitionsPass(), )), new CheckExceptionOnInvalidReferenceBehaviorPass(), + new CheckServiceClassPass(), ); } diff --git a/src/Symfony/Component/DependencyInjection/Exception/BadServiceClassException.php b/src/Symfony/Component/DependencyInjection/Exception/BadServiceClassException.php new file mode 100644 index 0000000000000..a6d3a9d15c2b1 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Exception/BadServiceClassException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +class BadServiceClassException extends RuntimeException +{ + public function __construct($id, $className, $key) + { + parent::__construct( + sprintf( + 'Class "%s" not found. Check the spelling on the "%s" configuration for your "%s" service.', + $className, + $key, + $id + ) + ); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckServiceClassPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckServiceClassPassTest.php new file mode 100644 index 0000000000000..ade1d96f40bae --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckServiceClassPassTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CheckServiceClassPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class CheckServiceClassPassTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\BadServiceClassException + * @expectedExceptionMessage Class "\InvalidName\TestClass" not found. Check the spelling on the "class" configuration for your "a" service. + */ + public function testThrowsBadNameExceptionWhenServiceHasInvalidClassName() + { + $container = new ContainerBuilder(); + $container->register('a', '\InvalidName\TestClass'); + + $this->process($container); + } + + public function testDoesNotThrowExceptionIfClassExists() + { + $container = new ContainerBuilder(); + $container->register('a', '\Symfony\Component\DependencyInjection\Compiler\CheckServiceClassPass'); + $container->register('b', '\stdClass'); + $container->register('c', '\Symfony\Component\DependencyInjection\Tests\Compiler\MyTestService'); + $container + ->register('d', '\stdClass') + ->setFactoryService('\Symfony\Component\DependencyInjection\Tests\Compiler\MyTestService') + ->setFactoryMethod('factoryMethod') + ; + + $this->process($container); + } + + public function testSynteticServicesClassNamesAreNotChecked() + { + $container = new ContainerBuilder(); + $container + ->register('a', '\InvalidName\TestClass') + ->setSynthetic(true) + ; + + $this->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\BadServiceClassException + * @expectedExceptionMessage Class "\InvalidName\TestClass" not found. Check the spelling on the "factory_class" configuration for your "a" service. + */ + public function testFactoryMethodServicesClassNamesAreNotChecked() + { + $container = new ContainerBuilder(); + $container + ->register('a', '\stdClass') + ->setFactoryClass('\InvalidName\TestClass') + ->setFactoryMethod('factoryMethod') + ; + + $this->process($container); + } + + public function testServicesWithoutNameAreNotChecked() + { + $container = new ContainerBuilder(); + $container + ->register('a') + ; + + $this->process($container); + } + + public function testSynteticServicesNameAreNotChecked() + { + $container = new ContainerBuilder(); + $container + ->register('a', '\InvalidName\TestClass') + ->setSynthetic(true) + ; + + $this->process($container); + } + + protected function process(ContainerBuilder $container) + { + $pass = new CheckServiceClassPass(); + $pass->process($container); + } +} + +class MyTestService +{ + public function factoryMethod() + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 1d4ea079c3aec..cca6779d358ee 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -111,4 +111,20 @@ public function testProcessInlinesWhenThereAreMultipleReferencesButFromTheSameDe $this->assertFalse($container->hasDefinition('b')); $this->assertFalse($container->hasDefinition('c'), 'Service C was not inlined.'); } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\BadServiceClassException + * @expectedExceptionMessage Class "\NotExist\InvalidClass" not found. Check the spelling on the "class" configuration for your "a" service. + */ + public function testFriendlyErrorIsThrowedWhenServiceHasBadClassName() + { + $container = new ContainerBuilder(); + + $container + ->register('a', '\NotExist\InvalidClass') + ; + + $container->compile(); + } + } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php index 15ccc03d0c44f..b30327909eaac 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php @@ -1,6 +1,7 @@ bar = $bar; } } + +class Baz +{ +} + +class Request +{ +} + +class ConfClass +{ +}