Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Proxy / ProxyFactory.php
1 <?php
2 /*
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.
14  *
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>.
18  */
19
20 namespace Doctrine\ORM\Proxy;
21
22 use Doctrine\ORM\EntityManager,
23     Doctrine\ORM\Mapping\ClassMetadata,
24     Doctrine\Common\Util\ClassUtils;
25
26 /**
27  * This factory is used to create proxy objects for entities at runtime.
28  *
29  * @author Roman Borschel <roman@code-factory.org>
30  * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
31  * @since 2.0
32  */
33 class ProxyFactory
34 {
35     /** The EntityManager this factory is bound to. */
36     private $_em;
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. */
42     private $_proxyDir;
43
44     /**
45      * Used to match very simple id methods that don't need
46      * to be proxied since the identifier is known.
47      *
48      * @var string
49      */
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';
51
52     /**
53      * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
54      * connected to the given <tt>EntityManager</tt>.
55      *
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.
60      */
61     public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false)
62     {
63         if ( ! $proxyDir) {
64             throw ProxyException::proxyDirectoryRequired();
65         }
66         if ( ! $proxyNs) {
67             throw ProxyException::proxyNamespaceRequired();
68         }
69         $this->_em = $em;
70         $this->_proxyDir = $proxyDir;
71         $this->_autoGenerate = $autoGenerate;
72         $this->_proxyNamespace = $proxyNs;
73     }
74
75     /**
76      * Gets a reference proxy instance for the entity of the given type and identified by
77      * the given identifier.
78      *
79      * @param string $className
80      * @param mixed $identifier
81      * @return object
82      */
83     public function getProxy($className, $identifier)
84     {
85         $fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace);
86
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);
91             }
92             require $fileName;
93         }
94
95         $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className);
96
97         return new $fqn($entityPersister, $identifier);
98     }
99
100     /**
101      * Generate the Proxy file name
102      *
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.
107      * @return string
108      */
109     private function getProxyFileName($className, $baseDir = null)
110     {
111         $proxyDir = $baseDir ?: $this->_proxyDir;
112
113         return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
114     }
115
116     /**
117      * Generates proxy classes for all given classes.
118      *
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.
124      */
125     public function generateProxyClasses(array $classes, $toDir = null)
126     {
127         $proxyDir = $toDir ?: $this->_proxyDir;
128         $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR);
129         $num = 0;
130
131         foreach ($classes as $class) {
132             /* @var $class ClassMetadata */
133             if ($class->isMappedSuperclass || $class->reflClass->isAbstract()) {
134                 continue;
135             }
136
137             $proxyFileName = $this->getProxyFileName($class->name, $proxyDir);
138
139             $this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
140             $num++;
141         }
142
143         return $num;
144     }
145
146     /**
147      * Generates a proxy class file.
148      *
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
152      */
153     private function _generateProxyClass(ClassMetadata $class, $fileName, $file)
154     {
155         $methods = $this->_generateMethods($class);
156         $sleepImpl = $this->_generateSleep($class);
157         $cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive
158
159         $placeholders = array(
160             '<namespace>',
161             '<proxyClassName>', '<className>',
162             '<methods>', '<sleepImpl>', '<cloneImpl>'
163         );
164
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]);
170
171         $replacements = array(
172             $proxyClassNamespace,
173             $proxyClassName,
174             $className,
175             $methods,
176             $sleepImpl,
177             $cloneImpl
178         );
179
180         $file = str_replace($placeholders, $replacements, $file);
181
182         $parentDirectory = dirname($fileName);
183
184         if ( ! is_dir($parentDirectory)) {
185             if (false === @mkdir($parentDirectory, 0775, true)) {
186                 throw ProxyException::proxyDirectoryNotWritable();
187             }
188         } else if ( ! is_writable($parentDirectory)) {
189             throw ProxyException::proxyDirectoryNotWritable();
190         }
191
192         $tmpFileName = $fileName . '.' . uniqid("", true);
193         file_put_contents($tmpFileName, $file);
194         rename($tmpFileName, $fileName);
195     }
196
197     /**
198      * Generates the methods of a proxy class.
199      *
200      * @param ClassMetadata $class
201      * @return string The code of the generated methods.
202      */
203     private function _generateMethods(ClassMetadata $class)
204     {
205         $methods = '';
206
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()])) {
211                 continue;
212             }
213             $methodNames[$method->getName()] = true;
214
215             if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
216                 $methods .= "\n" . '    public function ';
217                 if ($method->returnsReference()) {
218                     $methods .= '&';
219                 }
220                 $methods .= $method->getName() . '(';
221                 $firstParam = true;
222                 $parameterString = $argumentString = '';
223
224                 foreach ($method->getParameters() as $param) {
225                     if ($firstParam) {
226                         $firstParam = false;
227                     } else {
228                         $parameterString .= ', ';
229                         $argumentString  .= ', ';
230                     }
231
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 ';
237                     }
238
239                     if ($param->isPassedByReference()) {
240                         $parameterString .= '&';
241                     }
242
243                     $parameterString .= '$' . $param->getName();
244                     $argumentString  .= '$' . $param->getName();
245
246                     if ($param->isDefaultValueAvailable()) {
247                         $parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
248                     }
249                 }
250
251                 $methods .= $parameterString . ')';
252                 $methods .= "\n" . '    {' . "\n";
253                 if ($this->isShortIdentifierGetter($method, $class)) {
254                     $identifier = lcfirst(substr($method->getName(), 3));
255
256                     $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : '';
257
258                     $methods .= '        if ($this->__isInitialized__ === false) {' . "\n";
259                     $methods .= '            return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n";
260                     $methods .= '        }' . "\n";
261                 }
262                 $methods .= '        $this->__load();' . "\n";
263                 $methods .= '        return parent::' . $method->getName() . '(' . $argumentString . ');';
264                 $methods .= "\n" . '    }' . "\n";
265             }
266         }
267
268         return $methods;
269     }
270
271     /**
272      * Check if the method is a short identifier getter.
273      *
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).
279      *
280      * @param ReflectionMethod $method
281      * @param ClassMetadata $class
282      * @return bool
283      */
284     private function isShortIdentifierGetter($method, ClassMetadata $class)
285     {
286         $identifier = lcfirst(substr($method->getName(), 3));
287         $cheapCheck = (
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'))
294         );
295
296         if ($cheapCheck) {
297             $code = file($method->getDeclaringClass()->getFileName());
298             $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1)));
299
300             $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
301
302             if (preg_match($pattern, $code)) {
303                 return true;
304             }
305         }
306         return false;
307     }
308
309     /**
310      * Generates the code for the __sleep method for a proxy class.
311      *
312      * @param $class
313      * @return string
314      */
315     private function _generateSleep(ClassMetadata $class)
316     {
317         $sleepImpl = '';
318
319         if ($class->reflClass->hasMethod('__sleep')) {
320             $sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
321         } else {
322             $sleepImpl .= "return array('__isInitialized__', ";
323             $first = true;
324
325             foreach ($class->getReflectionProperties() as $name => $prop) {
326                 if ($first) {
327                     $first = false;
328                 } else {
329                     $sleepImpl .= ', ';
330                 }
331
332                 $sleepImpl .= "'" . $name . "'";
333             }
334
335             $sleepImpl .= ');';
336         }
337
338         return $sleepImpl;
339     }
340
341     /** Proxy class code template */
342     private static $_proxyClassTemplate =
343 '<?php
344
345 namespace <namespace>;
346
347 /**
348  * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
349  */
350 class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
351 {
352     private $_entityPersister;
353     private $_identifier;
354     public $__isInitialized__ = false;
355     public function __construct($entityPersister, $identifier)
356     {
357         $this->_entityPersister = $entityPersister;
358         $this->_identifier = $identifier;
359     }
360     /** @private */
361     public function __load()
362     {
363         if (!$this->__isInitialized__ && $this->_entityPersister) {
364             $this->__isInitialized__ = true;
365
366             if (method_exists($this, "__wakeup")) {
367                 // call this after __isInitialized__to avoid infinite recursion
368                 // but before loading to emulate what ClassMetadata::newInstance()
369                 // provides.
370                 $this->__wakeup();
371             }
372
373             if ($this->_entityPersister->load($this->_identifier, $this) === null) {
374                 throw new \Doctrine\ORM\EntityNotFoundException();
375             }
376             unset($this->_entityPersister, $this->_identifier);
377         }
378     }
379
380     /** @private */
381     public function __isInitialized()
382     {
383         return $this->__isInitialized__;
384     }
385
386     <methods>
387
388     public function __sleep()
389     {
390         <sleepImpl>
391     }
392
393     public function __clone()
394     {
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();
401             }
402             foreach ($class->reflFields as $field => $reflProperty) {
403                 $reflProperty->setValue($this, $reflProperty->getValue($original));
404             }
405             unset($this->_entityPersister, $this->_identifier);
406         }
407         <cloneImpl>
408     }
409 }';
410 }