1<?php 2 3namespace Doctrine\Bundle\DoctrineBundle\Mapping; 4 5use Doctrine\ORM\Mapping\ClassMetadata; 6use Doctrine\ORM\Mapping\MappingException; 7use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; 8use Doctrine\Persistence\ManagerRegistry; 9use ReflectionClass; 10use RuntimeException; 11use Symfony\Component\HttpKernel\Bundle\BundleInterface; 12 13/** 14 * This class provides methods to access Doctrine entity class metadata for a 15 * given bundle, namespace or entity class, for generation purposes 16 */ 17class DisconnectedMetadataFactory 18{ 19 /** @var ManagerRegistry */ 20 private $registry; 21 22 /** 23 * @param ManagerRegistry $registry A ManagerRegistry instance 24 */ 25 public function __construct(ManagerRegistry $registry) 26 { 27 $this->registry = $registry; 28 } 29 30 /** 31 * Gets the metadata of all classes of a bundle. 32 * 33 * @param BundleInterface $bundle A BundleInterface instance 34 * 35 * @return ClassMetadataCollection A ClassMetadataCollection instance 36 * 37 * @throws RuntimeException When bundle does not contain mapped entities. 38 */ 39 public function getBundleMetadata(BundleInterface $bundle) 40 { 41 $namespace = $bundle->getNamespace(); 42 $metadata = $this->getMetadataForNamespace($namespace); 43 if (! $metadata->getMetadata()) { 44 throw new RuntimeException(sprintf('Bundle "%s" does not contain any mapped entities.', $bundle->getName())); 45 } 46 47 $path = $this->getBasePathForClass($bundle->getName(), $bundle->getNamespace(), $bundle->getPath()); 48 49 $metadata->setPath($path); 50 $metadata->setNamespace($bundle->getNamespace()); 51 52 return $metadata; 53 } 54 55 /** 56 * Gets the metadata of a class. 57 * 58 * @param string $class A class name 59 * @param string $path The path where the class is stored (if known) 60 * 61 * @return ClassMetadataCollection A ClassMetadataCollection instance 62 * 63 * @throws MappingException When class is not valid entity or mapped superclass. 64 */ 65 public function getClassMetadata($class, $path = null) 66 { 67 $metadata = $this->getMetadataForClass($class); 68 if (! $metadata->getMetadata()) { 69 throw MappingException::classIsNotAValidEntityOrMappedSuperClass($class); 70 } 71 72 $this->findNamespaceAndPathForMetadata($metadata, $path); 73 74 return $metadata; 75 } 76 77 /** 78 * Gets the metadata of all classes of a namespace. 79 * 80 * @param string $namespace A namespace name 81 * @param string $path The path where the class is stored (if known) 82 * 83 * @return ClassMetadataCollection A ClassMetadataCollection instance 84 * 85 * @throws RuntimeException When namespace not contain mapped entities. 86 */ 87 public function getNamespaceMetadata($namespace, $path = null) 88 { 89 $metadata = $this->getMetadataForNamespace($namespace); 90 if (! $metadata->getMetadata()) { 91 throw new RuntimeException(sprintf('Namespace "%s" does not contain any mapped entities.', $namespace)); 92 } 93 94 $this->findNamespaceAndPathForMetadata($metadata, $path); 95 96 return $metadata; 97 } 98 99 /** 100 * Find and configure path and namespace for the metadata collection. 101 * 102 * @param string|null $path 103 * 104 * @throws RuntimeException When unable to determine the path. 105 */ 106 public function findNamespaceAndPathForMetadata(ClassMetadataCollection $metadata, $path = null) 107 { 108 $all = $metadata->getMetadata(); 109 if (class_exists($all[0]->name)) { 110 $r = new ReflectionClass($all[0]->name); 111 $path = $this->getBasePathForClass($r->getName(), $r->getNamespaceName(), dirname($r->getFilename())); 112 $ns = $r->getNamespaceName(); 113 } elseif ($path) { 114 // Get namespace by removing the last component of the FQCN 115 $nsParts = explode('\\', $all[0]->name); 116 array_pop($nsParts); 117 $ns = implode('\\', $nsParts); 118 } else { 119 throw new RuntimeException(sprintf('Unable to determine where to save the "%s" class (use the --path option).', $all[0]->name)); 120 } 121 122 $metadata->setPath($path); 123 $metadata->setNamespace($ns); 124 } 125 126 /** 127 * Get a base path for a class 128 * 129 * @param string $name class name 130 * @param string $namespace class namespace 131 * @param string $path class path 132 * 133 * @return string 134 * 135 * @throws RuntimeException When base path not found. 136 */ 137 private function getBasePathForClass($name, $namespace, $path) 138 { 139 $namespace = str_replace('\\', '/', $namespace); 140 $search = str_replace('\\', '/', $path); 141 $destination = str_replace('/' . $namespace, '', $search, $c); 142 143 if ($c !== 1) { 144 throw new RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination)); 145 } 146 147 return $destination; 148 } 149 150 /** 151 * @param string $namespace 152 * 153 * @return ClassMetadataCollection 154 */ 155 private function getMetadataForNamespace($namespace) 156 { 157 $metadata = []; 158 foreach ($this->getAllMetadata() as $m) { 159 if (strpos($m->name, $namespace) !== 0) { 160 continue; 161 } 162 163 $metadata[] = $m; 164 } 165 166 return new ClassMetadataCollection($metadata); 167 } 168 169 /** 170 * @param string $entity 171 * 172 * @return ClassMetadataCollection 173 */ 174 private function getMetadataForClass($entity) 175 { 176 foreach ($this->registry->getManagers() as $em) { 177 $cmf = new DisconnectedClassMetadataFactory(); 178 $cmf->setEntityManager($em); 179 180 if (! $cmf->isTransient($entity)) { 181 return new ClassMetadataCollection([$cmf->getMetadataFor($entity)]); 182 } 183 } 184 185 return new ClassMetadataCollection([]); 186 } 187 188 /** 189 * @return ClassMetadata[] 190 */ 191 private function getAllMetadata() 192 { 193 $metadata = []; 194 foreach ($this->registry->getManagers() as $em) { 195 $cmf = new DisconnectedClassMetadataFactory(); 196 $cmf->setEntityManager($em); 197 foreach ($cmf->getAllMetadata() as $m) { 198 $metadata[] = $m; 199 } 200 } 201 202 return $metadata; 203 } 204} 205