Laravel

提供:senooken JP Wiki
2024年8月27日 (火) 12:21時点におけるSenooken (トーク | 投稿記録)による版 (Rendering JSON)

About

人気の理由

Laravelの人気を大検証 何が凄いの? | テクフリ

CakePHPのほうが早い。

  • 簡単にマスター可能
  • 自由度が高い

情報源

Getting Started

Configuration

Configuration - Laravel 5.8 - The PHP Framework For Web Artisans

Environment Configuration

DotEnvライブラリーを使っており、ルートディレクトリーの.env.exampleを.envにリネームするとこの設定を利用する。

Accessing Configuration Values

configグローバルヘルパー関数で、アプリ内のどこからでも設定値を参照できる。

$value = config('app.timezone');

実行時に設定もできる。

config(['app.timezone' => 'America/Chicago']);
Configuration Caching

config:cacheで設定をキャッシュすることができるらしい。ただし、このコマンドを実行すると、.envファイルは読み込まれないので、env関数は設定ファイル内でしか使ってはいけない。

つまり、基本は設定値の参照はconfig関数で行う。

Directory Structure

Directory Structure - Laravel 5.8 - The PHP Framework For Web Artisans

  • app
  • bootstrap
  • config
  • database
  • public
  • resources
  • routes
  • storage: フレームワークで自動生成される、コンパイル済みのBladeテンプレート、ファイル系セッション、ファイルキャッシュ類を格納。app, framework, logsがある。
    • app: アプリ生成ファイルの格納。
    • framework: フレームワーク生成ファイルとキャッシュ。
    • logs: ログ。
  • tests
  • vendor

Laravelの名前空間と、ディレクトリーは同一ではない。

命名規則の都合だろうと思われる。少々気持ち悪い。

Architecture Concepts

Service Container

Service Container - Laravel 5.8 - The PHP Framework For Web Artisans

Service Class

Serviceクラスは特定のビジネスロジックの管理に重点を置いたクラス。ビジネスロジックに特化しているから、他のクラスと異なり、通常はプロパティーを継承しない。

app/Servicesに配置して、クラス名の末尾にはServiceの接尾辞をつける。

多くの場合、Eloquentモデルにリンクされたロジックの追加で役立つ。例えば、Userモデルに対するUserServiceのような。ただ、Eloquentモデルとは関係なしに、PaymentServiceのように特定の機能 (ビジネスロジック) に合わせて調整したサービスクラスもある。

ビジネスロジックに特化したユーティリティークラスとかに近い。

うまい作りとしては、Controllerではtry-catchを含んだサービスクラスに定義した関数を呼ぶだけ、呼び終わった結果を返すだけにするとか。

コードがモジュール化されて、保守しやすく整理される。

The Basics

Routing

Basic Routing

routes/web.phpでURL別のアクセス時 (ルーティング) の処理 を設定する。

web.phpはwebミドルウェアグループに割り当てられていて、CSRFガードなどが入っている。

Route::get('/user', 'UserController@index');

上記のような書式で、パスとアクションを対応付ける。

Named Routes

ルートに名前を付けて、後で流用・参照できる。

Other

動的アクション

ルートに応じて、DBにアクセスして、値の取得や処理をしたいことがある。

evalでアクションメソッドを呼び出すこともできる。が、パラメーターとして渡して、1個のメソッドで処理するのがスマート。

evalでやるのはいまいち。

Middleware

リクエスト受信後にコントローラー処理の前後に割り込んで行う処理の仕組み。プログラムの基本はコントローラーのアクション。

このアクションに共通処理を一括で仕込む場合、コントローラーのアクション単位で処理が必要になる。そのままだと何回も同じことを書く必要がある。

例えば、フォームの送信チェックやログイン認証など。これらを一括で行うための仕組みがミドルウェア。

Controllers

Controllers - Laravel 5.8 - The PHP Framework For Web Artisans

Resource Controllers
Verb URI Action Route Name
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

create/editは新規作成、編集用の画面表示。実際の処理はstore/updateで行う。

Requests

HTTP Requests - Laravel 5.8 - The PHP Framework For Web Artisans

Retrieving Input
Retrieving All Input Data

allメソッドで入力のすべてを取得できる。

Retrieving An Input Value

inputメソッドで指定した入力 (HTTP ボディー) をキーで参照できる。第二引数になかった場合のデフォルト値を設定できる。

$name = $request->input('name', 'Sally');

$names = $request->input('products.*.name');

$request->input();

引数空だと、連想配列で全部取得できる。

Retrieving Input From The Query String

input同様、queryでURLクエリーを取得できる。

$name = $request->query('name');
$name = $request->query('name', 'Helen');
$request->query();

LaravelでURLのクエリパラメーターを取得する二つの方法と注意点 #PHP - Qiita

Bladeで使う場合、request() (Helpers - Laravel 5.8 - The PHP Framework For Web Artisans) のグローバルヘルパー関数を経由して、Requestインスタンス経由でアクセスする。

Retrieving Input Via Dynamic Properties
$name = $request->name;

input/queryなどを使わなくても、動的にプロパティーに持っている。

Old Input

Laravelは次のリクエストの送信完了まで、前回のリクエストの入力を保存している。これにより、バリデーションエラー時などに入力内容を復元できる。

Retrieving Old Input

Request$ondにより、セッションから前回の入力を取得できる。

$username = $request->old('username');

Bladeでも使える。フォーム要素の前回の値表示として使える。基本は指定するとよいだろう。

<input type="text" name="username" value="{{ old('username') }}">

