Laravel5.4 ドキュメント補足

コントローラ

リソースコントローラ

リソースコントローラのメソッドに個別ミドルウェアを割り当てたい場合は、コンストラクタに書く。

class UserController extends Controller
{
    /**
     * 新しいUserControllerインスタンスの生成
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}

バリデーション

バリデーション時に自動でリダイレクトされる場合の仕組みを知るには。

\app\Exceptions\Handlerや\Illuminate\Foundation\Exceptions\Handlerを読むと良い。

フォームリクエストバリデーション
class CustomRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        //デフォルトではfalseなのでtrueにしないと動作しない。
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [];
    }
}

php artisan make:request CustomRequest」実行後のソースコードで「This action is unauthorized」が出る場合、authorize()の戻り値をtrueにする事。
デフォルトのソースコードではここはfalseになっている。

max:値

バリデーションの実体は「Illuminate\Validation\Validator」、値の識別には「Illuminate\Validation\Concerns\ValidatesAttributes」使用されていると思われる。
最大値の識別には「Illuminate\Validation\Concerns\ValidatesAttributes#validateMax」、「Illuminate\Validation\Concerns\ValidatesAttributes#getSize」が使用される。

    /**
     * Get the size of an attribute.
     *
     * @param  string  $attribute
     * @param  mixed   $value
     * @return mixed
     */
    protected function getSize($attribute, $value)
    {
        $hasNumeric = $this->hasRule($attribute, $this->numericRules);
        dd($attribute, $hasNumeric, $value);

        // This method will determine if the attribute is a number, string, or file and
        // return the proper size accordingly. If it is a number, then number itself
        // is the size. If it is a file, we take kilobytes, and for a string the
        // entire length of the string will be considered the attribute size.
        if (is_numeric($value) && $hasNumeric) {
            return $value;
        } elseif (is_array($value)) {
            return count($value);
        } elseif ($value instanceof File) {
            return $value->getSize() / 1024;
        }

        return mb_strlen($value);
    }

Eloquent

タイムスタンプ
  1. データを変更してsaveメソッドを行なった場合、updated_atが更新される。
  2. データを変更しなかった、データに変わりがなかった場合、updated_atは更新されない(そもそもDBに書き込みを行わない)。

updated_atをとりあえず更新したい場合は

$model->setUpdatedAt(new Carbon())->fill($data)->save();

$model->setUpdatedAt($model->freshTimestamp())->fill($data)->save();

と書くと良さげ。


updated_at自動更新の挙動

namespace Illuminate\Database\Eloquent;

abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
    //省略

    public function save(array $options = [])
    {
        //省略

        if ($this->exists) {
            //$this->isDirty()は変更された値があるとtrueが立つ。
            //$this->isDirty()の実体は\Illuminate\Database\Eloquent\Concerns\HasAttributes。詳解は省くがfillメソッドや$model->hogeでアクセス可能な値に対して、DBから取得した値と一致するかどうかをループで見ている。
            //$this->performUpdate($query)で更新を行う
            $saved = $this->isDirty() ?
                        $this->performUpdate($query) : true;
        } else {
            $saved = $this->performInsert($query);
        }

        //省略

        return $saved;
    }

    protected function performUpdate(Builder $query)
    {
        //省略

        if ($this->usesTimestamps()) {
            //ここで自分自身のインスタンスのupdated_atの値を変更する。
            $this->updateTimestamps();
        }

        //省略
    }

    //ちなみにhttp://qiita.com/gomaaa/items/91e5cbd319279a2db6ecではsaveとupdateの違いはupdated_atの更新有無とされているが、このupdateはクエリビルダのものと思われる。
    //Modelクラスでのupdateはfill + save
    public function update(array $attributes = [], array $options = [])
    {
        if (! $this->exists) {
            return false;
        }

        return $this->fill($attributes)->save($options);
    }

