Laravel 模型自动更新

在laravel项目中,当模型某些字段更新时,模型中与之关联的另外一些字段也需要通过一些固定的计算方式来进行更新。
对于上面的应用场景,当然简单的做法可以通过手动更新模型关联的字段,更好的办法也许可以通过一个trait来实现这个过程的自动化。

下面以一个实际应用场景为例:

存在一个模型,其数据结构如下:

{
  "id": 1,
  "knit_factory_id": 1,
  "knit_factory_name": "dignissimos",
  "knit_machine_id": 9,
  "knit_machine_name": "perspiciatis",
  "year": 2017,
  "month": 3,
  "idle_info": {
  "day1": 2,
  "day2": 1,
  "day3": 2,
  "day4": 1,
  "day5": 1,
  "day6": 2,
  "day7": 1,
  "day8": 2,
  "day9": 2,
  "day10": 1,
  "day11": 1,
  "day12": 2,
  "day13": 1,
  "day14": 2,
  "day15": 2,
  "day16": 1,
  "day17": 2,
  "day18": 2,
  "day19": 2,
  "day20": 2,
  "day21": 2,
  "day22": 2,
  "day23": 2,
  "day24": 2,
  "day25": 2,
  "day26": 2,
  "day27": 1,
  "day28": 2,
  "day29": 1,
  "day30": 1,
  "day31": 1
  },
  "idle_days": 19,
  "continue_idle_days": 10,
  "idle_capacity": 35081592.433402
}

其对应model定义:

    <?php
    
    namespace App\Model;
    
    use App\Helpers\KnitIdleCapacityHelper;
    use App\Traits\InsertCreator;
    use App\Traits\InsertKnitIdleCapacityInfo;
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Database\Eloquent\SoftDeletes;
    
    /**
     * Class KnitIdleCapacity
     * @package App\Model
     */
    class KnitIdleCapacity extends Model
    {
        use InsertCreator, SoftDeletes, InsertKnitIdleCapacityInfo;
    
        /**
         * @var array
         */
        protected $fillable = [
            'knit_factory_id', 'knit_factory_name', 'knit_machine_id',
            'knit_machine_name', 'year', 'month', 'idle_info',
            'idle_days', 'continue_idle_days', 'idle_capacity'
        ];
    
        /**
         * @var array
         */
        protected $hidden = ['creator_id', 'created_at', 'updated_at', 'deleted_at'];
    
        /**
         * @var array
         */
        protected $casts = ['idle_info' => 'array'];
    
        /**
         *  
         */
        const STATUS_WORK = 1;
    
        /**
         * 
         */
        const STATUS_IDLE = 2;
    
        /**
         * @var array
         */
        public static $status = [self::STATUS_WORK, self::STATUS_IDLE];
    
        /**
         * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
         */
        public function knitFactory()
        {
            return $this->belongsTo(CompanyAccount::class, 'knit_factory_id');
        }
    
        /**
         * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
         */
        public function knitMachine()
        {
            return $this->belongsTo(KnitMachine::class, 'knit_machine_id');
        }
    
        /**
         * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
         */
        public function creator()
        {
            return $this->belongsTo(Account::class, 'creator_id', 'FID');
        }
    
        /**
         * @param $query
         * @return mixed
         */
        public function scopeWithRelationships($query)
        {
            return $query->with('knitFactory', 'knitMachine', 'creator');
        }
    
        /**
         * 
         * @return KnitIdleCapacityHelper
         */
        public function idleCapacity()
        {
            return new KnitIdleCapacityHelper($this, $this->knitMachine, $this->idle_info);
        }
    }

