11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravelは良くも悪くも便利機能が備わっています。
ここではModelに搭載されている$fillable$guardedのような便利機能を整理しました。

Modelクラスを確認しよう

ドキュメントで確認することはもちろん可能なのですが、やはりModelのことは直接Modelクラスを参照することとします。

Laravelのバージョンによって中身が異なります

abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
    use Concerns\HasAttributes,
        Concerns\HasEvents,
        Concerns\HasGlobalScopes,
        Concerns\HasRelationships,
        Concerns\HasTimestamps,
        Concerns\HidesAttributes,
        Concerns\GuardsAttributes,
        ForwardsCalls;

    /**
     * The connection name for the model.
     *
     * @var string|null
     */
    protected $connection;

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table;

    /**
     * The primary key for the model.
     *
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * The "type" of the primary key ID.
     *
     * @var string
     */
    protected $keyType = 'int';

    /**
     * Indicates if the IDs are auto-incrementing.
     *
     * @var bool
     */
    public $incrementing = true;

    /**
     * The relations to eager load on every query.
     *
     * @var array
     */
    protected $with = [];

    /**
     * The relationship counts that should be eager loaded on every query.
     *
     * @var array
     */
    protected $withCount = [];

    /**
     * Indicates whether lazy loading will be prevented on this model.
     *
     * @var bool
     */
    public $preventsLazyLoading = false;

    /**
     * The number of models to return for pagination.
     *
     * @var int
     */
    protected $perPage = 15;

    /**
     * Indicates if the model exists.
     *
     * @var bool
     */
    public $exists = false;

    /**
     * Indicates if the model was inserted during the current request lifecycle.
     *
     * @var bool
     */
    public $wasRecentlyCreated = false;

    /**
     * Indicates that the object's string representation should be escaped when __toString is invoked.
     *
     * @var bool
     */
    protected $escapeWhenCastingToString = false;

    /**
     * The connection resolver instance.
     *
     * @var \Illuminate\Database\ConnectionResolverInterface
     */
    protected static $resolver;

    /**
     * The event dispatcher instance.
     *
     * @var \Illuminate\Contracts\Events\Dispatcher
     */
    protected static $dispatcher;

    /**
     * The array of booted models.
     *
     * @var array
     */
    protected static $booted = [];

    /**
     * The array of trait initializers that will be called on each new instance.
     *
     * @var array
     */
    protected static $traitInitializers = [];

    /**
     * The array of global scopes on the model.
     *
     * @var array
     */
    protected static $globalScopes = [];

    /**
     * The list of models classes that should not be affected with touch.
     *
     * @var array
     */
    protected static $ignoreOnTouch = [];

    /**
     * Indicates whether lazy loading should be restricted on all models.
     *
     * @var bool
     */
    protected static $modelsShouldPreventLazyLoading = false;

    /**
     * The callback that is responsible for handling lazy loading violations.
     *
     * @var callable|null
     */
    protected static $lazyLoadingViolationCallback;

    /**
     * Indicates if an exception should be thrown instead of silently discarding non-fillable attributes.
     *
     * @var bool
     */
    protected static $modelsShouldPreventSilentlyDiscardingAttributes = false;

    /**
     * The callback that is responsible for handling discarded attribute violations.
     *
     * @var callable|null
     */
    protected static $discardedAttributeViolationCallback;

    /**
     * Indicates if an exception should be thrown when trying to access a missing attribute on a retrieved model.
     *
     * @var bool
     */
    protected static $modelsShouldPreventAccessingMissingAttributes = false;

    /**
     * The callback that is responsible for handling missing attribute violations.
     *
     * @var callable|null
     */
    protected static $missingAttributeViolationCallback;

    /**
     * Indicates if broadcasting is currently enabled.
     *
     * @var bool
     */
    protected static $isBroadcasting = true;

    /** .... */

上から確認していきましょう

$connection

用途

  • 別のDBに接続させたい場合
    config/database.phpの設定と違う場合に使用)

サンプル

class User extends Model
{
    protected $connection = 'mariadb2';
}

$table

用途

  • モデル名と違うテーブル名を指定したい場合

サンプル

class User extends Model
{
    protected $table = 'staffs';
}

$primaryKey

用途

  • id以外のカラムに主キーを設定したい場合

サンプル

class Article extends Model
{
    protected $primaryKey = 'article_id';
}

$keyType

用途

  • 主キーにint系意外の型を利用したい場合

サンプル

class User extends Model
{
    protected $keyType = 'string';
}

$incrementing

