「PHP」の版間の差分
細 (→One To Many) |
|||
381行目: | 381行目: | ||
<!-- components/message.blade.php --> | <!-- components/message.blade.php --> | ||
<nowiki><div class="message"> | <nowiki><div class="message"> | ||
<p class="msg_title">{{$msg_title}}</nowiki><nowiki></p></nowiki> | |||
<nowiki><p class="msg_content">{{$msg_content}}</nowiki><nowiki></p></nowiki> | <nowiki><p class="msg_content">{{$msg_content}}</nowiki><nowiki></p></nowiki> | ||
<nowiki> </nowiki><nowiki>{{$slot}}</nowiki> | <nowiki> </nowiki><nowiki>{{$slot}}</nowiki> | ||
477行目: | 477行目: | ||
@verbatim | @verbatim | ||
<nowiki> </nowiki> <nowiki><div class="container"> | <nowiki> </nowiki> <nowiki><div class="container"> | ||
Hello, {{ name }}</nowiki>. | |||
<nowiki> </nowiki> <nowiki></div></nowiki> | <nowiki> </nowiki> <nowiki></div></nowiki> | ||
@endverbatim | @endverbatim | ||
642行目: | 642行目: | ||
<nowiki> </nowiki> fields = [['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>]] | <nowiki> </nowiki> fields = [['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => field1, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>]] | ||
<nowiki> </nowiki> items = [['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki><nowiki>]] | <nowiki> </nowiki> items = [['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki>], ['label' => <nowiki>''</nowiki>, 'class' => <nowiki>''</nowiki>, 'style' => <nowiki>''</nowiki><nowiki>]] | ||
--}}</nowiki> | |||
<nowiki><table class="table table-sm table-bordered"> | <nowiki><table class="table table-sm table-bordered"> | ||
<thead class="bg-info text-center"> | |||
@foreach ($fields as $field) | |||
@if (!is_array($field)) | |||
<th>{{ $field }}</nowiki><nowiki></th></nowiki> | |||
<nowiki> </nowiki> @else | <nowiki> </nowiki> @else | ||
<nowiki> </nowiki> <nowiki><th class="{{empty($field['class']) ? '' : $field['class']}}" | <nowiki> </nowiki> <nowiki><th class="{{empty($field['class']) ? '' : $field['class']}}" | ||
style="{{empty($field['class']) ? '' : $field['style']}}"> | |||
{{empty($field['label']) ? '' : $field['label']}}</nowiki><nowiki></th></nowiki> | |||
<nowiki> </nowiki> @endif | <nowiki> </nowiki> @endif | ||
<nowiki> </nowiki> @endforeach | <nowiki> </nowiki> @endforeach | ||
<nowiki> </nowiki> <nowiki></thead></nowiki> | <nowiki> </nowiki> <nowiki></thead></nowiki> | ||
<nowiki> </nowiki> <nowiki><tbody> | <nowiki> </nowiki> <nowiki><tbody> | ||
@foreach ($items as $row) | |||
<tr></nowiki> | |||
<nowiki> </nowiki> @foreach ($row as $column) | <nowiki> </nowiki> @foreach ($row as $column) | ||
<nowiki> </nowiki> @if (!is_array($column)) | <nowiki> </nowiki> @if (!is_array($column)) | ||
663行目: | 663行目: | ||
<nowiki> </nowiki> @else | <nowiki> </nowiki> @else | ||
<nowiki> </nowiki> <nowiki><td class="{{empty($column['class']) ? '' : $column['class']}}" | <nowiki> </nowiki> <nowiki><td class="{{empty($column['class']) ? '' : $column['class']}}" | ||
style="{{empty($column['style']) ? '' : $column['style']}}"></nowiki> | |||
<nowiki> </nowiki> <nowiki>{{empty($column['label']) ? '' : $column['label']}}</nowiki><nowiki></td></nowiki> | <nowiki> </nowiki> <nowiki>{{empty($column['label']) ? '' : $column['label']}}</nowiki><nowiki></td></nowiki> | ||
<nowiki> </nowiki> @endif | <nowiki> </nowiki> @endif | ||
1,061行目: | 1,061行目: | ||
デフォルトだと、外部キーは、 [モデル名_id] であることを想定している。これじゃないなら、以下のように引数で指定しておく。 | デフォルトだと、外部キーは、 [モデル名_id] であることを想定している。これじゃないなら、以下のように引数で指定しておく。 | ||
return $this->hasOne('App\Phone', 'foreign_key'); | 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 ====== |
2024年8月2日 (金) 11:23時点における版
Library
Framework
- Symfony
- CakePHP
- FuelPHP: 2010年誕生。
- Codeigniter: シンプル、軽量。
- Zend
- Laravel: 2011年誕生。
- Phalcon
- Yii - Wikipedia
Template
いろいろある。
- Blade: Laravel標準。
- DIV: 1ファイルでシンプル。大規模には向かない。
- Smarty: 万能。
- Twig: 拡張はしにくい。
使うとしたら、歴史の長いSmarty。
- Smartyとは?基礎知識と具体的なメリットをわかりやすく解説 - システム開発のプロが発注成功を手助けする【発注ラウンジ】
- Smartyとは? - smarty @Wiki - atwiki(アットウィキ)
高速らしい。
そもそもテンプレートエンジンがいるのかどうかという議論がある。
- Smarty って、要らなくない? — INWORKS
- 素のPHPはもはやテンプレートエンジンとしては使えない - ぱせらんメモ
- 本当に倒すべきだったのは jQuery ではなくテンプレートエンジンだった - fsubal
- サーバサイドHTMLテンプレートからの脱却のススメ (1/4)|CodeZine(コードジン)
UI/UXを突き詰めると、JavaScriptを使わざるを得ず、サーバーテンプレートエンジンは初回だけなので、いっそのことJSで全部やろうというのが最近の流れの模様。
PHP自体が一種のテンプレートエンジンという主張がある。が、関数をあれこれ書く必要があり、可読性が悪い。
SmartyよりTwigのほうが性能が上とか。
「Volt: テンプレートエンジン — Phalcon 3.0.2 ドキュメント (Japanese / 日本語)」。高速フレームワークのPhalconではVoltを使っている。
Twig
- 素のPHPはもはやテンプレートエンジンとしては使えない - ぱせらんメモ
- Templating Engines in PHP |Articles - Fabien Potencier
- PHPテンプレートエンジンを使おう Twig編 | AkisiのWEB制作日記
- Decoded Node: Smarty vs. Twig (a benchmark done poorly)
- GitHub - AliShareei/smarty-vs-twig-benchmark: A benchmark of up-to-date versions of Smarty and Twig templating engines
- GitHub - dominics/smarty-twig-benchmark: A benchmark of up-to-date versions of Smarty and Twig templating engines
- The fastest template engine for PHP | by Serge Gotsuliak | Medium
- Pure PHP/HTML views VS template engines views - Stack Overflow
Twig v3のほうが速いらしいが、Smarty v3のほうが速いというデータもある。
Smarty
- GitHub - smarty-php/smarty: Smarty is a template engine for PHP, facilitating the separation of presentation (HTML/CSS) from application logic.
- Smarty Documentation
「Pure PHP/HTML views VS template engines views - Stack Overflow」が決定的だった。Smartyの開発者がSmartyのほうがTwigより速いと回答していた。2012年。Smartyでいいと思う。
ORM
Ref:
いろいろある。Doctrineが有名。
- Doctrine: Symfonyで採用。有名。
- Eloquent
- Propel: Symfony v1.2で採用されていた。
- PHP activerecord
- PHPDAO
- PDO: PHP標準。
- Xyster
ただ、速度を優先する場合、PDOが最速になるらしい。
ORMは別になくてもいいか。
Migrate
- Phinx
- Doctrine Migrations
「PHPで「Doctrine Migrations」を使ってみる」
CakePHPに採用されているPhinxのほうが人気なのでPhinxを使ったほうがよいだろう。
Test
- PHPUnit
Search
検索キーワードをフォームから受信後、DBにSQLで検索をかけて取得結果を返すのが基本。
それとは別に、検索用のアプリにリクエストを受け渡しして検索するという方法がある。どちらでもいけるような、ドライバーのライブラリーがある。
- Laravel Scout - Laravel 9.x - The PHP Framework For Web Artisans
- teamtnt/tntsearch: A fully featured full text search engine written in PHP
- ruflin/elastica - Packagist (40 Best PHP Libraries For Web Applications in 2022)
検索サービスで有名なのは以下。
- ElasticSearch/OpenSearch
- MeiliSearch
- Algolia
- Sphinx Search (Sphinx | Open Source Search Engine)
- Apache Solr (Welcome to Apache Solr - Apache Solr)/Apache Lucene (Apache Lucene - Welcome to Apache Lucene)
Laravel Scoutでtntsearchを使う方法がある (Laravel Scout + TNTSearchによる小規模プロジェクトへの全文検索機能の追加 #PHP - Qiita/Laravel ScoutとTNTSearchを使用してサイト全文検索を実装してみる – helog)。
「Packagist」の検索結果をみても、tntsearchが特に人気の模様。
Laravel
人気の理由
「Laravelの人気を大検証 何が凄いの? | テクフリ」
CakePHPのほうが早い。
- 簡単にマスター可能
- 自由度が高い
Getting Started
Configuration
Configuration - Laravel 5.8 - The PHP Framework For Web Artisans
Environment Configuration
DotEnvライブラリーを使っており、ルートディレクトリーの.env.exampleを.envにリネームするとこの設定を利用する。
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 Container - Laravel 5.8 - The PHP Framework For Web Artisans
- Understanding Laravel Service Classes: A Comprehensive Guide | by Laravel Pro Tips | Medium
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
ルートに名前を付けて、後で流用・参照できる。
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();
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') }}">
Views
- Views - Laravel 5.8 - The PHP Framework For Web Artisans
- Helpers - Laravel 5.8 - The PHP Framework For Web Artisans
- Laravelで現在のユーザーを全ページで共有したい時はview composerが便利 | 40代からプログラミング!
- Illuminate\View\View | Laravel API
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などを使う。
debugbar
- barryvdh/laravel-debugbar: Debugbar for Laravel (Integrates PHP Debug Bar)
- Laravel Debugbarについて #Laravel - Qiita
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扱い。二重波括弧内で表示文字列扱いしたいなら、文字列にする必要がある。
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
t
特に@isset/@emptyをよく使うかも。
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
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
- bladeのcomponent化による再利用 #PHP - Qiita
- 最適化されたWebページデザイン: Laravel Bladeで個別ページのJavaScriptとCSSファイルを効果的に追記・管理する詳細ガイド|DAD UNION - エンジニア同盟
名前付きのスタックに、他の場所で使用するビューやレイアウトを格納して、流用できる。
まず@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の描画結果を埋め込みたいことがある。
- php - How to get Blade template view as a raw HTML string? - Stack Overflow
- Illuminate\View\View | Laravel API
- framework/src/Illuminate/View/View.php at 5.6 · laravel/framework
view関数でViewインスタンス作成後に、render/renderContents/getContentsを呼ぶと描画後のHTML文字列を取得できる。
これを使う。{!! $var !!} のような感じ。必要に応じてe()でエスケープする。renderを実行しなくても、Viewインスタンスをそのまま渡すとHTMLになっている。けど、エラーが出たときよくわからないのでrender()しておいたほうがいい。
パスの取得
- How to Get the Current URL Inside @if Statement (Blade) in Laravel 4? - Stack Overflow
- How to get Current url path
現在表示ビューのパスを取得したいことがある。いくつか方法がある。
- Request::path()
- Route::current()->uri()
Request::path()がシンプル。
Database
Getting Started
Database: Getting Started - Laravel 5.8 - The PHP Framework For Web 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を記載する。
Transaction
- Database: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans
- 新卒がLaravelのトランザクション実装について学んだこと #初心者 - Qiita
- Laravelのトランザクションとエラーハンドリグについて | Happy Life Creators
- Laravelのトランザクション処理を2パターン徹底解説 | 現役プログラマーYuiの開発ブログ
- Laravelでトランザクション処理のエラーハンドリングを行う方法 | Shinya Sunamachi
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のmigrateを途中から実行する - mk-toolブログ
- Laravel 既存DBをマイグレーションする方法
- Laravel Migration: how to auto-skip tables that already exist? : r/laravel
今後の変更分だけ管理するということもできる。
なお、中途半端に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でのマイグレーション
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するだけ。
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
Eqloquentのallメソッドはモデルテーブルの全結果を返す。これはクエリービルダーとして、各モデルで提供されているので、条件を追加するなら、getメソッドを使う。
つまり、whereなどを使うなら、最後の取得はgetメソッドを使う必要がある。
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(); } }
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のどちらかが最低限必要。
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'); }
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/laravel、Laravel - Eager loading can be bad! | Personal Blog、How 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を使用した様々なデータの取得方法
- [Laravelwithメソッドを理解する #初心者 - Qiita]
複数のテーブルと結合したい場合、withに複数指定する。必要な列が決まっているなら、withのテーブル名の後に:で列名指定でOK。
Constraining Eager Loads
Other
リレーションの列名エイリアス
- laravel Model の with() のリレーション先カラム名を変更して取得したい
- php - Laravel 4 Eloquent Column Alias - Stack Overflow
Eloquentでリレーションで結合時、カラム名が同じだと困る。
別名をつける方法がいくつかある。
- .get/.select: ->get(['tags.name AS tag_name', 'products.*'])/ ->select('tags.name AS tag_name', 'products.*')
- アクセサー
get/selectでやるのがよさそう。
Other
列の存在確認
- Test attributes/columns existence
- php - Check if column exist in Laravel model's table and then apply condition - Stack Overflow
いくつか方法がある。
- use Illuminate\Support\Facades\Schema; Schema::hasColumn('テーブル名', '列名'): これが一番シンプル。
- $model['列名']: インスタンス生成後ならこれ。
Testing
Database Testing
- Database Testing - Laravel 5.8 - The PHP Framework For Web Artisans
- 簡単!Laravelで日本語の仮データをDBに自動生成(factoryとseeder) - ネビ活 | ネットビジネス生活
シーダーでデータの自動登録方法を整理した。ただ、たくさんデータを登録してチェックする場合、ダミーデータを自動生成したい。Factoryというのでダミーデータを作成できる。
Generating Factories
php artisan make:factory PostFactory
database/factoriesに生成される。
--modelオプションで使用するモデル (テーブル名) を指定しておくと、テンプレートも作成してくれる。
Other
Facade
【Laravel】ファサードとは?何が便利か?どういう仕組みか? #初心者 - Qiita
インスタンスメソッドをstaticメソッドのように使うための仕組み。ややこしいだけなのでいらないかなと思う。
Controllerの共通処理
- php - Common logic between various Laravel controllers method - Stack Overflow
- Laravelの実装について、複数のControllerから扱... - Yahoo!知恵袋
例えば、/city/nycで都市都市の一覧表示。/city/nyc/streetで該当都市の通りの一覧表示。こういうタイプの処理を都市ごとに実装するというようなことがよくある。
ただし、表示処理は共通処理があったりする。
この共通処理をどうまとめて実装するか?いくつか方法がある。
- 親Controller
- ファサード/トレイト (関数クラス)/サービスクラス
- middleware
- モデルに共通処理を定義して呼び出し。
- validation/FormRequest
Controllerの前後に挟み込む感じならmiddleware、Controllerの処理の中でやりたいなら親Controllerか、ファサード/トレイト/サービスクラス、DB周りならModel?
基本はmiddlewareの模様。ビジネスロジックになるなら、サービスクラスを使うのがいい。
Coding Style Guide
PSR-2/4に準拠。
ローカル変数名やプロパティーの記法の指定がない。
- framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework
- framework/src/Illuminate/Foundation/Console/ServeCommand.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework
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]。
CakePHP
Basic
Structure
- bin: cakeコマンド類。
- config: CakePHPの全体設定にかかるファイル群。
- logs
- plugins
- src
- tests
- tmp
- vendor
- webroot: アプリケーションルート。
Convention
Ref: CakePHP の規約 - 3.10.
テーブル名は小文字、複数形、アンダーバー区切り。
Routing
Ref: ルーティング - 3.10.
config/routes.phpでルーティングの基本が設定される。ここのコードで、後述のMVCがある程度自動設定されている。
ほかに、アプリケーショントップの/アクセス時の処理を行う、重要な設定もここで行う。/はMVCの命名規則になっていないので、ここは手動設定?的なことが必要になる。基本はここがLoginになる。
Router::scopeとRouter::connectを使って処理する。
scopeはルーティングのグループ化。connectで個別に紐づける。
Router::scope('/blog', ['plugin' => 'Blog'], function ($routes) {
$routes->connect('/', ['controller' => 'Articles']);
});
例えば、上記は/blogへのアクセスをArticlesController.index()に紐づける。
connectの第二引数にいろいろパラメーターを指定できて、actionのメソッドを指定できたりする。
Controller
AppControllerを継承して定義する。indexメソッドを最低限実装しておく。
XXXControllerのXXXの小文字部分がURLになっていて、これでアクセスするとindexが発動する。
メソッド名が、パスになっていて、それで表示される。このパスメソッドを一般的にacitonと呼んでいる。
$this->request
リクエストに関する情報が入っている。PHPの$_FILESとか$_POSTなどを使わなくも、これを使えばよくなる。
2次元配列にもなっているが、1次元配列部分はプロパティーとしてもアクセスできる。
- params: 送信されたすべてが入っている。
- data: POSTのボディー。
- query: URLクエリー。
- url
- base
- webroot
- here:
特にdataをよく使うだろう。
Component
Ref: コンポーネント - 3.10.
コントローラー間で共有されるロジックのパッケージ。認証など処理を共通化できる。
$this->loadComponent('コンポーネント');
これでコンポーネントを読み込めて、以降は$this->コンポーネントでインスタンスにアクセスできる。
View
Ref: ビュー - 3.10.
Controllerで表示もできるものの、Viewで表示部分をメインにすることもできる。
Viewはビューテンプレートとレイアウトで大きく構成される。
テンプレートは実際のページ表示用。レイアウトは、テンプレートを含み、ヘッダー、フッターなどをまとめている。
レイアウトを用意して、その中にビューテンプレートを埋め込んで表示するイメージ。これにより、ページ全体でレイアウトを統一しやすくなる。
テンプレートファイルのデフォルトの拡張子は.ctp (CakePHP Templateの略)。
Template
src/TemplateがViewテンプレート。ここにコントローラー名に対応したディレクトリーを作って、そこにViewテンプレートを作成する。
例えば、src/Template/Helloにindex.ctpを用意する。
Controller側で、public $autoRender = true;があると、テンプレートを使う。命名規則で自動的に。
そして、$this->viewBuilder()->autoLayout(false) でレイアウトの使用可否を設定する。
index.ctpはテンプレートなので、ここにPHPコードを記入してもOK。
テンプレート内では変数が使える。この変数は、コントローラーで$this->setで設定されたものになる。
基本的には以下のようなphpコードで埋め込む。
<?php echo $variable; ?> <?= $variable ?>
Layout
デフォルトで使用するレイアウトは、src/Template/Layout/default.ctpにある。
ここにファイルを追加することが、レイアウトの作成になる。
レイアウト内で、テンプレートを表示する関数類がある。
- $this->fetch('content'): ビューテンプレート
Controller側では、$this->viewBuilder()->layout('Hello');でレイアウトを指定できる。
Element
レイアウトよりも小さいパーツ。ボタンなど、流用するようなものをElementとして用意できる。
$this->element(エレメント名, [キー=>値,...])で表示できる。
src/Template/Element内に作成する。エレメント名.ctpで作成する。キー=>値の関係で、パラメーターを渡せる。
Controllerのaction内でsetで、layout内で使用する変数を指定できる。これで、layout-element間で変数を渡せる。
Model
DBを処理する部分。
具体的には、テーブルクラス (テーブルへのアクセス処理) とエンティティークラス (テーブルから取り出した1行分のデータのための列の表現) で実現する。
CakePHP内では、モデル名Table/モデル名でそれぞれ命名する。テーブルクラスは複数形、エンティティークラスは単数形で扱う。テーブル自体も複数形。
CakePHPでDBにアクセスする際には、まず設定ファイルにDBの情報を用意する。Config/app.phpのDatasourcesのdefaultに入力する。
モデル自体は、src/Model内に配置する。EntityとTableディレクトリーでそれぞれ用意する。
継承するだけで、基本的な動作は機能する。
Controllerから、これらのモデルにアクセスする。
DBへのアクセスにはORMのQueryBuilderとSQLベースのConnectionManagerがある。
ConnectionManager
Ref: データベースの基本 - 3.10.
SQLをそのまま発行するタイプのクラス。
$conn = ConnectionManager::get('設定名'); $statement = $conn->execute('SQL'); $statement->fetchAll('num' | 'assoc');
ConnectionManagerからConnectionインスタンスを取得し、DBオブジェクトとして扱う。
executeでステートメントを取得し、statementのメソッド呼び出しのタイミングでSQLが実行される。
実行結果は配列。numを指定すると、キーが数字、assocだと項目名 (カラム名) がキーになる。基本はassocを指定するとよい模様。
TableRegistry
Ref: Saving Data - 3.10.
use Cake\ORM\TableRegistry; // Prior to 3.6 use TableRegistry::get('Articles') $articlesTable = TableRegistry::getTableLocator()->get('Articles'); $article = $articlesTable->get(12); // Return article with id 12 $article->title = 'CakePHP is THE best PHP framework!'; $articlesTable->save($article);
組み込まれていないテーブルはgetで取得する必要がある。
Basic
Query logging
Ref: Database Basics - 3.10.
// クエリーログを有効 $conn->logQueries(true); // クエリーログを停止 $conn->logQueries(false);
CakePHPのクエリービルダーの実際のSQLを確認できる。
「[cakephp Queryオブジェクトから実行したSQL生成 #MySQL - Qiita]」にあるように、他に、sql()やgetValueBinder()->bindings();でも取得できる。
$this->log($this->MBase->findMBase($options)->sql());
データの取り出しと結果セット
Ref: データの取り出しと結果セット - 3.10.
hydrate(false)でfindなどした際の結果を、オブジェクトではなく配列にできる。
Migration
Ref:
PHPファイルでDBのスキーマ変更を行うための仕組み。VCSでDB設定を管理でき、コマンドでDBの設定などを行える利点がある。
CakePHPでは、Phinxをマイグレーションに使っており、コマンド類はPhinxのラッパーになっている。細かいことはPhinxにあたる必要がある。
config/Migrationsディレクトリーに、マイグレーションファイルを配置し、以下のmigrationsコマンドの実行でDBにテーブルを作成できる。
bin/cake migrations migrate bin/cake migrations rollback
戻す場合はrollback。
マイグレーションファイルは、config/Migrationディレクトリーで、YYYYmmddHHMMSS_MigrationName.phpというように、作成日を入れて用意する。
自分で手作業でマイグレーションファイルを作成できるが、bakeコマンドでひな形を用意できる。これを使ったほうがおそらくいい。
マイグレーションファイル
Valid Column Types
Phinx
で一般的に利用可能なフィールドの型は次の通り:
- string
- text
- integer
- biginteger
- float
- decimal
- datetime
- timestamp
- time
- date
- binary
- boolean
- uuid
このほかに、以下も可能。
In addition, the MySQL adapter supports enum
, set
, blob
, tinyblob
, mediumblob
, longblob
, bit
and json
column types (json
in MySQL 5.7 and above). When providing a limit value and using binary
, varbinary
or blob
and its subtypes, the retained column type will be based on required length (see Limit Option and MySQL for details);
In addition, the Postgres adapter supports interval
, json
, jsonb
, uuid
, cidr
, inet
and macaddr
column types (PostgreSQL 9.3 and above).
既存のテーブルにカラムを追加
以下のコマンドでカラム追加を含むコードを作成できる。
bin/cake bake migration AddPriceToProducts price:decimal
<?php use Migrations\AbstractMigration; class AddPriceToProducts extends AbstractMigration { public function change() { $table = $this->table('products'); $table->addColumn('price', 'decimal') ->update(); } }
addColumnの3引数にいろいろパラメーターを指定できる。
public function change() { $table = $this->table('m_grade'); $table->addColumn('image_data', 'blob', [ 'default' => null, 'limit' => Phinx\Db\Adapter\MysqlAdapter::BLOB_MEDIUM, 'null' => true, 'after' => 'image_path', ]); $table->update(); }
afterで直前の列を指定できる。
Blobの対応
- CakePHP Migrations の limit オプションについて #PHP - Qiita
- cakephp3 の Migration で mediumblob を認識させる #MySQL - Qiita
CakePHP自体は、MySQLのblobには直接は対応していない。binaryでひな形を作って、手動でlimitを変更する。
列の更新
Updating columns name and using Table objects
If you use a CakePHP ORM Table object to manipulate values from your database along with renaming or removing a column, make sure you create a new instance of your Table object after the
update()
call. The Table object registry is cleared after anupdate()
call in order to refresh the schema that is reflected and stored in the Table object upon instantiation.https://book.cakephp.org/migrations/3/en/#updating-columns-name-and-using-table-objects
updateした後に、CakePHPのORMを使う模様。
// Update exisiting column default row data from path. $mbaseTable = TableRegistry::get('m_base'); foreach($mbaseTable->find() as $row) { $file_path = WWW_ROOT . $row->LOGO_PATH; if (is_readable($file_path)) { $raw_data = file_get_contents($file_path); if ($raw_data) { $row->LOGO_DATA = $raw_data; $mbaseTable->save($row); } } }
こういうイメージ。
Debug
Ref:
CakePHP関係のクラスであればlogメソッドがある。
他に、Cake\Log\Log::write()
のstaticメソッドもある。
Log::debug('text')などで使う。
- pr: print_r+pre
- debug:
- dd: debug+die
Log::debug()を使うと、logs/debug.logに出力される。
Topic
Pagination
コントローラーのpublic $paginateプロパティーに、ページネーションの設定を記載する。
その後、$this->paginate(テーブル)で自動的にページネーションされたデータを取得できる。
Test
Ref: テスト - 3.10.
CakePHPはPHPUnitでのテストに対応している。
まずDBをテスト用に置換する。config/app.phpのDatasourcesにtestを追加しておく。
bakeコマンドの中で、fixtureとtestが関係するコマンド。
まずfixtureでテストデータを作成する。
cake bake fixture <model>
modelでMVCの一単位を指定する。
続いて以下のコマンド群でMVCのテストファイルを作成。
cake bake test controller <Controller> cake bake test entiity <Entity> cake bake test table <Table>
testsディレクトリー内の以下に生成される。
- Fixture: テーブル名Fixture.php
- TestCase: クラス名Test.php
Test.php内で、以下のコードで該当Fixtureを自動読み込みする。
public $fixtures ['app.テーブル名'];
TestCaseクラスがCakePHP特有で、これでうまくやっている模様。
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
class ArticlesTableTest extends TestCase
{
public $fixtures = ['app.Articles'];
public function setUp()
{
parent::setUp();
$this->Articles = TableRegistry::getTableLocator()->get('Articles');
}
public function testFindPublished()
{
$query = $this->Articles->find('published');
$this->assertInstanceOf('Cake\ORM\Query', $query);
$result = $query->enableHydration(false)->toArray();
$expected = [
['id' => 1, 'title' => 'First Article'],
['id' => 2, 'title' => 'Second Article'],
['id' => 3, 'title' => 'Third Article']
];
$this->assertEquals($expected, $result);
}
}
そして、setUp内で、まずはテーブルをメンバー変数に格納して、それを参照する形。
Error
PHP message: PHP Warning: file_put_contents(/var/www/html/logs/error.log) [<a href='http://php.net/function.file-put-contents'>function.file-put-contents</a>]: failed to open stream: Permission denied in /var/www/html/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php on line 133
起動してトップ画面を開くと、logs/error.logに以下のエラー。
2024/04/04 07:27:48 [error] 30#30: *1 FastCGI sent in stderr: "PHP message: PHP Warning: file_put_contents(/var/www/html/logs/error.log) [<a href='http://php.net/function.file-put-contents'>function.file-put-contents</a>]: failed to open stream: Permission denied in /var/www/html/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php on line 133 PHP message: PHP Warning: file_put_contents(/var/www/html/logs/error.log) [<a href='http://php.net/function.file-put-contents'>function.file-put-contents</a>]: failed to open stream: Permission denied in /var/www/html/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php on line 133
パーミッションの設定がDockerに不足している?
「CakePHP Error Warning: file_put_contents(/var/www/html/logs/error.log) #PHP - Qiita」にあるように、
docker exec -ti php_jaccs_auto bashでchmod a+w ./logs/*相当を実行すると解決した。ただし、事前にWindowsのExplorerで書き込みを許可しておく必要がある。git bashのchmodはWindowsには機能しない。
Warning: Warning (512): SplFileInfo::openFile(/var/www/html/tmp/cache/persistent/myapp_cake_core_translations_cake_en__u_s) [<a href='http://php.net/splfileinfo.openfile'>splfileinfo.openfile</a>]: failed to open stream: Permission denied in [/var/www/html/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php, line 398]
Ref: CakePHP3でWarning Error: SplFileInfo::openFile()エラーが発生した場合の対処方法 | エス技研.
app.phpにmask=>0666を追加する。既存のキャッシュファイルは削除しておく。
dockerのアクセス権www-dataの問題の気がする。