19
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

サムザップAdvent Calendar 2021

Day 12

Laravel Octaneの開発時のハマりポイントと回避方法について

Last updated at Posted at 2021-12-11

本記事は、サムザップ Advent Calendar 2021の12/12の記事です。

はじめに

Laravel Octane(Swoole版)でアプリケーションをつくるときにはまったポイントを紹介していきます

ハマりポイントをすぐ知りたい方は、ローカルの開発環境構築はすっとばしてください(サンプルアプリケーションの構築から読んでください)

ローカルの開発環境構築

基本は公式ドキュメントにしたがって構築していきます
今回は、Laravel Sailを使う方法を選択しています

Laravel アプリケーションの作成

まずはLaravelのアプリケーションを作成します

$ composer create-project laravel/laravel sample

Laravel Octaneのインストール

つづいてLaravel Octaneのインストール

$ cd sample
$ composer require laravel/octane

Laravel Sailのインストール

つづいてLaravel Sailのインストール

$ composer require laravel/sail --dev
$ php artisan sail:install

使用するミドルウェアの選択肢がでてきます
今回は、MySQLだけを使用するので、0を選択します

 Which services would you like to install? [mysql]:
  [0] mysql
  [1] pgsql
  [2] mariadb
  [3] redis
  [4] memcached
  [5] meilisearch
  [6] minio
  [7] mailhog
  [8] selenium
>0

ドキュメントに従い一度、buildします

$ ./vendor/bin/sail build --no-cache

supervisor.confファイルを調整するためにPublishします

$ ./vendor/bin/sail up
$ ./vendor/bin/sail artisan sail:publish

※ コンテナ名を変更している場合は、環境変数 APP_SERVICE に変更したコンテナ名を渡してください
変更しないと、 sail:publish で使用するコンテナ名の解決に失敗するため

カレントディレクトリ配下に docker ディレクトリが作成されます

sailがつかう docker-compose.yml にて

docker-compose.yml
context: ./docker/8.0

になっていることを確認します(PHP8.0を使います)

supervisord.confを編集して、Swooleを使うようにします

$ vi docker/8.0/supervisord.conf

commandの行を書き換えます

変更前

supervisord.conf(変更前)
command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80
user=sail

変更後

supervisord.conf(変更後)
command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port=8000

serveoctane:start --server=swoole に変更しています

もう一度コンテナイメージを作り直します

$ ./vendor/bin/sail build --no-cache

Laravel OctaneのSwooleモードでは、Port 8000番をListenするように変更したので、それにあわせて、 docker-compose.yml も修正します

docker-compose.yml
        ports:
            - '${APP_PORT:-80}:80'
            - 8000:8000

8000番を追加します

Laravel Octaneの起動

Laravel OctaneをSwooleモードで起動します

$ ./vendor/bin/sail up

起動時のメッセージでLarave Octaneが8000番をListenしているのが確認できます

sample-laravel.sample-1  | 2021-11-20 09:28:47,887 INFO Set uid to user 0 succeeded
sample-laravel.sample-1  | 2021-11-20 09:28:47,891 INFO supervisord started with pid 16
sample-laravel.sample-1  | 2021-11-20 09:28:48,897 INFO spawned: 'php' with pid 17
sample-laravel.sample-1  | 2021-11-20 09:28:49,899 INFO success: php entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
sample-laravel.sample-1  | 
sample-laravel.sample-1  |    INFO  Server running…
sample-laravel.sample-1  | 
sample-laravel.sample-1  |   Local: http://0.0.0.0:8000 
sample-laravel.sample-1  | 
sample-laravel.sample-1  |   Press Ctrl+C to stop the server
sample-laravel.sample-1  | 

最後にoctaneのconfigファイルを生成します

$ ./vendor/bin/sail artisan octane:install
 Which application server you would like to use?:
  [0] roadrunner
  [1] swoole
 >1

今回はSwooleなので1を選択します
これにより、config/octane.php が生成されます

サンプルアプリケーションの構築

次のディレクトリ構成でファイルを作成します

Sample
├── app
│   ├── Console
│   ├── Exceptions
│   ├── Http
│   │   ├── Controllers
│   │   │   ├── Controller.php
│   │   │   └── SampleController.php   新規作成
│   │   ├── Kernel.php
│   │   └── Middleware
│   ├── Models
│   ├── Providers
│   └── Service
│       └── RandomNumberService.php 新規作成
├── artisan
├── bootstrap
├── composer.json
├── config
├── database
├── docker-compose.yml
├── package.json
├── phpunit.xml
├── public
├── resources
├── routes
│   ├── api.php
│   ├── channels.php
│   ├── console.php
│   └── web.php   変更
├── server.php
├── storage
├── tests
└── vendor

