AnnotationDriver.php 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. /*
  3. * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. namespace JMS\DiExtraBundle\Metadata\Driver;
  18. use JMS\DiExtraBundle\Annotation\AfterSetup;
  19. use JMS\DiExtraBundle\Annotation\FormType;
  20. use JMS\DiExtraBundle\Annotation\DoctrineListener;
  21. use JMS\DiExtraBundle\Annotation\Reference as AnnotReference;
  22. use JMS\DiExtraBundle\Annotation\LookupMethod;
  23. use JMS\DiExtraBundle\Annotation\Validator;
  24. use JMS\DiExtraBundle\Annotation\InjectParams;
  25. use JMS\DiExtraBundle\Exception\InvalidTypeException;
  26. use JMS\DiExtraBundle\Annotation\Observe;
  27. use Doctrine\Common\Annotations\Reader;
  28. use JMS\DiExtraBundle\Annotation\Inject;
  29. use JMS\DiExtraBundle\Annotation\Service;
  30. use JMS\DiExtraBundle\Annotation\Tag;
  31. use JMS\DiExtraBundle\Metadata\ClassMetadata;
  32. use Metadata\Driver\DriverInterface;
  33. use Symfony\Component\DependencyInjection\ContainerInterface;
  34. use Symfony\Component\DependencyInjection\Reference;
  35. class AnnotationDriver implements DriverInterface
  36. {
  37. private $reader;
  38. public function __construct(Reader $reader)
  39. {
  40. $this->reader = $reader;
  41. }
  42. public function loadMetadataForClass(\ReflectionClass $class)
  43. {
  44. $metadata = new ClassMetadata($className = $class->getName());
  45. if (false !== $filename = $class->getFilename()) {
  46. $metadata->fileResources[] = $filename;
  47. }
  48. // this is a bit of a hack, but avoids any timeout issues when a class
  49. // is moved into one of the compiled classes files, and Doctrine
  50. // Common 2.1 is used.
  51. if (false !== strpos($filename, '/classes.php')
  52. || false !== strpos($filename, '/bootstrap.php')) {
  53. return null;
  54. }
  55. foreach ($this->reader->getClassAnnotations($class) as $annot) {
  56. if ($annot instanceof Service) {
  57. if (null === $annot->id) {
  58. $metadata->id = $this->generateId($className);
  59. } else {
  60. $metadata->id = $annot->id;
  61. }
  62. $metadata->parent = $annot->parent;
  63. $metadata->public = $annot->public;
  64. $metadata->scope = $annot->scope;
  65. $metadata->abstract = $annot->abstract;
  66. } else if ($annot instanceof Tag) {
  67. $metadata->tags[$annot->name][] = $annot->attributes;
  68. } else if ($annot instanceof Validator) {
  69. // automatically register as service if not done explicitly
  70. if (null === $metadata->id) {
  71. $metadata->id = $this->generateId($className);
  72. }
  73. $metadata->tags['validator.constraint_validator'][] = array(
  74. 'alias' => $annot->alias,
  75. );
  76. } else if ($annot instanceof DoctrineListener) {
  77. if (null === $metadata->id) {
  78. $metadata->id = $this->generateId($className);
  79. }
  80. foreach ($annot->events as $event) {
  81. $metadata->tags['doctrine.event_listener'][] = array(
  82. 'event' => $event,
  83. 'connection' => $annot->connection ?: 'default',
  84. 'lazy' => $annot->lazy,
  85. 'priority' => $annot->priority,
  86. );
  87. }
  88. } else if ($annot instanceof FormType) {
  89. if (null === $metadata->id) {
  90. $metadata->id = $this->generateId($className);
  91. }
  92. $alias = $annot->alias;
  93. // try to extract it from the class itself
  94. if (null === $alias) {
  95. $instance = unserialize(sprintf('O:%d:"%s":0:{}', strlen($className), $className));
  96. $alias = $instance->getName();
  97. }
  98. $metadata->tags['form.type'][] = array(
  99. 'alias' => $alias,
  100. );
  101. }
  102. }
  103. $hasInjection = false;
  104. foreach ($class->getProperties() as $property) {
  105. if ($property->getDeclaringClass()->getName() !== $className) {
  106. continue;
  107. }
  108. $name = $property->getName();
  109. foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
  110. if ($annot instanceof Inject) {
  111. $hasInjection = true;
  112. $metadata->properties[$name] = $this->convertReferenceValue($name, $annot);
  113. }
  114. }
  115. }
  116. foreach ($class->getMethods() as $method) {
  117. if ($method->getDeclaringClass()->getName() !== $className) {
  118. continue;
  119. }
  120. $name = $method->getName();
  121. foreach ($this->reader->getMethodAnnotations($method) as $annot) {
  122. if ($annot instanceof Observe) {
  123. $metadata->tags['kernel.event_listener'][] = array(
  124. 'event' => $annot->event,
  125. 'method' => $name,
  126. 'priority' => $annot->priority,
  127. );
  128. } else if ($annot instanceof InjectParams) {
  129. $params = array();
  130. foreach ($method->getParameters() as $param) {
  131. if (!isset($annot->params[$paramName = $param->getName()])) {
  132. $params[] = $this->convertReferenceValue($paramName, new Inject(array('value' => null)));
  133. continue;
  134. }
  135. $params[] = $this->convertReferenceValue($paramName, $annot->params[$paramName]);
  136. }
  137. if (!$params) {
  138. continue;
  139. }
  140. $hasInjection = true;
  141. if ('__construct' === $name) {
  142. $metadata->arguments = $params;
  143. } else {
  144. $metadata->methodCalls[] = array($name, $params);
  145. }
  146. } else if ($annot instanceof LookupMethod) {
  147. $hasInjection = true;
  148. if ($method->isFinal()) {
  149. throw new \RuntimeException(sprintf('The method "%s::%s" is marked as final and cannot be declared as lookup-method.', $className, $name));
  150. }
  151. if ($method->isPrivate()) {
  152. throw new \RuntimeException(sprintf('The method "%s::%s" is marked as private and cannot be declared as lookup-method.', $className, $name));
  153. }
  154. if ($method->getParameters()) {
  155. throw new \RuntimeException(sprintf('The method "%s::%s" must have a no-arguments signature if you want to use it as lookup-method.', $className, $name));
  156. }
  157. $metadata->lookupMethods[$name] = $this->convertReferenceValue('get' === substr($name, 0, 3) ? substr($name, 3) : $name, $annot);
  158. } else if ($annot instanceof AfterSetup) {
  159. if (!$method->isPublic()) {
  160. throw new \RuntimeException(sprintf('The init method "%s::%s" must be public.', $method->class, $method->name));
  161. }
  162. $metadata->initMethod = $method->name;
  163. }
  164. }
  165. }
  166. if (null == $metadata->id && !$hasInjection) {
  167. return null;
  168. }
  169. return $metadata;
  170. }
  171. private function convertReferenceValue($name, AnnotReference $annot)
  172. {
  173. if (null === $annot->value) {
  174. return new Reference($this->generateId($name), false !== $annot->required ? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE : ContainerInterface::NULL_ON_INVALID_REFERENCE);
  175. }
  176. if (false === strpos($annot->value, '%')) {
  177. return new Reference($annot->value, false !== $annot->required ? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE : ContainerInterface::NULL_ON_INVALID_REFERENCE);
  178. }
  179. return $annot->value;
  180. }
  181. private function generateId($name)
  182. {
  183. $name = preg_replace('/(?<=[a-zA-Z0-9])[A-Z]/', '_\\0', $name);
  184. return strtolower(strtr($name, '\\', '.'));
  185. }
  186. }