背景
Laravel Laravel

【Laravel】モデルクラスにgetアクセサを設定する方法

こんにちは、スズキです。

今回はLaravelのモデルクラスにgetアクセサを設定する方法を紹介します。

流れとしては、以下の順で紹介します。
  • getアクセサの実装方法
  • 注意事項:getAttributeメソッドを上書きする方法はNG
 

モデルクラスにgetアクセサを設定する方法


モデルクラスにgetアクセサを設定する方法ですが、
「getXxxxAttribute」という形式のメソッドを追加すれば、これがgetアクセサとして機能するようになっています。


例えば、以下のようなFaxNumberモデルがあったとします。

●FaxNumberモデル
class FaxNumber extends Model
{
    public function getTotalNumberAttribute()
    {
        $arrCode = [];
        foreach ([$this->area_code, $this->local_number, $this->subscriber_number] as $code) {
            // 未設定のコード値が存在する場合は空文字を返す
            if (!strlen($code)) {
                return "";
            }
            $arrCode[] = $code;
        }
        $totalCode = join("-", $arrCode);
        return $totalCode;
    }
}
 
このとき、「getTotalNumberAttribute」メソッドというgetアクセサ用メソッドを実装しているため、
以下のように「total_number」にアクセスすることで、getTotalNumberAttributeの実行結果を取得することができます。
// Fax情報取得
$fax =  FaxNumber::query()->first();

// 「111-2222-3333」のような値が取得できる
$faxTotalNumber = $fax->total_number;
 

DBテーブルに存在する項目に対して、モデルクラスにgetアクセサを設定する場合


DBテーブルに存在する項目に対して、モデルクラスにgetアクセサを設定する場合ですが、
これも先ほどと同様にgetXxxxAttributeという形式のメソッドを実装することで実現できます。

例えば、以下のように「Company(会社情報)」「Branch(支店情報)」「BranchShare(共有時の支店情報)」の3つのモデルクラスがあり、
共有フラグのON/OFFで参照値が分岐するような実装が可能です。

●Companyモデル
class Company extends Model
{
    public function branch_share()
    {
        return $this->hasOne(BranchShare::class, 'company_id');
    }

    public function branches()
    {
        return $this->hasMany(Branch::class, 'company_id');
    }
}
 
●BranchShareモデル
class BranchShare extends Model
{
    public function company()
    {
        return $this->belongsTo(Company::class, 'company_id');
    }
}
 
●Branchモデル
class Branch extends Model
{
    public function company()
    {
        return $this->belongsTo(Company::class, 'company_id');
    }

    public function getTelNoAttribute($value)
    {
        return $this->getAttributeForShareInfo('tel_no');
    }

    public function getFaxNoAttribute($value)
    {
        return $this->getAttributeForShareInfo('fax_no');
    }

    /**
     * @param string $key
     * @return mixed
     */
    public function getAttributeForShareInfo($key)
    {
        // 共有設定取得
        $isUseShareInfo = ($this->company->is_use_branch_share == 1);

        // 共有設定を使用しない場合
        if (!$isUseShareInfo) {
            // 本来の値を使用
            // ※補足:このときに、getAttributeを利用してしまうと無限ループに陥るので注意
            return $this->getAttributeFromArray($key);
        }
        // 共有設定を使用する場合
        else {
            // 共有情報を使用
            if (!$this->company->branch_share) {
                \Log::warning("共有支店情報の取得に失敗しました。【会社ID:{$this->company->id}】");
                return null;
            }
            return $this->company->branch_share->getAttribute($key);
        }
    }
}
 
このとき、「getTelNoAttribute」メソッドというgetアクセサ用メソッドを実装しているため、
以下のように「tel_no」にアクセスした際に、DBから取得した'tel_no'を直接取得するのではなく、getTelNoAttributeメソッドに定義した内容を取得します。
(今回の実装の場合、
  • Companyテーブルの共有フラグがOFF → Branchテーブルのtel_no値を取得
  • Companyテーブルの共有フラグがON → BranchShareテーブルのtel_no値を取得
 と取得値が分岐します。)
// Branch情報取得
$branch =  Branch::query()->first();

// Branchのtel_no値を直接取得するのではなく、getTelNoAttributeメソッドの戻り値を取得する
$telNo = $branch->tel_no;
 
なお、「
$telNo = $branch->tel_no;」のようなアクセスの仕方をした際の取得値の優先順位は以下のようになっています。
  1. 該当のgetXxxxAtributeメソッドを実装している場合 → 該当のgetXxxxAttributeメソッドの実行結果を取得
  2. 該当のDB項目が存在する場合 → 該当のDB項目値を取得
  3. 該当のリレーション先が存在する場合 → リレーション先のモデル情報を取得
  4. 上記以外 → NULLを取得
 

注意事項!!:getAttributeメソッドを上書きする方法はNG!!


最後に、「モデルクラスにgetアクセサを設定する際に、getAttributeメソッドを上書きする方法はNG」という話をします。

Laravelを利用したことのある方であればご存知の方もいると思いますが、
$telNo = $branch->tel_no;
というアクセスの仕方をした場合、
まず最初にモデルクラスのgetAttributeメソッドが呼ばれ、getAttributeメソッドの中で取得値の分岐(「getXxxxAttributeメソッドの結果」or「該当DB項目値」or「リレーション先の情報」or「NULL」)が行われます。

そこで、人によっては「getXxxxAttibuteメソッドを個別に作らずに、getAttributeメソッドを上書きしたらgetアクセサ用メソッドを一本化できて良いんじゃない?」と考える方もいると思います。

ですが、以前「getAttributeメソッドを上書きする方法」によるgetアクセサの実装を実際に試したことがあるのですが、これはNGなケースがあるため、基本的に使ってはいけません。



「getAttributeメソッドを上書きする方法」ですが、確認した範囲でいうと、少なくとも、toArrayメソッド利用時(json化の際)にうまく行きません。(※Laravel5.2で確認)

実はLaravelは、「'getXxxxAttribute'形式のメソッドを検索→メソッドを退避(=cacheを作成)」という処理を行い、一部の処理ではこのcacheした結果を利用するようになっています。
実際、モデルクラスのtoArrayメソッドを使用した際には、以下の値が利用されます。
  • DB項目値
  • cacheしたgetXxxxAttributeメソッドの実行結果(該当のDB項目が存在する場合は、getXxxxAttributeの結果を優先)
  • リレーション先モデル情報の内容 
上記に書いてないことからもわかると思いますが、toArray時にはgetAtributeメソッドが利用されません。

そのため、getAttributeメソッドを上書きする方法でgetアクセサを実装してしまうと、
  • $telNo = $branch->tel_no;」のようなアクセス → 意図した値を取得できる(getアクセサ値を取得)
  • toArrayメソッド利用時 → 意図した値を取得できない(DB値を直接取得)
となってしまい、不具合の元になります。

よって、getアクセサを実装する際には、面倒だったとしても「getXxxxAttribute」メソッドを個数分、個別に用意するようにしないといけません。
≪ baserCMS用のLaravel Valetカスタムドライバを書いた  |  [Laravel]Facadeもどきを実装してFacadeの仕組みを理解する ≫

Web制作のお問い合わせ

075-744-6842

(平日/土曜 10:00~17:00)

 お問い合わせ