なお、このBladeのold関数は「Helpers - Laravel 5.8 - The PHP Framework For Web Artisans」のグローバルのヘルパー関数。

Views

ControllerからViewにデータを渡す際、user情報など毎回渡すデータがあったりする。そういうのをControllerとは別の場所で自動処理する仕組みがView Composer。Controllerの処理がすっきりする。

ViewはHTMLを保持している。view関数で、テンプレートを指定して、テンプレートで使用する変数を渡せば、Viewインスタンスを取得する。ViewインスタンスがBladeのHTMLを保有している。

いくつかViewインスタンスのメソッドがある。

first: 指定した配列の最初のテンプレートを表示に使う。基本的にアプリなどでユーザーが上書きするよう。あまり使わない。

return view()->first(['custom.admin', 'admin'], $data);
return view('greetings', ['name' => 'Victoria']);
return view('greeting')->with('name', 'Victoria'); 

引数で渡すほかに、with関数でも渡せる。

Validation

主にPOST系のアクション実行用に、バリデーションというデータの検証の仕組みがある。

ControllerのValidateRequestsトレイトに用意されており、Controllerのメソッドとして使える。

$this->validate($request, [検証設定の配列]);

上記の書式で使う。

ただ、この基本的なバリデーションだとコントローラーに都度記載が必要。できれば、リクエストなどで別でやりたい。

FormRequestという仕組みがある。フォームに関する機能をリクエストに組み込む。コントローラーではなく、リクエストでフォーム処理をやってくれる。

FormRequestを派生させたクラスを作成し、適用するURLパスとルールを定義。使用するコントローラーのアクションの引数に、Reqeustではなく作ったFormRequest派生クラスに変更するとOK。これだけ。

エラーメッセージもFormRequest派生クラスで作れる。

Logging

Logging - Laravel 5.8 - The PHP Framework For Web Artisans

Other

Laravelで便利なログ出力方法がいくつかある。

  • ヘルパー関数 (Helpers - Laravel 5.8 - The PHP Framework For Web Artisans)
    • dd: dump and die。引数の変数をその場で表示して終了。
    • dump: 引数の変数をその場で表示。
  • Log::debug(): ログファイルstorage/logsに出力 (use Illuminate\Support\Facades\Log;)。
    • Log::info(print_r($user, true));
    • Log::info(json_encode($user));
    • オブジェクト類はjson_encodeがいい。
Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes)

なお、print_r($request, true) などをすると、以下のエラーが出る。

[2017-09-06 15:19:44] production.ERROR: Symfony\Component\Debug\Exception\FatalErrorException: Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes) in [path to file reducted] Stack trace: #0 {main}

[print_r($request) causes out of memory error.] にあるように、print_rは継承元も再帰的に出力し、Laravelはたくさん継承しているからいっぱいになるらしい。

これはせずに、dumpなどを使う。

Helpers

Helpers - Laravel 5.8 - The PHP Framework For Web Artisans

デバッグに役立つグローバルなヘルパー関数がいくつかある。Bladeでもそのまま使用できる。

  • info: Log::info相当。第二引数に配列データも渡せる。
  • logger: Log::debug相当。引数を空にするとloggerインスタンスを取得できて、そこから個別のエラーレベルに出力もできる。

Log::info/Log::debugはuse宣言が必要で面倒なので、info/loggerを使ったほうがいい。

debugbar

DebugbarというLaravelの開発デバッグにかなり便利なツールがある。

導入方法

composer require barryvdh/laravel-debugbar --dev

.envでAPP_DEBUG=trueの場合に機能する。

クエリーの発行数、メモリー使用量、実行時間。このあたりが特に重要と思われる。

Frontend

Blade Templates

Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans

@section/@yield: Template Inheritance

Bladeでは、継承とセクションの2種類のテンプレートの流用方法がある。

Layoutはページ全体のテンプレート。セクションは区画単位のテンプレート。

セクションは@section/@yieldを使って実現する。

@sectionは@showか@endsectionで終わる。

@show/@endsectionの違い (Bladeテンプレートの@showと@endsectionを間違えないようにする #Laravel - Qiita)。親テンプレートで定義する場合は@show。親でも@endsectionを使うと、sectionの内容が消える。sectionは本来、元テンプレートから継承したものを埋め込むためのものなので、ベーステンプレートだと埋め込み元がないので消えるのだと思う。だから、@showを使う必要があると思われる。

@component/@slot: Components & Slots

テンプレートよりも細かい部品単位の流用方法がcomponent。ヘッダーやフッター、ボタンなどの部品単位で流用できる。

viewsディレクトリー以下に格納する。一般的にはviews/components/ok.blade.phpなどのように配置し、components.okなどで、コンポーネント名を指定して読み込む。

component定義側は、通常のBladeと同じだが、コンポーネント内で変数のプレースホルダーを使用できる。これは、利用側の@slotのブロックで引き渡す。

<div class="message">
                                                         <p class="msg_title">{{$msg_title}}</p>
 <p class="msg_content">{{$msg_content}}</p>
 {{$slot}}
</div>

component利用側で組み込むために工夫する。

@component(名前)
  @slot('msg_title')
    title
  @endslot
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

$slot変数には、@componentsのテキスト、@slotブロック以外が入る。

Laravelに複数の在不明のcomponentを順番に適用させたい場合componentFirstを使う。

@componentFirst(['custom.alert', 'alert'])
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

@slot以外に、変数を渡すこともできる。

@component('alert', ['foo' => 'bar'])
    ...
@endcomponent

@component('alert')
  @slot('foo', 'bar')
@endcomponent

slotの設定方法は複数ある。@slot/@endslotよりかは@slot()で設定するほうが短い。が、@component内は$slotのデフォルト値を入れるとしたほうがわかりやすいかもしれない。

ただし、@endcomponentは省略できない。

名前付きslotのデフォルト値設定はない。やりたければ、??や@if/@issetで自分でやっておく。

デフォルトの$slotは、@componentの配列式 (@component(, ['slot']) では指定できないが、view関数で呼ぶ際は['slot']で指定できる。

基本は短くできるので、component内では$slotを使うほうがいい。

なお、$slotは扱いに注意が必要。使う側で指定がなかったら、nullではないが見えない変な値が入っている。変数として使う際は"$slot"のように二重引用符で囲んで、値がない場合に確実に空にしておく。

@include: Including Sub-Views

レイアウトやコンポーネントのように、変数の引き渡しなど複雑なことをしない場合、単純な定形固定文字列を読み込むような場合、Sub-Viewsというのを使うこともできる。

これは@includeでテンプレートファイルをそのまま読み込むのが基本。親の変数もそのまま使える。他に、引数で変数を渡すこともできる。

@include('view.name', ['some' => 'data'])
@includeIf('view.name', ['some' => 'data'])
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])

