2016-09-19 73 views
0

我目前正在使用Model事件在数据库中执行数据验证/同步的Laravel应用程序。如何知道laravel中给定模型上的关系是否已经更新?

我有一个包含重要数据的表。此表在不同型号的更新中更新。我使用模型事件来处理它。它适用于模型属性示例:

<?php 
Product::saved(function (Product $p) { 
    $dirty = collect($p->getDirty()); 
    if ($dirty->has('ugr') || $dirty->has('ra')) { 
     //Do some stuff here 
    } 
}); 

使用这种逻辑,我可以限制我在特定模型属性更新上执行“stuff”操作。

如何在Product关系上进行这种检查?

我有一个ManyToMany关系,可通过applications方法访问Product,我怎么能知道链接的应用程序列表是否自模型加载后已更改?

感谢您的帮助!

回答

2

我还没有找到一种方法来直接与Laravel做到这一点。我已经使用应用程序事件和关系继承构建了一个解决方案。

我添加了一个名为trait其中App\Database\Eloquent\FollowUpdatedRelations有目标,以通知相关更新:

<?php 

namespace App\Database\Eloquent; 

use Illuminate\Database\Eloquent\Relations\BelongsToMany; 
use App\Library\Decorator; 
use App\Events\RelationUpdated; 

trait FollowUpdatedRelations 
{ 
    /** 
    * The default error bag. 
    * 
    * @var string 
    */ 
    protected $updatedRelations = []; 

    /** 
    * Check if the belongs to many relation has been updated 
    * @param BelongsToMany $relation 
    * @param array   $syncResult Result of the `sync` method call 
    * @return boolean 
    */ 
    protected function hasBeenUpdated(BelongsToMany $relation, array $syncResult) 
    { 
     if (isset($syncResult['attached']) && count($syncResult['attached']) > 0) { 
      $this->updatedRelations[$relation->getRelationName()] = true; 
      event(new RelationUpdated($relation)); 
     } elseif (isset($syncResult['detached']) && count($syncResult['detached']) > 0) { 
      $this->updatedRelations[$relation->getRelationName()] = true; 
      event(new RelationUpdated($relation)); 
     } 
    } 

    /** 
    * Decorate a BelongsToMany to listen to relation update 
    * @param BelongsToMany $relation 
    * @return Decorator 
    */ 
    protected function decorateBelongsToMany(BelongsToMany $relation) 
    { 
     $decorator = new Decorator($relation); 
     $decorator->decorate('sync', function ($decorated, $arguments) { 
      $updates = call_user_func_array([$decorated, 'sync'], $arguments); 
      $this->hasBeenUpdated($decorated, $updates); 
      return $updates; 
     }); 

     return $decorator; 
    } 

    /** 
    * Retrieve the list of dirty relations 
    * @return array 
    */ 
    public function getDirtyRelations() 
    { 
     return $this->updatedRelations; 
    } 
} 

我用这种特质在我需要遵循相关更新模型和我已经更新的关系定义:

<?php 

... 

class Product extends Model 
{ 
    use FollowUpdatedRelations; 

    .... 


    /** 
    * Defines relationship with App\Applications model 
    * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 
    */ 
    public function applications() 
    { 
     return $this->decorateBelongsToMany(
      $this->belongsToMany('App\Application', 'product_application') 
     ); 
    } 
} 

App\Library\Decorator类包装一个对象,并添加重写方法的能力:

<?php 

namespace App\Library; 

use Closure; 

class Decorator 
{ 
    /** 
    * Decorated instance 
    * @var mixed 
    */ 
    private $decorated; 

    private $methods = []; 

    /** 
    * Decorate given instance 
    * @param mixed $toDecorate 
    */ 
    public function __construct($toDecorate) 
    { 
     $this->decorated = $toDecorate; 
    } 

    /** 
    * Decorate a method 
    * @param string $name 
    * @param Closure $callback Method to run instead of decorated one 
    */ 
    public function decorate($name, Closure $callback) 
    { 
     $this->methods[$name] = $callback; 
     return $this; 
    } 

