ProxyFactory.php 9.83 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */

22
namespace Doctrine\ORM\Proxy;
23

24 25
use Doctrine\ORM\EntityManager,
    Doctrine\ORM\Mapping\ClassMetadata,
26
    Doctrine\ORM\Mapping\AssociationMapping;
27 28

/**
29
 * This factory is used to create proxy objects for entities at runtime.
30 31
 *
 * @author Roman Borschel <roman@code-factory.org>
32
 * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
33 34
 * @since 2.0
 */
35
class ProxyFactory
36
{
37
    /** The EntityManager this factory is bound to. */
38
    private $_em;
39 40 41 42 43 44
    /** 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;
45 46

    /**
47 48
	 * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
	 * connected to the given <tt>EntityManager</tt>.
49
	 *
50 51 52 53
	 * @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.
54
     */
55
    public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false)
56
    {
57
        if ( ! $proxyDir) {
58
            throw ProxyException::proxyDirectoryRequired();
59 60
        }
        if ( ! $proxyNs) {
61
            throw ProxyException::proxyNamespaceRequired();
62
        }
63
        $this->_em = $em;
64 65 66
        $this->_proxyDir = $proxyDir;
        $this->_autoGenerate = $autoGenerate;
        $this->_proxyNamespace = $proxyNs;
67 68
    }

69
    /**
70 71
     * Gets a reference proxy instance for the entity of the given type and identified by
     * the given identifier.
72 73 74 75 76
     *
     * @param string $className
     * @param mixed $identifier
     * @return object
     */
77
    public function getProxy($className, $identifier)
78
    {
79
        $proxyClassName = str_replace('\\', '', $className) . 'Proxy';
80
        $fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
81

82 83 84 85 86
        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;
        }
87

88 89 90
        if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) {
            $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($className));
        }
91

92
        $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className);
93

94
        return new $fqn($entityPersister, $identifier);
95
    }
96

97 98
    /**
     * Generates proxy classes for all given classes.
99
     *
100 101 102 103 104 105 106 107 108 109
     * @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) {
110 111 112
            $proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
            $proxyFileName = $proxyDir . $proxyClassName . '.php';
            $this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate);
113 114
        }
    }
115

116 117
    /**
     * Generates a (reference or association) proxy class.
118
     *
119 120 121 122 123 124 125 126 127 128 129 130 131
     * @param $class
     * @param $originalClassName
     * @param $proxyClassName
     * @param $file The path of the file to write to.
     */
    private function _generateProxyClass($class, $proxyClassName, $fileName, $file)
    {
        $methods = $this->_generateMethods($class);
        $sleepImpl = $this->_generateSleep($class);

        $placeholders = array(
            '<namespace>',
            '<proxyClassName>', '<className>',
132
            '<methods>', '<sleepImpl>'
133
        );
134 135 136 137 138 139 140

        if(substr($class->name, 0, 1) == "\\") {
            $className = substr($class->name, 1);
        } else {
            $className = $class->name;
        }

141 142
        $replacements = array(
            $this->_proxyNamespace,
143
            $proxyClassName, $className,
144
            $methods, $sleepImpl
145
        );
146

147 148 149 150
        $file = str_replace($placeholders, $replacements, $file);

        file_put_contents($fileName, $file);
    }
151

152 153
    /**
     * Generates the methods of a proxy class.
154
     *
155 156 157 158 159 160
     * @param ClassMetadata $class
     * @return string The code of the generated methods.
     */
    private function _generateMethods(ClassMetadata $class)
    {
        $methods = '';
161

162
        foreach ($class->reflClass->getMethods() as $method) {
163 164
            /* @var $method ReflectionMethod */
            if ($method->isConstructor() || strtolower($method->getName()) == "__sleep") {
165 166
                continue;
            }
167

168 169 170 171
            if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
                $methods .= PHP_EOL . '        public function ' . $method->getName() . '(';
                $firstParam = true;
                $parameterString = $argumentString = '';
172

173 174 175 176 177 178 179
                foreach ($method->getParameters() as $param) {
                    if ($firstParam) {
                        $firstParam = false;
                    } else {
                        $parameterString .= ', ';
                        $argumentString  .= ', ';
                    }
180

181 182 183 184 185 186
                    // We need to pick the type hint class too
                    if (($paramClass = $param->getClass()) !== null) {
                        $parameterString .= '\\' . $paramClass->getName() . ' ';
                    } else if ($param->isArray()) {
                        $parameterString .= 'array ';
                    }
187

188 189 190
                    if ($param->isPassedByReference()) {
                        $parameterString .= '&';
                    }
191

192 193
                    $parameterString .= '$' . $param->getName();
                    $argumentString  .= '$' . $param->getName();
194

195 196 197 198
                    if ($param->isDefaultValueAvailable()) {
                        $parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
                    }
                }
199

200 201
                $methods .= $parameterString . ')';
                $methods .= PHP_EOL . '        {' . PHP_EOL;
202 203 204 205 206
                $methods .= '            $this->_load();' . PHP_EOL;
                $methods .= '            return parent::' . $method->getName() . '(' . $argumentString . ');';
                $methods .= PHP_EOL . '        }' . PHP_EOL;
            }
        }
207

208 209
        return $methods;
    }
210

211 212
    /**
     * Generates the code for the __sleep method for a proxy class.
213
     *
214 215 216 217 218 219
     * @param $class
     * @return string
     */
    private function _generateSleep(ClassMetadata $class)
    {
        $sleepImpl = '';
220

221 222 223 224 225
        if ($class->reflClass->hasMethod('__sleep')) {
            $sleepImpl .= 'return parent::__sleep();';
        } else {
            $sleepImpl .= 'return array(';
            $first = true;
226

227 228 229 230 231 232
            foreach ($class->getReflectionProperties() as $name => $prop) {
                if ($first) {
                    $first = false;
                } else {
                    $sleepImpl .= ', ';
                }
233

234 235
                $sleepImpl .= "'" . $name . "'";
            }
236

237 238
            $sleepImpl .= ');';
        }
239

240 241
        return $sleepImpl;
    }
242

243 244 245
    /** Reference Proxy class code template */
    private static $_proxyClassTemplate =
'<?php
246 247
namespace <namespace>
{
248 249 250
    /**
     * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
     */
251 252
    class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
    {
253 254
        private $_entityPersister;
        private $_identifier;
255
        public $__isInitialized__ = false;
256 257
        public function __construct($entityPersister, $identifier)
        {
258 259 260
            $this->_entityPersister = $entityPersister;
            $this->_identifier = $identifier;
        }
261 262
        private function _load()
        {
263
            if (!$this->__isInitialized__ && $this->_entityPersister) {
264
                $this->__isInitialized__ = true;
265 266 267
                if ($this->_entityPersister->load($this->_identifier, $this) === null) {
                    throw new \Doctrine\ORM\EntityNotFoundException();
                }
268 269 270 271 272 273 274
                unset($this->_entityPersister);
                unset($this->_identifier);
            }
        }

        <methods>

275 276
        public function __sleep()
        {
277
            if (!$this->__isInitialized__) {
278 279 280 281 282 283
                throw new \RuntimeException("Not fully loaded proxy can not be serialized.");
            }
            <sleepImpl>
        }
    }
}';
284
}