用途

  • 主キーのauto increment(増分整数値)を阻止したい場合

サンプル

class User extends Model
{
    public $incrementing = false;
}

$timestamps

用途

  • created_at. updated_atをINSERT, UPDATEに伴い自動更新させたくない場合

サンプル

class User extends Model
{
    public $timestamps = false;
}

$dateFormat

用途

  • created_atupdated_atのようなタイムスタンプのフォーマットをカスタムしたい場合

サンプル

class User extends Model
{
    protected $dateFormat = 'Y-m-d H:i:s';
}

CREATED_AT / UPDATED_AT

用途

  • タイムスタンプのカラム名をcreated_at, updated_atから変更したい場合

サンプル

class User extends Model
{
    const CREATED_AT = 'creat_at';
    const UPDATED_AT = 'updat_at';
}

$attributes

用途

  • モデルのデフォルト値の定義をしたい場合

サンプル

class User extends Model
{
    protected $attributes = [
        'icon_url' => 'https://storage.com/xxxx/xxx.png', // デフォルト画像
        'is_admin' => false,
    ];
}

$with

用途

  • 常にロードしたいリレーションがある場合

サンプル

class Book extends Model
{
    /**
     * 常にロードする必要があるリレーション
     *
     * @var array
     */
    protected $with = ['author'];

    /**
     * この本を書いた著者を取得
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}

$withCount

用途

  • $withの件数だけ取得したい場合

サンプル

class Book extends Model
{
    /**
     * 常にロードする必要があるリレーション
     *
     * @var array
     */
    protected $withCount = ['author'];

