---
# System prepended metadata

title: PHPerのための「PHPフレームワーク」を語り合うPHP TechCafe
tags: [PHPTechCafe]

---

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)
##### デフォルトルートファイル
デフォルトでは下記２つファイルにルーティングを定義する
- 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)
バリデーションは複数の方法がある
２つの例を紹介する

##### 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
 - '規約'
 - 以下にある図を参照

![公式の図](https://book.cakephp.org/4/ja/_images/typical-cake-request.png)


##### 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
 - '規約'
 - 以下にある図を参照

![公式の図](https://book.cakephp.org/4/ja/_images/typical-cake-request.png)


##### 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

#### デフォルトルートファイル
デフォルトでは下記２つファイルにルーティングを定義する
- 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




