Skip to content

Commit ea3d004

Browse files
committed
bug #12784 [DependencyInjection] make paths relative to __DIR__ in the generated container (nicolas-grekas)
This PR was merged into the 2.3 branch. Discussion ---------- [DependencyInjection] make paths relative to __DIR__ in the generated container | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #6484, #3079, partially #9238, #10894, #10999 | License | MIT | Doc PR | n/a This is an alternative approach to #10999 for removing absolute paths from the generated container: instead of trying to fix the container file after it has been dumped, telling to the PhpDumper where its output will be written allows it to replace parts of strings by an equivalent value based on `__DIR__`. This should be safe, thus the PR is on 2.3. Commits ------- edd7057 [DependencyInjection] make paths relative to __DIR__ in the generated container
2 parents e3f3e2c + 0170965 commit ea3d004

File tree

3 files changed

+182
-5
lines changed

3 files changed

+182
-5
lines changed

Dumper/PhpDumper.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class PhpDumper extends Dumper
5151
private $referenceVariables;
5252
private $variableCount;
5353
private $reservedVariables = array('instance', 'class');
54+
private $targetDirRegex;
55+
private $targetDirMaxMatches;
5456

5557
/**
5658
* @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
@@ -95,11 +97,35 @@ public function setProxyDumper(ProxyDumper $proxyDumper)
9597
*/
9698
public function dump(array $options = array())
9799
{
100+
$this->targetDirRegex = null;
98101
$options = array_merge(array(
99102
'class' => 'ProjectServiceContainer',
100103
'base_class' => 'Container',
101104
), $options);
102105

106+
if (!empty($options['file']) && is_dir($dir = dirname($options['file']))) {
107+
// Build a regexp where the first two root dirs are mandatory,
108+
// but every other sub-dir is optional up to the full path in $dir
109+
110+
$dir = explode(DIRECTORY_SEPARATOR, realpath($dir));
111+
$i = count($dir);
112+
113+
if (3 <= $i) {
114+
$regex = '';
115+
$this->targetDirMaxMatches = $i - 3;
116+
117+
while (2 < --$i) {
118+
$regex = sprintf('(%s%s)?', preg_quote(DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex);
119+
}
120+
121+
do {
122+
$regex = preg_quote(DIRECTORY_SEPARATOR.$dir[$i], '#').$regex;
123+
} while (0 < --$i);
124+
125+
$this->targetDirRegex = '#'.preg_quote($dir[0], '#').$regex.'#';
126+
}
127+
}
128+
103129
$code = $this->startClass($options['class'], $options['base_class']);
104130

105131
if ($this->container->isFrozen()) {
@@ -114,6 +140,7 @@ public function dump(array $options = array())
114140
$this->endClass().
115141
$this->addProxyClasses()
116142
;
143+
$this->targetDirRegex = null;
117144

118145
return $code;
119146
}
@@ -979,7 +1006,7 @@ private function exportParameters($parameters, $path = '', $indent = 12)
9791006
} elseif ($value instanceof Reference) {
9801007
throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key));
9811008
} else {
982-
$value = var_export($value, true);
1009+
$value = $this->export($value);
9831010
}
9841011

9851012
$php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value);
@@ -1214,14 +1241,14 @@ private function dumpValue($value, $interpolate = true)
12141241
return "'.".$that->dumpParameter(strtolower($match[2])).".'";
12151242
};
12161243

1217-
$code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, var_export($value, true)));
1244+
$code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, $this->export($value)));
12181245

12191246
return $code;
12201247
}
12211248
} elseif (is_object($value) || is_resource($value)) {
12221249
throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
12231250
} else {
1224-
return var_export($value, true);
1251+
return $this->export($value);
12251252
}
12261253
}
12271254

@@ -1323,4 +1350,26 @@ private function getNextVariableName()
13231350
return $name;
13241351
}
13251352
}
1353+
1354+
private function export($value)
1355+
{
1356+
if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) {
1357+
$prefix = $matches[0][1] ? var_export(substr($value, 0, $matches[0][1]), true).'.' : '';
1358+
$suffix = $matches[0][1] + strlen($matches[0][0]);
1359+
$suffix = isset($value[$suffix]) ? '.'.var_export(substr($value, $suffix), true) : '';
1360+
$dirname = '__DIR__';
1361+
1362+
for ($i = $this->targetDirMaxMatches - count($matches); 0 <= $i; --$i) {
1363+
$dirname = sprintf('dirname(%s)', $dirname);
1364+
}
1365+
1366+
if ($prefix || $suffix) {
1367+
return sprintf('(%s%s%s)', $prefix, $dirname, $suffix);
1368+
}
1369+
1370+
return $dirname;
1371+
}
1372+
1373+
return var_export($value, true);
1374+
}
13261375
}

Tests/Dumper/PhpDumperTest.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ public function testDumpOptimizationString()
8080
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services10.php', $dumper->dump(), '->dump() dumps an empty container as an empty PHP class');
8181
}
8282

