PHPerのための「PHPフレームワーク」を語り合うPHP TechCafe
==================================================
清書→https://hackmd.io/@E6hhS-xhQIqrpKokpl_yQQ/rkZtoNGwi
https://rakus.connpass.com/event/264108/
# PHPer's NEWS
# 特集
## そもそもフレームワークとは?
* Webアプリケーションフレームワーク とは([wiki](https://ja.wikipedia.org/wiki/Web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF))
* 動的な ウェブサイト、Webアプリケーション、Webサービスの開発をサポートするために設計されたアプリケーションフレームワーク
* Web開発で用いられる共通した作業に伴う労力を軽減する
* データベースへのアクセス
* テンプレートエンジン
* セッション管理
* コードの再利用を促進させる
* そもそもFWってなんで必要?
* 開発速度向上
* Webアプリケーション開発でよく利用する処理(セッション管理やDBアクセス、Cookieなど)が既に用意されているため、それらを再利用するだけで開発が進められる
* セキュリティ対応
* 脆弱性が見つかった場合に修正版がリリースされる
* メンテされているOSSの場合
* 開発ルールの順守
* フレームワークのルールに従って作成することが強いられる反面、開発チーム全体で共通のルールで開発できるため、ルールに逸脱するようなコードが生まれにくい
## 設計思想
## ルーティング
## セッション管理
## リクエスト管理
## エラーハンドリング
## DBサポート
* コネクション
* マイグレーション
## バリデーション
## マイグレーション
## 調査対象のフレームワーク
* Laravel(浅野、沼本)
* Symfony(森下、久山)
* CakePHP(北岡、加納
* Slim / Lumen(廣部、神山)
### 調査対象外(MTG2以降)
* Yii
* FuelPHP
* Laminas
* 生PHP
-------------------------
### Laravel(浅野、沼本)
#### [設計思想](https://laravel.com/docs/9.x/installation)
- プログレッシブフレームワーク
- we mean that Laravel grows with you.
- Laravel はあなたと共に成長するということです。
- Laravel は依存性注入、単体テスト、キュー、リアルタイム イベント などのための堅牢なツールを提供します。
- スケーラブルなフレームワーク
- Laravel は信じられないほどスケーラブルです。
- 実際、Laravel アプリケーションは、1 か月あたり数億のリクエストを処理するように簡単にスケーリングされています。
- コミュニティ フレームワーク
#### [ルーティング](https://laravel.com/docs/9.x/routing)
##### デフォルトルートファイル
デフォルトでは下記2つファイルにルーティングを定義する
- routes/web.php
- routes/api.php
##### 定義方法
UserControllerにindxメソッドを定義している場合
下記のように定義すると`/user`のパスに対して、UserControllerのindexメソッドが対応される
```php=
<?php
use App\Http\Controllers\UserController;
Route::get('/user', [UserController::class, 'index']);
```
UserController.phpが下記のような内容だと`resource/user/index.blade.php`
```php=
<?php
public function index()
{
return view('user.index');
}
```
##### 利用可能なルーターメソッド
```php=
<?php
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
```
##### ルーティングの一覧表示
下記コマンドで定義したルーティング一覧を出力することができる
```
php artisan route:list
```
##### よく使われる方法
###### ルートパラメータ
```php=
<?php
Route::get('/posts/{post}/comments/{comment}', [CommentController::class, 'show']);
```
CommentController.phpが下記のように引数に指定することでコントローラー内でルートパラメータを使うことができる
```php=
<?php
public function index($post, $comment)
{
//
}
```
###### 名前付きルート
定義したルートに名前を付けることができる。
```php=
<?php
Route::get('/user/profile', function () {
//
})->name('profile');
```
#### [セッション管理](https://laravel.com/docs/9.x/session)
##### 設定ファイル
config/session.php
##### セッションドライバ
file- セッションは に保存されstorage/framework/sessionsます。
cookie- セッションは安全で暗号化された Cookie に保存されます。
database- セッションはリレーショナル データベースに保存されます。
memcached/ redis- セッションは、これらの高速なキャッシュ ベースのストアの 1 つに保存されます。
dynamodb- セッションは AWS DynamoDB に保存されます。
array- セッションは PHP 配列に保存され、永続化されません。
##### セッションの操作方法
グローバルヘルパーとRequestインスタンス経由の2 つの主な方法があります。
###### グローバルセッションヘルパー
```php=
<?php
$value = session('key');
```
###### Requestインスタンス経由
```php=
<?php
public function show(Request $request, $id)
{
$value = $request->session()->get('key');
//
}
```
#### [リクエスト管理](https://laravel.com/docs/9.x/requests)
##### リクエストの内容を取得する
Illuminate\Http\Requestクラスのインスタンスを生成して取得する
```php=
<?php
$name = $request->input('name');
$name = $request->query('name');
```
#### [エラーハンドリング](https://readouble.com/laravel/9.x/ja/errors.html)
* `App\Exceptions\Handler`クラスによって、アプリケーションが投げるすべての例外がログに記録され、ユーザーへレンダーされる
##### エラーハンドリングのカスタマイズ
* `Handler`クラスは、カスタム例外レポートとレンダリングコールバックを登録できる`register`メソッドを持っている。
* `reportable`メソッドで、例外をさまざまな方法で報告できる。(エラー監視ツールに登録するなど。デフォルトではログに記録される。)
* `renderable`メソッドで、特定の例外に対して、個別にレンダリング方法を指定することができる。(デフォルトでは例外はHTTPレスポンスに変換される)
* HTTPエラーが返された場合、`resources/views/errors`下のHTTPステータスコード名のbladeファイルがレンダリングされる(`404.blade.php`など)
```
use App\Exceptions\InvalidOrderException;
/**
* アプリケーションの例外処理コールバックを登録
*
* @return void
*/
public function register()
{
$this->reportable(function (InvalidOrderException $e) {
// 例外を報告
});
$this->renderable(function (InvalidOrderException $e, $request) {
return response()->view('errors.invalid-order', [], 500);
});
}
```
#### [DBサポート](https://readouble.com/laravel/9.x/ja/database.html)
##### コネクション
* Lavavelでは以下のDBがサポートされる
* MariaDB
* MySQL
* PostgreSQL
* SQLite
* SQL Server
* config/database.phpで設定を行う
* データベースにアクセスする方法は以下
* DBファサード
```
$users = DB::select('select * from users where active = ?', [1]);
```
* クエリビルダ
```
$users = DB::table('users')->where('active', $isActive)->get();
```
* Eloquent
```
$users = User::where('active', $isActive)->get();
```
#### [マイグレーション](https://readouble.com/laravel/9.x/ja/migrations.html)
* マイグレージョン関連のコマンドがartisanコマンドで準備されている
* マイグレーションの生成
```
php artisan make:migration create_flights_table
```
* マイグレーションの構造
```php=
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFlightsTable extends Migration
{
/**
* マイグレーションの実行
*
* @return void
*/
public function up()
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
/**
* マイグレーションを戻す
*
* @return void
*/
public function down()
{
Schema::drop('flights');
}
}
```
#### [バリデーション](https://readouble.com/laravel/9.x/ja/validation.html)
バリデーションは複数の方法がある
2つの例を紹介する
##### Requestクラスを使う方法
連想配列形式で各パラメータに適応したいバリデーションルールを指定する
[他のバリデーションルール](https://readouble.com/laravel/9.x/ja/validation.html?header=unique:_%25E3%2583%2586%25E3%2583%25BC%25E3%2583%2596%25E3%2583%25AB_,_%25E3%2582%25AB%25E3%2583%25A9%25E3%2583%25A0_#rule-accepted)
```php=
<?php
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
```
##### Validatorファサードを使用する方法
ファサードを使ってバリデーションをすることも可能
第一引数に対象のデータ、第二引数にバリデーションルールを指定する
```php=
<?php
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
```
### Symfony(森下、久山)
* 2022/11/18 時点(v6.1 の情報)
#### 設計思想
> What could be more useful than an application developed by users for their own needs
> https://symfony.com/at-a-glance#a-philosophy
ユーザーが自分たちのニーズに合わせて開発したアプリケーションほど便利なものはないでしょう ([Deeplで翻訳](https://www.deepl.com/ja/translator?share=generic#en/ja/What%20could%20be%20more%20useful%20than%20an%20application%20developed%20by%20users%20for%20their%20own%20needs%20%3F%0A))
作成されたコンポーネントを組み合わせて、フルスタックフレームワークを作成することもマイクロサービスを作成することも可能。
開発者の目的に応じて規模を変えることができることが特徴。
コンポーネントが標準化されており、アプリケーションが成熟しても使用したいコンポーネントを自由に導入することができる。
Java の [Spring Framework](https://spring.io/) や Ruby の [Ruby on Rails](https://rubyonrails.org/) の影響を受けているとのこと。
Symfony コンポーネントは [Drupal](https://www.drupal.org/), [Prestashop](https://www.prestashop.com/en), [Laravel](https://laravel.com/) で利用されている
#### [ルーティング](https://symfony.com/doc/current/routing.html)
ベースとなるコントローラを作成する
```php=
<?php
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
class LuckyController
{
public function number(): Response
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
```
##### routes.yaml に記載するパターン
`routes.yaml` を以下の通り編集する
```yaml=
app_lucky_number:
path: /lucky/number
controller: App\Controller\LuckyController::number
```
`/lucky/number` にアクセスすることで `LuckyController` の `number` メソッドにルーティングされる
##### アノテーションまたはアトリビュートを利用するパターン(こちらが推奨の模様)
PHP8 以前の環境で以下の手法を利用する場合は、以下コマンドで [doctrine/annotations](https://github.com/doctrine/annotations) パッケージを取得
```bash=
composer require annotations
```
コントローラを以下の通り変更する
```php=
<?php
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
#[Route('/lucky/number')]
public function number(): Response
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
```
上記の通り記述することで `routes.yaml` を作成しなくともルーティングされる
##### ルーティング一覧の取得
```bash=
php bin/console debug:router
```
```bash=
-------------------------- -------- -------- ------ -----------------------------------
Name Method Scheme Host Path
-------------------------- -------- -------- ------ -----------------------------------
_preview_error ANY ANY ANY /_error/{code}.{_format}
_wdt ANY ANY ANY /_wdt/{token}
_profiler_home ANY ANY ANY /_profiler/
_profiler_search ANY ANY ANY /_profiler/search
_profiler_search_bar ANY ANY ANY /_profiler/search_bar
_profiler_phpinfo ANY ANY ANY /_profiler/phpinfo
_profiler_xdebug ANY ANY ANY /_profiler/xdebug
_profiler_search_results ANY ANY ANY /_profiler/{token}/search/results
_profiler_open_file ANY ANY ANY /_profiler/open
_profiler ANY ANY ANY /_profiler/{token}
_profiler_router ANY ANY ANY /_profiler/{token}/router
_profiler_exception ANY ANY ANY /_profiler/{token}/exception
_profiler_exception_css ANY ANY ANY /_profiler/{token}/exception.css
app_lucky_number ANY ANY ANY /lucky/number
-------------------------- -------- -------- ------ -----------------------------------
```
#### セッション管理
##### [RequestStack](https://symfony.com/doc/current/session.html) から取得するパターン
* `HttpFoundation component` を追加することで利用可能
```bash=
composer require symfony/http-foundation
```
* セッションの設定は `config/packages/framework.yaml` に記載
* 公式にはXMLとPHPの説明もあるが、デフォルトは `yaml` ?
* `handler_id`
* null
* 稼働しているPHPの設定に依存
* session.handler.native_file
* `save_path` に記載したパスにSymfonyのセッションメタデータを保存
* [その他詳細](https://symfony.com/doc/current/reference/configuration/framework.html#session)
###### 基本的な使い方
```php=
use Symfony\Component\HttpFoundation\RequestStack;
class SomeService
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
// Accessing the session in the constructor is *NOT* recommended, since
// it might not be accessible yet or lead to unwanted side-effects
// $this->session = $requestStack->getSession();
}
public function someMethod()
{
$session = $this->requestStack->getSession();
// stores an attribute in the session for later reuse
$session->set('attribute-name', 'attribute-value');
// gets an attribute by name
$foo = $session->get('foo');
// the second argument is the value returned when the attribute doesn't exist
$filters = $session->get('filters', []);
// ...
}
}
```
* 公式にはコンストラクタ内でセッションを利用することはおすすめしていない。
* Symfony 6.0 より、旧方式のセッションは[廃止](https://symfony.com/blog/new-in-symfony-5-3-session-service-deprecation)された模様
##### [SessionInterface](https://symfony.com/doc/current/controller.html#managing-the-session) から取得するパターン
`SessionInterface` でタイプヒントしてコントローラ引数に渡すだけ。
###### 基本的な使い方
```php=
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
// ...
public function index(SessionInterface $session): Response
{
// stores an attribute for reuse during a later user request
$session->set('foo', 'bar');
// gets the attribute set by another controller in another request
$foobar = $session->get('foobar');
// uses a default value if the attribute doesn't exist
$filters = $session->get('filters', []);
// ...
}
```
#### [リクエスト管理](https://symfony.com/doc/current/components/http_kernel.html)
`Request` でタイプヒントしてコントローラー引数に渡すだけ。
```PHP=
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
public function index(Request $request): Response
{
$request->isXmlHttpRequest(); // is it an Ajax request?
$request->getPreferredLanguage(['en', 'fr']);
// retrieves GET and POST variables respectively
$request->query->get('page');
$request->request->get('page');
// retrieves SERVER variables
$request->server->get('HTTP_HOST');
// retrieves an instance of UploadedFile identified by foo
$request->files->get('foo');
// retrieves a COOKIE value
$request->cookies->get('PHPSESSID');
// retrieves an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content-type');
}
```
#### エラーハンドリング
Symfony アプリケーションでは、エラーが 404 Not Found エラーであろうと、コードで何らかの例外をスローすることによってトリガーされた致命的なエラーであろうと、すべてのエラーは例外として扱われます。
組み込みの Twig エラーレンダラーを使用して、デフォルトのエラーテンプレートをオーバーライドできます。
```bash
composer require symfony/twig-pack
```
これらのテンプレートをオーバーライドするには、標準の Symfony メソッドを使用し て、バンドル内にあるテンプレートをオーバーライドし、それらをtemplates/bundles/TwigBundle/Exception/ディレクトリに配置します。
HTML ページを返す典型的なプロジェクトは次のようになります。
```
Copy
templates/
└─ bundles/
└─ TwigBundle/
└─ Exception/
├─ error404.html.twig
├─ error403.html.twig
└─ error.html.twig # All other HTML errors (including 500)
```
HTML ページの 404 エラー テンプレートをオーバーライドするには、次の場所にある新しい error404.html.twigテンプレートを作成しtemplates/bundles/TwigBundle/Exception/ます。
```htmlembedded
{# templates/bundles/TwigBundle/Exception/error404.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Page not found</h1>
<p>
The requested page couldn't be located. Checkout for any URL
misspelling or <a href="{{ path('homepage') }}">return to the homepage</a>.
</p>
{% endblock %}
```
[error_controller](https://github.com/symfony/symfony/blob/6.1/src/Symfony/Component/HttpKernel/Controller/ErrorController.php)
[参考](https://symfony.com/doc/current/controller/error_pages.html)
#### DBサポート
* コネクション
Doctrine(ORM)を使用
```
composer require doctrine maker
```
インストールが完了すると .env ファイルにデータベースへの接続設定に関する項目が書き足されます。
DATABASE_URL の箇所を接続するデータベースに合わせて書き換えて下さい。
```
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
```
```
php bin/console doctrine:database:create
```
エンティティクラスを作成する
```
symfony console make:entity hoge
```
#### バリデーション
config/validator/ ディレクトリ内の .yaml または .xml ファイルとして定義する。
(例)$nameプロパティが空
```
Attributes
// src/Entity/Author.php
namespace App\Entity;
// ...
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
#[Assert\NotBlank]
private $name;
}
```
```
YAML
# config/validator/validation.yaml
App\Entity\Author:
properties:
name:
- NotBlank: ~
```
```
<!-- config/validator/validation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="App\Entity\Author">
<property name="name">
<constraint name="NotBlank"/>
</property>
</class>
</constraint-mapping>
```
```php=
PHP
// src/Entity/Author.php
namespace App\Entity;
// ...
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadata;
class Author
{
private $name;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('name', new NotBlank());
}
}
```
※値が空白にならないという保証はまだない
#### マイグレーション
Doctrine Migrationsを使用。
```
symfony console make:migration
```
生成されたファイル名が出力されます(migrations/Version20191019083640.php のような名前のファイル):
```php=
migrations/Version20191019083640.php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version00000000000000 extends AbstractMigration
{
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SEQUENCE comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE conference_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE comment (id INT NOT NULL, conference_id INT NOT NULL, author VARCHAR(255) NOT NULL, text TEXT NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, photo_filename VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_9474526C604B8382 ON comment (conference_id)');
$this->addSql('CREATE TABLE conference (id INT NOT NULL, city VARCHAR(255) NOT NULL, year VARCHAR(4) NOT NULL, is_international BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C604B8382 FOREIGN KEY (conference_id) REFERENCES conference (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
// ...
}
}
```
()内と合うものを取得(プライマリーキー以外) findBy
```php=
$age= 22;
$peopleList = $em->getRepository(people::class)->findBy([ 'age' => $age, ]);
```
### Slim / Lumen(廣部、神山)
※Lumenは非推奨になったので詳細な調査なし
#### 設計思想
> Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
> https://www.slimframework.com/
Slimはシンプルかつ強力な Web アプリケーションと API をすばやく作成するのに役立つ PHP マイクロ フレームワークです。
> At its core, Slim is a dispatcher that receives an HTTP request, invokes an appropriate callback routine, and returns an HTTP response. That’s it.
> You don’t always need a kitchen-sink solution like Symfony or Laravel. These are great tools, for sure. But they are often overkill. Instead, Slim provides only a minimal set of tools that do what you need and nothing else.
> https://www.slimframework.com/docs/v4/
本質的に、Slim は HTTP リクエストを受け取り、適切なコールバック ルーチンを呼び出し、HTTP レスポンスを返すディスパッチャーです。それでおしまい。
SymfonyやLaravelのようなキッチン シンク ソリューションが常に必要なわけではありません。これらは確かに優れたツールです。しかし、それらはしばしばやり過ぎです。代わりに、Slim は、必要なことだけを行う最小限のツール セットのみを提供します。
#### ルーティング
https://www.slimframework.com/docs/v4/objects/routing.html
```php=
$app->get('/books/{id}', function ($request, $response, array $args) {
// Show book identified by $args['id']
});
```
#### セッション管理
実装されていないっぽい
公式ドキュメントへの記載もなし
少し古いが、公式Skeltonでもsesstion_start()を使っているとの記述があった
https://gallu.hatenadiary.jp/entry/20180628/p1
Slim4に対応している外部ミドルウェアがあるので、やはり未実装だと考えられる
https://github.com/bryanjhv/slim-session
#### リクエスト管理
https://www.slimframework.com/docs/v4/objects/request.html
```php=
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$app->get('/hello', function (Request $request, Response $response) {
$response->getBody()->write('Hello World');
return $response;
});
$app->run();
```
#### エラーハンドリング
https://www.slimframework.com/docs/v4/middleware/error-handling.html
```php=
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
/**
* The routing middleware should be added earlier than the ErrorMiddleware
* Otherwise exceptions thrown from it will not be handled by the middleware
*/
$app->addRoutingMiddleware();
/**
* Add Error Middleware
*
* @param bool $displayErrorDetails -> Should be set to false in production
* @param bool $logErrors -> Parameter is passed to the default ErrorHandler
* @param bool $logErrorDetails -> Display error details in error log
* @param LoggerInterface|null $logger -> Optional PSR-3 Logger
*
* Note: This middleware should be added last. It will not handle any exceptions/errors
* for middleware added after it.
*/
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// ...
$app->run();
```
・slimが用意したエラー画面を出すかどうかを選択できたり、カスタムエラー画面を表示するなど柔軟な設定が可能。
#### DBサポート
* コネクション
公式ドキュメントの記載もなし(slim3はある)
https://www.slimframework.com/docs/v3/cookbook/database-doctrine.html#provide-database-credentials
slim4-skeletonには記載があるがお金がかかる
https://odan.github.io/slim4-skeleton/database.html
#### バリデーション
ドキュメントなさそう
以下補足情報
↓は非公式。公式にページは404
https://sites.google.com/site/slim3documentja/documents/slim3-controller/validation
slim4-skeletonはドキュメントを読むとお金を要求される?
https://odan.github.io/slim4-skeleton/validation.html
#### マイグレーション
ドキュメントに無い。
phinxをつかうのがベター?
`composer require robmorgan/phinx`
initコマンドで生成された`phinx.yml`を編集。
下記はポスグレ。MySQLなどDBを使用する場合はadapterを変更する。
```
development:
adapter: pgsql
host: 127.0.0.1
name: XXXX
user: XXXX
pass: XXXX
port: XXXX
charset: utf8
```
下記コマンドでマイグレーション実行
`bin/cake bake migration CreateArticles title:string body:text`
https://book.cakephp.org/3/en/tutorials-and-examples/blog/part-three.html#migrations-plugin
### CakePHP(北岡、加納)
北岡)すみません…とりあえずでかくなりすぎました↓
#### 設計思想
- MVC
- '規約'
- 以下にある図を参照

##### MVCの実態(コード)
https://book.cakephp.org/4/ja/quickstart.html
###### Model
- `Table`と`Entity`でモデル構成
- src/Model/Table/
- 命名規則で`id`カラムは主キーと解釈する'規約'
- src/Model/Entity/
- データベースの一つのレコードを表す
- データに対して行レベルの振る舞いを提供
- 一括代入
- `$article->set($properties)`
- カラムごとにアクセス制御も可能
- `setAccess('user_id', true)`
- ただし、そのインスタンスのみに有効
- `isNew()` ... そのエンティティが永続化しているか
```php
<?php
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table // Model
{
public function initialize(array $config) : void
{
$this->addBehavior('Timestamp'); //created, modified
}
}
```
```php
<?php
// src/Model/Entity/Article.php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
protected $_accessible = [
'*' => true,
'id' => false,
'slug' => false,
];
}
```
###### Controller
- `src/Controller`
- CakePHP のコントローラーは、 レスポンスを準備するために、HTTP リクエストを処理し、**モデルのメソッドに含まれるビジネスロジック**を 実行します。
- メソッド名がそのままルーティング名になる
- CakePHP は、 コントローラーのアクションが完了した後、自動的にテンプレートを描画します
```php
<?php
// src/Controller/ArticlesController.php
namespace App\Controller;
class ArticlesController extends AppController
{
// indexアクション. example.com/articles/index にマッピング という規約
public function index()
{
$this->loadComponent('Paginator');
$articles = $this->Paginator->paginate($this->Articles->find());
$this->set(compact('articles'));
}
// viewアクション. example.com/articles/view にマッピング
// example.com/articles/foobar → $slug = 'foobar'
public function view($slug = null)
{
$article = $this->Articles->findBySlug($slug)->firstOrFail(); // 最初に見つかったエンティティを返す or エラーページを返す
$this->set(compact('article'));
}
}
```
###### View
- `template/<コントローラーの名前>/index.php`
- `$this->Html` CakePHP ヘルパー関数
- `link(<リンクテキスト>,<URL>)`
```php
<!-- File: templates/Articles/index.php -->
<h1>記事一覧</h1>
<table>
<tr>
<th>タイトル</th>
<th>作成日時</th>
</tr>
<!-- ここで、$articles クエリーオブジェクトを繰り返して、記事の情報を出力します -->
<?php foreach ($articles as $article): ?>
<tr>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
```
#### ルーティング
- 基本的に`<Controller名>/<アクションメソッド名>`で自動ルーティング
#### セッション管理
- CakePHP は PHP のネイティブ session 拡張上に、ユーティリティ機能のスイートとラッパーを 提供します。
- https://book.cakephp.org/4/ja/development/sessions.html
##### 設定
###### データベースセッション
```php
'Session' => [
'defaults' => 'database',
'handler' => [
'engine' => 'DatabaseSession',
'model' => 'CustomSessions'
]
]
```
- 1行目:cakePHPにセッションはデータベースに保持することを伝える
- セッション保持するためのカスタムモデルを定義することもできる(`model => 'CustomSessions'`)
###### キャッシュセッション
```php
Configure::write('Session', [
'defaults' => 'cache',
'handler' => [
'config' => 'session'
]
]);
```
- これは Session に `CacheSession` クラスをセッション保存先として 委任する
###### セッションを利用する
- セッションはリクエストオブジェクトを呼び出せる場所ならどこでも呼び出せる
- Controllers
- Views
- Helpers
- Cells
- Components
```php
$name = $this->getRequest()->getSession()->read('User.name');
// 複数回セッションにアクセスする場合、
// ローカル変数にしたくなるでしょう。
$session = $this->getRequest()->getSession();
$name = $session->read('User.name');
```
- Session::read($key)
- Session::write($key)
- Session::check($key)
- Session::destroy()
- ユーザーがログインやログアウトした時、 `AuthComponent` は自動的にセッション ID を更新
#### リクエスト管理
- 全ての CakePHP のリクエストは、 $this->request を使用してアクセス可能なリクエストオブジェクトを 含みます
- `Cake\Http\ServerRequest::is(['post'])` // POSTのみ
#### エラーハンドリング(加納)
#### DBサポート(加納)
```php=
use Cake\Datasource\ConnectionManager;
$dsn = 'mysql://root:password@localhost/my_database';
ConnectionManager::config('default', ['url' => $dsn]);
```
```php=
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$results = $connection
->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1])
->fetchAll('assoc');
```
#### バリデーション(加納)
https://book.cakephp.org/4/en/core-libraries/validation.html#namespace-Cake\Validation
```php=
$validator
->requirePresence('title')
->notEmptyString('title', 'このフィールドに入力してください')
->add('title', [
'length' => [
'rule' => ['minLength', 10],
'message' => 'タイトルは10文字以上必要です。',
]
])
->allowEmptyDateTime('published')
->add('published', 'boolean', [
'rule' => 'boolean'
])
->requirePresence('body')
->add('body', 'length', [
'rule' => ['minLength', 50],
'message' => '記事の内容は意味がなければなりません。'
]);
```
#### マイグレーション(加納)
https://book.cakephp.org/migrations/3/ja/index.html
* Cake PHP 本体には含まれていない
* Cake PHP コアメンバが開発するプラグインとして利用可能
```
composer require cakephp/migrations "@stable"
```
```PHP=
<?php
use Migrations\AbstractMigration;
class CreateProducts extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
* @return void
*/
public function change()
{
$table = $this->table('products');
$table->addColumn('name', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);
$table->addColumn('description', 'text', [
'default' => null,
'null' => false,
]);
$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}
```
* 主キーとなるカラム id は、 勝手に追加される
------------------
* 各種調査バージョンを明記
* 各自調査したバージョンを明記
* CakePHP(4.x)
* 各FWで比較ができるように
# 特集:「PHPフレームワーク」を語り合う
* Webアプリケーションフレームワーク とは([wiki](https://ja.wikipedia.org/wiki/Web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF))
* 動的な ウェブサイト、Webアプリケーション、Webサービスの開発をサポートするために設計されたアプリケーションフレームワーク
* Web開発で用いられる共通した作業に伴う労力を軽減する
* データベースへのアクセス
* テンプレートエンジン
* セッション管理
* コードの再利用を促進させる
* そもそもFWってなんで必要?
* 開発速度向上
* Webアプリケーション開発でよく利用する処理(セッション管理やDBアクセス、Cookieなど)が既に用意されているため、それらを再利用するだけで開発が進められる
* セキュリティ対応
* 脆弱性が見つかった場合に修正版がリリースされる
* メンテされているOSSの場合
* 開発ルールの順守
* フレームワークのルールに従って作成することが強いられる反面、開発チーム全体で共通のルールで開発できるため、ルールに逸脱するようなコードが生まれにくい
## 対象のフレームワーク
* Laravel
* Symfony
* CakePHP
* Slim
## 設計思想
### Laravel
https://laravel.com/docs/9.x/installation
- プログレッシブフレームワーク
- we mean that Laravel grows with you.
- Laravel はあなたと共に成長するということです。
- Laravel は依存性注入、単体テスト、キュー、リアルタイム イベント などのための堅牢なツールを提供します。
- スケーラブルなフレームワーク
- Laravel は信じられないほどスケーラブルです。
- 実際、Laravel アプリケーションは、1 か月あたり数億のリクエストを処理するように簡単にスケーリングされています。
- コミュニティ フレームワーク
### Symfony
> What could be more useful than an application developed by users for their own needs
> https://symfony.com/at-a-glance#a-philosophy
ユーザーが自分たちのニーズに合わせて開発したアプリケーションほど便利なものはない
作成されたコンポーネントを組み合わせて、フルスタックフレームワークを作成することもマイクロサービスを作成することも可能。
開発者の目的に応じて規模を変えることができることが特徴。
コンポーネントが標準化されており、アプリケーションが成熟しても使用したいコンポーネントを自由に導入することができる。
Java の [Spring Framework](https://spring.io/) や Ruby の [Ruby on Rails](https://rubyonrails.org/) の影響を受けているとのこと。
Symfony コンポーネントは [Drupal](https://www.drupal.org/), [Prestashop](https://www.prestashop.com/en), [Laravel](https://laravel.com/) で利用されている
### CakePHP
- MVC
- '規約'
- 以下にある図を参照

##### MVCの実態(コード)
https://book.cakephp.org/4/ja/quickstart.html
###### Model
- `Table`と`Entity`でモデル構成
- src/Model/Table/
- 命名規則で`id`カラムは主キーと解釈する'規約'
- src/Model/Entity/
- データベースの一つのレコードを表す
- データに対して行レベルの振る舞いを提供
- 一括代入
- `$article->set($properties)`
- カラムごとにアクセス制御も可能
- `setAccess('user_id', true)`
- ただし、そのインスタンスのみに有効
- `isNew()` ... そのエンティティが永続化しているか
```php
<?php
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table // Model
{
public function initialize(array $config) : void
{
$this->addBehavior('Timestamp'); //created, modified
}
}
```
```php
<?php
// src/Model/Entity/Article.php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
protected $_accessible = [
'*' => true,
'id' => false,
'slug' => false,
];
}
```
###### Controller
- `src/Controller`
- CakePHP のコントローラーは、 レスポンスを準備するために、HTTP リクエストを処理し、**モデルのメソッドに含まれるビジネスロジック**を 実行します。
- メソッド名がそのままルーティング名になる
- CakePHP は、 コントローラーのアクションが完了した後、自動的にテンプレートを描画します
```php
<?php
// src/Controller/ArticlesController.php
namespace App\Controller;
class ArticlesController extends AppController
{
// indexアクション. example.com/articles/index にマッピング という規約
public function index()
{
$this->loadComponent('Paginator');
$articles = $this->Paginator->paginate($this->Articles->find());
$this->set(compact('articles'));
}
// viewアクション. example.com/articles/view にマッピング
// example.com/articles/foobar → $slug = 'foobar'
public function view($slug = null)
{
$article = $this->Articles->findBySlug($slug)->firstOrFail(); // 最初に見つかったエンティティを返す or エラーページを返す
$this->set(compact('article'));
}
}
```
###### View
- `template/<コントローラーの名前>/index.php`
- `$this->Html` CakePHP ヘルパー関数
- `link(<リンクテキスト>,<URL>)`
```php
<!-- File: templates/Articles/index.php -->
<h1>記事一覧</h1>
<table>
<tr>
<th>タイトル</th>
<th>作成日時</th>
</tr>
<!-- ここで、$articles クエリーオブジェクトを繰り返して、記事の情報を出力します -->
<?php foreach ($articles as $article): ?>
<tr>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
```
### Slim
> Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
> https://www.slimframework.com/
Slimはシンプルかつ強力な Web アプリケーションと API をすばやく作成するのに役立つ PHP マイクロ フレームワークです。
> At its core, Slim is a dispatcher that receives an HTTP request, invokes an appropriate callback routine, and returns an HTTP response. That’s it.
> You don’t always need a kitchen-sink solution like Symfony or Laravel. These are great tools, for sure. But they are often overkill. Instead, Slim provides only a minimal set of tools that do what you need and nothing else.
> https://www.slimframework.com/docs/v4/
本質的に、Slim は HTTP リクエストを受け取り、適切なコールバック ルーチンを呼び出し、HTTP レスポンスを返すディスパッチャーです。それでおしまい。
SymfonyやLaravelのようなキッチン シンク ソリューションが常に必要なわけではありません。これらは確かに優れたツールです。しかし、それらはしばしばやり過ぎです。代わりに、Slim は、必要なことだけを行う最小限のツール セットのみを提供します。
## ルーティング
### Laravel
https://laravel.com/docs/9.x/routing
#### デフォルトルートファイル
デフォルトでは下記2つファイルにルーティングを定義する
- routes/web.php
- routes/api.php
#### 定義方法
UserControllerにindxメソッドを定義している場合
下記のように定義すると`/user`のパスに対して、UserControllerのindexメソッドが対応される
```php=
<?php
use App\Http\Controllers\UserController;
Route::get('/user', [UserController::class, 'index']);
```
UserController.phpが下記のような内容だと`resource/user/index.blade.php`
```php=
<?php
public function index()
{
return view('user.index');
}
```
#### 利用可能なルーターメソッド
```php=
<?php
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
```
#### ルーティングの一覧表示
下記コマンドで定義したルーティング一覧を出力することができる
```
php artisan route:list
```
#### よく使われる方法
##### ルートパラメータ
```php=
<?php
Route::get('/posts/{post}/comments/{comment}', [CommentController::class, 'show']);
```
CommentController.phpが下記のように引数に指定することでコントローラー内でルートパラメータを使うことができる
```php=
<?php
public function index($post, $comment)
{
//
}
```
##### 名前付きルート
定義したルートに名前を付けることができる。
```php=
<?php
Route::get('/user/profile', function () {
//
})->name('profile');
```
### Symfony
ベースとなるコントローラを作成する
```php=
<?php
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
class LuckyController
{
public function number(): Response
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
```
#### routes.yaml に記載するパターン
`routes.yaml` を以下の通り編集する
```yaml=
app_lucky_number:
path: /lucky/number
controller: App\Controller\LuckyController::number
```
`/lucky/number` にアクセスすることで `LuckyController` の `number` メソッドにルーティングされる
#### アノテーションまたはアトリビュートを利用するパターン(こちらが推奨の模様)
PHP8 以前の環境で以下の手法を利用する場合は、以下コマンドで [doctrine/annotations](https://github.com/doctrine/annotations) パッケージを取得
```bash=
composer require annotations
```
コントローラを以下の通り変更する
```php=
<?php
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
#[Route('/lucky/number')]
public function number(): Response
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
```
上記の通り記述することで `routes.yaml` を作成しなくともルーティングされる
#### ルーティング一覧の取得
```bash=
php bin/console debug:router
```
```bash=
-------------------------- -------- -------- ------ -----------------------------------
Name Method Scheme Host Path
-------------------------- -------- -------- ------ -----------------------------------
_preview_error ANY ANY ANY /_error/{code}.{_format}
_wdt ANY ANY ANY /_wdt/{token}
_profiler_home ANY ANY ANY /_profiler/
_profiler_search ANY ANY ANY /_profiler/search
_profiler_search_bar ANY ANY ANY /_profiler/search_bar
_profiler_phpinfo ANY ANY ANY /_profiler/phpinfo
_profiler_xdebug ANY ANY ANY /_profiler/xdebug
_profiler_search_results ANY ANY ANY /_profiler/{token}/search/results
_profiler_open_file ANY ANY ANY /_profiler/open
_profiler ANY ANY ANY /_profiler/{token}
_profiler_router ANY ANY ANY /_profiler/{token}/router
_profiler_exception ANY ANY ANY /_profiler/{token}/exception
_profiler_exception_css ANY ANY ANY /_profiler/{token}/exception.css
app_lucky_number ANY ANY ANY /lucky/number
-------------------------- -------- -------- ------ -----------------------------------
```
### CakePHP
- 基本的に`<Controller名>/<アクションメソッド名>`で自動ルーティング
### Slim
https://www.slimframework.com/docs/v4/objects/routing.html
```php=
$app->get('/books/{id}', function ($request, $response, array $args) {
// Show book identified by $args['id']
});
```
## セッション管理
### Laravel
https://laravel.com/docs/9.x/session
#### 設定ファイル
config/session.php
#### セッションドライバ
file- セッションは に保存されstorage/framework/sessionsます。
cookie- セッションは安全で暗号化された Cookie に保存されます。
database- セッションはリレーショナル データベースに保存されます。
memcached/ redis- セッションは、これらの高速なキャッシュ ベースのストアの 1 つに保存されます。
dynamodb- セッションは AWS DynamoDB に保存されます。
array- セッションは PHP 配列に保存され、永続化されません。
#### セッションの操作方法
グローバルヘルパーとRequestインスタンス経由の2 つの主な方法があります。
##### グローバルセッションヘルパー
```php=
<?php
$value = session('key');
```
##### Requestインスタンス経由
```php=
<?php
public function show(Request $request, $id)
{
$value = $request->session()->get('key');
//
}
```
### Symfony
#### [RequestStack](https://symfony.com/doc/current/session.html) から取得するパターン
* `HttpFoundation component` を追加することで利用可能
```bash=
composer require symfony/http-foundation
```
* セッションの設定は `config/packages/framework.yaml` に記載
* 公式にはXMLとPHPの説明もあるが、デフォルトは `yaml` ?
* `handler_id`
* null
* 稼働しているPHPの設定に依存
* session.handler.native_file
* `save_path` に記載したパスにSymfonyのセッションメタデータを保存
* [その他詳細](https://symfony.com/doc/current/reference/configuration/framework.html#session)
###### 基本的な使い方
```php=
use Symfony\Component\HttpFoundation\RequestStack;
class SomeService
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
// Accessing the session in the constructor is *NOT* recommended, since
// it might not be accessible yet or lead to unwanted side-effects
// $this->session = $requestStack->getSession();
}
public function someMethod()
{
$session = $this->requestStack->getSession();
// stores an attribute in the session for later reuse
$session->set('attribute-name', 'attribute-value');
// gets an attribute by name
$foo = $session->get('foo');
// the second argument is the value returned when the attribute doesn't exist
$filters = $session->get('filters', []);
// ...
}
}
```
* 公式にはコンストラクタ内でセッションを利用することはおすすめしていない。
* Symfony 6.0 より、旧方式のセッションは[廃止](https://symfony.com/blog/new-in-symfony-5-3-session-service-deprecation)された模様
##### [SessionInterface](https://symfony.com/doc/current/controller.html#managing-the-session) から取得するパターン
`SessionInterface` でタイプヒントしてコントローラ引数に渡すだけ。
###### 基本的な使い方
```php=
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
// ...
public function index(SessionInterface $session): Response
{
// stores an attribute for reuse during a later user request
$session->set('foo', 'bar');
// gets the attribute set by another controller in another request
$foobar = $session->get('foobar');
// uses a default value if the attribute doesn't exist
$filters = $session->get('filters', []);
// ...
}
```
### CakePHP
- CakePHP は PHP のネイティブ session 拡張上に、ユーティリティ機能のスイートとラッパーを 提供します。
- https://book.cakephp.org/4/ja/development/sessions.html
#### 設定
##### データベースセッション
```php
'Session' => [
'defaults' => 'database',
'handler' => [
'engine' => 'DatabaseSession',
'model' => 'CustomSessions'
]
]
```
- 1行目:cakePHPにセッションはデータベースに保持することを伝える
- セッション保持するためのカスタムモデルを定義することもできる(`model => 'CustomSessions'`)
##### キャッシュセッション
```php
Configure::write('Session', [
'defaults' => 'cache',
'handler' => [
'config' => 'session'
]
]);
```
- これは Session に `CacheSession` クラスをセッション保存先として 委任する
##### セッションを利用する
- セッションはリクエストオブジェクトを呼び出せる場所ならどこでも呼び出せる
- Controllers
- Views
- Helpers
- Cells
- Components
```php
$name = $this->getRequest()->getSession()->read('User.name');
// 複数回セッションにアクセスする場合、
// ローカル変数にしたくなるでしょう。
$session = $this->getRequest()->getSession();
$name = $session->read('User.name');
```
- Session::read($key)
- Session::write($key)
- Session::check($key)
- Session::destroy()
- ユーザーがログインやログアウトした時、 `AuthComponent` は自動的にセッション ID を更新
### Slim
実装されていないっぽい
公式ドキュメントへの記載もなし
少し古いが、公式Skeltonでもsesstion_start()を使っているとの記述があった
https://gallu.hatenadiary.jp/entry/20180628/p1
Slim4に対応している外部ミドルウェアがあるので、やはり未実装だと考えられる
https://github.com/bryanjhv/slim-session
## リクエスト管理
### Laravel
https://laravel.com/docs/9.x/requests
#### リクエストの内容を取得する
Illuminate\Http\Requestクラスのインスタンスを生成して取得する
```php=
<?php
$name = $request->input('name');
$name = $request->query('name');
```
### Symfony
https://symfony.com/doc/current/components/http_kernel.html
`Request` でタイプヒントしてコントローラー引数に渡すだけ。
```php=
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
public function index(Request $request): Response
{
$request->isXmlHttpRequest(); // is it an Ajax request?
$request->getPreferredLanguage(['en', 'fr']);
// retrieves GET and POST variables respectively
$request->query->get('page');
$request->request->get('page');
// retrieves SERVER variables
$request->server->get('HTTP_HOST');
// retrieves an instance of UploadedFile identified by foo
$request->files->get('foo');
// retrieves a COOKIE value
$request->cookies->get('PHPSESSID');
// retrieves an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content-type');
}
```
### CakePHP
- 全ての CakePHP のリクエストは、 $this->request を使用してアクセス可能なリクエストオブジェクトを 含みます
- `Cake\Http\ServerRequest::is(['post'])` // POSTのみ
### Slim
https://www.slimframework.com/docs/v4/objects/request.html
```php=
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$app->get('/hello', function (Request $request, Response $response) {
$response->getBody()->write('Hello World');
return $response;
});
$app->run();
```
## エラーハンドリング
### Laravel
https://readouble.com/laravel/9.x/ja/errors.html
* `App\Exceptions\Handler`クラスによって、アプリケーションが投げるすべての例外がログに記録され、ユーザーへレンダーされる
#### エラーハンドリングのカスタマイズ
* `Handler`クラスは、カスタム例外レポートとレンダリングコールバックを登録できる`register`メソッドを持っている。
* `reportable`メソッドで、例外をさまざまな方法で報告できる。(エラー監視ツールに登録するなど。デフォルトではログに記録される。)
* `renderable`メソッドで、特定の例外に対して、個別にレンダリング方法を指定することができる。(デフォルトでは例外はHTTPレスポンスに変換される)
* HTTPエラーが返された場合、`resources/views/errors`下のHTTPステータスコード名のbladeファイルがレンダリングされる(`404.blade.php`など)
```php=
use App\Exceptions\InvalidOrderException;
/**
* アプリケーションの例外処理コールバックを登録
*
* @return void
*/
public function register()
{
$this->reportable(function (InvalidOrderException $e) {
// 例外を報告
});
$this->renderable(function (InvalidOrderException $e, $request) {
return response()->view('errors.invalid-order', [], 500);
});
}
```
### Symfony
Symfony アプリケーションでは、エラーが 404 Not Found エラーであろうと、コードで何らかの例外をスローすることによってトリガーされた致命的なエラーであろうと、すべてのエラーは例外として扱われます。
組み込みの Twig エラーレンダラーを使用して、デフォルトのエラーテンプレートをオーバーライドできます。
```bash
composer require symfony/twig-pack
```
これらのテンプレートをオーバーライドするには、標準の Symfony メソッドを使用し て、バンドル内にあるテンプレートをオーバーライドし、それらをtemplates/bundles/TwigBundle/Exception/ディレクトリに配置します。
HTML ページを返す典型的なプロジェクトは次のようになります。
```
Copy
templates/
└─ bundles/
└─ TwigBundle/
└─ Exception/
├─ error404.html.twig
├─ error403.html.twig
└─ error.html.twig # All other HTML errors (including 500)
```
HTML ページの 404 エラー テンプレートをオーバーライドするには、次の場所にある新しい error404.html.twigテンプレートを作成しtemplates/bundles/TwigBundle/Exception/ます。
```htmlembedded
{# templates/bundles/TwigBundle/Exception/error404.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Page not found</h1>
<p>
The requested page couldn't be located. Checkout for any URL
misspelling or <a href="{{ path('homepage') }}">return to the homepage</a>.
</p>
{% endblock %}
```
[error_controller](https://github.com/symfony/symfony/blob/6.1/src/Symfony/Component/HttpKernel/Controller/ErrorController.php)
[参考](https://symfony.com/doc/current/controller/error_pages.html)
### CakePHP
```php=
use Cake\Datasource\ConnectionManager;
$dsn = 'mysql://root:password@localhost/my_database';
ConnectionManager::config('default', ['url' => $dsn]);
```
```php=
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$results = $connection
->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1])
->fetchAll('assoc');
```
### Slim
https://www.slimframework.com/docs/v4/middleware/error-handling.html
```php=
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
/**
* The routing middleware should be added earlier than the ErrorMiddleware
* Otherwise exceptions thrown from it will not be handled by the middleware
*/
$app->addRoutingMiddleware();
/**
* Add Error Middleware
*
* @param bool $displayErrorDetails -> Should be set to false in production
* @param bool $logErrors -> Parameter is passed to the default ErrorHandler
* @param bool $logErrorDetails -> Display error details in error log
* @param LoggerInterface|null $logger -> Optional PSR-3 Logger
*
* Note: This middleware should be added last. It will not handle any exceptions/errors
* for middleware added after it.
*/
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// ...
$app->run();
```
・slimが用意したエラー画面を出すかどうかを選択できたり、カスタムエラー画面を表示するなど柔軟な設定が可能。
## DBサポート
### Laravel
https://readouble.com/laravel/9.x/ja/database.html
#### コネクション
* Lavavelでは以下のDBがサポートされる
* MariaDB
* MySQL
* PostgreSQL
* SQLite
* SQL Server
* config/database.phpで設定を行う
* データベースにアクセスする方法は以下
* DBファサード
```
$users = DB::select('select * from users where active = ?', [1]);
```
* クエリビルダ
```
$users = DB::table('users')->where('active', $isActive)->get();
```
* Eloquent
```
$users = User::where('active', $isActive)->get();
```
### Symfony
* コネクション
Doctrine(ORM)を使用
```
composer require doctrine maker
```
インストールが完了すると .env ファイルにデータベースへの接続設定に関する項目が書き足されます。
DATABASE_URL の箇所を接続するデータベースに合わせて書き換えて下さい。
```
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
```
```
php bin/console doctrine:database:create
```
エンティティクラスを作成する
```
symfony console make:entity hoge
```
### CakePHP
### Slim
公式ドキュメントの記載もなし(slim3はある)
https://www.slimframework.com/docs/v3/cookbook/database-doctrine.html#provide-database-credentials
slim4-skeletonには記載があるがお金がかかる
https://odan.github.io/slim4-skeleton/database.html
## バリデーション
### Laravel
https://readouble.com/laravel/9.x/ja/validation.html
複数のバリデーション方法がある
例を2つ紹介
#### Requestクラスを使う方法
連想配列形式で各パラメータに適応したいバリデーションルールを指定する
[他のバリデーションルール](https://readouble.com/laravel/9.x/ja/validation.html?header=unique:_%25E3%2583%2586%25E3%2583%25BC%25E3%2583%2596%25E3%2583%25AB_,_%25E3%2582%25AB%25E3%2583%25A9%25E3%2583%25A0_#rule-accepted)
```php=
<?php
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
```
#### Validatorファサードを使用する方法
ファサードを使ってバリデーションをすることも可能
第一引数に対象のデータ、第二引数にバリデーションルールを指定する
```php=
<?php
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
```
### Symfony
config/validator/ ディレクトリ内の .yaml または .xml ファイルとして定義する。
(例)$nameプロパティが空
```php=
Attributes
// src/Entity/Author.php
namespace App\Entity;
// ...
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
#[Assert\NotBlank]
private $name;
}
```
```
YAML
# config/validator/validation.yaml
App\Entity\Author:
properties:
name:
- NotBlank: ~
```
```
<!-- config/validator/validation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="App\Entity\Author">
<property name="name">
<constraint name="NotBlank"/>
</property>
</class>
</constraint-mapping>
```
```php=
PHP
// src/Entity/Author.php
namespace App\Entity;
// ...
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadata;
class Author
{
private $name;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('name', new NotBlank());
}
}
```
※値が空白にならないという保証はまだない
### CakePHP
https://book.cakephp.org/4/en/core-libraries/validation.html#namespace-Cake\Validation
```php=
$validator
->requirePresence('title')
->notEmptyString('title', 'このフィールドに入力してください')
->add('title', [
'length' => [
'rule' => ['minLength', 10],
'message' => 'タイトルは10文字以上必要です。',
]
])
->allowEmptyDateTime('published')
->add('published', 'boolean', [
'rule' => 'boolean'
])
->requirePresence('body')
->add('body', 'length', [
'rule' => ['minLength', 50],
'message' => '記事の内容は意味がなければなりません。'
]);
```
### Slim
ドキュメントなさそう
以下補足情報
↓は非公式。公式にページは404
https://sites.google.com/site/slim3documentja/documents/slim3-controller/validation
slim4-skeletonはドキュメントを読むとお金を要求される?
https://odan.github.io/slim4-skeleton/validation.html
## マイグレーション
### Laravel
https://readouble.com/laravel/9.x/ja/migrations.html
* マイグレージョン関連のコマンドがartisanコマンドで準備されている
* マイグレーションの生成
```
php artisan make:migration create_flights_table
```
* マイグレーションの構造
```php=
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFlightsTable extends Migration
{
/**
* マイグレーションの実行
*
* @return void
*/
public function up()
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
/**
* マイグレーションを戻す
*
* @return void
*/
public function down()
{
Schema::drop('flights');
}
}
```
### Symfony
Doctrine Migrationsを使用。
```
symfony console make:migration
```
生成されたファイル名が出力されます(migrations/Version20191019083640.php のような名前のファイル):
```php=
migrations/Version20191019083640.php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version00000000000000 extends AbstractMigration
{
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SEQUENCE comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE conference_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE comment (id INT NOT NULL, conference_id INT NOT NULL, author VARCHAR(255) NOT NULL, text TEXT NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, photo_filename VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_9474526C604B8382 ON comment (conference_id)');
$this->addSql('CREATE TABLE conference (id INT NOT NULL, city VARCHAR(255) NOT NULL, year VARCHAR(4) NOT NULL, is_international BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C604B8382 FOREIGN KEY (conference_id) REFERENCES conference (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
// ...
}
}
```
()内と合うものを取得(プライマリーキー以外) findBy
```php=
$age= 22;
$peopleList = $em->getRepository(people::class)->findBy([ 'age' => $age, ]);
```
### CakePHP
https://book.cakephp.org/migrations/3/ja/index.html
* Cake PHP 本体には含まれていない
* Cake PHP コアメンバが開発するプラグインとして利用可能
```
composer require cakephp/migrations "@stable"
```
```PHP=
<?php
use Migrations\AbstractMigration;
class CreateProducts extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
* @return void
*/
public function change()
{
$table = $this->table('products');
$table->addColumn('name', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);
$table->addColumn('description', 'text', [
'default' => null,
'null' => false,
]);
$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}
```
* 主キーとなるカラム id は、 勝手に追加される
### Slim
ドキュメントに無い。
phinxをつかうのがベター?
`composer require robmorgan/phinx`
initコマンドで生成された`phinx.yml`を編集。
下記はポスグレ。MySQLなどDBを使用する場合はadapterを変更する。
```
development:
adapter: pgsql
host: 127.0.0.1
name: XXXX
user: XXXX
pass: XXXX
port: XXXX
charset: utf8
```
下記コマンドでマイグレーション実行
`bin/cake bake migration CreateArticles title:string body:text`
https://book.cakephp.org/3/en/tutorials-and-examples/blog/part-three.html#migrations-plugin