Laravel 中的一個後期靜態繫結

紙牌屋弗蘭克發表於2017-08-10

(原文地址,內容以原文為準,可能修改或者補充: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 協議》,轉載必須註明作者和本文連結

相關文章