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 $cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt);
212 if ($cached instanceof ClassMetadata) {
213 $this->loadedMetadata[$realClassName] = $cached;
215 $this->wakeupReflection($cached, $this->getReflectionService());
217 foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
218 $this->cacheDriver->save(
219 $loadedClassName . $this->cacheSalt,
220 $this->loadedMetadata[$loadedClassName],
226 $this->loadMetadata($realClassName);
228 } catch (MappingException $loadingException) {
229 if (! $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName)) {
230 throw $loadingException;
233 $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
236 if ($className !== $realClassName) {
237 // We do not have the alias name in the map, include it
238 $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
241 return $this->loadedMetadata[$className];
245 * Checks whether the factory has the metadata for a class loaded already.
247 * @param string $className
249 * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
251 public function hasMetadataFor($className)
253 return isset($this->loadedMetadata[$className]);
257 * Sets the metadata descriptor for a specific class.
259 * NOTE: This is only useful in very special cases, like when generating proxy classes.
261 * @param string $className
262 * @param ClassMetadata $class
266 public function setMetadataFor($className, $class)
268 $this->loadedMetadata[$className] = $class;
272 * Gets an array of parent classes for the given entity class.
274 * @param string $name
278 protected function getParentClasses($name)
280 // Collect parent classes, ignoring transient (not-mapped) classes.
282 foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
283 if ( ! $this->getDriver()->isTransient($parentClass)) {
284 $parentClasses[] = $parentClass;
287 return $parentClasses;
291 * Loads the metadata of the class in question and all it's ancestors whose metadata
292 * is still not loaded.
294 * Important: The class $name does not necessarily exist at this point here.
295 * Scenarios in a code-generation setup might have access to XML/YAML
296 * Mapping files without the actual PHP code existing here. That is why the
297 * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface
298 * should be used for reflection.
300 * @param string $name The name of the class for which the metadata should get loaded.
304 protected function loadMetadata($name)
306 if ( ! $this->initialized) {
312 $parentClasses = $this->getParentClasses($name);
313 $parentClasses[] = $name;
315 // Move down the hierarchy of parent classes, starting from the topmost class
317 $rootEntityFound = false;
319 $reflService = $this->getReflectionService();
320 foreach ($parentClasses as $className) {
321 if (isset($this->loadedMetadata[$className])) {
322 $parent = $this->loadedMetadata[$className];
323 if ($this->isEntity($parent)) {
324 $rootEntityFound = true;
325 array_unshift($visited, $className);
330 $class = $this->newClassMetadataInstance($className);
331 $this->initializeReflection($class, $reflService);
333 $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);
335 $this->loadedMetadata[$className] = $class;
339 if ($this->isEntity($class)) {
340 $rootEntityFound = true;
341 array_unshift($visited, $className);
344 $this->wakeupReflection($class, $reflService);
346 $loaded[] = $className;
353 * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
355 * Override this method to implement a fallback strategy for failed metadata loading
357 * @param string $className
359 * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata|null
361 protected function onNotFoundMetadata($className)
367 * Actually loads the metadata from the underlying metadata.
369 * @param ClassMetadata $class
370 * @param ClassMetadata|null $parent
371 * @param bool $rootEntityFound
372 * @param array $nonSuperclassParents All parent class names
373 * that are not marked as mapped superclasses.
377 abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents);
380 * Creates a new ClassMetadata instance for the given class name.
382 * @param string $className
384 * @return ClassMetadata
386 abstract protected function newClassMetadataInstance($className);
391 public function isTransient($class)
393 if ( ! $this->initialized) {
397 // Check for namespace alias
398 if (strpos($class, ':') !== false) {
399 list($namespaceAlias, $simpleClassName) = explode(':', $class, 2);
400 $class = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName);
403 return $this->getDriver()->isTransient($class);
407 * Sets the reflectionService.
409 * @param ReflectionService $reflectionService
413 public function setReflectionService(ReflectionService $reflectionService)
415 $this->reflectionService = $reflectionService;
419 * Gets the reflection service associated with this metadata factory.
421 * @return ReflectionService
423 public function getReflectionService()
425 if ($this->reflectionService === null) {
426 $this->reflectionService = new RuntimeReflectionService();
428 return $this->reflectionService;