83+
public function testDumpRelativeDir()
84+
{
85+
$definition = new Definition();
86+
$definition->setClass('stdClass');
87+
$definition->addArgument('%foo%');
88+
$definition->addArgument(array('%foo%' => '%foo%'));
89+
90+
$container = new ContainerBuilder();
91+
$container->setDefinition('test', $definition);
92+
$container->setParameter('foo', 'wiz'.dirname(dirname(__FILE__)));
93+
$container->setParameter('bar', dirname(__FILE__));
94+
$container->setParameter('baz', '%bar%/PhpDumperTest.php');
95+
$container->compile();
96+
97+
$dumper = new PhpDumper($container);
98+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services12.php', $dumper->dump(array('file' => __FILE__)), '->dump() dumps __DIR__ relative strings');
99+
}
100+
83101
/**
84102
* @expectedException \InvalidArgumentException
85103
*/
@@ -101,13 +119,13 @@ public function testAddService()
101119
// without compilation
102120
$container = include self::$fixturesPath.'/containers/container9.php';
103121
$dumper = new PhpDumper($container);
104-
$this->assertEquals(str_replace('%path%', str_replace('\\','\\\\',self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), file_get_contents(self::$fixturesPath.'/php/services9.php')), $dumper->dump(), '->dump() dumps services');
122+
$this->assertEquals(str_replace('%path%', str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), file_get_contents(self::$fixturesPath.'/php/services9.php')), $dumper->dump(), '->dump() dumps services');
105123

106124
// with compilation
107125
$container = include self::$fixturesPath.'/containers/container9.php';
108126
$container->compile();
109127
$dumper = new PhpDumper($container);
110-
$this->assertEquals(str_replace('%path%', str_replace('\\','\\\\',self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), file_get_contents(self::$fixturesPath.'/php/services9_compiled.php')), $dumper->dump(), '->dump() dumps services');
128+
$this->assertEquals(str_replace('%path%', str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), file_get_contents(self::$fixturesPath.'/php/services9_compiled.php')), $dumper->dump(), '->dump() dumps services');
111129

112130
$dumper = new PhpDumper($container = new ContainerBuilder());
113131
$container->register('foo', 'FooClass')->addArgument(new \stdClass());

Tests/Fixtures/php/services12.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
use Symfony\Component\DependencyInjection\ContainerInterface;
4+
use Symfony\Component\DependencyInjection\Container;
5+
use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
6+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
7+
use Symfony\Component\DependencyInjection\Exception\LogicException;
8+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
9+
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
10+
11+
/**
12+
* ProjectServiceContainer
13+
*
14+
* This class has been auto-generated
15+
* by the Symfony Dependency Injection Component.
16+
*/
17+
class ProjectServiceContainer extends Container
18+
{
19+
/**
20+
* Constructor.
21+
*/
22+
public function __construct()
23+
{
24+
$this->parameters = $this->getDefaultParameters();
25+
26+
$this->services =
27+
$this->scopedServices =
28+
$this->scopeStacks = array();
29+
30+
$this->set('service_container', $this);
31+
32+
$this->scopes = array();
33+
$this->scopeChildren = array();
34+
$this->methodMap = array(
35+
'test' => 'getTestService',
36+
);
37+
38+
$this->aliases = array();
39+
}
40+
41+
/**
42+
* Gets the 'test' service.
43+
*
44+
* This service is shared.
45+
* This method always returns the same instance of the service.
46+
*
47+
* @return \stdClass A stdClass instance.
48+
*/
49+
protected function getTestService()
50+
{
51+
return $this->services['test'] = new \stdClass(('wiz'.dirname(__DIR__)), array(('wiz'.dirname(__DIR__)) => ('wiz'.dirname(__DIR__))));
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function getParameter($name)
58+
{
59+
$name = strtolower($name);
60+
61+
if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) {
62+
throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
63+
}
64+
65+
return $this->parameters[$name];
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
public function hasParameter($name)
72+
{
73+
$name = strtolower($name);
74+
75+
return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters);
76+
}
77+
78+
/**
79+
* {@inheritdoc}
80+
*/
81+
public function setParameter($name, $value)
82+
{
83+
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
84+
}
85+
86+
/**
87+
* {@inheritdoc}
88+
*/
89+
public function getParameterBag()
90+
{
91+
if (null === $this->parameterBag) {
92+
$this->parameterBag = new FrozenParameterBag($this->parameters);
93+
}
94+
95+
return $this->parameterBag;
96+
}
97+
/**
98+
* Gets the default parameters.
99+
*
100+
* @return array An array of the default parameters
101+
*/
102+
protected function getDefaultParameters()
103+
{
104+
return array(
105+
'foo' => ('wiz'.dirname(__DIR__)),
106+
'bar' => __DIR__,
107+
'baz' => (__DIR__.'/PhpDumperTest.php'),
108+
);
109+
}
110+
}

0 commit comments

Comments
 (0)