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条)

报告相同问题?

悬赏问题

  • ¥15 关于#hadoop#的问题
  • ¥15 (标签-Python|关键词-socket)
  • ¥15 keil里为什么main.c定义的函数在it.c调用不了
  • ¥50 切换TabTip键盘的输入法
  • ¥15 可否在不同线程中调用封装数据库操作的类
  • ¥15 微带串馈天线阵列每个阵元宽度计算
  • ¥15 keil的map文件中Image component sizes各项意思
  • ¥20 求个正点原子stm32f407开发版的贪吃蛇游戏
  • ¥15 划分vlan后,链路不通了?
  • ¥20 求各位懂行的人,注册表能不能看到usb使用得具体信息,干了什么,传输了什么数据