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\Common\Persistence\Mapping;
22 use Doctrine\Common\Cache\Cache,
23 Doctrine\Common\Util\ClassUtils;
26 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
27 * metadata mapping informations of a class which describes how a class should be mapped
28 * to a relational database.
30 * This class was abstracted from the ORM ClassMetadataFactory
33 * @author Benjamin Eberlei <kontakt@beberlei.de>
34 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
35 * @author Jonathan Wage <jonwage@gmail.com>
36 * @author Roman Borschel <roman@code-factory.org>
38 abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
41 * Salt used by specific Object Manager implementation.
45 protected $cacheSalt = "\$CLASSMETADATA";
48 * @var \Doctrine\Common\Cache\Cache
55 private $loadedMetadata = array();
60 protected $initialized = false;
63 * @var ReflectionService
65 private $reflectionService;
68 * Sets the cache driver used by the factory to cache ClassMetadata instances.
70 * @param Doctrine\Common\Cache\Cache $cacheDriver
72 public function setCacheDriver(Cache $cacheDriver = null)
74 $this->cacheDriver = $cacheDriver;
78 * Gets the cache driver used by the factory to cache ClassMetadata instances.
80 * @return Doctrine\Common\Cache\Cache
82 public function getCacheDriver()
84 return $this->cacheDriver;
88 * Return an array of all the loaded metadata currently in memory.
92 public function getLoadedMetadata()
94 return $this->loadedMetadata;
98 * Forces the factory to load the metadata of all classes known to the underlying
101 * @return array The ClassMetadata instances of all mapped classes.
103 public function getAllMetadata()
105 if ( ! $this->initialized) {
109 $driver = $this->getDriver();
111 foreach ($driver->getAllClassNames() as $className) {
112 $metadata[] = $this->getMetadataFor($className);
119 * Lazy initialization of this stuff, especially the metadata driver,
120 * since these are not needed at all when a metadata cache is active.
124 abstract protected function initialize();
127 * Get the fully qualified class-name from the namespace alias.
129 * @param string $namespaceAlias
130 * @param string $simpleClassName
133 abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName);
136 * Return the mapping driver implementation.
138 * @return \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver
140 abstract protected function getDriver();
143 * Wakeup reflection after ClassMetadata gets unserialized from cache.
145 * @param ClassMetadata $class
146 * @param ReflectionService $reflService
149 abstract protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService);
152 * Initialize Reflection after ClassMetadata was constructed.
154 * @param ClassMetadata $class
155 * @param ReflectionService $reflService
158 abstract protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService);
161 * Checks whether the class metadata is an entity.
163 * This method should false for mapped superclasses or
166 * @param ClassMetadata $class
169 abstract protected function isEntity(ClassMetadata $class);
172 * Gets the class metadata descriptor for a class.
174 * @param string $className The name of the class.
175 * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata
177 public function getMetadataFor($className)
179 if (isset($this->loadedMetadata[$className])) {
180 return $this->loadedMetadata[$className];
183 $realClassName = $className;
185 // Check for namespace alias
186 if (strpos($className, ':') !== false) {
187 list($namespaceAlias, $simpleClassName) = explode(':', $className);
188 $realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
190 $realClassName = ClassUtils::getRealClass($realClassName);
193 if (isset($this->loadedMetadata[$realClassName])) {
194 // We do not have the alias name in the map, include it
195 $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
197 return $this->loadedMetadata[$realClassName];
200 if ($this->cacheDriver) {
201 if (($cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt)) !== false) {
202 $this->loadedMetadata[$realClassName] = $cached;
203 $this->wakeupReflection($cached, $this->getReflectionService());
205 foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
206 $this->cacheDriver->save(
207 $loadedClassName . $this->cacheSalt, $this->loadedMetadata[$loadedClassName], null
212 $this->loadMetadata($realClassName);
215 if ($className != $realClassName) {
216 // We do not have the alias name in the map, include it
217 $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
220 return $this->loadedMetadata[$className];
224 * Checks whether the factory has the metadata for a class loaded already.
226 * @param string $className
227 * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
229 public function hasMetadataFor($className)
231 return isset($this->loadedMetadata[$className]);
235 * Sets the metadata descriptor for a specific class.
237 * NOTE: This is only useful in very special cases, like when generating proxy classes.
239 * @param string $className
240 * @param ClassMetadata $class
242 public function setMetadataFor($className, $class)
244 $this->loadedMetadata[$className] = $class;
248 * Get array of parent classes for the given entity class
250 * @param string $name
251 * @return array $parentClasses
253 protected function getParentClasses($name)
255 // Collect parent classes, ignoring transient (not-mapped) classes.
256 $parentClasses = array();
257 foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
258 if ( ! $this->getDriver()->isTransient($parentClass)) {
259 $parentClasses[] = $parentClass;
262 return $parentClasses;
266 * Loads the metadata of the class in question and all it's ancestors whose metadata
267 * is still not loaded.
269 * @param string $name The name of the class for which the metadata should get loaded.
273 protected function loadMetadata($name)
275 if ( ! $this->initialized) {
281 $parentClasses = $this->getParentClasses($name);
282 $parentClasses[] = $name;
284 // Move down the hierarchy of parent classes, starting from the topmost class
286 $rootEntityFound = false;
288 $reflService = $this->getReflectionService();
289 foreach ($parentClasses as $className) {
290 if (isset($this->loadedMetadata[$className])) {
291 $parent = $this->loadedMetadata[$className];
292 if ($this->isEntity($parent)) {
293 $rootEntityFound = true;
294 array_unshift($visited, $className);
299 $class = $this->newClassMetadataInstance($className);
300 $this->initializeReflection($class, $reflService);
302 $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);
304 $this->loadedMetadata[$className] = $class;
308 if ($this->isEntity($class)) {
309 $rootEntityFound = true;
310 array_unshift($visited, $className);
313 $this->wakeupReflection($class, $reflService);
315 $loaded[] = $className;
322 * Actually load the metadata from the underlying metadata
324 * @param ClassMetadata $class
325 * @param ClassMetadata|null $parent
326 * @param bool $rootEntityFound
327 * @param array $nonSuperclassParents classnames all parent classes that are not marked as mapped superclasses
330 abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents);
333 * Creates a new ClassMetadata instance for the given class name.
335 * @param string $className
336 * @return ClassMetadata
338 abstract protected function newClassMetadataInstance($className);
341 * Check if this class is mapped by this Object Manager + ClassMetadata configuration
346 public function isTransient($class)
348 if ( ! $this->initialized) {
352 // Check for namespace alias
353 if (strpos($class, ':') !== false) {
354 list($namespaceAlias, $simpleClassName) = explode(':', $class);
355 $class = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
358 return $this->getDriver()->isTransient($class);
362 * Set reflectionService.
364 * @param ReflectionService $reflectionService
366 public function setReflectionService(ReflectionService $reflectionService)
368 $this->reflectionService = $reflectionService;
372 * Get the reflection service associated with this metadata factory.
374 * @return ReflectionService
376 public function getReflectionService()
378 if ($this->reflectionService === null) {
379 $this->reflectionService = new RuntimeReflectionService();
381 return $this->reflectionService;