diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index cf2520a371549..447549b970442 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -71,7 +71,12 @@ public function getProxyFactoryCode(Definition $definition, $id) $instantiation .= " \$this->services['$id'] ="; } - $methodName = 'get'.Container::camelize($id).'Service'; + if (func_num_args() >= 3) { + $methodName = func_get_arg(2); + } else { + @trigger_error(sprintf('You must use the third argument of %s to define the method to call to construct your service since version 3.1, not using it won\'t be supported in 4.0.', __METHOD__), E_USER_DEPRECATED); + $methodName = 'get'.Container::camelize($id).'Service'; + } $proxyClass = $this->getProxyClassName($definition); $generatedClass = $this->generateProxyClass($definition); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index de7f9ea221a7e..cd3af44a81f16 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -60,6 +60,27 @@ public function testGetProxyCode() ); } + public function testGetProxyFactoryCodeWithCustomMethod() + { + $definition = new Definition(__CLASS__); + + $definition->setLazy(true); + + $code = $this->dumper->getProxyFactoryCode($definition, 'foo', 'getFoo2Service'); + + $this->assertStringMatchesFormat( + '%wif ($lazyLoad) {%wreturn $this->services[\'foo\'] =%s' + .'SymfonyBridgeProxyManagerTestsLazyProxyPhpDumperProxyDumperTest_%s(%wfunction ' + .'(&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {' + .'%w$wrappedInstance = $this->getFoo2Service(false);%w$proxy->setProxyInitializer(null);' + .'%wreturn true;%w}%w);%w}%w', + $code + ); + } + + /** + * @group legacy + */ public function testGetProxyFactoryCode() { $definition = new Definition(__CLASS__); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index e0a763dfe8d5b..a8894f39e2f15 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -58,6 +58,8 @@ class PhpDumper extends Dumper private $targetDirRegex; private $targetDirMaxMatches; private $docStar; + private $serviceIdToMethodNameMap; + private $usedMethodNames; /** * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface @@ -106,6 +108,9 @@ public function dump(array $options = array()) 'namespace' => '', 'debug' => true, ), $options); + + $this->initializeMethodNamesMap($options['base_class']); + $this->docStar = $options['debug'] ? '*' : ''; if (!empty($options['file']) && is_dir($dir = dirname($options['file']))) { @@ -625,6 +630,7 @@ private function addService($id, $definition) // with proxies, for 5.3.3 compatibility, the getter must be public to be accessible to the initializer $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); $visibility = $isProxyCandidate ? 'public' : 'protected'; + $methodName = $this->generateMethodName($id); $code = <<docStar} @@ -632,12 +638,12 @@ private function addService($id, $definition) *$lazyInitializationDoc * $return */ - {$visibility} function get{$this->camelize($id)}Service($lazyInitialization) + {$visibility} function {$methodName}($lazyInitialization) { EOF; - $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id) : ''; + $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $methodName) : ''; if ($definition->isSynthetic()) { $code .= sprintf(" throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n }\n", $id); @@ -864,7 +870,7 @@ private function addMethodMap() $code = " \$this->methodMap = array(\n"; ksort($definitions); foreach ($definitions as $id => $definition) { - $code .= ' '.var_export($id, true).' => '.var_export('get'.$this->camelize($id).'Service', true).",\n"; + $code .= ' '.var_export($id, true).' => '.var_export($this->generateMethodName($id), true).",\n"; } return $code." );\n"; @@ -1338,6 +1344,25 @@ private function getServiceCall($id, Reference $reference = null) } } + /** + * Initializes the method names map to avoid conflicts with the Container methods. + * + * @param string $class the container base class + */ + private function initializeMethodNamesMap($class) + { + $this->serviceIdToMethodNameMap = array(); + $this->usedMethodNames = array(); + + try { + $reflectionClass = new \ReflectionClass($class); + foreach ($reflectionClass->getMethods() as $method) { + $this->usedMethodNames[strtolower($method->getName())] = true; + } + } catch (\ReflectionException $e) { + } + } + /** * Convert a service id to a valid PHP method name. * @@ -1347,15 +1372,26 @@ private function getServiceCall($id, Reference $reference = null) * * @throws InvalidArgumentException */ - private function camelize($id) + private function generateMethodName($id) { + if (isset($this->serviceIdToMethodNameMap[$id])) { + return $this->serviceIdToMethodNameMap[$id]; + } + $name = Container::camelize($id); + $name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name); + $methodName = 'get'.$name.'Service'; + $suffix = 1; - if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/', $name)) { - throw new InvalidArgumentException(sprintf('Service id "%s" cannot be converted to a valid PHP method name.', $id)); + while (isset($this->usedMethodNames[strtolower($methodName)])) { + ++$suffix; + $methodName = 'get'.$name.$suffix.'Service'; } - return $name; + $this->serviceIdToMethodNameMap[$id] = $methodName; + $this->usedMethodNames[strtolower($methodName)] = true; + + return $methodName; } /** diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php index 878d965b1c39d..3ff2e01c13582 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php @@ -34,10 +34,11 @@ public function isProxyCandidate(Definition $definition); * * @param Definition $definition * @param string $id service identifier + * @param string $methodName the method name to get the service, will be added to the interface in 4.0. * * @return string */ - public function getProxyFactoryCode(Definition $definition, $id); + public function getProxyFactoryCode(Definition $definition, $id/**, $methodName = null */); /** * Generates the code for the lazy proxy. diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 81b633dcbb91b..ae0c82e405d12 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -132,16 +132,46 @@ public function testServicesWithAnonymousFactories() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services19.php', $dumper->dump(), '->dump() dumps services with anonymous factories'); } - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Service id "bar$" cannot be converted to a valid PHP method name. - */ - public function testAddServiceInvalidServiceId() + public function testAddServiceIdWithUnsupportedCharacters() { + $class = 'Symfony_DI_PhpDumper_Test_Unsupported_Characters'; $container = new ContainerBuilder(); $container->register('bar$', 'FooClass'); + $container->register('bar$!', 'FooClass'); $dumper = new PhpDumper($container); - $dumper->dump(); + eval('?>'.$dumper->dump(array('class' => $class))); + + $this->assertTrue(method_exists($class, 'getBarService')); + $this->assertTrue(method_exists($class, 'getBar2Service')); + } + + public function testConflictingServiceIds() + { + $class = 'Symfony_DI_PhpDumper_Test_Conflicting_Service_Ids'; + $container = new ContainerBuilder(); + $container->register('foo_bar', 'FooClass'); + $container->register('foobar', 'FooClass'); + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(array('class' => $class))); + + $this->assertTrue(method_exists($class, 'getFooBarService')); + $this->assertTrue(method_exists($class, 'getFoobar2Service')); + } + + public function testConflictingMethodsWithParent() + { + $class = 'Symfony_DI_PhpDumper_Test_Conflicting_Method_With_Parent'; + $container = new ContainerBuilder(); + $container->register('bar', 'FooClass'); + $container->register('foo_bar', 'FooClass'); + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(array( + 'class' => $class, + 'base_class' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\containers\CustomContainer', + ))); + + $this->assertTrue(method_exists($class, 'getBar2Service')); + $this->assertTrue(method_exists($class, 'getFoobar2Service')); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/CustomContainer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/CustomContainer.php new file mode 100644 index 0000000000000..2251435324b38 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/CustomContainer.php @@ -0,0 +1,17 @@ +