当模型中idle_info创建或更新时,自动计算出对应的idle_days、continue_idle_days和idle_capacity。
由于idle_info存储结构是json类型,因此专门写了一个helper来进行计算。
代码如下:

    <?php
    
    namespace App\Helpers;
    
    use App\Model\KnitIdleCapacity;
    use App\Model\KnitMachine;
    use League\Flysystem\Exception;
    
    /**
     * 
     * Class KnitIdleCapacityHelper
     * @package App\Helpers
     */
    class KnitIdleCapacityHelper
    {
        /**
         * @var KnitIdleCapacity
         */
        protected $knitIdleCapacity;
    
        /**
         * @var KnitMachine
         */
        protected $knitMachine;
    
        /**
         * @var array
         */
        protected $idleInfo = [];
    
        /**
         * 
         * @var null
         */
        protected $idleDays = null;
    
        /**
         * 
         * @var null
         */
        protected $continueIdleDays = null;
    
        /**
         * 
         * @var null
         */
        protected $idleCapacity = null;
    
        /**
         * KnitIdleCapacityHelper constructor.
         * @param array $idleInfo
         */
        public function __construct(KnitIdleCapacity $knitIdleCapacity, KnitMachine $knitMachine, array $idleInfo)
        {
            $this->knitIdleCapacity = $knitIdleCapacity;
            $this->knitMachine = $knitMachine;
            $this->idleInfo = $idleInfo;
        }
    
        /**
         * 
         * @param $key
         * @return mixed
         */
        public function get($key)
        {
            return array_get($this->idleInfo, $key);
        }
    
        /**
         * 
         * @param $key
         * @param $value
         * @throws Exception
         */
        public function set($key, $value)
        {
            if (!in_array($value, KnitIdleCapacity::$status)) {
                throw new Exception("{$value}");
            }
            $this->idleInfo[$key] = $value;
    
            $this->persist();
        }
    
        /**
         * 
         * @param $key
         * @return bool
         */
        public function has($key)
        {
            return array_key_exists($key, $this->idleInfo);
        }
    
        /**
         * 
         * @return array
         */
        public function all()
        {
            return $this->idleInfo;
        }
    
        /**
         * 
         * @param array $attributes
         * @return bool
         */
        public function merge(array $attributes)
        {
            $this->idleInfo = array_merge(
                $this->idleInfo,
                array_only($attributes, array_keys($this->idleInfo))
            );
    
            return $this->persist();
        }
    
        /**
         * 
         * @return bool
         */
        public function persist()
        {
            $this->knitIdleCapacity->idle_info = $this->idleInfo;
    
            return $this->knitIdleCapacity->save();
        }
    
        /**
         * 
         * @return null
         */
        public function idleDays()
        {
            if ($this->idleDays > 0) return $this->idleDays;
            $arr = array_count_values($this->idleInfo);
            $this->idleDays = $arr[KnitIdleCapacity::STATUS_IDLE];
            
            return $this->idleDays;
        }
    
        /**
         * 
         * @return mixed|null
         */
        public function continueIdleDays()
        {
            if ($this->continueIdleDays > 0) return $this->continueIdleDays;
            $idle = true;
            $result = [];
            $temp = 0;
            foreach ($this->idleInfo as $capacity) {
                if ($capacity === KnitIdleCapacity::STATUS_WORK) {
                    $idle = false;
                    if ($temp > 0) array_push($result, $temp);
                    $temp = 0;
                }
                if ($idle) $temp++;
                $idle = true;
            }
            rsort($result);
            $this->continueIdleDays = $result[0];
    
            return $this->continueIdleDays;
        }
    
        /**
         * 
         * @return mixed|null
         */
        public function idleCapacity()
        {
            if ($this->idleCapacity) return $this->idleCapacity;
            $dailyOutput = $this->knitMachine->daily_output;
            $this->idleCapacity = $dailyOutput * $this->idleDays();
    
            return $this->idleCapacity;
        }
    
    
        /**
         * 
         * @return bool
         */
        public function updateIdleInfo()
        {
            $this->knitIdleCapacity->idle_days = $this->idleDays();
            $this->knitIdleCapacity->continue_idle_days = $this->continueIdleDays();
            $this->knitIdleCapacity->idle_capacity = $this->idleCapacity();
    
            return $this->knitIdleCapacity->save();
        }
    
        /**
         * 
         * @param $key
         * @return mixed
         * @throws Exception
         */
        public function __get($key)
        {
            if ($this->has($key)) {
                return $this->get($key);
            }
    
            throw new Exception("{$key}");
        }
    }