    /** 
    * Call a method on decorated instance 
    * @param string $name 
    * @param array $arguments 
    * @return mixed 
    */ 
    public function __call($name, $arguments) 
    { 
     if (isset($this->methods[$name])) { 
      return call_user_func_array($this->methods[$name], [$this->decorated, $arguments]); 
     } 

     return call_user_func_array([$this->decorated, $name], $arguments); 
    } 
} 

有了这个对象,我可以在BelongsToMany Laravel关系上创建我自定义的sync方法。我使用sync方法来跟踪更新,因为它会返回数据透视表中已连接,已分离和已更新模型的列表。

我只需要计算是否有连接或分离的模型,并分派相应的事件。我的活动是App\Events\RelationUpdated并包含更新后的关系属性。

然后,我可以在EventServiceProvider像添加事件侦听器:

<?php 

namespace App\Providers; 

use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; 
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; 
use App\Events\RelationUpdated; 
use App\Product; 

class EventServiceProvider extends ServiceProvider 
{ 
    /** 
    * Register any other events for your application. 
    * 
    * @param \Illuminate\Contracts\Events\Dispatcher $events 
    * @return void 
    */ 
    public function boot(DispatcherContract $events) 
    { 
     parent::boot($events); 

     ... 

     //When a listened relation is updated, we perform a Model save 
     $events->listen(RelationUpdated::class, function ($event) { 
      //Here I do my stuff 
     }); 
    } 
} 

我可以把所有时的关系被更新时必须执行的东西。看起来有点复杂,但我认为依靠类似的东西比在每个模型构建中添加逻辑要轻。

希望得到这个帮助!

1

没有现成的方法来做到这一点。实际上,无论何时更新子对象,都要通知父对象。换句话说,如果任何相关的应用程序更新,您希望触发产品上保存的处理程序。

如果我的理解是正确的,那么您需要通过产品模型注册处理程序,因为如果您只想通知其相关的子模型更新的相关模型,那么您可以简单地在子模型中使用protected $touches属性因此,如果任何子模型发生更改,父模型将始终更新。

因为您只想跟踪更改,所以您需要通过父/主模型本身在相关模型上注册已保存的处理程序(加载时急切地)加载相关模型。如果我仍然在正确的轨道上,那么,你可以尝试这样的事情:

$product = Product::with('applications')->findOrFail($id); 

// Check if there are any Application loaded (eagerly) with Product 
if($product->relationLoaded('applications')) { 

    // Loop through each Application instance loaded with Product 
    $product->applications->each(function($application) use($product) { 

     // Register the saved event handler on each Application instance 
     $application->saved(function($app) use($product) { 

      // If nothing has been changed do nothing 
      if(!count($dirty = $app->getDirty())) return true; 

      // Check if prop_1 or prop_2 has been changed in $app 
      $props = ['prop_1', 'prop_2']; // You may add more props 

      $keys = array_intersect_key($props, array_keys($dirty)); 

      if(count($keys)) { 
       // Do some stuff here 
       // You can do something with $product as well 
       // If you have saved handler on Product then just try 
       // $product->touch(); // it'll trigger that handler on Product 
      } 
     }); 
    }); 
} 

希望,你想要像我上面描述的东西。顺便说一句,这只是一个想法,你可以使用单独的类/方法在子模型上注册事件处理程序,以保持代码清洁。

+0

您的解决方案似乎有效,但对我来说有点复杂。注册每个加载的应用程序,与产品相关的,已保存的处理程序...此外,我使用数据透视表,因此相关模型不是真正的模型,它是一个透视图... 昨天晚上我使用了一个解决方案应用程序事件和事件监听器。我今天将提交它作为答案。 – shulard

+0

当然,请提交您的解决方案,这对未来的访问者和我也有帮助,以便了解它。 –

+0

我已经添加了我的解决方案,您可以查看一下吗? – shulard

相关问题