Skip to content

Commit ff5fe7c

Browse files
committed
feature #18167 [DependencyInjection] Fix a limitation of the PhpDumper (Ener-Getick)
This PR was squashed before being merged into the 3.1-dev branch (closes #18167). Discussion ---------- [DependencyInjection] Fix a limitation of the PhpDumper | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | symfony/symfony#17801 | License | MIT | Doc PR | The ``PhpDumper`` cannot currently dump several services' id containing characters unsupported by php. This PR tries to solve this issue by removing the unsupported characters and by sufixing their camelized association to avoid conflicts. Commits ------- 0d147b2 [DependencyInjection] Fix a limitation of the PhpDumper
2 parents d10b29d + 1670ea0 commit ff5fe7c

File tree

4 files changed

+98
-14
lines changed

4 files changed

+98
-14
lines changed

Dumper/PhpDumper.php

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class PhpDumper extends Dumper
5858
private $targetDirRegex;
5959
private $targetDirMaxMatches;
6060
private $docStar;
61+
private $serviceIdToMethodNameMap;
62+
private $usedMethodNames;
6163

6264
/**
6365
* @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
@@ -106,6 +108,9 @@ public function dump(array $options = array())
106108
'namespace' => '',
107109
'debug' => true,
108110
), $options);
111+
112+
$this->initializeMethodNamesMap($options['base_class']);
113+
109114
$this->docStar = $options['debug'] ? '*' : '';
110115

111116
if (!empty($options['file']) && is_dir($dir = dirname($options['file']))) {
@@ -625,19 +630,20 @@ private function addService($id, $definition)
625630
// with proxies, for 5.3.3 compatibility, the getter must be public to be accessible to the initializer
626631
$isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
627632
$visibility = $isProxyCandidate ? 'public' : 'protected';
633+
$methodName = $this->generateMethodName($id);
628634
$code = <<<EOF
629635
630636
/*{$this->docStar}
631637
* Gets the '$id' service.$doc
632638
*$lazyInitializationDoc
633639
* $return
634640
*/
635-
{$visibility} function get{$this->camelize($id)}Service($lazyInitialization)
641+
{$visibility} function {$methodName}($lazyInitialization)
636642
{
637643
638644
EOF;
639645

640-
$code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id) : '';
646+
$code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $methodName) : '';
641647

642648
if ($definition->isSynthetic()) {
643649
$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()
864870
$code = " \$this->methodMap = array(\n";
865871
ksort($definitions);
866872
foreach ($definitions as $id => $definition) {
867-
$code .= ' '.var_export($id, true).' => '.var_export('get'.$this->camelize($id).'Service', true).",\n";
873+
$code .= ' '.var_export($id, true).' => '.var_export($this->generateMethodName($id), true).",\n";
868874
}
869875

870876
return $code." );\n";
@@ -1338,6 +1344,25 @@ private function getServiceCall($id, Reference $reference = null)
13381344
}
13391345
}
13401346

1347+
/**
1348+
* Initializes the method names map to avoid conflicts with the Container methods.
1349+
*
1350+
* @param string $class the container base class
1351+
*/
1352+
private function initializeMethodNamesMap($class)
1353+
{
1354+
$this->serviceIdToMethodNameMap = array();
1355+
$this->usedMethodNames = array();
1356+
1357+
try {
1358+
$reflectionClass = new \ReflectionClass($class);
1359+
foreach ($reflectionClass->getMethods() as $method) {
1360+
$this->usedMethodNames[strtolower($method->getName())] = true;
1361+
}
1362+
} catch (\ReflectionException $e) {
1363+
}
1364+
}
1365+
13411366
/**
13421367
* Convert a service id to a valid PHP method name.
13431368
*
@@ -1347,15 +1372,26 @@ private function getServiceCall($id, Reference $reference = null)
13471372
*
13481373
* @throws InvalidArgumentException
13491374
*/
1350-
private function camelize($id)
1375+
private function generateMethodName($id)
13511376
{
1377+
if (isset($this->serviceIdToMethodNameMap[$id])) {
1378+
return $this->serviceIdToMethodNameMap[$id];
1379+
}
1380+
13521381
$name = Container::camelize($id);
1382+
$name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name);
1383+
$methodName = 'get'.$name.'Service';
1384+
$suffix = 1;
13531385