@includeで指定したテンプレートが不在の場合、Laravelはエラーを投げる。このエラーを回避したい場合、@includeIf指令を使う。@includeIfのバリエーションの一種で、配列のテンプレート名で存在する最初のものを使う場合、@includeFirstを使う。

条件がtrueなら読み込む場合、@includeWhenがある。

@include($boolean, 'view.name', ['some' => 'data'])

@ifよりもシンプル。

【Laravel】bladeの@includeと@componentの違い #PHP - Qiita

includeはcomponentと違って@endincludeがいらない。

componentはslotで渡すこともできる。$slotを使える点が大きな違い。

複雑で長いHTMLなどを、引き渡して使いたい場合、componentのほうがいい。

@each: Rendering Views For Collections

意外と使用頻度が高いのが繰り返し表示。例えば、リストの項目、テーブルの行など。これようの指令が@each

 @each('components.item', $data, 'item');

$data配列の要素をコンポーネントのitem変数に渡す。

// components/item.blade.php
<li>{{$item['name']}} [{{$item['mail']}}]</li>

Displaying Data

Displaying escaped Data

Bladeでデータを表示する際は、二重波括弧を使う。

Route::get('greeting', function () {
    return view('welcome', ['name' => 'Samantha']);
});
Hello, {{ $name }}.

二重波括弧は自動的にhtmlspecialcharsでXSS対策してくれる。変数の他に、PHP関数の結果も表示できる。

Displaying Unescaped Data

なお、生のHTMLなどのデータをそのまま表示させたい場合、二重波括弧の代わりに{!! !!}で囲む。

Hello, {!! $name !!}.
Hello, {!! e($name) !!}.

