Permalink
Browse files

feature #23741 [DI] Generate one file per service factory (nicolas-gr…

…ekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[DI] Generate one file per service factory

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #23601
| License       | MIT
| Doc PR        | -

See #23678 for background on this proposal.

Commits
-------

4037009 [DI] Generate one file per service factory
  • Loading branch information...
fabpot committed Aug 7, 2017
2 parents 0effd27 + 4037009 commit 100fe4e97ed4db1f228203351d2af6e0dcfe0596
Showing with 778 additions and 235 deletions.
  1. +6 −2 src/Symfony/Bridge/Doctrine/ManagerRegistry.php
  2. +7 −10 src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php
  3. +5 −6 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt
  4. +6 −14 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php
  5. +1 −1 src/Symfony/Bridge/ProxyManager/composer.json
  6. +21 −4 src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
  7. +10 −3 src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php
  8. +45 −35 src/Symfony/Component/DependencyInjection/Container.php
  9. +202 −150 src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
  10. +12 −0 src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
  11. +1 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php
  12. +1 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot
  13. +1 −1 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php
  14. +434 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
  15. +1 −1 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php
  16. +3 −1 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml
  17. +1 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml
  18. +19 −5 src/Symfony/Component/HttpKernel/Kernel.php
  19. +2 −2 src/Symfony/Component/HttpKernel/composer.json