    /**
     * この本を書いた著者を取得
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}

$preventsLazyLoading

用途

  • 遅延ローディングの有効無効設定
  • N + 1問題の対策としても有効

サンプル

以下のような場合発生する遅延ロードが発生した際にエラーで教えてくれるようです。

$posts = Post::all();  // すべての投稿を取得
foreach ($posts as $post) {
    echo $post->comments;  // 各投稿に対してコメントをロード(1つの投稿ごとに1回クエリ)
}
$posts = Post::with('comments')->get();  // 投稿とコメントを一括でロード
foreach ($posts as $post) {
    echo $post->comments;  // 追加クエリなしでコメントにアクセス
}

以下のように利用します

public function boot()
{
    Model::preventLazyLoading(true);
}

$perPage

用途

  • ->paginateメソッドでページネーションを実装した際のページ単位の件数を変更したい場合

モデルクラスにはデフォルトで15が設定されています

protected $perPage = 15;

サンプル

class User extends Model
{
    protected $perPage = 30;
}

$exists

用途

  • DB上に存在するかどうかを指定したい場合

サンプル

$user = User::first();
Log::info($user->exists);

$wasRecentlyCreated

用途

  • 現在のリクエスト中に作成されたか確認する
  • setによる更新時はfalseを返す

サンプル

$user = new User();
$user->fill($data)->save();
Log::info($user->wasRecentlyCreated);

$escapeWhenCastingToString

用途

  • オブジェクトの文字列表現が__toStringメソッドで呼び出された際に、エスケープされるべきであることを示す

サンプル

class User extends Model
{
    $escapeWhenCastingToString = false;
}

// name: <script>alert('XSS');</script>
// name: &lt;script&gt;alert(&#039;XSS&#039;);&lt;/script&gt;

$resolver

用途

  • 通常の設定以外の特殊なDB接続をしたい場合

サンプル

use Illuminate\Database\ConnectionResolverInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Connection;

// 独自のConnectionResolver実装例
class CustomConnectionResolver implements ConnectionResolverInterface
{
    protected $connections = [];
    protected $default = 'default';

    public function __construct()
    {
        // ここで必要な接続インスタンスを生成または注入
        $this->connections['default'] = app('db')->connection(); // デフォルト接続
        $this->connections['test'] = app('db')->connection('sqlite'); // テスト用接続 など
    }

    public function connection($name = null): Connection
    {
        $name = $name ?: $this->getDefaultConnection();
        return $this->connections[$name];
    }

    public function getDefaultConnection(): string
    {
        return $this->default;
    }

    public function setDefaultConnection($name)
    {
        $this->default = $name;
    }
}

// カスタムリゾルバのセット
$customResolver = new CustomConnectionResolver();
Model::setConnectionResolver($customResolver);

// 以降、このアプリケーション内の全てのEloquentモデルは
// `$customResolver`を通してDB接続が解決される
$user = User::find(1); // カスタムリゾルバ経由で接続取得

$dispatcher

用途

  • LaravelのEloquentモデル内部のイベント(creating, created, updating, updated, saving, saved, deleting, deleted)を管理
  • SeederやFactory, Testなどでイベントが発火してほしくない時もここで管理する

サンプル

use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Model;
use Mockery as m;

class User extends Model {
    // 通常のEloquentモデル
}

// テストなどで、カスタムディスパッチャをセット
$mockDispatcher = m::mock(Dispatcher::class);

// 例えば、"creating"イベントが発火されたことを確認したいとき
$mockDispatcher->shouldReceive('dispatch')->once()->with(m::on(function($event) {
    return $event->model instanceof User && $event->name === 'eloquent.creating: ' . User::class;
}));

User::setEventDispatcher($mockDispatcher);

// ここでUserを保存すると、"creating"イベントがディスパッチされ、モックで受け取れる
$user = new User();
$user->name = 'John Doe';
$user->save();

$booted

用途

  • 対象モデル以外のboot()を呼び出したいモデルを羅列
  • boot()はモデル呼び出し時に自動でコールされるメソッド

サンプル

class User extends Model
{
    $booted = ['books', 'doctors'];
}

$traitInitializers

用途

  • モデルクラスがuseしているトレイトを走査する
  • そのトレイトにinitialize{TraitName}というメソッド(例えばinitializeSoftDeletes)があるかをチェックする
  • 見つかった場合、そのメソッドを呼び出すためのクロージャを$traitInitializersに格納する。

サンプル

trait HasSluggable {
    // このメソッドは「イニシャライザーメソッド」として認識される
    protected function initializeHasSluggable()
    {
        // このトレイトに必要な初期化処理
        $this->slug = Str::slug($this->title);
    }
}

class Post extends Model {
    use HasSluggable;
}

// 上記のPostモデルがロードされた際、LaravelはHasSluggableトレイト内に
// `initializeHasSluggable`メソッドがあることを検出し、
// `$traitInitializers[] = function($model) { $model->initializeHasSluggable(); };`
// のようなクロージャを内部的に登録する。

$post = new Post(['title' => 'Hello World']);
// インスタンス生成時、$traitInitializers内のクロージャが呼ばれ、
// initializeHasSluggable()が実行され、$post->slug に "hello-world" が設定される。

$globalScopes

用途

  • グローバルスコープを管理します
  • $globalScopesへの追加はModelクラス::addGlobalScopeで行われます
  • モデルのbooted()で以下のように実装します

サンプル

class AncientScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('created_at', '<', now()->subYears(2000));
    }
}

class User extends Model
{
    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::addGlobalScope(new AncientScope);
    }
}

$ignoreOnTouch

用途

  • 通常モデルが更新された時にはtouchメソッドによりupdated_atも更新されるがそれを阻止したい場合
  • 通常、belongToのようにリレーションがあるモデルもtouchされてしまうのでそれを阻止したい場合

サンプル

class Post extends Model
{
    protected static $ignoreOnTouch = [User::class];
}

$modelsShouldPreventLazyLoading

用途

  • モデルがまだロードしていないリレーションへアクセスした際に行われる「レイジーローディング」を全モデルで制御するための静的プロパティ
  • trueに切り替えると、アプリケーション内の全てのEloquentモデルがまだロードされていないリレーションへアクセスすると例外がスローされます

サンプル

以下のように設定することでアプリ全体に適応することができます。
ただし、例外を投げてしまうので十分なテストをするか本番環境ではオフとしておきましょう。

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // 本番以外の環境ではレイジーローディングを防ぐ
        // 本番環境であればパフォーマンス問題にはならないように、この制御はオフにするなど環境によって切り替えるとよい
        Model::preventLazyLoading(! $this->app->isProduction());
    }
}

$lazyLoadingViolationCallback

用途

  • 上記の$modelsShouldPreventLazyLoadingがtrueの場合有効
  • 通常LazyLoadingViolationExceptionを返すのでカスタム例外を実装したい場合

サンプル

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\LazyLoadingViolationException;

// 独自の違反処理ロジックを定義
Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
    // 例えばログを出した後、例外をスローするといったカスタム処理
    \Log::warning("Lazy loading violation on model " . get_class($model) . " for relation {$relation}.");
    
    // デフォルト動作(例外スロー)を保ちたい場合
    throw new LazyLoadingViolationException($model, $relation);
});

$modelsShouldPreventSilentlyDiscardingAttributes

用途

  • $fillableに指定のないパラメーターを指定した場合例外をスローする

サンプル

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassAssignmentException;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // ローカル環境では非フィルアブル属性を指定した時に例外を起こすようにする
        Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
    }
}

class User extends Model
{
    protected $fillable = ['name', 'email'];
}

$user = new User();

// 'password'はfillableではない属性として仮定
try {
    $user->fill(['name' => 'John Doe', 'password' => 'secret']);
} catch (MassAssignmentException $e) {
    // ここに来ることで、"password"が非許可属性だったことをすぐに把握できる
}

$discardedAttributeViolationCallback

用途

  • $modelsShouldPreventSilentlyDiscardingAttributesがtrueの時に発生する例外のカスタム

サンプル

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassAssignmentException;

// たとえば、無視しようとしている属性が見つかったら例外をスローするコールバックを登録
Model::handleDiscardedAttributesUsing(function ($model, $attributes) {
    // ここで任意の処理を行う。例えばログを残したり、例外を投げたりできる
    \Log::warning('Attempted to mass assign non-fillable attributes', [
        'model' => get_class($model),
        'attributes' => $attributes,
    ]);
    
    throw new MassAssignmentException(sprintf(
        'Attempted to set non-fillable attributes on %s: %s',
        get_class($model),
        implode(', ', $attributes)
    ));
});

// `$modelsShouldPreventSilentlyDiscardingAttributes`をtrueにして
// 実際にdiscardedAttributeViolationCallbackを有効化する
Model::preventSilentlyDiscardingAttributes(true);

$modelsShouldPreventAccessingMissingAttributes

用途

  • モデルに存在しないパラメーターへアクセスしようとした場合に例外を発生させる

サンプル

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MissingAttributeException;

// アプリケーション起動時、たとえばAppServiceProviderで設定
Model::preventAccessingMissingAttributes(true);

$user = User::find(1);

// Userモデルには存在しない属性 'nickname' にアクセスしてみる
try {
    $nickname = $user->nickname; // 存在しない属性
} catch (MissingAttributeException $e) {
    // ここで例外がスローされる
    dd($e->getMessage());
}

$missingAttributeViolationCallback

用途

  • $modelsShouldPreventAccessingMissingAttributesがtrueの際にスローされる例外のカスタム

サンプル

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MissingAttributeException;

// 1. 開発環境などで、存在しない属性アクセスを例外として扱う設定を有効にする
Model::preventAccessingMissingAttributes(true);

// 2. 存在しない属性にアクセスした際に呼ばれるカスタムコールバックを登録
Model::handleMissingAttributeViolationUsing(function ($model, $key) {
    // ここでログを残したり、独自処理を加えたりできる
    \Log::error("Attempted to access missing attribute [{$key}] on model [".get_class($model)."]");

    // デフォルトでは MissingAttributeException をスローするが、
    // カスタム例外を投げてもいいし、単にnull返すなども可能
    throw new MissingAttributeException($model, $key);
});

// テスト
$user = User::find(1);
$nickname = $user->nickname; 
// `nickname`が存在しなければ、上記で登録したコールバックが呼び出され、ログ記録+例外スローとなる。

$isBroadcasting

用途

  • モデルイベント(created, updated, deletedなど)をブロードキャストする機能を管理する

サンプル

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Indicates if broadcasting is currently enabled.
     *
     * @var bool
     */
    protected static $isBroadcasting = true;

    /**
     * モデルイベントと紐づけるイベントクラス
     * ここでは created イベント時に UserCreated イベントをディスパッチすることを想定
     */
    protected $dispatchesEvents = [
        'created' => \App\Events\UserCreated::class,
    ];

    protected $fillable = ['name', 'email'];
}

// ---------------------------------------
// コントローラやtinkerなどでの挙動テスト例
// ---------------------------------------

// Userモデルを作成
$user = User::create([
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);

// 上記では、$isBroadcasting が true の場合、UserCreatedイベントがブロードキャストチャネルを通して
// リアルタイムにフロントエンドへ通知されます。

// もしブロードキャストを一時的に止めたい場合は、以下のようにすることで停止可能
User::preventBroadcasting(true);
// または、モデルクラス上で $isBroadcasting = false; に設定し直す

$user = User::create([
    'name' => 'Jane Doe',
    'email' => 'jane@example.com'
]);
// ここでは、$isBroadcasting = false となっていればイベントはブロードキャストされず、
// 通常のイベントディスパッチ(アプリ内ロジックのみ)に留まります。

以上です。
マイナーなものも当然ありますが、これらを用いることでファットモデルを解消する手助けになったり、品質向上につながったりすると思います。気になるものがあれば活用してみてください。

11
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?