3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\ORM\Proxy;
22 use Doctrine\ORM\EntityManager,
23 Doctrine\ORM\Mapping\ClassMetadata,
24 Doctrine\Common\Util\ClassUtils;
27 * This factory is used to create proxy objects for entities at runtime.
29 * @author Roman Borschel <roman@code-factory.org>
30 * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
35 /** The EntityManager this factory is bound to. */
37 /** Whether to automatically (re)generate proxy classes. */
38 private $_autoGenerate;
39 /** The namespace that contains all proxy classes. */
40 private $_proxyNamespace;
41 /** The directory that contains all proxy classes. */
45 * Used to match very simple id methods that don't need
46 * to be proxied since the identifier is known.
50 const PATTERN_MATCH_ID_METHOD = '((public\s)?(function\s{1,}%s\s?\(\)\s{1,})\s{0,}{\s{0,}return\s{0,}\$this->%s;\s{0,}})i';
53 * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
54 * connected to the given <tt>EntityManager</tt>.
56 * @param EntityManager $em The EntityManager the new factory works for.
57 * @param string $proxyDir The directory to use for the proxy classes. It must exist.
58 * @param string $proxyNs The namespace to use for the proxy classes.
59 * @param boolean $autoGenerate Whether to automatically generate proxy classes.
61 public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false)
64 throw ProxyException::proxyDirectoryRequired();
67 throw ProxyException::proxyNamespaceRequired();
70 $this->_proxyDir = $proxyDir;
71 $this->_autoGenerate = $autoGenerate;
72 $this->_proxyNamespace = $proxyNs;
76 * Gets a reference proxy instance for the entity of the given type and identified by
77 * the given identifier.
79 * @param string $className
80 * @param mixed $identifier
83 public function getProxy($className, $identifier)
85 $fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace);
87 if (! class_exists($fqn, false)) {
88 $fileName = $this->getProxyFileName($className);
89 if ($this->_autoGenerate) {
90 $this->_generateProxyClass($this->_em->getClassMetadata($className), $fileName, self::$_proxyClassTemplate);
95 $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className);
97 return new $fqn($entityPersister, $identifier);
101 * Generate the Proxy file name
103 * @param string $className
104 * @param string $baseDir Optional base directory for proxy file name generation.
105 * If not specified, the directory configured on the Configuration of the
106 * EntityManager will be used by this factory.
109 private function getProxyFileName($className, $baseDir = null)
111 $proxyDir = $baseDir ?: $this->_proxyDir;
113 return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
117 * Generates proxy classes for all given classes.
119 * @param array $classes The classes (ClassMetadata instances) for which to generate proxies.
120 * @param string $toDir The target directory of the proxy classes. If not specified, the
121 * directory configured on the Configuration of the EntityManager used
122 * by this factory is used.
123 * @return int Number of generated proxies.
125 public function generateProxyClasses(array $classes, $toDir = null)
127 $proxyDir = $toDir ?: $this->_proxyDir;
128 $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR);
131 foreach ($classes as $class) {
132 /* @var $class ClassMetadata */
133 if ($class->isMappedSuperclass || $class->reflClass->isAbstract()) {
137 $proxyFileName = $this->getProxyFileName($class->name, $proxyDir);
139 $this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
147 * Generates a proxy class file.
149 * @param ClassMetadata $class Metadata for the original class
150 * @param string $fileName Filename (full path) for the generated class
151 * @param string $file The proxy class template data
153 private function _generateProxyClass(ClassMetadata $class, $fileName, $file)
155 $methods = $this->_generateMethods($class);
156 $sleepImpl = $this->_generateSleep($class);
157 $cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive
159 $placeholders = array(
161 '<proxyClassName>', '<className>',
162 '<methods>', '<sleepImpl>', '<cloneImpl>'
165 $className = ltrim($class->name, '\\');
166 $proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->_proxyNamespace);
167 $parts = explode('\\', strrev($proxyClassName), 2);
168 $proxyClassNamespace = strrev($parts[1]);
169 $proxyClassName = strrev($parts[0]);
171 $replacements = array(
172 $proxyClassNamespace,
180 $file = str_replace($placeholders, $replacements, $file);
182 $parentDirectory = dirname($fileName);
184 if ( ! is_dir($parentDirectory)) {
185 if (false === @mkdir($parentDirectory, 0775, true)) {
186 throw ProxyException::proxyDirectoryNotWritable();
188 } else if ( ! is_writable($parentDirectory)) {
189 throw ProxyException::proxyDirectoryNotWritable();
192 $tmpFileName = $fileName . '.' . uniqid("", true);
193 file_put_contents($tmpFileName, $file);
194 rename($tmpFileName, $fileName);
198 * Generates the methods of a proxy class.
200 * @param ClassMetadata $class
201 * @return string The code of the generated methods.
203 private function _generateMethods(ClassMetadata $class)
207 $methodNames = array();
208 foreach ($class->reflClass->getMethods() as $method) {
209 /* @var $method ReflectionMethod */
210 if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
213 $methodNames[$method->getName()] = true;
215 if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
216 $methods .= "\n" . ' public function ';
217 if ($method->returnsReference()) {
220 $methods .= $method->getName() . '(';
222 $parameterString = $argumentString = '';
224 foreach ($method->getParameters() as $param) {
228 $parameterString .= ', ';
229 $argumentString .= ', ';
232 // We need to pick the type hint class too
233 if (($paramClass = $param->getClass()) !== null) {
234 $parameterString .= '\\' . $paramClass->getName() . ' ';
235 } else if ($param->isArray()) {
236 $parameterString .= 'array ';
239 if ($param->isPassedByReference()) {
240 $parameterString .= '&';
243 $parameterString .= '$' . $param->getName();
244 $argumentString .= '$' . $param->getName();
246 if ($param->isDefaultValueAvailable()) {
247 $parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
251 $methods .= $parameterString . ')';
252 $methods .= "\n" . ' {' . "\n";
253 if ($this->isShortIdentifierGetter($method, $class)) {
254 $identifier = lcfirst(substr($method->getName(), 3));
256 $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : '';
258 $methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
259 $methods .= ' return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n";
260 $methods .= ' }' . "\n";
262 $methods .= ' $this->__load();' . "\n";
263 $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
264 $methods .= "\n" . ' }' . "\n";
272 * Check if the method is a short identifier getter.
274 * What does this mean? For proxy objects the identifier is already known,
275 * however accessing the getter for this identifier usually triggers the
276 * lazy loading, leading to a query that may not be necessary if only the
277 * ID is interesting for the userland code (for example in views that
278 * generate links to the entity, but do not display anything else).
280 * @param ReflectionMethod $method
281 * @param ClassMetadata $class
284 private function isShortIdentifierGetter($method, ClassMetadata $class)
286 $identifier = lcfirst(substr($method->getName(), 3));
288 $method->getNumberOfParameters() == 0 &&
289 substr($method->getName(), 0, 3) == "get" &&
290 in_array($identifier, $class->identifier, true) &&
291 $class->hasField($identifier) &&
292 (($method->getEndLine() - $method->getStartLine()) <= 4)
293 && in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string'))
297 $code = file($method->getDeclaringClass()->getFileName());
298 $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1)));
300 $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
302 if (preg_match($pattern, $code)) {
310 * Generates the code for the __sleep method for a proxy class.
315 private function _generateSleep(ClassMetadata $class)
319 if ($class->reflClass->hasMethod('__sleep')) {
320 $sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
322 $sleepImpl .= "return array('__isInitialized__', ";
325 foreach ($class->getReflectionProperties() as $name => $prop) {
332 $sleepImpl .= "'" . $name . "'";
341 /** Proxy class code template */
342 private static $_proxyClassTemplate =
345 namespace <namespace>;
348 * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
350 class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
352 private $_entityPersister;
353 private $_identifier;
354 public $__isInitialized__ = false;
355 public function __construct($entityPersister, $identifier)
357 $this->_entityPersister = $entityPersister;
358 $this->_identifier = $identifier;
361 public function __load()
363 if (!$this->__isInitialized__ && $this->_entityPersister) {
364 $this->__isInitialized__ = true;
366 if (method_exists($this, "__wakeup")) {
367 // call this after __isInitialized__to avoid infinite recursion
368 // but before loading to emulate what ClassMetadata::newInstance()
373 if ($this->_entityPersister->load($this->_identifier, $this) === null) {
374 throw new \Doctrine\ORM\EntityNotFoundException();
376 unset($this->_entityPersister, $this->_identifier);
381 public function __isInitialized()
383 return $this->__isInitialized__;
388 public function __sleep()
393 public function __clone()
395 if (!$this->__isInitialized__ && $this->_entityPersister) {
396 $this->__isInitialized__ = true;
397 $class = $this->_entityPersister->getClassMetadata();
398 $original = $this->_entityPersister->load($this->_identifier);
399 if ($original === null) {
400 throw new \Doctrine\ORM\EntityNotFoundException();
402 foreach ($class->reflFields as $field => $reflProperty) {
403 $reflProperty->setValue($this, $reflProperty->getValue($original));
405 unset($this->_entityPersister, $this->_identifier);