それぞれのファイルは次のように記述しています

(1)コントローラー

SampleController.php
<?php

namespace App\Http\Controllers;

use App\Service\RandomNumberService;
use Illuminate\Http\Response;

class SampleController extends Controller
{
    public function __construct(
        private RandomNumberService $service
    ){}

    public function index(): Response
    {
        return response('Number:' . $this->service->getNumber(), Response::HTTP_OK);
    }
}

(2)サービス

RandomNumberService.php
<?php
namespace App\Service;

class RandomNumberService
{
    protected $number;

    public function __construct()
    {
        $this->number = mt_rand(1, 10000);
    }

    public function getNumber() :int
    {
        return $this->number;
    }    
}

(3)ルーティング

web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/sample', 'App\Http\Controllers\SampleController@index');

動作確認

実際に 「 http://127.0.0.1:8000/sample 」にブラウザでアクセスしてみると
次のように表示されます(初回表示)

ブラウザでの表示

ハマりポイント

ここでブラウザをリロードしてみましょう

プログラムの挙動として、乱数で生成された数字を表示しようとするため、前回と違う数字が表示されることが期待されます

が、実際には、次のように表示されます(2回目以降の表示)

ブラウザでの表示2

かわっていない!

Laravel Octaneの挙動

通常のPHPはリクエストごとに(リクエストが終わるごとに)状態が破棄されますが、Laravel Octaneは、自身がWebサーバーとなり常時PHPのプログラムがうごいたままになっています
つまり、サンプルアプリケーションの記述の仕方では、コントローラーのインスタンスの生成が1回しかされず、そのまま使いまわされます(2回目以降のリクエストは、生成済みのコントローラーのインスタンスがレスポンスを返している)

はまりました・・・

解決案(1)

Workerに注目する

実は、Laravel OctaneのSwoole版には起動オプションがいくつかあります
その一つが max-requests のオプションです
Laravel Octaneは厳密に言うとWorkerがリクエストの処理を担います
1つのWorkerが何回もリクエストを捌くことになるのですが、Worker自身をリロードする契機としてこの max-reqeust があります
ここに指定した回数のリクエストを処理すると、Workerはリロードします

max-requests=1 にする

では、起動オプションで max-requests を1にしてみましょう

supervisord.confを編集します

supervisord.conf
command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port=8000 --max-requests=1

sail build でコンテナイメージは作り直します)
では実際の挙動はどうなるでしょうか?

ブラウザでアクセスしてみます

Worker1.png

かわった!
けど、おそい!
max-request を1に変更する前は、1回目のリクエスト以外は、50ms以内にレスポンスがかえってきました
しかし、max-request を1に変更したあとのレスポンスは、概ね1000msです
(手元のPCでの計測です)
これは、Laravel Octaneをつかっている意味がなさそうです

解決案(2)

Closureに注目

Laravelのルーターは、第二引数にClosureをとることができます
これを利用します

つまり、ルーターを次のようにします

web.php(追記)
Route::get('/sample', 'App\Http\Controllers\SampleController@index');
Route::get('/sample2', fn () => app()->make(\App\Http\Controllers\SampleController::class)->index());

では、実際に sample2 にアクセスしてみましょう
(画像は省略します)

リロードするたびに値が変わるのが確認できます!
しかもレスポンスも、1回目のリクエスト以外は、50ms以内にレスポンスがかえってきます

解決案(3)(未解決)

Octaneの作法

上記以外にも、Laravel Octaneで用意している仕組みがあります

config/octane.php に次のセクションがあります

octane.php
    /*
    |--------------------------------------------------------------------------
    | Octane Listeners
    |--------------------------------------------------------------------------
    |
    | All of the event listeners for Octane's events are defined below. These
    | listeners are responsible for resetting your application's state for
    | the next request. You may even add your own listeners to the list.
    |
    */

    'listeners' => [
        WorkerStarting::class => [
            EnsureUploadedFilesAreValid::class,
            EnsureUploadedFilesCanBeMoved::class,
        ],

        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            //
        ],

RequestReceived::class にて、リクエストを受け付けた時に処理をはしらせるhookの記述があります
ここのhookがつかえそうです

が、コントローラーをうまく初期化する方法まで調べることができませんでした
もしご存知の方がいれば、情報下さい!

まとめ

Laravel Octaneでアプリケーションを作った際に、最初に、コントローラーのコンストラクタが1回しか呼ばれない現象になやまされました
よくよく考えれば、わかることなのですが、その回避策をいくつか検討してみました
現時点では、Closureを使う方法で回避することができました

しかし、よりOctaneらしい記述があれば、そちらを踏襲していければと思います

明日は@norimatsu_yusukeの記事です!

19
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?