@@ -59,8 +59,12 @@ function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) {
if (isset($this->aliases[$name])) {
$name = $this->aliases[$name];
}
$method = !isset($this->methodMap[$name]) ? 'get'.strtr($name, $this->underscoreMap).'Service' : $this->methodMap[$name];
$wrappedInstance = $this->{$method}(false);
if (isset($this->fileMap[$name])) {
$wrappedInstance = $this->load($this->fileMap[$name], false);
} else {
$method = !isset($this->methodMap[$name]) ? 'get'.strtr($name, $this->underscoreMap).'Service' : $this->methodMap[$name];
$wrappedInstance = $this->{$method}(false);
}
$manager->setProxyInitializer(null);
@@ -81,24 +81,21 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode =
}
$proxyClass = $this->getProxyClassName($definition);
$generatedClass = $this->generateProxyClass($definition);
$hasStaticConstructor = $this->generateProxyClass($definition)->hasMethod('staticProxyConstructor');
$constructorCall = $generatedClass->hasMethod('staticProxyConstructor')
? $proxyClass.'::staticProxyConstructor'
: 'new '.$proxyClass;
$constructorCall = sprintf($hasStaticConstructor ? '%s::staticProxyConstructor' : 'new %s', '\\'.$proxyClass);
return <<<EOF
if (\$lazyLoad) {
$instantiation $constructorCall(
function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
$instantiation \$this->createProxy('$proxyClass', function () {
return $constructorCall(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
\$wrappedInstance = $factoryCode;
\$proxy->setProxyInitializer(null);
return true;
}
);
});
});
}
@@ -122,7 +119,7 @@ public function getProxyCode(Definition $definition)
*/
private function getProxyClassName(Definition $definition)
{
return str_replace('\\', '', $definition->getClass()).'_'.spl_object_hash($definition).$this->salt;
return preg_replace('/^.*\\\\/', '', $definition->getClass()).'_'.substr(hash('sha256', spl_object_hash($definition).$this->salt), -7);
}
/**
@@ -3,19 +3,18 @@
use %a
class LazyServiceProjectServiceContainer extends Container
{%a
p%s function getFooService($lazyLoad = true)
protected function getFooService($lazyLoad = true)
{
if ($lazyLoad) {
return $this->services['foo'] =%sstdClass_%s(
function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
return $this->services['foo'] = $this->createProxy('stdClass_%s', function () {
return %S\stdClass_%s(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
$wrappedInstance = $this->getFooService(false);
$proxy->setProxyInitializer(null);
return true;
}
);
});
});
}
return new \stdClass();
@@ -55,13 +55,13 @@ public function testGetProxyCode()
$code = $this->dumper->getProxyCode($definition);
$this->assertStringMatchesFormat(
'%Aclass SymfonyBridgeProxyManagerTestsLazyProxyPhpDumperProxyDumperTest%aextends%w'
'%Aclass ProxyDumperTest%aextends%w'
.'\Symfony\Bridge\ProxyManager\Tests\LazyProxy\PhpDumper\ProxyDumperTest%a',
$code
);
}
public function testGetProxyFactoryCodeWithCustomMethod()
public function testGetProxyFactoryCode()
{
$definition = new Definition(__CLASS__);
@@ -70,19 +70,15 @@ public function testGetProxyFactoryCodeWithCustomMethod()
$code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)');
$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
'%A$wrappedInstance = $this->getFoo2Service(false);%w$proxy->setProxyInitializer(null);%A',
$code
);
}
/**
* @group legacy
*/
public function testGetProxyFactoryCode()
public function testLegacyGetProxyFactoryCode()
{
$definition = new Definition(__CLASS__);
@@ -91,11 +87,7 @@ public function testGetProxyFactoryCode()
$code = $this->dumper->getProxyFactoryCode($definition, 'foo');
$this->assertStringMatchesFormat(
'%wif ($lazyLoad) {%wreturn $this->services[\'foo\'] =%s'
.'SymfonyBridgeProxyManagerTestsLazyProxyPhpDumperProxyDumperTest_%s(%wfunction '
.'(&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {'
.'%w$wrappedInstance = $this->getFooService(false);%w$proxy->setProxyInitializer(null);'
.'%wreturn true;%w}%w);%w}%w',
'%A$wrappedInstance = $this->getFooService(false);%w$proxy->setProxyInitializer(null);%A',
$code
);
}
@@ -17,7 +17,7 @@
],
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/dependency-injection": "~2.8|~3.0|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"ocramius/proxy-manager": "~0.4|~1.0|~2.0"
},
"require-dev": {
@@ -15,6 +15,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
@@ -125,6 +126,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->filesystem->remove($oldCacheDir);
// The current event dispatcher is stale, let's not use it anymore
$this->getApplication()->setDispatcher(new EventDispatcher());
if ($output->isVerbose()) {
$io->comment('Finished');
}
@@ -213,13 +217,19 @@ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = tr
}
// fix references to container's class
$tempContainerClass = get_class($tempKernel->getContainer());
$realContainerClass = get_class($realKernel->getContainer());
$tempContainerClass = $tempKernel->getContainerClass();
$realContainerClass = $tempKernel->getRealContainerClass();
foreach (Finder::create()->files()->depth('<2')->name($tempContainerClass.'*')->in($warmupDir) as $file) {
$content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file));
file_put_contents($file, $content);
rename($file, str_replace(DIRECTORY_SEPARATOR.$tempContainerClass, DIRECTORY_SEPARATOR.$realContainerClass, $file));
}
if (is_dir($tempContainerDir = $warmupDir.'/'.get_class($tempKernel->getContainer()))) {
foreach (Finder::create()->files()->in($tempContainerDir) as $file) {
$content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file));
file_put_contents($file, $content);
}
}
// remove temp kernel file after cache warmed up
@unlink($tempKernelFile);
@@ -245,7 +255,9 @@ protected function getTempKernel(KernelInterface $parent, $namespace, $parentCla
// to avoid the many problems in serialized resources files
$class = substr($parentClass, 0, -1).'_';
// the temp container class must be changed too
$containerClass = var_export(substr(get_class($parent->getContainer()), 0, -1).'_', true);
$container = $parent->getContainer();
$realContainerClass = var_export($container->hasParameter('kernel.container_class') ? $container->getParameter('kernel.container_class') : get_class($parent->getContainer()), true);
$containerClass = substr_replace($realContainerClass, '_', -2, 1);
if (method_exists($parent, 'getProjectDir')) {
$projectDir = var_export(realpath($parent->getProjectDir()), true);
@@ -281,7 +293,12 @@ public function getLogDir()
return $logDir;
}
protected function getContainerClass()
public function getRealContainerClass()
{
return $realContainerClass;
}
public function getContainerClass()
{
return $containerClass;
}
@@ -68,8 +68,9 @@ public function testCacheIsFreshAfterCacheClearedWithWarmup()
}
// check that app kernel file present in meta file of container's cache
$containerRef = new \ReflectionObject($this->kernel->getContainer());
$containerFile = $containerRef->getFileName();
$containerClass = $this->kernel->getContainer()->getParameter('kernel.container_class');
$containerRef = new \ReflectionClass($containerClass);
$containerFile = dirname(dirname($containerRef->getFileName())).'/'.$containerClass.'.php';
$containerMetaFile = $containerFile.'.meta';
$kernelRef = new \ReflectionObject($this->kernel);
$kernelFile = $kernelRef->getFileName();
@@ -83,6 +84,12 @@ public function testCacheIsFreshAfterCacheClearedWithWarmup()
}
}
$this->assertTrue($found, 'Kernel file should present as resource');
$this->assertRegExp(sprintf('/\'kernel.container_class\'\s*=>\s*\'%s\'/', get_class($this->kernel->getContainer())), file_get_contents($containerFile), 'kernel.container_class is properly set on the dumped container');
if (defined('HHVM_VERSION')) {
return;
}
$containerRef = new \ReflectionClass(require $containerFile);
$containerFile = str_replace('tes_'.DIRECTORY_SEPARATOR, 'test'.DIRECTORY_SEPARATOR, $containerRef->getFileName());
$this->assertRegExp(sprintf('/\'kernel.container_class\'\s*=>\s*\'%s\'/', $containerClass), file_get_contents($containerFile), 'kernel.container_class is properly set on the dumped container');
}
}
@@ -55,6 +55,7 @@ class Container implements ResettableContainerInterface
protected $parameterBag;
protected $services = array();
protected $fileMap = array();
protected $methodMap = array();
protected $aliases = array();
protected $loading = array();
@@ -203,7 +204,7 @@ public function set($id, $service)
} else {
@trigger_error(sprintf('Setting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED);
}
} elseif (isset($this->methodMap[$id])) {
} elseif (isset($this->fileMap[$id]) || isset($this->methodMap[$id])) {
if (null === $service) {
@trigger_error(sprintf('Unsetting the "%s" pre-defined service is deprecated since Symfony 3.3 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED);
} else {
@@ -235,7 +236,7 @@ public function has($id)
return true;
}
if (isset($this->methodMap[$id])) {
if (isset($this->fileMap[$id]) || isset($this->methodMap[$id])) {
return true;
}
@@ -299,49 +300,48 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
throw new ServiceCircularReferenceException($id, array_keys($this->loading));
}
if (isset($this->methodMap[$id])) {
$method = $this->methodMap[$id];
} elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) {
$id = $normalizedId;
continue;
} elseif (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class && method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) {
// We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder,
// and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper)
@trigger_error('Generating a dumped container without populating the method map is deprecated since 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED);
// $method is set to the right value, proceed
} else {
if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
if (!$id) {
throw new ServiceNotFoundException($id);
}
$alternatives = array();
foreach ($this->getServiceIds() as $knownId) {
$lev = levenshtein($id, $knownId);
if ($lev <= strlen($id) / 3 || false !== strpos($knownId, $id)) {
$alternatives[] = $knownId;
}
}
throw new ServiceNotFoundException($id, null, null, $alternatives);
}
return;
}
$this->loading[$id] = true;
try {
$service = $this->$method();
if (isset($this->fileMap[$id])) {
return $this->load($this->fileMap[$id]);
} elseif (isset($this->methodMap[$id])) {
return $this->{$this->methodMap[$id]}();
} elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) {
$id = $normalizedId;
continue;
} elseif (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class && method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) {
// We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder,
// and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper)
@trigger_error('Generating a dumped container without populating the method map is deprecated since 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED);
return $this->{$method}();
}
break;
} catch (\Exception $e) {
unset($this->services[$id]);
throw $e;
} finally {
unset($this->loading[$id]);
}
}
if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
if (!$id) {
throw new ServiceNotFoundException($id);
}
$alternatives = array();
foreach ($this->getServiceIds() as $knownId) {
$lev = levenshtein($id, $knownId);
if ($lev <= strlen($id) / 3 || false !== strpos($knownId, $id)) {
$alternatives[] = $knownId;
}
}
return $service;
throw new ServiceNotFoundException($id, null, null, $alternatives);
}
}
@@ -401,7 +401,7 @@ public function getServiceIds()
}
$ids[] = 'service_container';
return array_unique(array_merge($ids, array_keys($this->methodMap), array_keys($this->services)));
return array_unique(array_merge($ids, array_keys($this->methodMap), array_keys($this->fileMap), array_keys($this->services)));
}
/**
@@ -428,6 +428,16 @@ public static function underscore($id)
return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), str_replace('_', '.', $id)));
}
/**
* Creates a service by requiring its factory file.
*
* @return object The service created by the file
*/
protected function load($file)
{
return require $file;
}
/**
* Fetches a variable from the environment.
*
Oops, something went wrong.

0 comments on commit 100fe4e

Please sign in to comment.