RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

PHPerのための「PHPフレームワーク」を語り合う【PHP TechCafe イベントレポート】

弊社で毎月開催し、PHPエンジニアの間でご好評をいただいているPHP TechCafe。
2022年11月のイベントでは「PHPフレームワーク」について語り合いました。
弊社メンバーがピックアップしたPHPの代表的なフレームワーク4種について、以下のShowNoteをベースに、参加者の皆様のご意見も伺いながら学んでいきました。今回はその内容についてレポートします。

rakus.connpass.com

hackmd.io

フレームワークとは

まず初めに、フレームワークとは、アプリケーション開発においてよく利用される機能をあらかじめ備えた枠組みのことです。 もう少し具体的に説明すると、以下の通りです。

  • Webアプリケーションフレームワーク

    • 動的な ウェブサイト、Webアプリケーション、Webサービスの開発をサポートするために設計されたアプリケーションフレームワーク
      • Web開発で用いられる共通した作業に伴う労力を軽減する
        • データベースへのアクセス
        • テンプレートエンジン
        • セッション管理
  • 何故フレームワークが必要なのか?

    • 開発速度向上
      • Webアプリケーション開発でよく利用する処理(セッション管理やDBアクセス、Cookieなど)が既に用意されているため、それらを再利用するだけで開発が進められる
    • セキュリティ対応
      • 脆弱性が見つかった場合に修正版がリリースされる
    • 開発ルールの順守
      • フレームワークのルールに従って作成することが強いられる反面、開発チーム全体で共通のルールで開発できるため、ルールに逸脱するようなコードが生まれにくい

ここでは、「セキュリティに関わる実装を自前で組むメリットは少ないため、実績のあるフレームワークを利用することが一番の正攻法ではないか」といった意見が挙がっていました。

代表的なPHPフレームワーク

次に、事前に弊社メンバーが抜粋した、代表的なPHPフレームワークそれぞれの設計思想について語り合いました。 (抽出対象や並び順に意図はございません)

Laravel

laravel.com

  • プログレッシブフレームワーク
    • どのような規模や段階の Web アプリケーションにも対応できるフレームワークの概念
    • 依存性注入、単体テスト、キュー、リアルタイム イベント などのための堅牢なツールを提供
  • スケーラブルなフレームワーク
    • システム規模や利用負荷などの増大に対応できる
    • Laravel アプリケーションは、1 か月あたり数億のリクエストを処理するように簡単にスケーリングされている
  • コミュニティフレームワーク
    • コミュニティの活動が活発であり、意見交換も頻繁に行われている

参加者からは「確かにイージーなイメージがあり、何でもできて簡単。」といった意見や、「依存性注入や単体テストのために独自に拡充された機能が提供されており、色々出来て便利」といった意見が挙がりました。Laravleにはサービスコンテナと呼ばれる機能が備わっているため、依存性注入を簡単に行うことができたり、ユニットテストを考慮して構築されていることがその理由となります。

Symfony

symfony.com

ここでは、Symfony自体がLaravelの中で使われているという点について活発にコメントが飛び交いました。 「Symfonyのリリースが遅れることによってLaravelにも影響が出たケースがあった」という声や、「Laravelのリリース頻度がSymfonyのバージョンアップに依存するような形になったということを過去に記事で見た」という声です。 現時点の最新バージョンであるLaravel9についても、Symfonyコンポーネントに依存していることが要因となりLTS(※LTSはLong Term Supportの略で長期サポートを意味)がなくなったりと、実際に様々な影響を及ぼしていることから、このような声が多く挙がっているようです。

CakePHP

cakephp.org

  • 「ケーキを焼くくらい簡単に開発できる」というコンセプトで設計されており、初心者向けであるといえる
  • Ruby on Rails の概念の多くが取り入れられている
  • 比較的小規模なWebアプリ開発向けである
  • MVCモデル」が採用されており、役割分担をさせて高速に開発を進められる

