【问题标题】:Yii2 Activerecord not saved before redirect and shown in "view"Yii2 Activerecord 在重定向之前未保存并显示在“视图”中
【发布时间】:2021-11-02 17:57:04
【问题描述】:

Yii2 框架。当我在另一个 ActiveRecord 的 AFTER_INSERT_EVENT 中保存多个 ActiveRecord 时,数据库中的值更新速度不够快,因此重定向查看数据时会显示旧值。

更具体地说:使用 PHP 7.2.9 的标准 XAMPP 环境。我制作了一个特征,可以轻松地在模型中拥有具有历史记录的额外属性(现有属性或新属性)。该特征用于 ActiveRecord。

注意函数TL_save中的sleep(5)。这解决了问题,但它不是正确的解决方案。 如何确保在再次读取之前更新所有内容? 我想避免在行上使用锁,因为这需要更改表才能使用它。有办法解决吗?交易 - 我已经尝试过了,但可能不正确,因为它没有效果。重新加载视图页面也解决了这个问题,但同样:不是很优雅:-)

另外:我应该在 GitHub 上分享这段代码吗?我以前没有这样做过,不太确定它是否真的对其他人有任何价值。

trait TimelineTrait
{   
    private $timelineConfig;

    public function timelineInit($config)
    {
        $std = [
            'attributes' => [],  // required
            '_oldAttributes'=>[],
            'datetime'=> date('Y-m-d H:i:s'),
            'validationRule'=>'safe',
            'table'=>$this->tableName(),
            'onlyDirty'=>true, // using !=, not !==
            'events'=>[
                self::EVENT_AFTER_INSERT=>[$this, 'TL_EventAfterInsert'],
                self::EVENT_AFTER_UPDATE=>[$this, 'TL_EventAfterUpdate'],
                self::EVENT_AFTER_FIND=>[$this, 'TL_EventAfterFind'],
                self::EVENT_AFTER_DELETE=>[$this, 'TL_EventAfterDelete'],
            ],
            'TimelineClass'=>Timeline::class,
            /*
                Must have the following attributes
                id integer primary key auto increment not null,
                table varchar(64) not null,
                table_id integer not null,
                attribute varchar(64) not null,
                datetime datetime not null
                value text (can be null)
            */
        ];
        $this->timelineConfig = array_replace_recursive($std, $config);
        foreach($this->timelineConfig["events"]??[] as $trigger=>$handler)
            $this->on($trigger, $handler);
    }

    public function __get($attr)
    {        
        $cfg = &$this->timelineConfig;
        if (in_array($attr, array_keys($cfg["attributes"])))
            return $cfg["attributes"][$attr];
        else
            return parent::__get($attr);
    }

    public function __set($attr, $val)
    {        
        $cfg = &$this->timelineConfig;        
        if (in_array($attr, array_keys($cfg["attributes"]))) {
            $cfg["attributes"][$attr] = $val;            
        } else
            parent::__set($attr, $val);
    }

    public function attributes()
    {
        return array_merge(parent::attributes(), $this->timelineConfig["attributes"]);
    }

    public function rules()
    {
        $temp  = parent::rules();
        $temp[] = [array_keys($this->timelineConfig["attributes"]), $this->timelineConfig["validationRule"]];
        return $temp;
    }

    public function TL_EventAfterInsert($event) 
    {
        $this->TL_save($event, true);
    }

    public  function TL_EventAfterUpdate($event)
    {
        $this->TL_save($event, false);
    }

    private function TL_save($event, $insert) 
    {
        $cfg = &$this->timelineConfig;    
        if ($cfg["onlyDirty"]) 
            $cfg["_oldAttributes"] = $this->TL_attributesOnTime();        
       
        foreach($cfg["attributes"] as $attr=>$val) {
            $a = [
                'table'=>$cfg["table"], 
                'table_id'=>$this->id,
                'attribute'=>$attr,
                'datetime'=>$cfg["datetime"],
            ];

            if ($insert)
                $model=null;
            else 
                $model = Timeline::find()->where($a)->one();

            $isNew = empty($model); // this exact attribute does not exist on timeline already

            if ($isNew) 
                $model = new $cfg["TimelineClass"]($a);

            $model->value = $val;
           
            if (!$cfg["onlyDirty"] 
                || $cfg["onlyDirty"] && $model->value!=($cfg["_oldAttributes"][$attr]??\uniqid('force_true'))) {
                
                $ok = $model->save();
                if (!$ok) $this->addErrors($attr, $model->getErrorSummary());
            } 
        }
        sleep(5);        
    }

