在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测试截图:
数据库截图:
问题分析:出现上面的问题,其实是在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的帮助
参考网站: