1111
1212namespace Symfony \Bridge \Doctrine \DependencyInjection \CompilerPass ;
1313
14+ use Symfony \Component \DependencyInjection \Compiler \CompilerPassInterface ;
1415use Symfony \Component \DependencyInjection \ContainerBuilder ;
16+ use Symfony \Component \DependencyInjection \Exception \InvalidArgumentException ;
17+ use Symfony \Component \DependencyInjection \Exception \RuntimeException ;
1518use Symfony \Component \DependencyInjection \Reference ;
16- use Symfony \Component \DependencyInjection \Compiler \CompilerPassInterface ;
1719
1820/**
1921 * Registers event listeners and subscribers to the available doctrine connections.
2022 *
2123 * @author Jeremy Mikola <jmikola@gmail.com>
2224 * @author Alexander <iam.asm89@gmail.com>
25+ * @author David Maicher <mail@dmaicher.de>
2326 */
2427class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
2528{
29+ /**
30+ * @var string|string[]
31+ */
2632 private $ connections ;
27- private $ container ;
2833 private $ eventManagers ;
2934 private $ managerTemplate ;
3035 private $ tagPrefix ;
3136
3237 /**
33- * Constructor.
34- *
3538 * @param string $connections Parameter ID for connections
3639 * @param string $managerTemplate sprintf() template for generating the event
3740 * manager's service ID for a connection name
@@ -53,105 +56,112 @@ public function process(ContainerBuilder $container)
5356 return ;
5457 }
5558
56- $ taggedSubscribers = $ container ->findTaggedServiceIds ($ this ->tagPrefix .'.event_subscriber ' );
57- $ taggedListeners = $ container ->findTaggedServiceIds ($ this ->tagPrefix .'.event_listener ' );
58-
59- if (empty ($ taggedSubscribers ) && empty ($ taggedListeners )) {
60- return ;
61- }
62-
63- $ this ->container = $ container ;
6459 $ this ->connections = $ container ->getParameter ($ this ->connections );
65- $ sortFunc = function ($ a , $ b ) {
66- $ a = isset ($ a ['priority ' ]) ? $ a ['priority ' ] : 0 ;
67- $ b = isset ($ b ['priority ' ]) ? $ b ['priority ' ] : 0 ;
68-
69- return $ a > $ b ? -1 : 1 ;
70- };
60+ $ this ->addTaggedSubscribers ($ container );
61+ $ this ->addTaggedListeners ($ container );
62+ }
7163
72- if (! empty ( $ taggedSubscribers )) {
73- $ subscribersPerCon = $ this -> groupByConnection ( $ taggedSubscribers );
74- foreach ( $ subscribersPerCon as $ con => $ subscribers ) {
75- $ em = $ this ->getEventManager ( $ con );
64+ private function addTaggedSubscribers ( ContainerBuilder $ container )
65+ {
66+ $ subscriberTag = $ this -> tagPrefix . ' .event_subscriber ' ;
67+ $ taggedSubscribers = $ this ->findAndSortTags ( $ subscriberTag , $ container );
7668
77- uasort ($ subscribers , $ sortFunc );
78- foreach ($ subscribers as $ id => $ instance ) {
79- if ($ container ->getDefinition ($ id )->isAbstract ()) {
80- throw new \InvalidArgumentException (sprintf ('The abstract service "%s" cannot be tagged as a doctrine event subscriber. ' , $ id ));
81- }
69+ foreach ($ taggedSubscribers as $ taggedSubscriber ) {
70+ $ id = $ taggedSubscriber [0 ];
71+ $ taggedSubscriberDef = $ container ->getDefinition ($ id );
8272
83- $ em -> addMethodCall ( ' addEventSubscriber ' , array ( new Reference ( $ id )));
84- }
73+ if ( $ taggedSubscriberDef -> isAbstract ()) {
74+ throw new InvalidArgumentException ( sprintf ( ' The abstract service "%s" cann
B94A
ot be tagged as a doctrine event subscriber. ' , $ id ));
8575 }
86- }
8776
88- if (!empty ($ taggedListeners )) {
89- $ listenersPerCon = $ this ->groupByConnection ($ taggedListeners , true );
90- foreach ($ listenersPerCon as $ con => $ listeners ) {
91- $ em = $ this ->getEventManager ($ con );
92-
93- uasort ($ listeners , $ sortFunc );
94- foreach ($ listeners as $ id => $ instance ) {
95- if ($ container ->getDefinition ($ id )->isAbstract ()) {
96- throw new \InvalidArgumentException (sprintf ('The abstract service "%s" cannot be tagged as a doctrine event listener. ' , $ id ));
97- }
98-
99- $ em ->addMethodCall ('addEventListener ' , array (
100- array_unique ($ instance ['event ' ]),
101- isset ($ instance ['lazy ' ]) && $ instance ['lazy ' ] ? $ id : new Reference ($ id ),
102- ));
77+ $ tag = $ taggedSubscriber [1 ];
78+ $ connections = isset ($ tag ['connection ' ]) ? array ($ tag ['connection ' ]) : array_keys ($ this ->connections );
79+ foreach ($ connections as $ con ) {
80+ if (!isset ($ this ->connections [$ con ])) {
81+ throw new RuntimeException (sprintf ('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s ' , $ con , $ taggedSubscriber , implode (', ' , array_keys ($ this ->connections ))));
10382 }
83+
84+ $ this ->getEventManagerDef ($ container , $ con )->addMethodCall ('addEventSubscriber ' , array (new Reference ($ id )));
10485 }
10586 }
10687 }
10788
108- private function groupByConnection ( array $ services , $ isListener = false )
89+ private function addTaggedListeners ( ContainerBuilder $ container )
10990 {
110- $ grouped = array ();
111- foreach ($ allCons = array_keys ($ this ->connections ) as $ con ) {
112- $ grouped [$ con ] = array ();
113- }
91+ $ listenerTag = $ this ->tagPrefix .'.event_listener ' ;
92+ $ taggedListeners = $ this ->findAndSortTags ($ listenerTag , $ container );
93+
94+ foreach ($ taggedListeners as $ taggedListener ) {
95+ $ id = $ taggedListener [0 ];
96+ $ taggedListenerDef = $ container ->getDefinition ($ taggedListener [0 ]);
97+ if ($ taggedListenerDef ->isAbstract ()) {
98+ throw new InvalidArgumentException (sprintf ('The abstract service "%s" cannot be tagged as a doctrine event listener. ' , $ id ));
99+ }
114100
115- foreach ($ services as $ id => $ instances ) {
116- foreach ($ instances as $ instance ) {
117- if ($ isListener ) {
118- if (!isset ($ instance ['event ' ])) {
119- throw new \InvalidArgumentException (sprintf ('Doctrine event listener "%s" must specify the "event" attribute. ' , $ id ));
120- }
121- $ instance ['event ' ] = array ($ instance ['event ' ]);
122-
123- if (isset ($ instance ['lazy ' ]) && $ instance ['lazy ' ]) {
124- $ this ->container ->getDefinition ($ id )->setPublic (true );
125- }
101+ $ tag = $ taggedListener [1 ];
102+ if (!isset ($ tag ['event ' ])) {
103+ throw new InvalidArgumentException (sprintf ('Doctrine event listener "%s" must specify the "event" attribute. ' , $ id ));
104+ }
105+
106+ $ connections = isset ($ tag ['connection ' ]) ? array ($ tag ['connection ' ]) : array_keys ($ this ->connections );
107+ foreach ($ connections as $ con ) {
108+ if (!isset ($ this ->connections [$ con ])) {
109+ throw new RuntimeException (sprintf ('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s ' , $ con , $ id , implode (', ' , array_keys ($ this ->connections ))));
126110 }
127111
128- $ cons = isset ($ instance ['connection ' ]) ? array ($ instance ['connection ' ]) : $ allCons ;
129- foreach ($ cons as $ con ) {
130- if (!isset ($ grouped [$ con ])) {
131- throw new \RuntimeException (sprintf ('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s ' , $ con , $ id , implode (', ' , array_keys ($ this ->connections ))));
132- }
133-
134- if ($ isListener && isset ($ grouped [$ con ][$ id ])) {
135- $ grouped [$ con ][$ id ]['event ' ] = array_merge ($ grouped [$ con ][$ id ]['event ' ], $ instance ['event ' ]);
136- } else {
137- $ grouped [$ con ][$ id ] = $ instance ;
138- }
112+ if ($ lazy = isset ($ tag ['lazy ' ]) && $ tag ['lazy ' ]) {
113+ $ taggedListenerDef ->setPublic (true );
139114 }
115+
116+ // we add one call per event per service so we have the correct order
117+ $ this ->getEventManagerDef ($ container , $ con )->addMethodCall ('addEventListener ' , array (
118+ $ tag ['event ' ],
119+ $ lazy ? $ id : new Reference ($ id ),
120+ ));
140121 }
141122 }
123+ }
142124
143- return $ grouped ;
125+ private function getEventManagerDef (ContainerBuilder $ container , $ name )
126+ {
127+ if (!isset ($ this ->eventManagers [$ name ])) {
128+ $ this ->eventManagers [$ name ] = $ container ->getDefinition (sprintf ($ this ->managerTemplate , $ name ));
129+ }
130+
131+ return $ this ->eventManagers [$ name ];
144132 }
145133
146- private function getEventManager ($ name )
134+ /**
135+ * Finds and orders all service tags with the given name by their priority.
136+ *
137+ * The order of additions must be respected for services having the same priority,
138+ * and knowing that the \SplPriorityQueue class does not respect the FIFO method,
139+ * we should not use this class.
140+ *
141+ * @see https://bugs.php.net/bug.php?id=53710
142+ * @see https://bugs.php.net/bug.php?id=60926
143+ *
144+ * @param string $tagName
145+ * @param ContainerBuilder $container
146+ *
147+ * @return array
148+ */
149+ private function findAndSortTags ($ tagName , ContainerBuilder $ container )
147150 {
148- if (null === $ this ->eventManagers ) {
149- $ this ->eventManagers = array ();
150- foreach ($ this ->connections as $ n => $ id ) {
151- $ this ->eventManagers [$ n ] = $ this ->container ->getDefinition (sprintf ($ this ->managerTemplate , $ n ));
151+ $ sortedTags = array ();
152+
153+ foreach ($ container ->findTaggedServiceIds ($ tagName ) as $ serviceId => $ tags ) {
154+ foreach ($ tags as $ attributes ) {
155+ $ priority = isset ($ attributes ['priority ' ]) ? $ attributes ['priority ' ] : 0 ;
156+ $ sortedTags [$ priority ][] = array ($ serviceId , $ attributes );
152157 }
153158 }
154159
155- return $ this ->eventManagers [$ name ];
160+ if ($ sortedTags ) {
161+ krsort ($ sortedTags );
162+ $ sortedTags = call_user_func_array ('array_merge ' , $ sortedTags );
163+ }
164+
165+ return $ sortedTags ;
156166 }
157167}
0 commit comments