听模型事件时无限循环

时间:2016-10-29 22:00:17

标签: php laravel events eloquent

我有一个属性的Laravel应用程序,让我在我的代码中的某处说:

$property = new Property();
$property->city = "New York";
 ...
$property->save();

然后我有一个侦听特定事件的事件监听器:

$events->listen(
    'eloquent.saved: Properties\\Models\\Property',
    'Google\Listeners\SetGeoLocationInfo@fire'
);

最后在SetGeoLocationInfo.php我有

public function fire($event)
 {
    $property = $event;
    ...
    //get GPS data from Google Maps

    $property->latitude = $googleMapsObject->latitude;
    $property->longitude = $googleMapsObject->longitude;
    $property->save();
 }

当我将模型保存到无限递归时,因为在处理程序中引发了save()

如何更改我的代码以使其在保存后只填充一次位置数据并避免递归?

我无法使用flushEventListeners(),因为在这种情况下,其他听众会停止工作(例如附加财产照片)。

5 个答案:

答案 0 :(得分:3)

我打了同样的话。在Laravel 5.6中,您可以简单地覆盖模型中的finishSave()函数:

protected function finishSave(array $options)
{
    // this condition allow to control whenever fire the vent or not
    if (!isset($options['nosavedevent']) or empty($options['nosavedevent'])) {
        $this->fireModelEvent('saved', false);
    }

    if ($this->isDirty() && ($options['touch'] ?? true)) {
        $this->touchOwners();
    }

    $this->syncOriginal();
}

然后你可以像这样使用它:

public function fire($event)
{
    $property = $event;
    ...
    //get GPS data from Google Maps

   $property->latitude = $googleMapsObject->latitude;
   $property->longitude = $googleMapsObject->longitude;
   $property->save(['nosavedevent' => true]);
}

答案 1 :(得分:1)

在这种情况下,最好使用saving方法。但请注意,在saving期间,您不应再使用save方法,因此您的fire方法应如下所示:

public function fire($event)
{
    $property = $event;
    ...
    //get GPS data from Google Maps

    $property->latitude = $googleMapsObject->latitude;
    $property->longitude = $googleMapsObject->longitude;
}

其他解决方案是添加条件以设置和保存GPS位置,只有它尚未设置:

if (empty($property->latitude) || empty($property->longitude)) {
    $property->latitude = $googleMapsObject->latitude;
    $property->longitude = $googleMapsObject->longitude;
    $property->save();
}

答案 2 :(得分:0)

您的属性保存方法(您必须为其定义属性常量):

=

称之为:

public function save($mode = Property::SAVE_DEFAULT)
{
    switch ($mode) {
        case Property::SAVE_FOO:
            // something for foo
        break;
        case Property::SAVE_BAR:
            // something for bar
        break;
        default:
            parent::save();
        break;
    }
}

public function fire($event)
 {
    $property = $event;
    ...
    //get GPS data from Google Maps

    $property->latitude = $googleMapsObject->latitude;
    $property->longitude = $googleMapsObject->longitude;
    $property->save(Property::SAVE_FOO);
 }

有什么好处? 所有条件都在一个地方(以保存方式)。

答案 3 :(得分:0)

您可以使用forget()取消设置事件监听器。

Event::listen('a', function(){
    Event::forget('a');

    echo 'update a ';
    event("b");
});

Event::listen('b', function(){
    Event::forget('b');

    echo 'update b ';
    event("a");
});

event("a"); // update a update b

模型事件键名为“eloquent.{$event}: {$name}”,例如“eloquent.updated: Foo

答案 4 :(得分:0)

在您使用fire方法的情况下,可以在应用新属性并保存之前让它调用$property->syncOriginal()

模型类具有“脏”属性与“原始”属性的概念,以了解哪些值已被推送到数据库,哪些值仍被计划用于即将进行的保存。通常,Laravel不会在观察者触发后将它们同步在一起。而且严格来说,让您的监听器知道被触发的上下文是一种蚂蚁模式。是因为您是通过saved操作触发它的,因此可以确信数据已经到达数据库。但是既然如此,问题就出在模型还没有意识到。后续的任何更新都将使观察者误以为使您到达那里的原始价值是全新的。因此,通过在应用任何其他更改之前自己显式调用syncOriginal()方法,您应该能够避免递归。