    public function TL_EventAfterFind($event)
    {
        $cfg = &$this->timelineConfig;
        $data = $this->TL_attributesOnTime();
        foreach($data as $attr=>$val)
            $cfg["attributes"][$attr] = $val;

        $cfg["_oldAttributes"] = $cfg["attributes"];
    }

    private function TL_attributesOnTime()
    {
        $cfg = &$this->timelineConfig;
        $timelineTable = $cfg["TimelineClass"]::tableName();        
        
        $sql = "SELECT t1.* FROM $timelineTable AS t1
                LEFT JOIN (SELECT * FROM $timelineTable WHERE `table`=:table AND table_id=:table_id AND datetime<=:datetime) AS t2
                ON (t1.table=t2.table and t1.table_id=t2.table_id and t1.datetime<t2.datetime AND t1.attribute=t2.attribute)
                WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id
                ";
        $params = [
            'table'=>$cfg["table"],
            'table_id'=>$this->id,
            ':datetime'=>$cfg["datetime"],
        ];        
        $data = \Yii::$app->db->createCommand($sql,$params)->queryAll();
        $data = ArrayHelper::map($data,'attribute','value');        
        return $data;
    }

    public function TL_EventAFterDelete($event) 
    {
        $cfg = &$this->timelineConfig;
        $cfg["TimelineClass"]::deleteAll([
            'table'=>$cfg["table"],
            'table_id'=>$event->sender->id
        ]);
    }

}

使用示例:

<?php
namespace app\models;

class KeyTime extends Key
{
    use \app\behaviors\TimelineTrait;

    public function init()
    {
        parent::init();
        $this->timelineInit([
            'attributes'=>[
                // default values for attributes
                'keyid'=>'historic id', // this is existing attribute in Key model
                'label'=>'mylabel', // label and color does not exist in Key model
                'color'=>'red',
            ],   
        ]);
    }
}

动作更新

   public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        }

        return $this->render('update', [
            'model' => $model,
        ]);
    }

【问题讨论】:

  • 你能展示你的行动吗?我想知道ActiveRecord 保存后重定向回页面是否可行。
  • @Alex 我已将其添加到问题中。 KeyController::findModel 发回一个 KeyTime 实例。 IE。该模型是一个 KeyTime 类。
  • 我也尝试过交易并确保它正确完成。错误仍然发生了 10 次尝试中的 1 次。会不会是硬件问题,比如写比读慢?
  • 我现在已经尝试了很多东西。但没有解决。我在TL_save 的末尾添加了stop(\Yii::$app-&gt;db-&gt;createCommand('select * from timeline order by id desc limit 3')-&gt;queryAll());。它会抛出一个异常,此时显示最后 3 条记录 - 值已正确保存在数据库中。所以问题出在模型填充的某个地方。仍然需要帮助。

标签: events yii2 xampp yii2-active-records


【解决方案1】:

在打开microtime(true) 多次“闪烁”之后,我找到了它有时与sleep(1) 一起工作的原因。

答案在TL_attributesOnTime$sql 的最后一行是

WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id

……但应该是……

WHERE t2.id IS NULL AND t1.datetime<=:datetime AND t1.table=:table AND t1.table_id=:table_id

注意 已更改为 否则,当记录在填充的同一秒内保存时,它不会被包含在内。 希望它可以帮助其他人。

【讨论】:

    猜你喜欢
    • 2018-10-21
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    • 2011-08-16
    • 1970-01-01
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    相关资源
    最近更新 更多