もっというと、e()でエスケープしておくと安心 (Bladeで変数に入れたhtml文字列を表示させる #Laravel - Qiita)。

Helpers - Laravel 5.8 - The PHP Framework For Web Artisans

混乱するが、二重波括弧内はPHP扱い、外はHTML扱い。二重波括弧内で表示文字列扱いしたいなら、文字列にする必要がある。

Rendering JSON

JavaScript変数として、配列をJSONとして描画したいことがあるだろう。

<script>
     var app = <?php echo json_encode($array); ?>;
     var app = {{json_encode($array)}};
</script>

手動でjson_encodeを呼ぶ代わりに、@json指令がある。

<script>
    var app = @json($array);
 
    var app = @json($array, JSON_PRETTY_PRINT);
</script>

なお、@jsonはjson_encodeと同じではない。以下のコードで{{json_encode部分を@jsonに変えるとうまくいかない。

<button type="button" onclick="
         ajaxGetAndSetTable('/ajax/楽楽販売_器具交換履歴_最新取得?消費者コード=' + document.getElementById('消費者コード').value, '#facilityHistory tbody', 
         {{json_encode($names)}}, '部屋番号');
     "
     >楽楽販売最新取得 (未実装)</button>
Blade & JavaScript Frameworks

JavaScriptフレームワークの中に、二重波括弧をプレースホルダーとしてそのまま使うものがある。そういう場合、@を波括弧に前置するとそのまま表示する。

<h1>Laravel</h1>
 
Hello, @{{ name }}.
The @verbatim Directive

JavaScript変数を表示する場合、波括弧の前に@をたくさん置かないで済むように、@verbatimで囲める。

@verbatim
    <div class="container">
                                                                    Hello, {{ name }}.
    </div>
@endverbatim
Control Structure/Blade Directives
If Statements
@if (count($records) === 1)
    I have one record!
@elseif (count($records) > 1)
    I have multiple records!
@else
    I don't have any records!
@endif

@unless (Auth::check())
    You are not signed in.
@endunless

@isset($records)
    // $records is defined and is not null...
@endisset
 
@empty($records)
    // $records is "empty"...
@endempty

@auth
    // The user is authenticated...
@endauth
 
@guest
    // The user is not authenticated...
@endguest

@auth('admin')
    // The user is authenticated...
@endauth
 
@guest('admin')
    // The user is not authenticated...
@endguest

@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>
 
    <div class="clearfix"></div>
@endif

特に@isset/@emptyをよく使うかも。ただ、これらには@elseはないので注意が必要かもしれない。やるなら、

@if(isset())
@else
@endif

@isset()
@endisset
@empty()
@endempty
Switch Statements
Loops

PHPの反復構造に近いものがある。重要。

@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor
 
@foreach ($users as $user)
    <p>This is user {{ $user->id }}</p>
@endforeach
 
@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse
 
@while (true)
    <p>I'm looping forever.</p>
@endwhile

公式マニュアルに記載がないが、foreachはPHPのforeachと同じく foreach($array as $key => $value) 形式にも対応している。

The Loop Variable

Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans

ループ内で使用可能な$loop変数があり、これに便利なプロパティーがある。

Property Description
$loop->index The index of the current loop iteration (starts at 0).
$loop->iteration The current loop iteration (starts at 1).
$loop->remaining The iterations remaining in the loop.
$loop->count The total number of items in the array being iterated.
$loop->first Whether this is the first iteration through the loop.
$loop->last Whether this is the last iteration through the loop.
$loop->even Whether this is an even iteration through the loop.
$loop->odd Whether this is an odd iteration through the loop.
$loop->depth The nesting level of the current loop.
$loop->parent When in a nested loop, the parent's loop variable.

first/last/even/oddあたりは特に便利だろう。

Additional Attributes

Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans

Laravel v9から使用可能。

@disabled

Comments

php - Laravel - Blade comments , blade rendering causing page to crash - Stack Overflow

Bladeテンプレートファイル内でのコメントには注意が必要。

{{-- code --}} これが基本
PHPコード扱いでのコメントアウトも便利。
@php
/* */
@endphp
<?php /* */ ?>

Bladeの{{-- code --}}は内部の{{}}が変数展開として解釈される。内部に波括弧がある場合は、phpコード扱いでコメントアウトしたほうが安全。

PHP

Bladeテンプレート内でPHPのコードをそのまま記述する際には、専用の指令を使う。

@php
@endphp

これを使うと、「Laravelのbladeで変数を定義する – FUNBREW」にあるように、変数を定義してBlade内で使うことができる。変数の定義や空チェックなどを最初にできるので、シンプルになる。

@php
$newValue = $somethingValue;
if (empty($newValue)) {
    $newValue = 'Not Defined';
}
@endphp

<div>{{ $newValue }}</div>
Forms
CSRF Field

アプリ内でHTMLフォームを使用する場合、CSRFトークンフィールドを記述する。

具体的には、form要素の冒頭に@csrfを指定する。

<form method="POST" action="/profile">
    @csrf
 
    ...
</form>
@stack/@push/@prepend: Stacks

名前付きのスタックに、他の場所で使用するビューやレイアウトを格納して、流用できる。

まず@pushする。使いたいか所で@stackすると取り出せる。

使い方としては、componentで@pushでscript要素やstyle要素を記述しておいて、レイアウトで@stackで呼び出す感じ。

@push('scripts')
    <script src="/example.js"></script>
@endpush
<head>
  
      @stack('scripts')
  </head>

順番が大事な場合、@prependでpushより先に詰め込める。

扱いは、sectionに似ている。componentのためのsectionのような感じだと思う。

pushしたものは描画のたびにstackで表示される。後のバージョンで@onceというのが登場したので、これを使えば1個だけになる。それまでは自作が必要。

push/stackを使わない場合、そのページに必要なくても、使う可能性のあるcss/jsを親で全部読み込んでおかないといけない。それを回避できる。

コンポーネントやテンプレート固有で必要なものを、同じファイルに記載しておいて、反映だけstackでまとめてできる。これが利点だろう。

Component

コンポーネントのパターンがある。

table

Table | Components | BootstrapVue

BootstrapVueを真似する。

{{--  
    fields = [field1, field2, field3]
    items = [[col1, col2, col3], {col1, col2, col3}]

    fields = [['label' => field1, 'class' => '', 'style' => ''], ['label' => field1, 'class' => '', 'style' => '']]
    items = [['label' => '', 'class' => '', 'style' => ''], ['label' => '', 'class' => '', 'style' => '']]
                                         --}}
<table class="table table-sm table-bordered">
                                              <thead class="bg-info text-center">
                                                  @foreach ($fields as $field)
                                                      @if (!is_array($field))
                                                          <th>{{ $field }}</th>
            @else
                <th class="{{empty($field['class']) ? '' : $field['class']}}"
                                                              style="{{empty($field['class']) ? '' : $field['style']}}">
                                                              {{empty($field['label']) ? '' : $field['label']}}</th>
            @endif
        @endforeach
    </thead>
    <tbody>
                                                  @foreach ($items as $row)
                                                      <tr>
                @foreach ($row as $column)
                    @if (!is_array($column))
                        <td>{{ $column }}</td>
                    @else
                        <td class="{{empty($column['class']) ? '' : $column['class']}}"
                                                                      style="{{empty($column['style']) ? '' : $column['style']}}">
                            {{empty($column['label']) ? '' : $column['label']}}</td>
                    @endif
                @endforeach
            </tr>
        @endforeach
    </tbody>
</table>
Other
Bladeのレンダー結果の取得

場合によっては、componentにBladeの描画結果を埋め込みたいことがある。

view関数でViewインスタンス作成後に、render/renderContents/getContentsを呼ぶと描画後のHTML文字列を取得できる。

これを使う。{!! $var !!} のような感じ。必要に応じてe()でエスケープする。renderを実行しなくても、Viewインスタンスをそのまま渡すとHTMLになっている。けど、エラーが出たときよくわからないのでrender()しておいたほうがいい。

パスの取得

現在表示ビューのパスを取得したいことがある。いくつか方法がある。

  • Request::path()
  • Route::current()->uri()

Request::path()がシンプル。

子ビューの変数の使用

基本は不能。全体で共有する方法ならある。

$slotの型

Bladeのcomponentなどで使用するslotはHtmlString。だから、これをstring扱いで、old($name) などに使うとエラーになる。oldは内部で、array_key_existsなどで、inputのキーなどをチェックしている。

$slotを他のプロパティーなどに引き渡す際に、toHtmlで文字列に変換して渡すとよい。

Database

LaravelのDBの取得結果は、以下のような行ごとの連想配列になっている。

[
['column1' => 1, 'column2' => 2],
['column1' => 3, 'column2' => 4],
]

Getting Started

Database: Getting Started - Laravel 5.8 - The PHP Framework For 1Web Artisans

クエリービルダー

use Illuminate\Support\Facades\DB;

上記のクラスメソッドで生SQLを使えeる。

Running Raw SQL Queries

LaravelのクエリビルダでSQL文を直接実行(select,insert,update,delete,その他) #Laravel - Qiita

DB::select/insert/update/deleteなどよく使うCRUD操作はメソッドがある。そういうのとは関係なしに、SQLを直実行する場合DB::statementを使う。

DB::statement('drop table users');

なお、DB::statementは1文ずつしか実行できない。複数実行する場合、1文ごとにDB::statementを記載する。

Query Builder

Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans

Retrieving Results

Aggregates

集約メソッドがある。

  • count: レコード数を返す。テーブルの行数などの把握で頻出。データの可否確認には、exists/doesntExistもある。がcountでいい気もする。
  • max
  • min
  • avg
  • sum
Joins
$users = DB::table('users')
            ->join('contacts', 'users.id', '=', 'contacts.user_id')
            ->join('orders', 'users.id', '=', 'orders.user_id')
            ->select('users.*', 'contacts.phone', 'orders.price')
            ->get();

Where Clauses

重要。いくつか書き方がある。

  • ->where('vote','=', 100);
  • ->where('vote', 100);: 演算子を省略すると=扱い。
  • ->where([['status', '=', '1'], ['subscribed', '<>', '1'],]);: 二次元配列にすれば一度に複数条件渡せる。キー・演算子・バリューのペア。
分割実行

php - Is it possible to split query builder in Laravel? - Stack Overflow

問題ない。

public function getStatuses($dates)
{
    $query = DB::table('tickets');
    if ($dates['from'])
        $query->where('from', $dates['from']);
    if ($dates['to'])
        $query->where('to', $dates['to']);
    $query->select('Active');
    return $query->get()->toArray();
}

whereの戻り値を変数に入れて流用しても問題ない。

Transaction

DB::transactionに処理の無名関数を指定してやると、DB処理が失敗したら自動的にロールバックする。

try {
$result = DB::transaction(function () use ($a, $b) {
    DB::table('users')->update(['votes' => 1]);
 
    DB::table('posts')->delete();
    return true;
});
} catch (Exception $e) {
    Log::error($e->getMessage());
    return;
}

2引数でデッドロック用のリトライ回数を指定できる。基本はいらない?

無名関数内でreturnすると、返せる。DB::transactionで失敗時の処理を個別にしたい場合、内部でthrowしてtry-catchをする。

Migrations

Database: Migrations - Laravel 5.8 - The PHP Framework For Web Artisans

マイグレーションはデータベースのバージョン管理のようなもの。データベーススキーマを簡単に修正・共有を可能にする。

Generating Migrations

make:migrationコマンドでマイグレーションファイルを作れる。

php artisan make:migration create_users_table

CRUD_テーブル名_tableなどのような命名規則にしておくとわかりやすい。

実行すると、database/migrationsディレクトリーにマイグレーションファイルが生成される。

オプションで--create=テーブル名、--table=テーブル名がある。

現在のテーブルの状況などで、マイグレーションファイルのひな形が変わる。ある程度マイグレーション名から推測してくれるが、日本語などが入るときかなくなる。オプションを指定したほうが無難。

Migration Structure

コマンド実行で生成されたテンプレートには、upとdownメソッドが用意されている。ここにテーブルの更新と、巻戻の処理を記載する。

Running Migrations

以下のコマンドで用意済みのマイグレーションを実行する。

php artisan migrate
Command
  • migrate:rollback: マイグレーションをロールバックする。デフォルトだと最後の1回分。--step=Nで回数を指定できる。
  • migrate:reset: 全部ロールバックする。
  • migrate:refresh: rollback+migrate。
  • migrate:fresh: ロールバックではなく、テーブルを削除してから戻す。
既存DBの途中からの管理

今後の変更分だけ管理するということもできる。

なお、中途半端にLaravelのマイグレーションファイルがある場合、migrationsテーブルにデータを手動で登録しておくと、マイグレーション済みと認識できる。

作ってあげるデータは、id,migration, batchを持つデータで、idは1からインクリメント、migrationはマイグレーションのファイル名(.phpは除く)、batchを1(たぶんこれは何度マイグレーションを実行したかを保っておくもので状態を1つ戻すときに使われるのでは?)に設定すればよい。

id migration batch
1 2017_09_01_134421_create_prefectures_table 1
2 2017_09_01_134422_create_cities_table 1

SQLだと以下相当。

insert into migrations(migration, batch) values('2015_12_08_134409_create_tables_script',1);
insert into migrations(migration, batch) values('2015_12_08_134410_create_foreign',1);
insert into migrations(migration, batch) values('2015_12_08_134411_create_index',1);
SQLでのマイグレーション

database - Laravel migration - is it possible to use SQL instead of schema commands to create tables and fields etc? - Stack Overflow

upメソッドにDB::updateなどで生のSQLをそのまま書けばOK。

class AddColumnsToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('name');
            $table->string('age');
            $table->timestamps();
        });

        DB::update('update users set age = 30 where name = ?', ['John']);
    }
}

