dongyin2390 2016-09-19 15:10
浏览 24
已采纳

如何知道laravel中给定模型上的关系是否已更新?

I'm currently working on a Laravel application which is using Model events to perform data validation / synchronization in the database.

I've a table which contains important data. This table is updated on different Model updates. I use model events to handle that. It works on Model properties example :

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

With that kind of logic I can restrict my "stuff" execution on specific model property update.

How can I make that kind of check on Product relations ?

I've a ManyToMany relationships accessible through the applications methods on Product, how can I know if the linked application list has changed since model loading ?

Thanks for your help !

  • 写回答

2条回答 默认 最新

  • douzong2206 2016-09-20 16:35
    关注

    I have'nt found a way to do this with Laravel directly. I've built a solution using Application events and Relation inheritance.

    I've added a trait named App\Database\Eloquent\FollowUpdatedRelations which have the goal to notify relation updates :

    <?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;
        }
    }
    

    I've used this trait in the Model on which I need to follow relation updates and I've updated the relation definition :

    <?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')
            );
        }
    }
    

    The App\Library\Decorator class wrap an object and add the ability to override methods :

    <?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);
        }
    }
    

    With that object I can create my custom sync method on the BelongsToMany Laravel relation. I use the sync method to follow updates because it returns the list of attached, detached and updated model in the pivot table.

    I just need to count if there are attached or detached models and dispatched the corresponding event. My event is App\Events\RelationUpdated and contains the updated relation as property.

    Then I can add an event listener in the EventServiceProvider like that :

    <?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
            });
        }
    }
    

    I can put all the stuff that must be executed when a relation is updated. Seems a bit complicated but I think that relying on something like that is lighter than adding logic on each model construction.

    Hope this help !

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?
  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?
  • ¥15 加热介质是液体,换热器壳侧导热系数和总的导热系数怎么算