phpjsonlaravel

laravel crashed when I use trait to auto update some values


I have an eloquent model, it's like this:

{
"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
}

Then, it's 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);
    }
}

When ever I create or update the idle_info, I want to update the related fields(idle_days, continue_idle_days, idle_capacity). Due to idle_info is json, so I write a helper function to interact with it, here it is:

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

So I write a trait to auto update the related fields, here it is:

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

}

At last, in the controller, I run a postman test with below data.

{
  "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
      }
    }
  ]
}

the controller method:

public function store(Request $request)
{
    $data = $request->get('data', []);
    foreach ($data as $item) {
        $knitIdleCapacity = KnitIdleCapacity::create($item);
    }

    return response()->json([
        'message' => 'created!',
    ], 201);
}

Unfortunately, the program failed, but the strange thing is the above data did save in database. here is some screenshots: postman screenshot database screenshot

thank you


Solution

  • @Ross Wilson Thank you!

    Sorry, I made a mistake. In the InsertKnitIdleCapacityInfo trait, I try to catch some KnitIdleCapacity model events, then update the KnitIdleCapacity. Obviously, this will cause a infine loop.

    Here is the solution, in the trait, I turn off the model event before saving the model, after saving, turn on model events.

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

    another solution: Or just listening the creating or updating model events, here is the code:

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