ここでは、「MVCに準拠しており、命名がすごく厳格だという印象がある」という意見が挙がりました。

Slim

www.slimframework.com

www.slimframework.com

  • シンプルかつ強力な Web アプリケーションと API をすばやく作成するのに役立つ PHP マイクロ フレームワーク
  • 必要なことだけを行う最小限のツール セットのみを提供
  • 最小限の機能のみを持つため、ルールがシンプルで、開発の自由度が高く、学習コストが低い

ここでは、マイクロフレームワークを謡っているという点について「テンプレートエンジンが標準で提供されておらず、細かなBot作成や小さな機能開発を行う際に利用しやすい」といった意見や、「自由に拡張できるという点がありがたい」といった意見が挙がりました。

機能比較

続いて、各フレームワークの基本的な利用方法に注目し、それぞれの特徴について比較しながらディスカッションを行いました。

ルーティング

Laravel

laravel.com

  • ルートファイル

    • デフォルトでは下記2つファイルにルーティングを定義する
  • 定義方法

    • UserControllerにindexメソッドを定義している場合、下記のように定義すると /user のパスに対して、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::getRoute::post のように、複数のHTTP動詞に対応したルートを登録できることが特徴的である」という意見が挙がりました。

Symfony

  • routes.yaml に記載するパターン
    • /lucky/number にアクセスすることで LuckyControllernumber メソッドにルーティングされる
app_lucky_number:
    path: /lucky/number
    controller: App\Controller\LuckyController::number
  • アノテーション または アトリビュート を利用するパターン(こちらが推奨)
    • コントローラを以下の通り変更することで routes.yaml を作成しなくともルーティングが行われる
<?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

book.cakephp.org

  • routes.php に記載
     例:/ にアクセスすると ArticlesControllerindex() メソッドを実行する
<?php
use Cake\Routing\Router;

// スコープ付きルートビルダーを使用。
Router::scope('/', function ($routes) {
    $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
});

// static メソッドを使用。
Router::connect('/', ['controller' => 'Articles', 'action' => 'index']);

 /articles/15 にアクセスすると ArticlesControllerview(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

www.slimframework.com

  • 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

laravel.com

  • 設定ファイル

    • config/session.php
  • セッションの操作方法

    • グローバルセッションヘルパーRequestインスタンス経由 の2つの方法がある

    ・グローバルセッションヘルパー

<?php
$value = session('key');

    ・Requestインスタンス経由

<?php
    public function show(Request $request, $id)
    {
        $value = $request->session()->get('key');
        //
    }

Symfony

  • セッションの操作方法

    • RequestStackRSessionInterface から取得する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

book.cakephp.org

  • 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

laravel.com

<?php
$name = $request->input('name');
$name = $request->query('name');


LaravelではRequest クラスのインスタンスから取得します。

Symfony

symfony.com

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

www.slimframework.com

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

readouble.com

  • App\Exceptions\Handler クラスによって、アプリケーションが投げるすべての例外がログに記録され、ユーザーへレンダーされる
  • エラーハンドリングのカスタマイズ
    • Handler クラスは、カスタム例外レポートとレンダリングコールバックを登録できる register メソッドを持っている。
      • reportable メソッドで、例外をさまざまな方法で報告できる。(エラー監視ツールに登録するなど。デフォルトではログに記録される。)
      • renderable メソッドで、特定の例外に対して、個別にレンダリング方法を指定することができる。(デフォルトでは例外は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

symfony.com

github.com

  • エラーが 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

book.cakephp.org

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

www.slimframework.com

  • 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

readouble.com

 ・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

symfony.com

  • 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に関する様々なテーマのイベントを企画していきます。皆さまのご参加をお待ちしております。

connpass.com


エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
20210916153018
https://career-recruit.rakus.co.jp/career_engineer/

カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com

ラクスDevelopers登録フォーム
20220701175429
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/

イベント情報
会社の雰囲気を知りたい方は、主催イベントにご参加ください!

◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.