downメソッドにはDropIfExistsで削除すればOK。

Seeding

Database: Seeding - Laravel 5.8 - The PHP Framework For Web Artisans

マイグレーションで用意したDBのデフォルトデータの用意の話。

デフォルトデータをシードと呼んでおり、シードを用意する機能をシーディングと呼んでいる。

シードを作成するためのスクリプト (シーダー) ファイルを生成して、記述して実行する流れ。

Writing Seeders

以下のコマンドでシーダーを作成する。

php artisan make:seeder UsersTableSeeder

database/seedsにファイルが作成される。

中身はrunメソッドがあるだけ。run内でinsertするだけ。

Calling Additional Seeders

追加したシーダーはそのままでは認識されない。

DatabaseSeederに登録する。DatabaseSeeder.phpのrunのcallメソッド内に追加する。

public function run()
{
    $this->call([
        UsersTableSeeder::class,
        PostsTableSeeder::class,
        CommentsTableSeeder::class,
    ]);
}
Running Seeders

追加したシーダーを認識させるために以下のコマンドでautoloadを更新する。

composer dump-autoload

シーダーを用意したらコマンドを実行して作成する。

php artisan db:seed

--classでシーダー名を指定もできる。あとからシーダーを追加する場合は--classで指定するイメージ。

Error

Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory"

