PageRenderTime 181ms CodeModel.GetById 60ms app.highlight 14ms RepoModel.GetById 96ms app.codeStats 1ms

/protected/extensions/doctrine/vendors/Doctrine/ORM/Proxy/ProxyFactory.php

https://bitbucket.org/NordLabs/yiidoctrine
PHP | 403 lines | 293 code | 30 blank | 80 comment | 19 complexity | 4f69ae52b98a240866a0150c35f8bd8c MD5 | raw file
  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\ORM\Mapping\AssociationMapping,
 25    Doctrine\Common\Util\ClassUtils;
 26
 27/**
 28 * This factory is used to create proxy objects for entities at runtime.
 29 *
 30 * @author Roman Borschel <roman@code-factory.org>
 31 * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
 32 * @since 2.0
 33 */
 34class ProxyFactory
 35{
 36    /** The EntityManager this factory is bound to. */
 37    private $_em;
 38    /** Whether to automatically (re)generate proxy classes. */
 39    private $_autoGenerate;
 40    /** The namespace that contains all proxy classes. */
 41    private $_proxyNamespace;
 42    /** The directory that contains all proxy classes. */
 43    private $_proxyDir;
 44
 45    /**
 46     * Used to match very simple id methods that don't need
 47     * to be proxied since the identifier is known.
 48     *
 49     * @var string
 50     */
 51    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';
 52
 53    /**
 54     * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
 55     * connected to the given <tt>EntityManager</tt>.
 56     *
 57     * @param EntityManager $em The EntityManager the new factory works for.
 58     * @param string $proxyDir The directory to use for the proxy classes. It must exist.
 59     * @param string $proxyNs The namespace to use for the proxy classes.
 60     * @param boolean $autoGenerate Whether to automatically generate proxy classes.
 61     */
 62    public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false)
 63    {
 64        if ( ! $proxyDir) {
 65            throw ProxyException::proxyDirectoryRequired();
 66        }
 67        if ( ! $proxyNs) {
 68            throw ProxyException::proxyNamespaceRequired();
 69        }
 70        $this->_em = $em;
 71        $this->_proxyDir = $proxyDir;
 72        $this->_autoGenerate = $autoGenerate;
 73        $this->_proxyNamespace = $proxyNs;
 74    }
 75
 76    /**
 77     * Gets a reference proxy instance for the entity of the given type and identified by
 78     * the given identifier.
 79     *
 80     * @param string $className
 81     * @param mixed $identifier
 82     * @return object
 83     */
 84    public function getProxy($className, $identifier)
 85    {
 86        $fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace);
 87
 88        if (! class_exists($fqn, false)) {
 89            $fileName = $this->getProxyFileName($className);
 90            if ($this->_autoGenerate) {
 91                $this->_generateProxyClass($this->_em->getClassMetadata($className), $fileName, self::$_proxyClassTemplate);
 92            }
 93            require $fileName;
 94        }
 95
 96        if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) {
 97            $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($className));
 98        }
 99
