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 use Doctrine\Common\Util\ClassUtils
;
24 use ReflectionException
;
27 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
28 * metadata mapping informations of a class which describes how a class should be mapped
29 * to a relational database.
31 * This class was abstracted from the ORM ClassMetadataFactory.
34 * @author Benjamin Eberlei <kontakt@beberlei.de>
35 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
36 * @author Jonathan Wage <jonwage@gmail.com>
37 * @author Roman Borschel <roman@code-factory.org>
39 abstract class AbstractClassMetadataFactory
implements ClassMetadataFactory
42 * Salt used by specific Object Manager implementation.
46 protected $cacheSalt = '$CLASSMETADATA';
49 * @var \Doctrine\Common\Cache\Cache|null
54 * @var ClassMetadata[]
56 private $loadedMetadata = [];
61 protected $initialized = false;
64 * @var ReflectionService|null
66 private $reflectionService = null;
69 * Sets the cache driver used by the factory to cache ClassMetadata instances.
71 * @param \Doctrine\Common\Cache\Cache $cacheDriver
75 public function setCacheDriver(Cache
$cacheDriver = null)
77 $this->cacheDriver
= $cacheDriver;
81 * Gets the cache driver used by the factory to cache ClassMetadata instances.
83 * @return \Doctrine\Common\Cache\Cache|null
85 public function getCacheDriver()
87 return $this->cacheDriver
;
91 * Returns an array of all the loaded metadata currently in memory.
93 * @return ClassMetadata[]
95 public function getLoadedMetadata()
97 return $this->loadedMetadata
;
101 * Forces the factory to load the metadata of all classes known to the underlying
104 * @return array The ClassMetadata instances of all mapped classes.
106 public function getAllMetadata()
108 if ( ! $this->initialized
) {
112 $driver = $this->getDriver();
114 foreach ($driver->getAllClassNames() as $className) {
115 $metadata[] = $this->getMetadataFor($className);
122 * Lazy initialization of this stuff, especially the metadata driver,
123 * since these are not needed at all when a metadata cache is active.
127 abstract protected function initialize();
130 * Gets the fully qualified class-name from the namespace alias.
132 * @param string $namespaceAlias
133 * @param string $simpleClassName
137 abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName);
140 * Returns the mapping driver implementation.
142 * @return \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver
144 abstract protected function getDriver();
147 * Wakes up reflection after ClassMetadata gets unserialized from cache.
149 * @param ClassMetadata $class
150 * @param ReflectionService $reflService
154 abstract protected function wakeupReflection(ClassMetadata
$class, ReflectionService
$reflService);
157 * Initializes Reflection after ClassMetadata was constructed.
159 * @param ClassMetadata $class
160 * @param ReflectionService $reflService
164 abstract protected function initializeReflection(ClassMetadata
$class, ReflectionService
$reflService);
167 * Checks whether the class metadata is an entity.
169 * This method should return false for mapped superclasses or embedded classes.
171 * @param ClassMetadata $class
175 abstract protected function isEntity(ClassMetadata
$class);
178 * Gets the class metadata descriptor for a class.
180 * @param string $className The name of the class.
182 * @return ClassMetadata
184 * @throws ReflectionException
185 * @throws MappingException
187 public function getMetadataFor($className)
189 if (isset($this->loadedMetadata
[$className])) {
190 return $this->loadedMetadata
[$className];
193 // Check for namespace alias
194 if (strpos($className, ':') !== false) {
195 list($namespaceAlias, $simpleClassName) = explode(':', $className, 2);
197 $realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
199 $realClassName = ClassUtils
::getRealClass($className);
202 if (isset($this->loadedMetadata
[$realClassName])) {
203 // We do not have the alias name in the map, include it
204 return $this->loadedMetadata
[$className] = $this->loadedMetadata
[$realClassName];
207 $loadingException = null;
210 if ($this->cacheDriver
) {
211 if (($cached = $this->cacheDriver
->fetch($realClassName . $this->cacheSalt
)) instanceof ClassMetadata
) {
212 $this->loadedMetadata
[$realClassName] = $cached;
214 $this->wakeupReflection($cached, $this->getReflectionService());
216 foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
217 $this->cacheDriver
->save(
218 $loadedClassName . $this->cacheSalt
,
219 $this->loadedMetadata
[$loadedClassName],
225 $this->loadMetadata($realClassName);
227 } catch (MappingException
$loadingException) {
228 if (! $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName)) {
229 throw $loadingException;
232 $this->loadedMetadata
[$realClassName] = $fallbackMetadataResponse;
235 if ($className !== $realClassName) {
236 // We do not have the alias name in the map, include it
237 $this->loadedMetadata
[$className] = $this->loadedMetadata
[$realClassName];
240 return $this->loadedMetadata
[$className];
244 * Checks whether the factory has the metadata for a class loaded already.
246 * @param string $className
248 * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
250 public function hasMetadataFor($className)
252 return isset($this->loadedMetadata
[$className]);
256 * Sets the metadata descriptor for a specific class.
258 * NOTE: This is only useful in very special cases, like when generating proxy classes.
260 * @param string $className
261 * @param ClassMetadata $class
265 public function setMetadataFor($className, $class)
267 $this->loadedMetadata
[$className] = $class;
271 * Gets an array of parent classes for the given entity class.
273 * @param string $name
277 protected function getParentClasses($name)
279 // Collect parent classes, ignoring transient (not-mapped) classes.
281 foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
282 if ( ! $this->getDriver()->isTransient($parentClass)) {
283 $parentClasses[] = $parentClass;
286 return $parentClasses;
290 * Loads the metadata of the class in question and all it's ancestors whose metadata
291 * is still not loaded.
293 * Important: The class $name does not necesarily exist at this point here.
294 * Scenarios in a code-generation setup might have access to XML/YAML
295 * Mapping files without the actual PHP code existing here. That is why the
296 * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface
297 * should be used for reflection.
299 * @param string $name The name of the class for which the metadata should get loaded.
303 protected function loadMetadata($name)
305 if ( ! $this->initialized
) {
311 $parentClasses = $this->getParentClasses($name);
312 $parentClasses[] = $name;
314 // Move down the hierarchy of parent classes, starting from the topmost class
316 $rootEntityFound = false;
318 $reflService = $this->getReflectionService();
319 foreach ($parentClasses as $className) {
320 if (isset($this->loadedMetadata
[$className])) {
321 $parent = $this->loadedMetadata
[$className];
322 if ($this->isEntity($parent)) {
323 $rootEntityFound = true;
324 array_unshift($visited, $className);
329 $class = $this->newClassMetadataInstance($className);
330 $this->initializeReflection($class, $reflService);
332 $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);
334 $this->loadedMetadata
[$className] = $class;
338 if ($this->isEntity($class)) {
339 $rootEntityFound = true;
340 array_unshift($visited, $className);
343 $this->wakeupReflection($class, $reflService);
345 $loaded[] = $className;
352 * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
354 * Override this method to implement a fallback strategy for failed metadata loading
356 * @param string $className
358 * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata|null
360 protected function onNotFoundMetadata($className)
366 * Actually loads the metadata from the underlying metadata.
368 * @param ClassMetadata $class
369 * @param ClassMetadata|null $parent
370 * @param bool $rootEntityFound
371 * @param array $nonSuperclassParents All parent class names
372 * that are not marked as mapped superclasses.
376 abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents);
379 * Creates a new ClassMetadata instance for the given class name.
381 * @param string $className
383 * @return ClassMetadata
385 abstract protected function newClassMetadataInstance($className);
390 public function isTransient($class)
392 if ( ! $this->initialized
) {
396 // Check for namespace alias
397 if (strpos($class, ':') !== false) {
398 list($namespaceAlias, $simpleClassName) = explode(':', $class, 2);
399 $class = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
402 return $this->getDriver()->isTransient($class);
406 * Sets the reflectionService.
408 * @param ReflectionService $reflectionService
412 public function setReflectionService(ReflectionService
$reflectionService)
414 $this->reflectionService
= $reflectionService;
418 * Gets the reflection service associated with this metadata factory.
420 * @return ReflectionService
422 public function getReflectionService()
424 if ($this->reflectionService
=== null) {
425 $this->reflectionService
= new RuntimeReflectionService();
427 return $this->reflectionService
;