php - Using Docker I get the error: "SQLSTATE[HY000 [2002] No such file or directory" - Stack Overflow]

PHPのPDOをDockerコンテナ内で使おうとしたところ、"No such file or directory" エラーが発生した話 #docker-compose - Qiita

dockerで.envのDB_HOSTの指定間違い。dockerのservice名をホスト名に指定する必要がある。

local.ERROR: could not find driver
[2024-07-02 15:35:42] local.ERROR: could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 0): could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:40)

このエラーはphpのモジュール不足。LaravelはPDOを使う。mysqliではなく。

RUN docker-php-ext-install pdo_mysql

これが必要。

Eloquent

Getting Started

Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans

Eloquent ORM。

Defining Models

make:modelコマンドでモデルインスタンスを作成できる。

php artisan make:model Flight

-mで新規作成のマイグレーションファイルも一緒に作れる。

Eloquent Model Conventions
Table Names

Laravelで「Base table or view not found: 1146 Table」エラーが出るときの対処法 #PHP - Qiita

Eloquentのモデルでは、デフォルトでクラス名を複数形のスネークケースにしたものをテーブル名とみなす。

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'your_db.your_models' doesn't exist (SQL: select * from `your_models`)

不在の場合、上記のエラーが出る。

デフォルト以外のテーブル名を使いたければ、$tableにテーブル名を指定する。

<?php
 
namespace App;

use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'my_flights';
}

プロジェクトでテーブル名に日本語を使う場合など、クラスメイとテーブル名が同一なら、以下のような親クラスを使って、これを継承すると同じ処理を記載しなくていい。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    function __construct()
    {
        // デフォルトだと複数形のスネークケースのテーブルを探すため、クラス名=テーブル名で上書き。
        $this->table = (new \ReflectionClass($this))->getShortName();
    }
}
Timestamps

LaravelのEloquentモデルでupdated_atがないテーブルを使う方法 #PHP - Qiita

php - Laravel Unknown Column 'updated_at' - Stack Overflow

デフォルトで、Eloquentはcreated_atとupdated_atカラムを期待する。使っていないならば、以下のエラーが出る。

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:82)
[stacktrace]

$timestamps=falseにしておく。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    public $timestamps = false;
}

Retrieving Models

Eloquentのモデルは、強力なクエリビルダーと考えてもいい。Eqloquentのallメソッドはモデルテーブルの全結果を返す。これはクエリービルダーとして、各モデルで提供されているので、条件を追加するなら、getメソッドを使う。

つまり、whereなどを使うなら、最後の取得はgetメソッドを使う必要がある。

Eloquentのモデルはクエリービルダーなので、クエリービルダーの全メソッドが使用可能。

モデル名::メソッドの他、select/where/findなども同じものを返すので、メソッドチェーンで呼び出せる。

Retrieving Single Models / Aggregates

モデルからデータを取得するいくつか主要なメソッドがある。

  • find: プライマリーキーから値を探す。
  • all: 全項目を取得する。
  • where:
  • findOrFail
  • findMany

メソッドチェーン

  • first: 先頭項目を取得する。
  • firstOrFail
  • count
  • max

【laravel】データベースの操作:Eloquent ORM編 #Laravel6 - Qiita

Eloquentのfindは扱いが特殊。引数が配列なら配列で返してくれる。

配列で結果が欲しければ、findManyを使う。findManyで常に配列で返すようにしたほうが、型が揃って扱いやすいかもしれない。

Inserting & Updating Models
Insert

モデルインスタンスを作成し、属性をセットし、最後にsaveを呼ぶ。

<?php
 
namespace App\Http\Controllers;
 
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
 