100        $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className);
101
102        return new $fqn($entityPersister, $identifier);
103    }
104
105    /**
106     * Generate the Proxy file name
107     *
108     * @param string $className
109     * @param string $baseDir Optional base directory for proxy file name generation.
110     *                        If not specified, the directory configured on the Configuration of the
111     *                        EntityManager will be used by this factory.
112     * @return string
113     */
114    private function getProxyFileName($className, $baseDir = null)
115    {
116        $proxyDir = $baseDir ?: $this->_proxyDir;
117
118        return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
119    }
120
121    /**
122     * Generates proxy classes for all given classes.
123     *
124     * @param array $classes The classes (ClassMetadata instances) for which to generate proxies.
125     * @param string $toDir The target directory of the proxy classes. If not specified, the
126     *                      directory configured on the Configuration of the EntityManager used
127     *                      by this factory is used.
128     * @return int Number of generated proxies.
129     */
130    public function generateProxyClasses(array $classes, $toDir = null)
131    {
132        $proxyDir = $toDir ?: $this->_proxyDir;
133        $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR);
134        $num = 0;
135
136        foreach ($classes as $class) {
137            /* @var $class ClassMetadata */
138            if ($class->isMappedSuperclass || $class->reflClass->isAbstract()) {
139                continue;
140            }
141
142            $proxyFileName = $this->getProxyFileName($class->name, $proxyDir);
143
144            $this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate);
145            $num++;
146        }
147
148        return $num;
149    }
150
151    /**
152     * Generates a proxy class file.
153     *
154     * @param $class
155     * @param $proxyClassName
156     * @param $file The path of the file to write to.
157     */
158    private function _generateProxyClass($class, $fileName, $file)
159    {
160        $methods = $this->_generateMethods($class);
161        $sleepImpl = $this->_generateSleep($class);
162        $cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive
163
164        $placeholders = array(
165            '<namespace>',
166            '<proxyClassName>', '<className>',
167            '<methods>', '<sleepImpl>', '<cloneImpl>'
168        );
169
170        $className = ltrim($class->name, '\\');
171        $proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->_proxyNamespace);
172        $parts = explode('\\', strrev($proxyClassName), 2);
173        $proxyClassNamespace = strrev($parts[1]);
174        $proxyClassName = strrev($parts[0]);
175
176        $replacements = array(
177            $proxyClassNamespace,
178            $proxyClassName,
179            $className,
180            $methods,
181            $sleepImpl,
182            $cloneImpl
183        );
184
185        $file = str_replace($placeholders, $replacements, $file);
186
187        file_put_contents($fileName, $file, LOCK_EX);
188    }
189
190    /**
191     * Generates the methods of a proxy class.
192     *
193     * @param ClassMetadata $class
194     * @return string The code of the generated methods.
195     */
196    private function _generateMethods(ClassMetadata $class)
197    {
198        $methods = '';
199
200        $methodNames = array();
201        foreach ($class->reflClass->getMethods() as $method) {
202            /* @var $method ReflectionMethod */
203            if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
204                continue;
205            }
206            $methodNames[$method->getName()] = true;
207
208            if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
209                $methods .= "\n" . '    public function ';
210                if ($method->returnsReference()) {
211                    $methods .= '&';
212                }
213                $methods .= $method->getName() . '(';
214                $firstParam = true;
215                $parameterString = $argumentString = '';
216
217                foreach ($method->getParameters() as $param) {
218                    if ($firstParam) {
219                        $firstParam = false;
220                    } else {
221                        $parameterString .= ', ';
222                        $argumentString  .= ', ';
223                    }
224
225                    // We need to pick the type hint class too
226                    if (($paramClass = $param->getClass()) !== null) {
227                        $parameterString .= '\\' . $paramClass->getName() . ' ';
228                    } else if ($param->isArray()) {
229                        $parameterString .= 'array ';
230                    }
231
232                    if ($param->isPassedByReference()) {
233                        $parameterString .= '&';
234                    }
235
236                    $parameterString .= '$' . $param->getName();
237                    $argumentString  .= '$' . $param->getName();
238
239                    if ($param->isDefaultValueAvailable()) {
240                        $parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
241                    }
242                }
243
244                $methods .= $parameterString . ')';
245                $methods .= "\n" . '    {' . "\n";
246                if ($this->isShortIdentifierGetter($method, $class)) {
247                    $identifier = lcfirst(substr($method->getName(), 3));
248
249                    $cast = in_array($class->fieldMappings[$identifier]['type'], array('integer', 'smallint')) ? '(int) ' : '';
250
251                    $methods .= '        if ($this->__isInitialized__ === false) {' . "\n";
252                    $methods .= '            return ' . $cast . '$this->_identifier["' . $identifier . '"];' . "\n";
253                    $methods .= '        }' . "\n";
254                }
255                $methods .= '        $this->__load();' . "\n";
256                $methods .= '        return parent::' . $method->getName() . '(' . $argumentString . ');';
257                $methods .= "\n" . '    }' . "\n";
258            }
259        }
260
261        return $methods;
262    }
263
264    /**
265     * Check if the method is a short identifier getter.
266     *
267     * What does this mean? For proxy objects the identifier is already known,
268     * however accessing the getter for this identifier usually triggers the
269     * lazy loading, leading to a query that may not be necessary if only the
270     * ID is interesting for the userland code (for example in views that
271     * generate links to the entity, but do not display anything else).
272     *
273     * @param ReflectionMethod $method
274     * @param ClassMetadata $class
275     * @return bool
276     */
277    private function isShortIdentifierGetter($method, $class)
278    {
279        $identifier = lcfirst(substr($method->getName(), 3));
280        $cheapCheck = (
281            $method->getNumberOfParameters() == 0 &&
282            substr($method->getName(), 0, 3) == "get" &&
283            in_array($identifier, $class->identifier, true) &&
284            $class->hasField($identifier) &&
285            (($method->getEndLine() - $method->getStartLine()) <= 4)
286            && in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string'))
287        );
288
289        if ($cheapCheck) {
290            $code = file($method->getDeclaringClass()->getFileName());
291            $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1)));
292
293            $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
294
295            if (preg_match($pattern, $code)) {
296                return true;
297            }
298        }
299        return false;
300    }
301
302    /**
303     * Generates the code for the __sleep method for a proxy class.
304     *
305     * @param $class
306     * @return string
307     */
308    private function _generateSleep(ClassMetadata $class)
309    {
310        $sleepImpl = '';
311
312        if ($class->reflClass->hasMethod('__sleep')) {
313            $sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
314        } else {
315            $sleepImpl .= "return array('__isInitialized__', ";
316            $first = true;
317
318            foreach ($class->getReflectionProperties() as $name => $prop) {
319                if ($first) {
320                    $first = false;
321                } else {
322                    $sleepImpl .= ', ';
323                }
324
325                $sleepImpl .= "'" . $name . "'";
326            }
327
328            $sleepImpl .= ');';
329        }
330
331        return $sleepImpl;
332    }
333
334    /** Proxy class code template */
335    private static $_proxyClassTemplate =
336'<?php
337
338namespace <namespace>;
339
340/**
341 * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
342 */
343class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
344{
345    private $_entityPersister;
346    private $_identifier;
347    public $__isInitialized__ = false;
348    public function __construct($entityPersister, $identifier)
349    {
350        $this->_entityPersister = $entityPersister;
351        $this->_identifier = $identifier;
352    }
353    /** @private */
354    public function __load()
355    {
356        if (!$this->__isInitialized__ && $this->_entityPersister) {
357            $this->__isInitialized__ = true;
358
359            if (method_exists($this, "__wakeup")) {
360                // call this after __isInitialized__to avoid infinite recursion
361                // but before loading to emulate what ClassMetadata::newInstance()
362                // provides.
363                $this->__wakeup();
364            }
365
366            if ($this->_entityPersister->load($this->_identifier, $this) === null) {
367                throw new \Doctrine\ORM\EntityNotFoundException();
368            }
369            unset($this->_entityPersister, $this->_identifier);
370        }
371    }
372
373    /** @private */
374    public function __isInitialized()
375    {
376        return $this->__isInitialized__;
377    }
378
379    <methods>
380
381    public function __sleep()
382    {
383        <sleepImpl>
384    }
385
386    public function __clone()
387    {
388        if (!$this->__isInitialized__ && $this->_entityPersister) {
389            $this->__isInitialized__ = true;
390            $class = $this->_entityPersister->getClassMetadata();
391            $original = $this->_entityPersister->load($this->_identifier);
392            if ($original === null) {
393                throw new \Doctrine\ORM\EntityNotFoundException();
394            }
395            foreach ($class->reflFields AS $field => $reflProperty) {
396                $reflProperty->setValue($this, $reflProperty->getValue($original));
397            }
398            unset($this->_entityPersister, $this->_identifier);
399        }
400        <cloneImpl>
401    }
402}';
403}