1354-
if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/', $name)) {
1355-
throw new InvalidArgumentException(sprintf('Service id "%s" cannot be converted to a valid PHP method name.', $id));
1386+
while (isset($this->usedMethodNames[strtolower($methodName)])) {
1387+
++$suffix;
1388+
$methodName = 'get'.$name.$suffix.'Service';
13561389
}
13571390

1358-
return $name;
1391+
$this->serviceIdToMethodNameMap[$id] = $methodName;
1392+
$this->usedMethodNames[strtolower($methodName)] = true;
1393+
1394+
return $methodName;
13591395
}
13601396

13611397
/**

LazyProxy/PhpDumper/DumperInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ public function isProxyCandidate(Definition $definition);
3434
*
3535
* @param Definition $definition
3636
* @param string $id service identifier
37+
* @param string $methodName the method name to get the service, will be added to the interface in 4.0.
3738
*
3839
* @return string
3940
*/
40-
public function getProxyFactoryCode(Definition $definition, $id);
41+
public function getProxyFactoryCode(Definition $definition, $id/**, $methodName = null */);
4142

4243
/**
4344
* Generates the code for the lazy proxy.

Tests/Dumper/PhpDumperTest.php

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,46 @@ public function testServicesWithAnonymousFactories()
132132
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services19.php', $dumper->dump(), '->dump() dumps services with anonymous factories');
133133
}
134134

135-
/**
136-
* @expectedException \InvalidArgumentException
137-
* @expectedExceptionMessage Service id "bar$" cannot be converted to a valid PHP method name.
138-
*/
139-
public function testAddServiceInvalidServiceId()
135+
public function testAddServiceIdWithUnsupportedCharacters()
140136
{
137+
$class = 'Symfony_DI_PhpDumper_Test_Unsupported_Characters';
141138
$container = new ContainerBuilder();
142139
$container->register('bar$', 'FooClass');
140+
$container->register('bar$!', 'FooClass');
143141
$dumper = new PhpDumper($container);
144-
$dumper->dump();
142+
eval('?>'.$dumper->dump(array('class' => $class)));
143+
144+
$this->assertTrue(method_exists($class, 'getBarService'));
145+
$this->assertTrue(method_exists($class, 'getBar2Service'));
146+
}
147+
148+
public function testConflictingServiceIds()
149+
{
150+
$class = 'Symfony_DI_PhpDumper_Test_Conflicting_Service_Ids';
151+
$container = new ContainerBuilder();
152+
$container->register('foo_bar', 'FooClass');
153+
$container->register('foobar', 'FooClass');
154+
$dumper = new PhpDumper($container);
155+
eval('?>'.$dumper->dump(array('class' => $class)));
156+
157+
$this->assertTrue(method_exists($class, 'getFooBarService'));
158+
$this->assertTrue(method_exists($class, 'getFoobar2Service'));
159+
}
160+
161+
public function testConflictingMethodsWithParent()
162+
{
163+
$class = 'Symfony_DI_PhpDumper_Test_Conflicting_Method_With_Parent';
164+
$container = new ContainerBuilder();
165+
$container->register('bar', 'FooClass');
166+
$container->register('foo_bar', 'FooClass');
167+
$dumper = new PhpDumper($container);
168+
eval('?>'.$dumper->dump(array(
169+
'class' => $class,
170+
'base_class' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\containers\CustomContainer',
171+
)));
172+
173+
$this->assertTrue(method_exists($class, 'getBar2Service'));
174+
$this->assertTrue(method_exists($class, 'getFoobar2Service'));
145175
}
146176

147177
/**
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\containers;
4+
5+
use Symfony\Component\DependencyInjection\Container;
6+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
7+
8+
class CustomContainer extends Container
9+
{
10+
public function getBarService()
11+
{
12+
}
13+
14+
public function getFoobarService()
15+
{
16+
}
17+
}

0 commit comments

Comments
 (0)