Problems with multiple observers in Laravel

I am stuck in a strange problem. It seems that in Laravel you are not allowed to observe how several observers of the model listen to the same event. In my case:

Parent model

class MyParent extends Eloquent { private static function boot() { parent::boot(); $called_class = get_called_class(); $called_class::creating(function($model) { doSomethingInParent(); return true; } } } 

Baby model

 class MyChild extends myParent { private static function boot() { parent::boot(); MyChild::creating(function($model) { doSomethingInChild(); return true; } } } 

In the above example, if I do:

$ instance = MyChild :: create ();

... the line doSomethingInChild () does not work. doSomethingInParent () does.

If I move parent :: boot () inside the child after MyChild :: create (), it works. (I have not confirmed if doSomethingInParent () is triggered, but I assume it is not)

Can Laravel log multiple events in Model :: create ()?

+5
source share
1 answer

It's complicated. Short version: Remove return values ​​from your handlers and both events will fire. The following is a long version.

First, I'm going to assume that you wanted to type MyParent (not MyParent ), that you meant that your boot methods are protected , not private , and that you included final ) in your calls to the create method. Otherwise, your code will not run. :)

However, the problem you are describing is real. The reason for this is a certain Eloquent events, which are considered "stops" of events. That is, for some events, if any nonzero value is returned from the event handlers (whether it is a closure or a PHP callback), the event ceases to propagate. You can see it in the manager

 #File: vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php public function fire($event, $payload = array(), $halt = false) { } 

See the third parameter $halt ? Later, when the dispatcher calls event listeners

 #File: vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php foreach ($this->getListeners($event) as $listener) { $response = call_user_func_array($listener, $payload); // If a response is returned from the listener and event halting is enabled // we will just return this response, and not call the rest of the event // listeners. Otherwise we will add the response on the response list. if ( ! is_null($response) && $halt) { array_pop($this->firing); return $response; } //... 

If halt true and the callback returned something that is not null ( true , false , sclaer, array , a object )), fire short circuits using return $response , and the events stop propagating. This is above and above this standard "return false to stop the propagation of events." Some events stop.

So which model events stop? If you look at the definition of fireModelEvent in the base class of eloquent models (Laravel aliases it's like Eloquent )

 #File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php protected function fireModelEvent($event, $halt = true) { //... } 

You can pause the display of model events by default . So, if we look at the model for firing events, we will see what to do to stop

 #File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php $this->fireModelEvent('deleting') $this->fireModelEvent('saving') $this->fireModelEvent('updating') $this->fireModelEvent('creating') 

and events that don't stop

 #File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php $this->fireModelEvent('booting', false); $this->fireModelEvent('booted', false); $this->fireModelEvent('deleted', false); $this->fireModelEvent('saved', false); $this->fireModelEvent('updated', false); $this->fireModelEvent('created', false); 

As you can see, creating is a stop event, so returning any value, even true , stopped the event, and your second listener did not fire. Stop events are usually used when the Model class wants to do something with the return value from the event. In particular, for creating

 #File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php protected function performInsert(Builder $query) { if ($this->fireModelEvent('creating') === false) return false; //... 

if you return false (not null) from your callback, Laravel will actually skip the execution of INSERT . Again, this is different from the standard propagation of a stop event, returning false. In the case of these four model events, returning false also cancels the action they are listening to.

Remove the return values ​​(or return null ) and you will be well off.

+15
source

Source: https://habr.com/ru/post/1204208/


All Articles