ExtensionCompilerPass.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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 => $attributes) {
  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_key($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 An array with the following structure.
  142. *
  143. * array(
  144. * 'excludes' => array('<admin_id>' => array('<extension_id>' => array('priority' => <int>))),
  145. * 'admins' => array('<admin_id>' => array('<extension_id>' => array('priority' => <int>))),
  146. * 'implements' => array('<interface>' => array('<extension_id>' => array('priority' => <int>))),
  147. * 'extends' => array('<class>' => array('<extension_id>' => array('priority' => <int>))),
  148. * 'instanceof' => array('<class>' => array('<extension_id>' => array('priority' => <int>))),
  149. * 'uses' => array('<trait>' => array('<extension_id>' => array('priority' => <int>))),
  150. * )
  151. */
  152. protected function flattenExtensionConfiguration(array $config)
  153. {
  154. $extensionMap = array(
  155. 'excludes' => array(),
  156. 'admins' => array(),
  157. 'implements' => array(),
  158. 'extends' => array(),
  159. 'instanceof' => array(),
  160. 'uses' => array(),
  161. );
  162. foreach ($config as $extension => $options) {
  163. $optionsMap = array_intersect_key($options, $extensionMap);
  164. foreach ($optionsMap as $key => $value) {
  165. foreach ($value as $source) {
  166. if (!isset($extensionMap[$key][$source])) {
  167. $extensionMap[$key][$source] = array();
  168. }
  169. $extensionMap[$key][$source][$extension]['priority'] = $options['priority'];
  170. }
  171. }
  172. }
  173. return $extensionMap;
  174. }
  175. /**
  176. * @param \ReflectionClass $class
  177. * @param $traitName
  178. *
  179. * @return bool
  180. */
  181. protected function hasTrait(\ReflectionClass $class, $traitName)
  182. {
  183. if (in_array($traitName, $class->getTraitNames())) {
  184. return true;
  185. }
  186. if (!$parentClass = $class->getParentClass()) {
  187. return false;
  188. }
  189. return $this->hasTrait($parentClass, $traitName);
  190. }
  191. /**
  192. * Add extension configuration to the targets array.
  193. *
  194. * @param array $targets
  195. * @param string $target
  196. * @param string $extension
  197. * @param array $attributes
  198. */
  199. private function addExtension(array &$targets, $target, $extension, array $attributes)
  200. {
  201. if (!isset($targets[$target])) {
  202. $targets[$target] = new \SplPriorityQueue();
  203. }
  204. $priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
  205. $targets[$target]->insert(new Reference($extension), $priority);
  206. }
  207. }