Skip to content

Commit

Permalink
[DI] Add compiler pass which check extistence of service class and
Browse files Browse the repository at this point in the history
factory_class name (using class_exists() ) if that is possible - definition is not syntetic or is
not definition via factory method.
  • Loading branch information
l3l0 committed Jul 19, 2014
1 parent 397687f commit e42e350
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 2 deletions.
Expand Up @@ -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',
Expand Down
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <leszek.prabucki@gmail.com>
*/
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();
}
}
Expand Up @@ -67,6 +67,7 @@ public function __construct()
new RemoveUnusedDefinitionsPass(),
)),
new CheckExceptionOnInvalidReferenceBehaviorPass(),
new CheckServiceClassPass(),
);
}

Expand Down
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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
)
);
}
}
@@ -0,0 +1,106 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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()
{
}
}
Expand Up @@ -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();
}

}
@@ -1,6 +1,7 @@
<?php

require_once __DIR__.'/../includes/classes.php';
require_once __DIR__.'/../includes/foo.php';

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand Down
Expand Up @@ -4,6 +4,19 @@
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;

//Services have to exist
class FooClass
{
}

class BAR
{
}

class Foo
{
}

class ProjectExtension implements ExtensionInterface
{
public function load(array $configs, ContainerBuilder $configuration)
Expand Down
Expand Up @@ -59,3 +59,15 @@ public function __construct(BarClass $bar)
$this->bar = $bar;
}
}

class Baz
{
}

class Request
{
}

class ConfClass
{
}

0 comments on commit e42e350

Please sign in to comment.