AnnotationDriver.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. namespace Doctrine\Persistence\Mapping\Driver;
  3. use Doctrine\Common\Annotations\Reader;
  4. use Doctrine\Persistence\Mapping\MappingException;
  5. use FilesystemIterator;
  6. use RecursiveDirectoryIterator;
  7. use RecursiveIteratorIterator;
  8. use RecursiveRegexIterator;
  9. use ReflectionClass;
  10. use RegexIterator;
  11. use function array_merge;
  12. use function array_unique;
  13. use function get_class;
  14. use function get_declared_classes;
  15. use function in_array;
  16. use function is_dir;
  17. use function preg_match;
  18. use function preg_quote;
  19. use function realpath;
  20. use function str_replace;
  21. use function strpos;
  22. /**
  23. * The AnnotationDriver reads the mapping metadata from docblock annotations.
  24. */
  25. abstract class AnnotationDriver implements MappingDriver
  26. {
  27. /**
  28. * The annotation reader.
  29. *
  30. * @var Reader
  31. */
  32. protected $reader;
  33. /**
  34. * The paths where to look for mapping files.
  35. *
  36. * @var string[]
  37. */
  38. protected $paths = [];
  39. /**
  40. * The paths excluded from path where to look for mapping files.
  41. *
  42. * @var string[]
  43. */
  44. protected $excludePaths = [];
  45. /**
  46. * The file extension of mapping documents.
  47. *
  48. * @var string
  49. */
  50. protected $fileExtension = '.php';
  51. /**
  52. * Cache for AnnotationDriver#getAllClassNames().
  53. *
  54. * @var string[]|null
  55. */
  56. protected $classNames;
  57. /**
  58. * Name of the entity annotations as keys.
  59. *
  60. * @var string[]
  61. */
  62. protected $entityAnnotationClasses = [];
  63. /**
  64. * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
  65. * docblock annotations.
  66. *
  67. * @param Reader $reader The AnnotationReader to use, duck-typed.
  68. * @param string|string[]|null $paths One or multiple paths where mapping classes can be found.
  69. */
  70. public function __construct($reader, $paths = null)
  71. {
  72. $this->reader = $reader;
  73. if (! $paths) {
  74. return;
  75. }
  76. $this->addPaths((array) $paths);
  77. }
  78. /**
  79. * Appends lookup paths to metadata driver.
  80. *
  81. * @param string[] $paths
  82. *
  83. * @return void
  84. */
  85. public function addPaths(array $paths)
  86. {
  87. $this->paths = array_unique(array_merge($this->paths, $paths));
  88. }
  89. /**
  90. * Retrieves the defined metadata lookup paths.
  91. *
  92. * @return string[]
  93. */
  94. public function getPaths()
  95. {
  96. return $this->paths;
  97. }
  98. /**
  99. * Append exclude lookup paths to metadata driver.
  100. *
  101. * @param string[] $paths
  102. */
  103. public function addExcludePaths(array $paths)
  104. {
  105. $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
  106. }
  107. /**
  108. * Retrieve the defined metadata lookup exclude paths.
  109. *
  110. * @return string[]
  111. */
  112. public function getExcludePaths()
  113. {
  114. return $this->excludePaths;
  115. }
  116. /**
  117. * Retrieve the current annotation reader
  118. *
  119. * @return Reader
  120. */
  121. public function getReader()
  122. {
  123. return $this->reader;
  124. }
  125. /**
  126. * Gets the file extension used to look for mapping files under.
  127. *
  128. * @return string
  129. */
  130. public function getFileExtension()
  131. {
  132. return $this->fileExtension;
  133. }
  134. /**
  135. * Sets the file extension used to look for mapping files under.
  136. *
  137. * @param string $fileExtension The file extension to set.
  138. *
  139. * @return void
  140. */
  141. public function setFileExtension($fileExtension)
  142. {
  143. $this->fileExtension = $fileExtension;
  144. }
  145. /**
  146. * Returns whether the class with the specified name is transient. Only non-transient
  147. * classes, that is entities and mapped superclasses, should have their metadata loaded.
  148. *
  149. * A class is non-transient if it is annotated with an annotation
  150. * from the {@see AnnotationDriver::entityAnnotationClasses}.
  151. *
  152. * @param string $className
  153. *
  154. * @return bool
  155. */
  156. public function isTransient($className)
  157. {
  158. $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
  159. foreach ($classAnnotations as $annot) {
  160. if (isset($this->entityAnnotationClasses[get_class($annot)])) {
  161. return false;
  162. }
  163. }
  164. return true;
  165. }
  166. /**
  167. * {@inheritDoc}
  168. */
  169. public function getAllClassNames()
  170. {
  171. if ($this->classNames !== null) {
  172. return $this->classNames;
  173. }
  174. if (! $this->paths) {
  175. throw MappingException::pathRequired();
  176. }
  177. $classes = [];
  178. $includedFiles = [];
  179. foreach ($this->paths as $path) {
  180. if (! is_dir($path)) {
  181. throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
  182. }
  183. $iterator = new RegexIterator(
  184. new RecursiveIteratorIterator(
  185. new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
  186. RecursiveIteratorIterator::LEAVES_ONLY
  187. ),
  188. '/^.+' . preg_quote($this->fileExtension) . '$/i',
  189. RecursiveRegexIterator::GET_MATCH
  190. );
  191. foreach ($iterator as $file) {
  192. $sourceFile = $file[0];
  193. if (! preg_match('(^phar:)i', $sourceFile)) {
  194. $sourceFile = realpath($sourceFile);
  195. }
  196. foreach ($this->excludePaths as $excludePath) {
  197. $exclude = str_replace('\\', '/', realpath($excludePath));
  198. $current = str_replace('\\', '/', $sourceFile);
  199. if (strpos($current, $exclude) !== false) {
  200. continue 2;
  201. }
  202. }
  203. require_once $sourceFile;
  204. $includedFiles[] = $sourceFile;
  205. }
  206. }
  207. $declared = get_declared_classes();
  208. foreach ($declared as $className) {
  209. $rc = new ReflectionClass($className);
  210. $sourceFile = $rc->getFileName();
  211. if (! in_array($sourceFile, $includedFiles) || $this->isTransient($className)) {
  212. continue;
  213. }
  214. $classes[] = $className;
  215. }
  216. $this->classNames = $classes;
  217. return $classes;
  218. }
  219. }