source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/Proxy/ProxyFactory.php @ 345

Last change on this file since 345 was 345, checked in by quyenla, 11 years ago

collaborator page

File size: 14.7 KB
Line 
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 LGPL. For more information, see
17 * <http://www.doctrine-project.org>.
18 */
19
20namespace Doctrine\ORM\Proxy;
21
22use 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 */
33class 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        if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) {
96            $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($className));
97        }
98
99        $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className);
100
101        return new $fqn($entityPersister, $identifier);
102    }
103
104    /**
105     * Generate the Proxy file name
106     *
107     * @param string $className
108     * @param string $baseDir Optional base directory for proxy file name generation.
109     *                        If not specified, the directory configured on the Configuration of the
110     *                        EntityManager will be used by this factory.
111     * @return string
112     */
113    private function getProxyFileName($className, $baseDir = null)
114    {
115        $proxyDir = $baseDir ?: $this->_proxyDir;
116
117        return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
118    }
119
120    /**
121     * Generates proxy classes for all given classes.
122     *
123     * @param array $classes The classes (ClassMetadata instances) for which to generate proxies.
124     * @param string $toDir The target directory of the proxy classes. If not specified, the
125     *                      directory configured on the Configuration of the EntityManager used
126     *                      by this factory is used.
127     * @return int Number of generated proxies.
128     */
129    public function generateProxyClasses(array $classes, $toDir = null)
130    {
131        $proxyDir = $toDir ?: $this->_proxyDir;
132        $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR);
133        $num = 0;
134
135        foreach ($classes as $class) {
136            /* @var $class ClassMetadata */
137            if ($class->isMappedSuperclass || $class->reflClass->isAbstract()) {
138                continue;
139            }
140
141            $proxyFileName = $this->getProxyFileName($class->name, $proxyDir);
142
143            $this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
144            $num++;
145        }
146
147        return $num;
148    }
149
150    /**
151     * Generates a proxy class file.
152     *
153     * @param $class
154     * @param $proxyClassName
155     * @param $file The path of the file to write to.
156     */
157    private function _generateProxyClass($class, $fileName, $file)
158    {
159        $methods = $this->_generateMethods($class);
160        $sleepImpl = $this->_generateSleep($class);
161        $cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive
162
163        $placeholders = array(
164            '<namespace>',
165            '<proxyClassName>', '<className>',
166            '<methods>', '<sleepImpl>', '<cloneImpl>'
167        );
168
169        $className = ltrim($class->name, '\\');
170        $proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->_proxyNamespace);
171        $parts = explode('\\', strrev($proxyClassName), 2);
172        $proxyClassNamespace = strrev($parts[1]);
173        $proxyClassName = strrev($parts[0]);
174
175        $replacements = array(
176            $proxyClassNamespace,
177            $proxyClassName,
178            $className,
179            $methods,
180            $sleepImpl,
181            $cloneImpl
182        );
183
184        $file = str_replace($placeholders, $replacements, $file);
185
186        $parentDirectory = dirname($fileName);
187
188        if ( ! is_dir($parentDirectory)) {
189            if (false === @mkdir($parentDirectory, 0775, true)) {
190                throw ProxyException::proxyDirectoryNotWritable();
191            }
192        } else if ( ! is_writable($parentDirectory)) {
193            throw ProxyException::proxyDirectoryNotWritable();
194        }
195
196        $tmpFileName = $fileName . '.' . uniqid("", true);
197        file_put_contents($tmpFileName, $file);
198        rename($tmpFileName, $fileName);
199    }
200
201    /**
202     * Generates the methods of a proxy class.
203     *
204     * @param ClassMetadata $class
205     * @return string The code of the generated methods.
206     */
207    private function _generateMethods(ClassMetadata $class)
208    {
209        $methods = '';
210
211        $methodNames = array();
212        foreach ($class->reflClass->getMethods() as $method) {
213            /* @var $method ReflectionMethod */
214            if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
215                continue;
216            }
217            $methodNames[$method->getName()] = true;
218
219            if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
220                $methods .= "\n" . '    public function ';
221                if ($method->returnsReference()) {
222                    $methods .= '&';
223                }
224                $methods .= $method->getName() . '(';
225                $firstParam = true;
226                $parameterString = $argumentString = '';
227
228                foreach ($method->getParameters() as $param) {
229                    if ($firstParam) {
230                        $firstParam = false;
231                    } else {
232                        $parameterString .= ', ';
233                        $argumentString  .= ', ';
234                    }
235
236                    // We need to pick the type hint class too
237                    if (($paramClass = $param->getClass()) !== null) {
238                        $parameterString .= '\\' . $paramClass->getName() . ' ';
239                    } else if ($param->isArray()) {
240                        $parameterString .= 'array ';
241                    }
242
243                    if ($param->isPassedByReference()) {
244                        $parameterString .= '&';
245                    }
246
247                    $parameterString .= '$' . $param->getName();
248                    $argumentString  .= '$' . $param->getName();
249
250                    if ($param->isDefaultValueAvailable()) {
251                        $parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
252                    }
253                }
254
255                $methods .= $parameterString . ')';
256                $methods .= "\n" . '    {' . "\n";
257                if ($this->isShortIdentifierGetter($method, $class)) {
258                    $identifier = lcfirst(substr($method->getName(), 3));
259
260                    $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : '';
261
262                    $methods .= '        if ($this->__isInitialized__ === false) {' . "\n";
263                    $methods .= '            return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n";
264                    $methods .= '        }' . "\n";
265                }
266                $methods .= '        $this->__load();' . "\n";
267                $methods .= '        return parent::' . $method->getName() . '(' . $argumentString . ');';
268                $methods .= "\n" . '    }' . "\n";
269            }
270        }
271
272        return $methods;
273    }
274
275    /**
276     * Check if the method is a short identifier getter.
277     *
278     * What does this mean? For proxy objects the identifier is already known,
279     * however accessing the getter for this identifier usually triggers the
280     * lazy loading, leading to a query that may not be necessary if only the
281     * ID is interesting for the userland code (for example in views that
282     * generate links to the entity, but do not display anything else).
283     *
284     * @param ReflectionMethod $method
285     * @param ClassMetadata $class
286     * @return bool
287     */
288    private function isShortIdentifierGetter($method, $class)
289    {
290        $identifier = lcfirst(substr($method->getName(), 3));
291        $cheapCheck = (
292            $method->getNumberOfParameters() == 0 &&
293            substr($method->getName(), 0, 3) == "get" &&
294            in_array($identifier, $class->identifier, true) &&
295            $class->hasField($identifier) &&
296            (($method->getEndLine() - $method->getStartLine()) <= 4)
297            && in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string'))
298        );
299
300        if ($cheapCheck) {
301            $code = file($method->getDeclaringClass()->getFileName());
302            $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1)));
303
304            $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
305
306            if (preg_match($pattern, $code)) {
307                return true;
308            }
309        }
310        return false;
311    }
312
313    /**
314     * Generates the code for the __sleep method for a proxy class.
315     *
316     * @param $class
317     * @return string
318     */
319    private function _generateSleep(ClassMetadata $class)
320    {
321        $sleepImpl = '';
322
323        if ($class->reflClass->hasMethod('__sleep')) {
324            $sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
325        } else {
326            $sleepImpl .= "return array('__isInitialized__', ";
327            $first = true;
328
329            foreach ($class->getReflectionProperties() as $name => $prop) {
330                if ($first) {
331                    $first = false;
332                } else {
333                    $sleepImpl .= ', ';
334                }
335
336                $sleepImpl .= "'" . $name . "'";
337            }
338
339            $sleepImpl .= ');';
340        }
341
342        return $sleepImpl;
343    }
344
345    /** Proxy class code template */
346    private static $_proxyClassTemplate =
347'<?php
348
349namespace <namespace>;
350
351/**
352 * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
353 */
354class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
355{
356    private $_entityPersister;
357    private $_identifier;
358    public $__isInitialized__ = false;
359    public function __construct($entityPersister, $identifier)
360    {
361        $this->_entityPersister = $entityPersister;
362        $this->_identifier = $identifier;
363    }
364    /** @private */
365    public function __load()
366    {
367        if (!$this->__isInitialized__ && $this->_entityPersister) {
368            $this->__isInitialized__ = true;
369
370            if (method_exists($this, "__wakeup")) {
371                // call this after __isInitialized__to avoid infinite recursion
372                // but before loading to emulate what ClassMetadata::newInstance()
373                // provides.
374                $this->__wakeup();
375            }
376
377            if ($this->_entityPersister->load($this->_identifier, $this) === null) {
378                throw new \Doctrine\ORM\EntityNotFoundException();
379            }
380            unset($this->_entityPersister, $this->_identifier);
381        }
382    }
383
384    /** @private */
385    public function __isInitialized()
386    {
387        return $this->__isInitialized__;
388    }
389
390    <methods>
391
392    public function __sleep()
393    {
394        <sleepImpl>
395    }
396
397    public function __clone()
398    {
399        if (!$this->__isInitialized__ && $this->_entityPersister) {
400            $this->__isInitialized__ = true;
401            $class = $this->_entityPersister->getClassMetadata();
402            $original = $this->_entityPersister->load($this->_identifier);
403            if ($original === null) {
404                throw new \Doctrine\ORM\EntityNotFoundException();
405            }
406            foreach ($class->reflFields AS $field => $reflProperty) {
407                $reflProperty->setValue($this, $reflProperty->getValue($original));
408            }
409            unset($this->_entityPersister, $this->_identifier);
410        }
411        <cloneImpl>
412    }
413}';
414}
Note: See TracBrowser for help on using the repository browser.