    //省略
}
namespace Illuminate\Database\Eloquent\Concerns;

trait HasTimestamps {
    protected function updateTimestamps()
    {
        //単純なnew Carbon()のラッパー
        $time = $this->freshTimestamp();

        if (! $this->isDirty(static::UPDATED_AT)) {
            //ここでモデルのupdated_atを更新。
            //setUpdatedAtメソッドはModelクラスを継承しているクラスでも使用可能
            $this->setUpdatedAt($time);
        }

        if (! $this->exists && ! $this->isDirty(static::CREATED_AT)) {
            $this->setCreatedAt($time);
        }
    }

    public function freshTimestamp()
    {
        return new Carbon;
    }
}
https://readouble.com/laravel/5.4/ja/eloquent-relationships.html#eager-loading
$books = App\Book::with('author')->get();
  • withに指定する引数はそのModelのメソッド名。
  • withの引数がそのままtoArray時のキー名になる(jsonも同様)
  • Modelがネストしている場合(a->model_b->model_cみたいな)、with('a.b')のようにドット区切りで指定するとデータを取得してくれる。
リレーション
class A extends Model
{
	protected static function boot()
	{
		parent::boot();

		#グローバルスコープの設定
		#リレーション時はwithoutGlobalScope('hoge')をしないとhoge入りのものを紐づけできない
		static::addGlobalScope('hoge', function (Builder $builder) {
			$builder->where('hoge', false);
		});
	}
}

class B extends Model
{
	public function page(){
		#Aを取得する時hogeがどんな状態でも取得する設定
		return $this->belongsTo('App\A')->withoutGlobalScope('hoge');
	}
}

#Aのwhereの結果を元にBのデータを取得
$bModels = B::whereHas('page', function($query){
    $query->where('column', true);
})->get();

グローバルスコープを実装した場合、リレーションにも反映される。
なので、リレーション時にそのデータが必要な場合はwithoutGlobalScopeの追加が必要。


リレーションメソッドで指定されたモデルから条件を絞り込んで元のモデルを検索したい場合はhasを使う。
Eloquent:リレーション 5.4 Laravel

ポリモーフィック関係
class Comment extends Model
{
    /**
     * 所有しているcommentableモデルの全取得
     */
    public function commentable()
    {
        #ここでは「関数名_type」「関数名_id」が自動で対象に選ばれる。
        #この例でいうと「commentable」関数の事。ここはDBのカラム名ではなく関数名で決定されている。
        return $this->morphTo();

        #関数名を別にしたい場合・DB上のカラム名を使用したい場合は「$this->morphTo('カラム名のプレフィックス')」を指定する。
        #return $this->morphTo('commentable');

        #さらにtypeとidもDBのカラム名を使用したい場合は第二・第三引数をつける。「_」は不要
        #return $this->morphTo('commentable', 'type', 'id');
    }
}

ちなみに関数名を変更してもtoArrayの結果はmorphToの第一引数をキー名にするみたいだ。

$post = Post::create([]);
$post->commentable()->save(new Comment([
    'body' => 'message'
]));

コメントを関係性のあるテーブルと一緒に保存するには上記を行う。
前提として、CommentよりPostのデータがDB上に先に存在しなければならない。


また、

commentable_typeはデフォルトでApp\PostかApp\Videoのどちらかになるでしょう。

https://readouble.com/laravel/5.4/ja/eloquent-relationships.html#polymorphic-relations

とあるように、DB上に保存されるtypeはModelの名前になる。

Eloquent:シリアライズ

JSONへ値を追加

特定の条件でJSONに値を追加したくない場合は、setAppendsの値を変更すると良い。

class Json extends Model {
    private $appends = [
        'hoge',
    ];

    public function getHogeAttribute(){
        return 111;
    }

    public function hiddenHoge(bool $is){
        //ここでsetAppendsの値を変更する事で値を出力しなくなる。
        $this->setAppends([]);
    }
}