有了上面的helper以后,我们就可以写一个trait来讲这个过程自动化,代码如下:

    <?php
    
    
    namespace App\Traits;
    
    
    trait InsertKnitIdleCapacityInfo
    {
        public static function bootInsertKnitIdleCapacityInfo()
        {
            foreach (static::getModelEvents() as $event) {
                static::$event(function ($model) use ($event) {
                    $idleCapacity = $model->idleCapacity();
                    $model->idle_days = $idleCapacity->idleDays();
                    $model->continue_idle_days = $idleCapacity->continueIdleDays();
                    $model->idle_capacity = $idleCapacity->idleCapacity();
    
                    $model->save();
                });
            }
        }
    
        /**
         * 
         * @return array
         */
        protected static function getModelEvents()
        {
            if (isset(static::$recordEvents)) {
                return static::$recodrdEvents;
            }
    
            return ['created', 'updated'];
        }
    
    }

最后,使用postman使用如下数据来对上面的功能进行测试:

    {
      "data": [
        {
          "knit_factory_id": 1,
          "knit_factory_name": "sed",
          "knit_machine_id": 1,
          "knit_machine_name": "minima",
          "year": 2017,
          "month": 3,
          "idle_info": {
            "day1": 2,
            "day2": 2,
            "day3": 2,
            "day4": 2,
            "day5": 2,
            "day6": 2,
            "day7": 2,
            "day8": 2,
            "day9": 2,
            "day10": 2,
            "day11": 1,
            "day12": 1,
            "day13": 1,
            "day14": 1,
            "day15": 1,
            "day16": 1,
            "day17": 1,
            "day18": 1,
            "day19": 1,
            "day20": 1,
            "day21": 1,
            "day22": 1,
            "day23": 2,
            "day24": 1,
            "day25": 2,
            "day26": 1,
            "day27": 1,
            "day28": 1,
            "day29": 1,
            "day30": 1,
            "day31": 1
          }
        }
      ]
    }

控制器中对提交过来的数据进行保存,代码如下:

    public function store(Request $request)
    {
        $data = $request->get('data', []);
        foreach ($data as $item) {
            $knitIdleCapacity = KnitIdleCapacity::create($item);
        }
    
        return response()->json([
            'message' => 'created!',
        ], 201);
    }

不幸的是,上面的功能并没有顺利通过,程序跑完测试以后直接就崩溃了。。神奇的是,数据确实保存到了数据库中。相关截图如下:

postman测试截图:

\1.PNG
\1.PNG

数据库截图:

\2.PNG
\2.PNG

问题分析:出现上面的问题,其实是在trait中尝试当模型created updated的时候,更新相关的字段,而这会导致模型陷入无限触发created updated的死循环中。。。

解决方案1:
在更新相关字段的时候,临时关闭模型事件:

<?php


namespace App\Traits;


trait InsertKnitIdleCapacityInfo
{
    public static function bootInsertKnitIdleCapacityInfo()
    {
        foreach (static::getModelEvents() as $event) {
            static::$event(function ($model) use ($event) {
                $idleCapacity = $model->idleCapacity();
                $model->idle_days = $idleCapacity->idleDays();
                $model->continue_idle_days = $idleCapacity->continueIdleDays();
                $model->idle_capacity = $idleCapacity->idleCapacity();
                $dispatcher = $model->getEventDispatcher();
                $model->unsetEventDispatcher();
                $model->save();
                $model->setEventDispatcher($dispatcher);
            });
        }
    }

    /**
     * 获取要响应的模型事件
     * @return array
     */
    protected static function getModelEvents()
    {
        if (isset(static::$recordEvents)) {
            return static::$recodrdEvents;
        }

        return ['created', 'updated'];
    }

}


解决方案2:
监听creating updating事件:

<?php


namespace App\Traits;


trait InsertKnitIdleCapacityInfo
{
    public static function bootInsertKnitIdleCapacityInfo()
    {
        foreach (static::getModelEvents() as $event) {
            static::$event(function ($model) use ($event) {
                $idleCapacity = $model->idleCapacity();
                $model->idle_days = $idleCapacity->idleDays();
                $model->continue_idle_days = $idleCapacity->continueIdleDays();
                $model->idle_capacity = $idleCapacity->idleCapacity();
            });
        }
    }

    /**
     * 获取要响应的模型事件
     * @return array
     */
    protected static function getModelEvents()
    {
        if (isset(static::$recordEvents)) {
            return static::$recodrdEvents;
        }

        return ['creating', 'updating'];
    }

}
                

最后感谢stackoverflow @Ross Wilson的帮助


参考网站:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容