diff --git a/DependencyInjection/Compiler/IntegrationPass.php b/DependencyInjection/Compiler/IntegrationPass.php index dc409ac..fd508db 100644 --- a/DependencyInjection/Compiler/IntegrationPass.php +++ b/DependencyInjection/Compiler/IntegrationPass.php @@ -21,7 +21,6 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; /** @@ -41,36 +40,50 @@ public function process(ContainerBuilder $container) } } - /** - * Integrates the DiAwareObjectManager with Doctrine. - * - * This is a bit trickier... mostly because Doctrine uses many factories, - * and we cannot directly inject the EntityManager. We circumvent this - * problem by renaming the original entity manager definition, and then - * placing our definition in its place. - * - * Note that this also currently only supports the ORM, for the ODM flavors - * a similar integration should be possible. - * - * @param ContainerBuilder $container - */ - private function integrateWithDoctrine($container) + private function integrateWithDoctrine(ContainerBuilder $container) { - foreach ($container->getDefinitions() as $id => $definition) { - if (!$definition instanceof DefinitionDecorator) { - continue; - } + // The parameter "doctrine.entity_managers" can be missed when not actually using ORM even if DoctrineBundle loaded. + if (!$container->hasParameter('doctrine.entity_managers')) { + return; + } + + // Can't support Doctrine/ORM older (< 2.4) version. + $reflection = new \ReflectionClass('Doctrine\ORM\Configuration'); + if (!$reflection->hasMethod('setRepositoryFactory')) { + return; + } + + $entityManagerNames = array_keys($container->getParameter('doctrine.entity_managers')); + + foreach ($entityManagerNames as $emName) { + // See: https://github.com/doctrine/DoctrineBundle/blob/c9f8cc06153a70433d2c67393f10725959f7bb43/DependencyInjection/DoctrineExtension.php#L384-L385 + $ormConfigDef = $container->getDefinition(sprintf('doctrine.orm.%s_configuration', $emName)); - if ('doctrine.orm.entity_manager.abstract' !== $definition->getParent()) { - continue; + $originalRepositoryFactoryRef = null; + + foreach ($ormConfigDef->getMethodCalls() as $methodCall) { + list($methodName, $arguments) = $methodCall; + + if ('setRepositoryFactory' === $methodName) { + $originalRepositoryFactoryRef = $arguments[0]; + + $ormConfigDef->removeMethodCall($methodName); + + break; + } } - $definition->setPublic(false); - $container->setDefinition($id.'.delegate', $definition); - $container->register($id, $container->getParameter('jms_di_extra.doctrine_integration.entity_manager.class')) - ->setFile($container->getParameter('jms_di_extra.doctrine_integration.entity_manager.file')) - ->addArgument(new Reference($id.'.delegate')) - ->addArgument(new Reference('service_container')); + $replacedRepositoryFactoryId = sprintf('jms_di_extra.doctrine.orm.%s.repository_factory', $emName); + + $container->register($replacedRepositoryFactoryId, 'JMS\DiExtraBundle\Doctrine\ORM\ContainerAwareRepositoryFactoryDecorator') + ->setPublic(false) + ->setArguments(array( + new Reference('service_container'), + $originalRepositoryFactoryRef, + )) + ; + + $ormConfigDef->addMethodCall('setRepositoryFactory', array(new Reference($replacedRepositoryFactoryId))); } } } diff --git a/DependencyInjection/JMSDiExtraExtension.php b/DependencyInjection/JMSDiExtraExtension.php index 71db845..4351355 100644 --- a/DependencyInjection/JMSDiExtraExtension.php +++ b/DependencyInjection/JMSDiExtraExtension.php @@ -18,10 +18,7 @@ namespace JMS\DiExtraBundle\DependencyInjection; -use CG\Core\DefaultNamingStrategy; -use CG\Proxy\Enhancer; use JMS\DiExtraBundle\Exception\RuntimeException; -use JMS\DiExtraBundle\Generator\RepositoryInjectionGenerator; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; @@ -67,10 +64,6 @@ public function load(array $configs, ContainerBuilder $container) $this->configureMetadata($config['metadata'], $container, $config['cache_dir'].'/metadata'); $this->configureAutomaticControllerInjections($config, $container); - if ($config['doctrine_integration']) { - $this->generateEntityManagerProxyClass($config, $container); - } - if (PHP_VERSION_ID < 70000) { $this->addClassesToCompile(array( 'JMS\\DiExtraBundle\\HttpKernel\ControllerResolver', @@ -83,27 +76,6 @@ public function blackListControllerFile($filename) $this->blackListedControllerFiles[] = realpath($filename); } - private function generateEntityManagerProxyClass(array $config, ContainerBuilder $container) - { - $cacheDir = $container->getParameterBag()->resolveValue($config['cache_dir']); - - if (!is_dir($cacheDir.'/doctrine')) { - if (false === @mkdir($cacheDir.'/doctrine', 0777, true)) { - throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir.'/doctrine')); - } - } - - $enhancer = new Enhancer($ref = new \ReflectionClass('Doctrine\ORM\EntityManager'), array(), array(new RepositoryInjectionGenerator())); - $uniqid = uniqid(); // We do have to use a non-static id to avoid problems with cache:clear. - if (strtoupper(PHP_OS) == 'CYGWIN') { - $uniqid = preg_replace('/\./', '_', $uniqid); // replace dot; cygwin always generates uniqid's with more_entropy - } - $enhancer->setNamingStrategy(new DefaultNamingStrategy('EntityManager'.$uniqid)); - $enhancer->writeClass($file = $cacheDir.'/doctrine/EntityManager_'.$uniqid.'.php'); - $container->setParameter('jms_di_extra.doctrine_integration.entity_manager.file', $file); - $container->setParameter('jms_di_extra.doctrine_integration.entity_manager.class', $enhancer->getClassName($ref)); - } - private function configureAutomaticControllerInjections(array $config, ContainerBuilder $container) { if (!isset($config['automatic_controller_injections'])) { diff --git a/Doctrine/ORM/ContainerAwareRepositoryFactoryDecorator.php b/Doctrine/ORM/ContainerAwareRepositoryFactoryDecorator.php new file mode 100644 index 0000000..abee672 --- /dev/null +++ b/Doctrine/ORM/ContainerAwareRepositoryFactoryDecorator.php @@ -0,0 +1,89 @@ + + */ +final class ContainerAwareRepositoryFactoryDecorator implements RepositoryFactory +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var RepositoryFactory + */ + private $wrappedFactory; + + /** + * @var MetadataFactory + */ + private $metadataFactory; + + public function __construct(ContainerInterface $container, RepositoryFactory $wrappedFactory = null) + { + $this->container = $container; + $this->wrappedFactory = $wrappedFactory ?: new DefaultRepositoryFactory(); + } + + /** + * {@inheritdoc} + */ + public function getRepository(EntityManagerInterface $entityManager, $entityName) + { + $repository = $this->wrappedFactory->getRepository($entityManager, $entityName); + + if ($repository instanceof ContainerAwareInterface) { + $repository->setContainer($this->container); + + return $repository; + } + + if (null !== $metadata = $this->getMetadataFactory()->getMetadataForClass(get_class($repository))) { + foreach ($metadata->classMetadata as $classMetadata) { + foreach ($classMetadata->methodCalls as $call) { + list($method, $arguments) = $call; + call_user_func_array(array($repository, $method), $this->prepareArguments($arguments)); + } + } + } + + return $repository; + } + + private function getMetadataFactory() + { + if (!$this->metadataFactory) { + $this->metadataFactory = $this->container->get('jms_di_extra.metadata.metadata_factory'); + } + + return $this->metadataFactory; + } + + private function prepareArguments(array $arguments) + { + $processed = array(); + foreach ($arguments as $arg) { + if ($arg instanceof Reference) { + $processed[] = $this->container->get((string) $arg, $arg->getInvalidBehavior()); + } elseif ($arg instanceof Parameter) { + $processed[] = $this->container->getParameter((string) $arg); + } else { + $processed[] = $arg; + } + } + + return $processed; + } +} diff --git a/Generator/RepositoryInjectionGenerator.php b/Generator/RepositoryInjectionGenerator.php deleted file mode 100644 index 86e96bc..0000000 --- a/Generator/RepositoryInjectionGenerator.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace JMS\DiExtraBundle\Generator; - -use CG\Generator\PhpClass; -use CG\Generator\PhpMethod; -use CG\Generator\PhpParameter; -use CG\Generator\PhpProperty; -use CG\Generator\Writer; -use CG\Proxy\GeneratorInterface; - -class RepositoryInjectionGenerator implements GeneratorInterface -{ - public function generate(\ReflectionClass $original, PhpClass $proxy) - { - $writer = new Writer(); - - // copy over all public methods - foreach ($original->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { - if ($method->isStatic()) { - continue; - } - - $writer->reset()->write('return $this->delegate->')->write($method->name)->write('('); - $first = true; - foreach ($method->getParameters() as $param) { - if (!$first) { - $writer->write(', '); - } - $first = false; - - $writer->write('$')->write($param->name); - } - $writer->write(');'); - - $proxyMethod = PhpMethod::fromReflection($method) - ->setBody($writer->getContent()); - $proxy->setMethod($proxyMethod); - } - - $proxy->setProperty(PhpProperty::create('delegate')->setVisibility('private')); - $proxy->setProperty(PhpProperty::create('container')->setVisibility('private')); - - $proxy->setMethod(PhpMethod::create('__construct') - ->setVisibility('public') - ->addParameter(PhpParameter::create('objectManager')) - ->addParameter(PhpParameter::create('container')->setType('Symfony\\Component\\DependencyInjection\\ContainerInterface')) - ->setBody($writer->reset()->writeln('$this->delegate = $objectManager;')->writeln('$this->container = $container;')->getContent()) - ); - - $proxy->setMethod(PhpMethod::fromReflection($original->getMethod('getRepository')) - ->setParameters(array(PhpParameter::create('className'))) - ->setBody($writer->reset()->writeln('$repository = $this->delegate->getRepository($className);'."\n") - ->writeln('if ($repository instanceof \Symfony\Component\DependencyInjection\ContainerAwareInterface) {') - ->indent() - ->writeln('$repository->setContainer($this->container);'."\n") - ->writeln('return $repository;') - ->outdent() - ->writeln("}\n") - ->writeln('if (null !== $metadata = $this->container->get("jms_di_extra.metadata.metadata_factory")->getMetadataForClass(get_class($repository))) {') - ->indent() - ->writeln('foreach ($metadata->classMetadata as $classMetadata) {') - ->indent() - ->writeln('foreach ($classMetadata->methodCalls as $call) {') - ->indent() - ->writeln('list($method, $arguments) = $call;') - ->writeln('call_user_func_array(array($repository, $method), $this->prepareArguments($arguments));') - ->outdent() - ->writeln('}') - ->outdent() - ->writeln('}') - ->outdent() - ->writeln('}'."\n") - ->writeln('return $repository;') - ->getContent() - ) - ); - - $proxy->setMethod(PhpMethod::create('prepareArguments') - ->setVisibility('private') - ->addParameter(PhpParameter::create('arguments')->setType('array')) - ->setBody($writer->reset()->writeln('$processed = array();') - ->writeln('foreach ($arguments as $arg) {') - ->indent() - ->writeln('if ($arg instanceof \Symfony\Component\DependencyInjection\Reference) {') - ->indent() - ->writeln('$processed[] = $this->container->get((string) $arg, $arg->getInvalidBehavior());') - ->outdent() - ->writeln('} else if ($arg instanceof \Symfony\Component\DependencyInjection\Parameter) {') - ->indent() - ->writeln('$processed[] = $this->container->getParameter((string) $arg);') - ->outdent() - ->writeln('} else {') - ->indent() - ->writeln('$processed[] = $arg;') - ->outdent() - ->writeln('}') - ->outdent() - ->writeln('}'."\n") - ->writeln('return $processed;') - ->getContent() - ) - ); - } -} diff --git a/composer.json b/composer.json index beeb94a..9ecf4cf 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "sensio/framework-extra-bundle": "~2.0|~3.0", "jms/security-extra-bundle": "~1.0", "doctrine/doctrine-bundle": "~1.5", - "doctrine/orm": "~2.3", + "doctrine/orm": "~2.4", "phpcollection/phpcollection": ">=0.2,<0.3-dev", "symfony/phpunit-bridge": "~3.3", "phpunit/phpunit": "^4.8.35|^5.4.4|^6.0.0",