弊社で毎月開催し、PHPエンジニアの間でご好評をいただいているPHP TechCafe。
2022年11月のイベントでは「PHPフレームワーク」について語り合いました。
弊社メンバーがピックアップしたPHPの代表的なフレームワーク4種について、以下のShowNoteをベースに、参加者の皆様のご意見も伺いながら学んでいきました。今回はその内容についてレポートします。
フレームワークとは
まず初めに、フレームワークとは、アプリケーション開発においてよく利用される機能をあらかじめ備えた枠組みのことです。 もう少し具体的に説明すると、以下の通りです。
ここでは、「セキュリティに関わる実装を自前で組むメリットは少ないため、実績のあるフレームワークを利用することが一番の正攻法ではないか」といった意見が挙がっていました。
代表的なPHPフレームワーク
次に、事前に弊社メンバーが抜粋した、代表的なPHPフレームワークそれぞれの設計思想について語り合いました。 (抽出対象や並び順に意図はございません)
Laravel
- プログレッシブフレームワーク
- スケーラブルなフレームワーク
- システム規模や利用負荷などの増大に対応できる
- Laravel アプリケーションは、1 か月あたり数億のリクエストを処理するように簡単にスケーリングされている
- コミュニティフレームワーク
- コミュニティの活動が活発であり、意見交換も頻繁に行われている
参加者からは「確かにイージーなイメージがあり、何でもできて簡単。」といった意見や、「依存性注入や単体テストのために独自に拡充された機能が提供されており、色々出来て便利」といった意見が挙がりました。Laravleにはサービスコンテナと呼ばれる機能が備わっているため、依存性注入を簡単に行うことができたり、ユニットテストを考慮して構築されていることがその理由となります。
Symfony
- 作成されたコンポーネントを組み合わせて、フルスタックフレームワークを作成することもマイクロサービスを作成することも可能
- Java の Spring Framework や Ruby の Ruby on Rails の影響を受けている
- Symfony コンポーネントは Drupal, Prestashop, Laravel で利用されている
ここでは、Symfony自体がLaravelの中で使われているという点について活発にコメントが飛び交いました。 「Symfonyのリリースが遅れることによってLaravelにも影響が出たケースがあった」という声や、「Laravelのリリース頻度がSymfonyのバージョンアップに依存するような形になったということを過去に記事で見た」という声です。 現時点の最新バージョンであるLaravel9についても、Symfonyのコンポーネントに依存していることが要因となりLTS(※LTSはLong Term Supportの略で長期サポートを意味)がなくなったりと、実際に様々な影響を及ぼしていることから、このような声が多く挙がっているようです。
CakePHP
- 「ケーキを焼くくらい簡単に開発できる」というコンセプトで設計されており、初心者向けであるといえる
- Ruby on Rails の概念の多くが取り入れられている
- 比較的小規模なWebアプリ開発向けである
- 「MVCモデル」が採用されており、役割分担をさせて高速に開発を進められる
ここでは、「MVCに準拠しており、命名がすごく厳格だという印象がある」という意見が挙がりました。
Slim
- シンプルかつ強力な Web アプリケーションと API をすばやく作成するのに役立つ PHP マイクロ フレームワーク
- 必要なことだけを行う最小限のツール セットのみを提供
- 最小限の機能のみを持つため、ルールがシンプルで、開発の自由度が高く、学習コストが低い
ここでは、マイクロフレームワークを謡っているという点について「テンプレートエンジンが標準で提供されておらず、細かなBot作成や小さな機能開発を行う際に利用しやすい」といった意見や、「自由に拡張できるという点がありがたい」といった意見が挙がりました。
機能比較
続いて、各フレームワークの基本的な利用方法に注目し、それぞれの特徴について比較しながらディスカッションを行いました。
ルーティング
Laravel
ルートファイル
定義方法
- UserControllerにindexメソッドを定義している場合、下記のように定義すると
/user
のパスに対して、UserControllerのindexメソッドが対応される
- UserControllerにindexメソッドを定義している場合、下記のように定義すると
<?php use App\Http\Controllers\UserController; Route::get('/user', [UserController::class, 'index']);
- 利用可能なルーターメソッド
<?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 Route::get('/posts/{post}/comments/{comment}', [CommentController::class, 'show']);
ここでは「Route::get
や Route::post
のように、複数のHTTP動詞に対応したルートを登録できることが特徴的である」という意見が挙がりました。
Symfony
routes.yaml
に記載するパターン/lucky/number
にアクセスすることでLuckyController
のnumber
メソッドにルーティングされる
app_lucky_number: path: /lucky/number controller: App\Controller\LuckyController::number
<?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
だが推奨パターンはアトリビュートを使うパターンのようだ」といった声や、「サンプルコードのようなアトリビュートを使うパターンが分かりやすい」といった声が挙がりました。
CakePHP
routes.php
に記載
例:/
にアクセスするとArticlesController
のindex()
メソッドを実行する
<?php use Cake\Routing\Router; // スコープ付きルートビルダーを使用。 Router::scope('/', function ($routes) { $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']); }); // static メソッドを使用。 Router::connect('/', ['controller' => 'Articles', 'action' => 'index']);
/articles/15
にアクセスすると ArticlesController
の view(15)
メソッドを実行する
<?php $routes->connect( '/articles/:id', ['controller' => 'Articles', 'action' => 'view'] ) ->setPatterns(['id' => '\d+']) ->setPass(['id']);
HTTPメソッドによって分けたいときは以下のような記述を行う
<?php // GET リクエストへのみ応答するルートの作成 $routes->get( '/cooks/:id', ['controller' => 'Users', 'action' => 'view'], 'users:view' ); // PUT リクエストへのみ応答するルートの作成 $routes->put( '/cooks/:id', ['controller' => 'Users', 'action' => 'update'], 'users:update' );
ここでは「必ずしもルーティングは必要でなく、URLから勝手にクラスを推測してくれる」という点に注目が集まりました。
しかし、「知らないと迷いそうなので明記して欲しい」といった声もあり、やはり明示的に定義するのがベストだろうという考えに落ち着きました。
Slim
get()
/post()
メソッドを使用
<?php $app->get('/books/{id}', function ($request, $response, array $args) { // Show book identified by $args['id'] });
<?php $app->post('/books', function ($request, $response, array $args) { // Create new book });
ここではルータメソッドが get()
/ post()
であることから「Laravelと似ている」という意見が挙がりました。
まとめ
ルーティングの利用方法に関して全体を見渡した後には、以下のような意見が挙がりました。
- Symfony:アトリビュートによるルーティングが特徴的
- CakePHP:ルーティングを記載せずともフレームワークで勝手に呼び出すクラスを推測してくれる点が特徴的
- Laravel:
Routes
を見に行く必要があることが面倒である、C#のルーティング定義に似ている - 全般:どこで何をやっているのかが分かるルーティングの方法であることが望ましい
- composerのインストール/アップデートのように、1つの操作が複数の役割を持つような仕組みは分かりづらい
セッション管理
続いて、セッション管理の方法について見ていきます。
Laravel
設定ファイル
- config/session.php
セッションの操作方法
グローバルセッションヘルパー
とRequestインスタンス経由
の2つの方法がある
・グローバルセッションヘルパー
<?php $value = session('key');
・Requestインスタンス経由
<?php public function show(Request $request, $id) { $value = $request->session()->get('key'); // }
Symfony
セッションの操作方法
RequestStack
とRSessionInterface
から取得する2つの方法がある
RequestStack から取得するパターン
- セッションの設定は
config/packages/framework.yaml
に記載 HttpFoundation component
を追加することで利用可能- その他の詳細
基本的な使い方
- セッションの設定は
<?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', []); // ... } }
- SessionInterface から取得するパターン
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
- PHP のネイティブ session 拡張上に、ユーティリティ機能のスイートとラッパーを提供
- 設定
・データベースセッション
・セッションをデータベースに保持することを指定
・セッション保持するためのカスタムモデルを定義することもできる(model => 'CustomSessions')
<?php 'Session' => [ 'defaults' => 'database', 'handler' => [ 'engine' => 'DatabaseSession', 'model' => 'CustomSessions' ] ]
・キャッシュセッション
・CacheSession クラスをセッション保存先として 指定
<?php Configure::write('Session', [ 'defaults' => 'cache', 'handler' => [ 'config' => 'session' ] ]);
- セッションの利用
- リクエストオブジェクトを呼び出せる場所ならどこでも呼び出せる
- 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()
Slim
なし
まとめ
セッションについては、それぞれのフレームワークで利用方法が多岐に渡りました。
ここでは、CakePHPについて、「セッションの参照に Get
ではなく Read
になっているのは何故なのか?」といったコメントや、Slimにはそもそも実装されていない点について、「それがSlimの良いところであり、必要であればcomposerで導入すればよい」といったコメントが挙がっていました。
リクエスト管理
Laravel
<?php $name = $request->input('name'); $name = $request->query('name');
LaravelではRequest
クラスのインスタンスから取得します。
Symfony
<?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もLaravelと同じ様にRequest
クラスのインスタンスから取得します。
CakePHP
<?php $controllerName = $this->request->getParam('controller'); // URL は /posts/index?page=1&sort=title の場合に page を取得するとき $page = $this->request->getQuery('page'); // POSTデータにアクセスするとき $title = $this->request->getData('MyModel.title');
CakePHPもLaravel、Symfonyと似たイメージです。
Slim
<?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();
セッション管理は実装されていませんでしたが、リクエスト管理はSlimにもちゃんと実装されています。
まとめ
ここでは、「いずれもRequestクラスのインスタンスから取得するという点において、だいたいどれも似たような形である」といった意見や、「これがフレームワークの大本みたいな感じがする」といった意見が挙がりました。 Webアプリケーションの基本となる部分であることからも、それぞれのフレームワークに大きな差はないようでした。
エラーハンドリング
Laravel
App\Exceptions\Handler
クラスによって、アプリケーションが投げるすべての例外がログに記録され、ユーザーへレンダーされる- エラーハンドリングのカスタマイズ
Handler
クラスは、カスタム例外レポートとレンダリングコールバックを登録できるregister
メソッドを持っている。reportable
メソッドで、例外をさまざまな方法で報告できる。(エラー監視ツールに登録するなど。デフォルトではログに記録される。)renderable
メソッドで、特定の例外に対して、個別にレンダリング方法を指定することができる。(デフォルトでは例外はHTTPレスポンスに変換される)- HTTPエラーが返された場合、
resources/views/errors
下のHTTPステータスコード名のbladeファイルがレンダリングされる(404.blade.php
など)
- HTTPエラーが返された場合、
<?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); }); }
Laravelの場合、例外を投げた際にデフォルトのものを使うか別のカスタマイズされたものを使うか振り分けることができます。
例えば、404エラーの場合は404.blade
のように定義しておけば、自動で読み込んで画面に表示してくれます。
Symfony
- エラーが 404 Not Found エラーであろうと、コードで何らかの例外をスローすることによってトリガーされた致命的なエラーであろうと、すべてのエラーを例外として扱う。
- 組み込みの Twig エラーレンダラーを使用して、デフォルトのエラーテンプレートをオーバーライド可能。
composer require symfony/twig-pack
- これらのテンプレートをオーバーライドするには、標準の
Symfony
メソッドを使用し て、バンドル内にあるテンプレートをオーバーライドし、それらをtemplates/bundles/TwigBundle/Exception/
ディレクトリに配置する
Copy templates/ └─ bundles/ └─ TwigBundle/ └─ Exception/ ├─ error404.html.twig ├─ error403.html.twig └─ error.html.twig # All other HTML errors (including 500)
ここではTwig
に関して、デフォルトのエラーテンプレートをオーバーライドする際の利用方法が分かり易いといったコメントが挙がりました。
CakePHP
<?php * ErrorController にてエラーページを描画 namespace App\Controller\Admin; use App\Controller\AppController; use Cake\Event\EventInterface; class ErrorController extends AppController { /** * Initialization hook method. * * @return void */ public function initialize(): void { $this->loadComponent('RequestHandler'); } /** * beforeRender callback. * * @param \Cake\Event\EventInterface $event Event. * @return void */ public function beforeRender(EventInterface $event) { $this->viewBuilder()->setTemplatePath('Error'); } }
ここでは、「Cakeの場合はデフォルトのエラーテンプレートをオーバーライドするというよりController
をそれぞれ作るイメージだ」とのコメントがありました。
Slim
- slimが用意したエラー画面を出すかどうかを選択できたり、カスタムエラー画面を表示するなど柔軟な設定が可能
<?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にはテンプレートエンジンがないことから、「用意したエラー画面を出すかカスタムエラーを出すか」であり、リクエスト時にAdd Error Middleware
の設定を行うことでエラーハンドリングを有効/無効化が制御できるという説明がありました。
DBサポート
Laravel
サポートされているDB
DBへのアクセス方法
・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();
ここでは、「LaravelではEloquentを利用するの良いだろう」という意見が挙がりました。 「様々なブログでも紹介されている」とう点や、「SQLを知っていると直感的で分かり易い」といったコメントが挙がっていました。
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
- SELECT文の実行
<?php use Cake\Datasource\ConnectionManager; $connection = ConnectionManager::get('default'); $results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc');
- INSERT文の実行
<?php use Cake\Datasource\ConnectionManager; use DateTime; $connection = ConnectionManager::get('default'); $connection->insert('articles', [ 'title' => 'A New Article', 'created' => new DateTime('now') ],
- UPDATE文の実行
<?php use Cake\Datasource\ConnectionManager; $connection = ConnectionManager::get('default'); $connection->update('articles', ['title' => 'New title'], ['id' => 10]);
- DELETE文の実行
<?php use Cake\Datasource\ConnectionManager; $connection = ConnectionManager::get('default'); $connection->delete('articles', ['id' => 10]);
Slim
なし
最後に
今回はPHPの主要なフレームワークについて、様々な観点から見比べてみましたがいかがでしたでしょうか? どのフレームワークにも様々な特徴がありますが、チーム特性や作成するアプリケーションによっても採用するフレームワークは変わってくると思います。 基本的な部分についてはどれも使い方は同じで、感覚的に使えるようになっているため、実際に触りながら色々と比べて見ると面白そうですね。 ShowNoteの方には「バリデーション」や「マイグレーション」についての比較も行っていますので是非ご覧になってください。 hackmd.io
「PHP TechCafe」では今後もPHPに関する様々なテーマのイベントを企画していきます。皆さまのご参加をお待ちしております。
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/
カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com
ラクスDevelopers登録フォーム
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/
イベント情報
会社の雰囲気を知りたい方は、主催イベントにご参加ください!
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com