Laravel 中的一个后期静态绑定

(原文地址,内容以原文为准,可能修改或者补充:https://blog.tanteng.me/2017/08/php-new-st...

关于 PHP 的 new static 延迟静态绑定,或者叫后期静态绑定,在 Laravel 中遇到一个使用上的问题。如下,在 Laravel 中调用 Model 新增数据的时候,首先给 Model 加了一个获取分表的方法:

protected function addToMessage($msgType, $userID, $commentID, $replyCommentID, $replyUserID, $gameID)
{
    if (!$userID) {
        return false;
    }

    $table = 't_message_' . hashID($userID, 100);
    $this->message->setTable($table)->create([
        'msg_type'         => $msgType,
        'user_id'          => $userID,
        'comment_id'       => $commentID,
        'reply_comment_id' => $replyCommentID,
        'reply_user_id'    => $replyUserID,
        'game_id'          => $gameID,
        'is_read'          => 0,
        'created_at'       => date('Y-m-d H:i:s'),
    ]);
    return true;
}

这里 setTable 方法是在 Model 里定义的获取分表的方法:

public function setTable($table)
{
    $this->table = $table;
    return $this;
}

从报错日志中发现 $this->table 并没有生效,但实际上在调用 create 方法之前打印表名的时候是期望的值,这里调用 create 方法为什么 $this->table 没有被重置呢?

这里 $this->message 是一个继承 Model 类的模型类,其中 create 方法:

public static function create(array $attributes = [])
{
    $model = new static($attributes);

    $model->save();

    return $model;
}

位于 vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php Line 557.

因为 Laravel 框架的这个 Model 类是一个 abstract 类型,PHP 中 abstract 类可以用 new static 后期静态绑定的方式实例化,而 create 方法里 $model = new static($attributes) 实际上就是重新实例化了并返回,而调用者 Model 类没有定义 table 属性,所以这个时候 $this->table 是没有值的。

解决办法是用 save 方法即可,如下所示。实际上 create 方法也调用了 save 方法。

$message = new Message();
$message->setTable($table);
$message->msg_type = $msgType;
$message->user_id = $userID;
$message->comment_id = $commentID;
$message->reply_comment_id = $replyCommentID;
$message->reply_user_id = $replyUserID;
$message->game_id = $gameID;
$message->is_read = 0;
$message->created_at = date('Y-m-d H:i:s');

$message->save();

实验

一个抽象类 A,有个 create 方法,通过延迟静态绑定实例化并返回。B 类继承 A,test 方法中修改父类的 name 属性。

<?php

abstract class A
{
    protected $name = "tanteng";

    public static function create()
    {
        return new static();
    }
}

class B extends A
{
    //protected $name = '纸牌屋弗兰克';

    public function test()
    {
        $this->name = "Tony Tan";
        return $this;
    }
}

$obj1 = (new B)->test();
$obj2 = (new B)->test()->create();
var_dump($obj1);
var_dump($obj2);

结果显示 $obj1 和 $obj2 这两个实例都是 B 的实例,调用 test 方法属性 name 改变了,但是调用 create 方法后,name 属性并没有改变。这也就是在本文中说的在 Lavarel 中遇到的场景。(这里如果把注释打开,打印的 name 就是重写的值)

如果把抽象类 A 改成普通类,new static 改成 new self 的方式实例化,结果就不同了,打印的属性 name 都是各自类的属性。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1
Silencewj

感觉好复杂

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!