. */ namespace Doctrine\ORM\Proxy; use Doctrine\ORM\EntityManager, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\AssociationMapping, Doctrine\Common\DoctrineException; /** * This factory is used to create proxy objects for entities at runtime. * * @author Roman Borschel * @author Giorgio Sironi * @since 2.0 */ class ProxyFactory { /** The EntityManager this factory is bound to. */ private $_em; /** Whether to automatically (re)generate proxy classes. */ private $_autoGenerate; /** The namespace that contains all proxy classes. */ private $_proxyNamespace; /** The directory that contains all proxy classes. */ private $_proxyDir; /** * Initializes a new instance of the ProxyFactory class that is * connected to the given EntityManager. * * @param EntityManager $em The EntityManager the new factory works for. * @param string $proxyDir The directory to use for the proxy classes. It must exist. * @param string $proxyNs The namespace to use for the proxy classes. * @param boolean $autoGenerate Whether to automatically generate proxy classes. */ public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false) { if ( ! $proxyDir) { throw DoctrineException::proxyDirectoryRequired(); } if ( ! $proxyNs) { throw DoctrineException::proxyNamespaceRequired(); } $this->_em = $em; $this->_proxyDir = $proxyDir; $this->_autoGenerate = $autoGenerate; $this->_proxyNamespace = $proxyNs; } /** * Gets a reference proxy instance for the entity of the given type and identified by * the given identifier. * * @param string $className * @param mixed $identifier * @return object */ public function getReferenceProxy($className, $identifier) { $proxyClassName = str_replace('\\', '', $className) . 'RProxy'; $fqn = $this->_proxyNamespace . '\\' . $proxyClassName; if ($this->_autoGenerate && ! class_exists($fqn, false)) { $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php'; $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate); require $fileName; } if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) { $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($className)); } $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className); return new $fqn($entityPersister, $identifier); } /** * Gets an association proxy instance. * * @param object $owner * @param AssociationMapping $assoc * @param array $joinColumnValues * @return object */ public function getAssociationProxy($owner, AssociationMapping $assoc, array $joinColumnValues) { $proxyClassName = str_replace('\\', '', $assoc->targetEntityName) . 'AProxy'; $fqn = $this->_proxyNamespace . '\\' . $proxyClassName; if ($this->_autoGenerate && ! class_exists($fqn, false)) { $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php'; $this->_generateProxyClass($this->_em->getClassMetadata($assoc->targetEntityName), $proxyClassName, $fileName, self::$_assocProxyClassTemplate); require $fileName; } if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) { $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($assoc->targetEntityName)); } return new $fqn($this->_em, $assoc, $owner, $joinColumnValues); } /** * Generates proxy classes for all given classes. * * @param array $classes The classes (ClassMetadata instances) for which to generate proxies. * @param string $toDir The target directory of the proxy classes. If not specified, the * directory configured on the Configuration of the EntityManager used * by this factory is used. */ public function generateProxyClasses(array $classes, $toDir = null) { $proxyDir = $toDir ?: $this->_proxyDir; $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; foreach ($classes as $class) { $AproxyClassName = str_replace('\\', '', $class->name) . 'AProxy'; $RproxyClassName = str_replace('\\', '', $class->name) . 'RProxy'; $AproxyFileName = $proxyDir . $AproxyClassName . '.php'; $RproxyFileName = $proxyDir . $RproxyClassName . '.php'; $this->_generateProxyClass($class, $RproxyClassName, $RproxyFileName, self::$_proxyClassTemplate); $this->_generateProxyClass($class, $AproxyClassName, $AproxyFileName, self::$_assocProxyClassTemplate); } } /** * Generates a (reference or association) proxy class. * * @param $class * @param $originalClassName * @param $proxyClassName * @param $file The path of the file to write to. * @return void */ private function _generateProxyClass($class, $proxyClassName, $fileName, $file) { $methods = $this->_generateMethods($class); $sleepImpl = $this->_generateSleep($class); $constructorInv = $class->reflClass->hasMethod('__construct') ? 'parent::__construct();' : ''; $placeholders = array( '', '', '', '', '', '' ); $replacements = array( $this->_proxyNamespace, $proxyClassName, $class->name, $methods, $sleepImpl, $constructorInv ); $file = str_replace($placeholders, $replacements, $file); file_put_contents($fileName, $file); } /** * Generates the methods of a proxy class. * * @param ClassMetadata $class * @return string The code of the generated methods. */ private function _generateMethods(ClassMetadata $class) { $methods = ''; foreach ($class->reflClass->getMethods() as $method) { if ($method->isConstructor()) { continue; } if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { $methods .= PHP_EOL . ' public function ' . $method->getName() . '('; $firstParam = true; $parameterString = $argumentString = ''; foreach ($method->getParameters() as $param) { if ($firstParam) { $firstParam = false; } else { $parameterString .= ', '; $argumentString .= ', '; } // We need to pick the type hint class too if (($paramClass = $param->getClass()) !== null) { $parameterString .= '\\' . $paramClass->getName() . ' '; } else if ($param->isArray()) { $parameterString .= 'array '; } if ($param->isPassedByReference()) { $parameterString .= '&'; } $parameterString .= '$' . $param->getName(); $argumentString .= '$' . $param->getName(); if ($param->isDefaultValueAvailable()) { $parameterString .= ' = ' . var_export($param->getDefaultValue(), true); } } $methods .= $parameterString . ') {' . PHP_EOL; $methods .= ' $this->_load();' . PHP_EOL; $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');'; $methods .= PHP_EOL . ' }' . PHP_EOL; } } return $methods; } /** * Generates the code for the __sleep method for a proxy class. * * @param $class * @return string */ private function _generateSleep(ClassMetadata $class) { $sleepImpl = ''; if ($class->reflClass->hasMethod('__sleep')) { $sleepImpl .= 'return parent::__sleep();'; } else { $sleepImpl .= 'return array('; $first = true; foreach ($class->getReflectionProperties() as $name => $prop) { if ($first) { $first = false; } else { $sleepImpl .= ', '; } $sleepImpl .= "'" . $name . "'"; } $sleepImpl .= ');'; } return $sleepImpl; } /** Reference Proxy class code template */ private static $_proxyClassTemplate = ' { /** * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. */ class extends \ implements \Doctrine\ORM\Proxy\Proxy { private $_entityPersister; private $_identifier; private $_loaded = false; public function __construct($entityPersister, $identifier) { $this->_entityPersister = $entityPersister; $this->_identifier = $identifier; } private function _load() { if ( ! $this->_loaded) { $this->_entityPersister->load($this->_identifier, $this); unset($this->_entityPersister); unset($this->_identifier); $this->_loaded = true; } } public function __isInitialized__() { return $this->_loaded; } public function __sleep() { if (!$this->_loaded) { throw new \RuntimeException("Not fully loaded proxy can not be serialized."); } } } }'; /** Association Proxy class code template */ private static $_assocProxyClassTemplate = ' { /** * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. */ class extends \ implements \Doctrine\ORM\Proxy\Proxy { private $_em; private $_assoc; private $_owner; private $_joinColumnValues; private $_loaded = false; public function __construct($em, $assoc, $owner, array $joinColumnValues) { $this->_em = $em; $this->_assoc = $assoc; $this->_owner = $owner; $this->_joinColumnValues = $joinColumnValues; } private function _load() { if ( ! $this->_loaded) { $this->_assoc->load($this->_owner, $this, $this->_em, $this->_joinColumnValues); unset($this->_em); unset($this->_owner); unset($this->_assoc); unset($this->_joinColumnValues); $this->_loaded = true; } } public function __isInitialized__() { return $this->_loaded; } public function __sleep() { if (!$this->_loaded) { throw new \RuntimeException("Not fully loaded proxy can not be serialized."); } } } }'; }