ExtensionCompilerPass.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. /*
  3. * This file is part of the Sonata Project package.
  4. *
  5. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Sonata\AdminBundle\DependencyInjection\Compiler;
  11. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\Definition;
  14. use Symfony\Component\DependencyInjection\Reference;
  15. /**
  16. * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  17. */
  18. class ExtensionCompilerPass implements CompilerPassInterface
  19. {
  20. /**
  21. * {@inheritdoc}
  22. */
  23. public function process(ContainerBuilder $container)
  24. {
  25. $universalExtensions = array();
  26. $targets = array();
  27. foreach ($container->findTaggedServiceIds('sonata.admin.extension') as $id => $tags) {
  28. foreach ($tags as $attributes) {
  29. $target = false;
  30. if (isset($attributes['target'])) {
  31. $target = $attributes['target'];
  32. }
  33. if (isset($attributes['global']) && $attributes['global']) {
  34. $universalExtensions[$id] = $attributes;
  35. }
  36. if (!$target || !$container->hasDefinition($target)) {
  37. continue;
  38. }
  39. $this->addExtension($targets, $target, $id, $attributes);
  40. }
  41. }
  42. $extensionConfig = $container->getParameter('sonata.admin.extension.map');
  43. $extensionMap = $this->flattenExtensionConfiguration($extensionConfig);
  44. foreach ($container->findTaggedServiceIds('sonata.admin') as $id => $attributes) {
  45. $admin = $container->getDefinition($id);
  46. if (!isset($targets[$id])) {
  47. $targets[$id] = new \SplPriorityQueue();
  48. }
  49. foreach ($universalExtensions as $extension => $extensionAttributes) {
  50. $this->addExtension($targets, $id, $extension, $extensionAttributes);
  51. }
  52. $extensions = $this->getExtensionsForAdmin($id, $admin, $container, $extensionMap);
  53. foreach ($extensions as $extension) {
  54. if (!$container->has($extension)) {
  55. throw new \InvalidArgumentException(
  56. sprintf('Unable to find extension service for id %s', $extension)
  57. );
  58. }
  59. $this->addExtension($targets, $id, $extension, $attributes);
  60. }
  61. }
  62. foreach ($targets as $target => $extensions) {
  63. $extensions = iterator_to_array($extensions);
  64. krsort($extensions);
  65. $admin = $container->getDefinition($target);
  66. foreach (array_values($extensions) as $extension) {
  67. $admin->addMethodCall('addExtension', array($extension));
  68. }
  69. }
  70. }
  71. /**
  72. * @param string $id
  73. * @param Definition $admin
  74. * @param ContainerBuilder $container
  75. * @param array $extensionMap
  76. *
  77. * @return array
  78. */
  79. protected function getExtensionsForAdmin($id, Definition $admin, ContainerBuilder $container, array $extensionMap)
  80. {
  81. $extensions = array();
  82. $classReflection = $subjectReflection = null;
  83. $excludes = $extensionMap['excludes'];
  84. unset($extensionMap['excludes']);
  85. foreach ($extensionMap as $type => $subjects) {
  86. foreach ($subjects as $subject => $extensionList) {
  87. if ('admins' == $type) {
  88. if ($id == $subject) {
  89. $extensions = array_merge($extensions, $extensionList);
  90. }
  91. } else {
  92. $class = $this->getManagedClass($admin, $container);
  93. if (!class_exists($class)) {
  94. continue;
  95. }
  96. $classReflection = new \ReflectionClass($class);
  97. $subjectReflection = new \ReflectionClass($subject);
  98. }
  99. if ('instanceof' == $type) {
  100. if ($subjectReflection->getName() == $classReflection->getName() || $classReflection->isSubclassOf($subject)) {
  101. $extensions = array_merge($extensions, $extensionList);
  102. }
  103. }
  104. if ('implements' == $type) {
  105. if ($classReflection->implementsInterface($subject)) {
  106. $extensions = array_merge($extensions, $extensionList);
  107. }
  108. }
  109. if ('extends' == $type) {
  110. if ($classReflection->isSubclassOf($subject)) {
  111. $extensions = array_merge($extensions, $extensionList);
  112. }
  113. }
  114. if ('uses' == $type) {
  115. if ($this->hasTrait($classReflection, $subject)) {
  116. $extensions = array_merge($extensions, $extensionList);
  117. }
  118. }
  119. }
  120. }
  121. if (isset($excludes[$id])) {
  122. $extensions = array_diff($extensions, $excludes[$id]);
  123. }
  124. return $extensions;
  125. }
  126. /**
  127. * Resolves the class argument of the admin to an actual class (in case of %parameter%).
  128. *
  129. * @param Definition $admin
  130. * @param ContainerBuilder $container
  131. *
  132. * @return string
  133. */
  134. protected function getManagedClass(Definition $admin, ContainerBuilder $container)
  135. {
  136. return $container->getParameterBag()->resolveValue($admin->getArgument(1));
  137. }
  138. /**
  139. * @param array $config
  140. *
  141. * @return array
  142. */
  143. protected function flattenExtensionConfiguration(array $config)
  144. {
  145. $extensionMap = array(
  146. 'excludes' => array(),
  147. 'admins' => array(),
  148. 'implements' => array(),
  149. 'extends' => array(),
  150. 'instanceof' => array(),
  151. 'uses' => array(),
  152. );
  153. foreach ($config as $extension => $options) {
  154. foreach ($options as $key => $value) {
  155. foreach ($value as $source) {
  156. if (!isset($extensionMap[$key][$source])) {
  157. $extensionMap[$key][$source] = array();
  158. }
  159. array_push($extensionMap[$key][$source], $extension);
  160. }
  161. }
  162. }
  163. return $extensionMap;
  164. }
  165. /**
  166. * @param \ReflectionClass $class
  167. * @param $traitName
  168. *
  169. * @return bool
  170. */
  171. protected function hasTrait(\ReflectionClass $class, $traitName)
  172. {
  173. if (in_array($traitName, $class->getTraitNames())) {
  174. return true;
  175. }
  176. if (!$parentClass = $class->getParentClass()) {
  177. return false;
  178. }
  179. return $this->hasTrait($parentClass, $traitName);
  180. }
  181. /**
  182. * Add extension configuration to the targets array.
  183. *
  184. * @param array $targets
  185. * @param string $target
  186. * @param string $extension
  187. * @param array $attributes
  188. */
  189. private function addExtension(array &$targets, $target, $extension, $attributes)
  190. {
  191. if (!isset($targets[$target])) {
  192. $targets[$target] = new \SplPriorityQueue();
  193. }
  194. $priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
  195. $targets[$target]->insert(new Reference($extension), $priority);
  196. }
  197. }