class FlightController extends Controller
{
    /**
     * Create a new flight instance.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        // Validate the request...
 
        $flight = new Flight;
 
        $flight->name = $request->name;
 
        $flight->save();
    }
}
Updates

Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans

$flight = App\Flight::find(1);
 
$flight->name = 'New Flight Name';
 
$flight->save();

saveメソッドはinsertだけでなく、updateにも使える。updated_atも自動で更新される。

クエリーのupdateで配列で複数レコードも一括更新できる。

注意点として、updateの一括更新時は、eloquentで本来発動するsaving/saved/updating/updatedのイベントは発動しない。

Mass Assignment

Add [ to fillable property to allow mass assignment onの解決方法 #初心者 - Qiita]

Add [_token] to fillable property to allow mass assignment on [App\]. 

こんなエラーが出る。

プロパティーの代入で1個ずつ設定するほかに、まとめて1行での保存もできる。ただし、一括割り当ては危険なので、保護されており、fillable属性かguarded属性の指定が必要。

fillメソッドは連想配列でデータをモデルにセットする。

$form = $request->all();
unset($form['_token']);
$flight->fill(['name' => 'Flight 22']);

フォームの項目と対応させておけばそのままセットできる。

fillable

まず、最初に一括割り当て可能な属性を定義する。

fillableプロパティーで、一括割り当て可能なカラムを定義できる。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name'];
}
Guarding Attributes

$fillableはホワイトリスト方式。$guardedも使用できる。一括割り当て可能にしたくない属性を指定する。ブラックリスト。 $fillableと$guardedのどちらかが最低限必要。

Other Creation Methods

insertもupdateもやることはほぼ同じなので、コードの冒頭で以下のような式でモデル生成部分だけ変更したらよいだろう。

        $model = empty($request['オーナーコード'])
            ? new 楽楽販売_オーナーマスタ : 楽楽販売_オーナーマスタ::find($request['オーナーコード']);

と思っていたが、もっといいのがあった。

firstOrCreate/firstOrNew

firstOrNewは自分でsaveできるように、モデルインスタンスを返してくれる (new モデル相当)。

// Retrieve flight by name, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
 
// Retrieve flight by name, or create it with the name, delayed, and arrival_time attributes...
$flight = App\Flight::firstOrCreate(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
 
// Retrieve by name, or instantiate...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
 
// Retrieve by name, or instantiate with the name, delayed, and arrival_time attributes...
$flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

1番目の配列の情報でモデルを検索して、不在ならば、2番目の配列の情報で作成する。

updateOrCreate

// If there's a flight from Oakland to San Diego, set the price to $99.
// If no matching model exists, create one.
$flight = App\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);

1番目の配列の情報で検索して、2番目の配列で更新する。1番目の情報で不在なら、全部の情報で更新する。

ただし、これらのメソッド類は、一度に1レコードずつしか更新できない。大量に行う場合、何回も発動するので遅いかもしれない。そこだけ注意が必要。あまり遅いなら、DBクエリーでやったほうがいいかもしれない。

Upserts

Laravel 8からupsertメソッドが登場して、一括更新が可能になった。以前から、updateOrCreateがあった。

Relationships

Eloquent: Relationships - Laravel 5.8 - The PHP Framework For Web Artisans

Defining Relationships
One To One
<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Get the phone record associated with the user.
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

主テーブルに外部キーが埋め込まれていて、シンプルな場合、上記のように外部テーブルをメソッド名にしてhasOneを実行すると全部返せる。非常にシンプル。

プロパティーとしてアクセス可能になる。1対1だから、以下のようにプライマリーキーなどで参照できる。

$phone = User::find(1)->phone;

デフォルトだと、外部キーは、 [モデル名_id] であることを想定している。これじゃないなら、以下のように引数で指定しておく。

return $this->hasOne('App\Phone', 'foreign_key');

そして、外部キーは、元のモデルのidか$primaryKeyと一致する想定になっている。これ以外のキーを使いたければ、hasOneの第3引数で、ローカルキーを指定する。

return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
One To Many

複数の他のモデルを持つ場合。例えば、ブログ投稿は、無限のコメントをもつ。こういう場合のコメント。がOne To Many。これはhasManyで関連付ける。

    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
Querying Relations
Querying Relationship Existence

リレーション先のテーブルで絞り込みたい場合、has/wherehasを使う。

ただ、このwherehasはコールバックなどを使わないといけなくて、少々面倒くさい。

joinしてフラットにして、カラム名にプレフィクスをつけたほうがわかりやすい。フラットにしなかったら、入れ子になって扱いが面倒になる。が、たいしてプレフィクスをつけるのと手間は変わらないから。

Eager Loading

Eloquentのリレーションにプロパティーでのアクセス時、リレーションデータは "lazy loaded" になる。これはプロパティーへのアクセスまで、実際には読み込まないことを意味する。つまり、アクセスのたびに読み込みが発生する。Eager Loadが可能になる。

Eagerは、熱心な、せっかちなという形容詞。

例えば、N件のデータを全部表示させたい場合、以下のようなコードを記述する。

<?php
 
namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Book extends Model
{
    /**
     * Get the author that wrote the book.
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}
$books = App\Book::all();
 
foreach ($books as $book) {
    echo $book->author->name;
}

最初に全取得+1、N件のデータを取得+Nで、合計1+N回のクエリーが発行される。

Eager loadによりこれを2回のクエリー発行で収めることができる。withメソッドを使う。

$books = App\Book::with('author')->get();
 
foreach ($books as $book) {
    echo $book->author->name;
}

これは具体的には以下のSQLの実行になる。

select * from books
 
select * from authors where id in (1, 2, 3, 4, 5, ...)

ただ、Eager loadすると、クエリーの数は減ってもモデルのアクセスが増えて、メモリーの使用量と実行速度が遅くなって、逆に悪化することがある (Laravel - Eager loading can be bad! : r/laravelLaravel - Eager loading can be bad! | Personal BlogHow the new 'One Of Many' Laravel relationship made my project 600 times faster - DEV Community)。

特に、hasManyの関係で、1対1じゃなくて、複数のモデルが入っている場合。

Laravel 8.42からLatestOfManyなどのメソッドが追加されており、これを使えば回避できる。これを使わない場合、JOINとMAXなどを駆使する必要があった。

ひとまず、hasManyとか、性能を見て、問題あったらeager loadingしたり工夫することにする。

複数のテーブルと結合したい場合、withに複数指定する。必要な列が決まっているなら、withのテーブル名の後に:で列名指定でOK。

Constraining Eager Loads
Other
リレーションの列名エイリアス

Eloquentでリレーションで結合時、カラム名が同じだと困る。

別名をつける方法がいくつかある。

  • .get/.select: ->get(['tags.name AS tag_name', 'products.*'])/ ->select('tags.name AS tag_name', 'products.*')
  • アクセサー

get/selectでやるのがよさそう。

Other

列の存在確認

いくつか方法がある。

  • use Illuminate\Support\Facades\Schema; Schema::hasColumn('テーブル名', '列名'): これが一番シンプル。
  • $model['列名']: インスタンス生成後ならこれ。
Eloquent vs. Query Builder

ORMとクエリービルダー。どこでどう使い分けるか?最初の2個の記事が参考になる。

  • ユーザーフォームのような基本的で簡単なCRUD処理はEloquent
  • 結合を含んだり、大量データの処理が必要な複雑な場合にクエリービルダー。

こういう方針で使い分けるといい。結合とかになると、モデルの範囲外になる。

レコードセットなのか、モデルなのかを意識する。

列名の取得
use Illuminate\Support\Facades\Schema;
$columns = Schema::getColumnListing('users'); // users table
dd($columns); // dump the result and die
        $headings = DB::connection()
            ->getSchemaBuilder()
            ->getColumnListing('colaborators');
::query

EloquentのORMのモデルを使用する際は、<Model>::findなどのメソッドを使う。

が、インスタンスを生成してから、条件に応じて処理を実行したい場合など、都合が悪いことがある。

インスタンス生成用に<Model>::query()がある。<Model>::query()->findなどのように、メソッドの前に挟める。

これを使う形で書いておくと、コードの書き方がきれいになったり、コード補完が効きやすかったりする。

SQL関数

how to Passing MySQL functions to Eloquent ORM query? - deBUG.to

Eloquentのwhere関数内でSQL関数は使えない。工夫が必要。

Database: Query Builder - Laravel 5.8 - The PHP Framework For Web Artisans

DB::rawで書く。

あるいは、selectRaw/whereRawなどを使う。

結果のオブジェクト・配列変換

EloquentのDB取得の結果は、配列になっている。が、DBクエリーの場合、オブジェクトになっている。

型が違っていろいろ困る。DBクエリーのオブジェクトを配列にしたい。

Eloquentを使わずに、配列にしたい場合、mapやtransformで変換必要。

$response['data'] = DB::table('customers')  
    // query conditions, etc
    ->get()
    ->map(function ($item, $key) {
        return (array) $item;
    })
    ->all();

最後のallも必要。これでEloquentと共通化できる。

Testing

Database Testing

シーダーでデータの自動登録方法を整理した。ただ、たくさんデータを登録してチェックする場合、ダミーデータを自動生成したい。Factoryというのでダミーデータを作成できる。

Generating Factories
php artisan make:factory PostFactory

database/factoriesに生成される。

--modelオプションで使用するモデル (テーブル名) を指定しておくと、テンプレートも作成してくれる。

Other

Facade

【Laravel】ファサードとは?何が便利か?どういう仕組みか? #初心者 - Qiita

インスタンスメソッドをstaticメソッドのように使うための仕組み。ややこしいだけなのでいらないかなと思う。

Controllerの共通処理

例えば、/city/nycで都市都市の一覧表示。/city/nyc/streetで該当都市の通りの一覧表示。こういうタイプの処理を都市ごとに実装するというようなことがよくある。

ただし、表示処理は共通処理があったりする。

この共通処理をどうまとめて実装するか?いくつか方法がある。

  • 親Controller
  • ファサード/トレイト (関数クラス)/サービスクラス
  • middleware
  • モデルに共通処理を定義して呼び出し。
  • validation/FormRequest

Controllerの前後に挟み込む感じならmiddleware、Controllerの処理の中でやりたいなら親Controllerか、ファサード/トレイト/サービスクラス、DB周りならModel?

基本はmiddlewareの模様。ビジネスロジックになるなら、サービスクラスを使うのがいい。トレイトでやる場合、app/traitsに格納する感じ。

やっぱりサービスクラスに出すのいい。

Coding Style Guide

PSR-2/4に準拠。

ローカル変数名やプロパティーの記法の指定がない。

Laravel本体のソースコードを見るとcamelCaseになっているのでこれに合わせる。

Bladeファイル名 [Laravel naming convention for blade files - Stack Overflow]

チェインケースかcamelCaseを推奨。特に決まってはいない。

framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework」にあるように、Laravel本体はチェインケースなので、チェインケースがよいだろう。

component類のslot名はcamelCase [framework/tests/View/Blade/BladeComponentTagCompilerTest.php at 7d26b7ee454a0ccc339db92a641487f668b44331 · laravel/framework]。