このエントリーをはてなブックマークに追加

2020年あけましておめでとうございます!今年もカルテット開発部ブログをよろしくおねがいします!

この記事は Symfony Advent Calendar 2019 19日目の記事です。大幅に遅刻しましたが検証に時間がかかったためなのでご容赦ください m(_ _)m

now.shとは

now.sh とは、サーバーレスで様々な言語のウェブアプリケーションを動かすことができるPaaSです。

公式にサポートされているのはnode.js, Go, pythonですが、サードパーティのビルダーを利用することでPHPも使えます。
※ ただし、 PHPのビルトインウェブサーバー を使うことになるので、本番環境にはおすすめできないかもしれません。試す際は自己責任でお願いします。

Symfony4アプリをGitHub経由でnow.shにデプロイしてWEBアプリケーションとして動かす

ソースコード側の準備

src/Kernel.php に細工しておく

now.shの環境では /var/cache/var/log に書き込めないので、キャッシュ書き込み先とログ書き込み先をnow.shでも書き込める /tmp 等に変えておく必要があります。 どこをどのように変えるのかは https://github.com/juicyfx/now-php/blob/master/examples/framework-symfony-microservice/src/Kernel.php を参考にしてください。

.env じゃないファイルを使う

now.shではデプロイ時に .env という名前のファイルはすべて無視されます。
無視しない設定はありません、必ず削除されます(T_T)

かと言って Symfony4+で .env は必要な環境変数のテンプレートなので、内容が全然なくても .env を置かないでアプリケーションを動かすことはできません(厳密に言うと違うんですが…気になる人はお手元のSymfonyアプリケーションのconfig/bootstrap.phpを読んでみてください)。now.shのデプロイ時に .env を消されないためには、 .env.env じゃない名前に変えます。私は myenv にしました。
.env を変えたら config/bootstrap.php でSymfonyDotEnvによって .env を読み込んでいる箇所を探して、 myenv を読むように変更しておきましょう。

Symfony4のphpunit.xml.distはテスト時のbootstrapとして config/bootstrap.php を読み込んでくれるので、ローカルでの機能テスト等も問題なく動きます。
テスト用の環境変数は .env.test でなく myenv.test、 ローカル動作確認用の環境変数は .env.local でなく myenv.local に記載すればOKです。

デプロイ設定

秘密の環境変数の準備(ローカル)

アプリケーションにはDBのパスワードとか、SECRETとか。レポジトリにコミットしたくない環境変数がありますよね。
そういった秘密の環境変数はローカルPCにnow-cliをインストールしてnow-secretsに登録しておきます。

# xxxという変数名でyyyという値を登録
$ now secrets add xxx yyy

注意しておきたいのは、secret名はユーザー別であってプロジェクト別ではないという点です。複数のWEBアプリをnow.shにデプロイしてそれぞれ違う値を使いたい場合はそれぞれ a-password b-password のように、どのsecretがどのプロジェクト用のものかわかるようにしておくと良いでしょう。

.nowignoreの準備

now.shにデプロイするときに無視するファイル・ディレクトリを指定するファイルです。gitignoreと同じようなものと考えてください。
ローカルから /vendor を含めてデプロイしようとするとビルドが FetchError というエラーでほぼ確実に落ちるので、 .nowignore/vendor は除外しておきましょう。
/vendor なしでデプロイしてもサーバー側で勝手に composer install してくれるようです。

now.jsonの準備

now.json を準備します。 now.jsonはnow.shのためのデプロイ指定を記述するjsonファイルです。プロジェクトのルートディレクトリに置きます。

{
  "version": 2,
  "env": {
      "APP_ENV": "prod",
      "MY_ENV": "@my-env"
  },
  "builds": [
    { "src": "public/index.php", "use": "now-php"}
  ],
  "routes": [
    { "src": "/(.*)", "dest": "public/index.php" }
  ]
}

{ "src": "public/index.php", "use": "now-php"} で指定している now-php というのがPHP用のビルダーです。
公式でなくサードパーティ扱いですが、特にベンダープリフィクスは不要のようです。

now.sh側で composer install するときは require-dev は無視されるようなので、 APP_ENV はかならず prod にしましょう。
now secretsに登録した秘密の環境変数は @ をつけて参照できます。(MY_ENVの定義を見てください)

デプロイ

GitHubにpushするだけです。

安定性

正直自分専用の軽いAPIアプリしか動かしてないので、まだわかりません。
最初に書いたように、PHPのビルトインウェブサーバーを使うことになるので、安定性は保証できないと思っています。


このエントリーをはてなブックマークに追加

Symfonyアドベントカレンダー 23日目の記事です :christmas_tree::crescent_moon:

こんにちは。カルテットコミュニケーションズ でフロントエンドエンジニアをしています、松岡です。

ここ数年はフロントエンド開発に没頭しているため Symfony の知識はほとんどありません…。ですが Symfony4+ で Server-Sent events が使えるらしい!という事を知って思わず飛びついてしまいました。

この記事では Server-Sent events をローカル開発環境で試すまでのステップを紹介させていただきます。

Symfony Gets Real-time Push Capabilities!
https://symfony.com/blog/symfony-gets-real-time-push-capabilities

Server-Sent events とは

平たく言えばバックエンドからフロントエンドに対してプッシュ通知する仕組みです。

データの更新を伴う処理は、画面での保存ボタンのクリックなどをトリガとして、フロントエンドからバックエンドに対して ajax でリクエストするのがよく使われる方法です。

エンティティの保存など、短時間かつ単独のプロセスで処理が完了するものについては 1回の ajax リクエストで完了したかどうかの判定をする事が多いですよね。ですがレポート生成など「いつ終わるか分からない」ものに対して ajax リクエストのレスポンスを待っていると数時間も待機状態になってしまう事があります。

このような場合は、数秒〜数分おきにステータス変更の問い合わせを ajax リクエストで行う ポーリング という手法をよく使っていました。ポーリングには、リクエストの間隔を短くしたり監視対象が増えたりすると ajax リクエストが爆発的に増えてしまうというデメリットがあります。

SSE(Server-Sent events) では、繰り返しの ajax リクエストは不要になり、ポーリングよりずっとスマートに処理を行う事ができます。

検証に使った環境とパッケージ

Symfony のローカルサーバーを立ち上げるまで

まずは Symfony のインストールから始めましょう。

参考サイト
【初心者向け】初めてのSymfony4
Symfony4+ が全く分からないので同僚エンジニアに入門記事を書いて!!と頼んで書いてもらった記事です。めちゃくちゃ参考になりました。

今回の SSE は Symfony 5系ではうまく動作しなかったので、4系 をインストールしました。

$ mkdir symfony-sse && cd $_
$ composer create-project symfony/website-skeleton:^4.4 symfony-sse

ローカル用の環境変数を設定します。

$ cp .env .env.local

# .env.local
# DATABASE_URL=mysql://{USER}:{PASSWORD}@127.0.0.1:3306/sample?serverVersion=5.3

今回は Symfony の Web アプリケーションとしての機能は使用しませんが、正常に動作している事を確認したいのでローカルサーバーをインストールします。

$ composer req server
$ php -S 127.0.0.1:8000 -t public/

bin/console server:start でサーバーは起動するもののブラウザで該当ページが Connecton refused になってしまうので php コマンドを使いました…。

ブラウザで http://127.0.0.1:8000 を開いて「Welcome to Symfony 4.4」が表示されればセットアップ完了です!

Symfony でプッシュ通知をする

バンドルのインストール

Symfony で SSE を実現するのは mercure(メルキュール)という外部のパッケージです。

コンポーネントとして symfony/mercure が公開されていて単体で利用する事もできますが、それをラップし設定を環境変数から読み込んでくれるバンドル symfony/mercure-bundle が便利です。今回はバンドルのほうをインストールします。

$ composer require mercure

インストールが完了すると .env.local に設定が追加されています。プッシュ通知の URL をローカル検証用のものに変更しておきます。

###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
- MERCURE_PUBLISH_URL=http://mercure/.well-known/mercure
+ MERCURE_PUBLISH_URL=http://localhost:3000/.well-known/mercure
# The default token is signed with the secret key: !ChangeMe!
MERCURE_JWT_TOKEN=...
###< symfony/mercure-bundle ###

Hub インストール

mercure は専用のプロトコルを使ってプッシュ通知を行います。専用のプロトコルでの GET / POST を処理する Hub と呼ばれるサーバーが必要なのでダウンロードします。私は Mac を使っているので _Darwin_x86_64 をダウンロードしました。

https://github.com/dunglas/mercure/releases/tag/v0.8.0

Hub サーバー起動

ダウンロードした圧縮ファイルを解凍して mercure のバイナリをターミナルから呼び出します。 プロダクション環境では jwt-keyorigin などもろもろ調整する必要がありそうですが、今回はローカルでの検証目的のためデフォルトをそのまま使う事にします。

$ cd {DOWNLOAD_DIR}

$ ./mercure --jwt-key='!ChangeMe!' \
  --addr=':3000' \
  --debug \
  --allow-anonymous \
  --cors-allowed-origins='*' \
  --publish-allowed-origins='http://localhost:3000'

macOS Catalina では初回のみ 開発元を検証できないため開けません というメッセージが表示されます。「システム環境設定 > セキュリティとプライバシー > ダウンロードしたアプリケーションの実行許可 > このまま許可」の操作を行ってください。

ブラウザで http://localhost:3000 を開くとこのような mercure のデバッグ用ページが表示されます。

デバッグ用ページで Pub/Sub を試す

mercure のデバッグ用ページではプッシュ通知(Publish)と待ち受け(Subscribe)を試す事ができます。

  • 「Subscribe」でトピックを入力し Subscribe ボタンを押します(青枠)
  • 「Publish」でトピックとデータを入力し Publish ボタンを押します(赤枠)
  • Publish ボタンを押すごとにプッシュ通知の内容が追加されます(オレンジ)

トピックはプッシュ通知のチャンネルのようなもので、チャンネル別にプッシュ通知や待ち受けを整理する事ができます。

Symfony コマンドの作成

公式ドキュメントでは コントローラを使用したプッシュ通知のサンプルコード が紹介されていますが、実際の運用ではバッチ処理の完了などがプッシュ通知の主なタイミングになると予想されます。そのためコンソールコマンドを経由したプッシュ通知を試す事にしました。

まずコンソールコマンドを作成します。

$ bin/console make:command push-message

作成したコマンドのソースコードを下記のように変更します。

<?php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Mercure\Update;

class PushMessageCommand extends Command
{
    protected static $defaultName = 'app:push-message';

    private $publisher;

    public function __construct(PublisherInterface $publisher)
    {
        $this->publisher = $publisher;
        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setDescription('push message demo')
            ->addArgument('message', InputArgument::REQUIRED, 'Argument description')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $message = $input->getArgument('message');
        $this->push($message);
        $io->success(sprintf('%s pushed.', $message));

        return 0;
    }

    private function push($message)
    {
        $update = new Update('topic1', $message);
        call_user_func($this->publisher, $update);
    }
}

Symfony\Component\Mercure\PublisherInterface

<?php
public function __construct(PublisherInterface $publisher)
{
  ...

mercure コンポーネントを autowire で注入するとデバッグ画面で試した「Publish」と同じ動作を Symfony コードから実行する事ができます。公式のサンプルコードではクラスを注入していますが、mercure の新しいリリースによりインターフェースに代わっています。
https://github.com/symfony/mercure-bundle/releases/tag/v0.2.0

Symfony\Component\Mercure\Update

<?php
private function push($message)
{
    $update = new Update('topic1', $message);
    call_user_func($this->publisher, $update);

Update はペイロードを格納するクラスです。このクラスを Publisher に渡すと環境変数に設定された Hub サーバーにリクエストを送信するという仕組みになっているようです。

コマンドを経由してプッシュ通知を試す

実際にコマンドを叩いてプッシュ通知を送信してみましょう。

$ bin/console app:push-message "テスト!!!"

mercure のデバッグ用ページを見てみましょう。 新しいプッシュ通知を受信しているはずです。

percel で待ち受ける

mercure のデバッグ用ページを見ればプッシュ通知が正常に動作している事は一目瞭然ですが、せっかくなのでフロントエンドでの待ち受け処理も書いてみましょう。

最小最速で Web アプリケーションを作るには PARCEL がおすすめです。

$ mkdir my-app && cd $_
$ touch index.html
$ touch index.js
<!-- index.html -->
<html>
  <head>
    <script src="./index.js"></script>
  </head>
  <body>Hello my-app!</body>
</html>
$ npm install -g parcel-bundler
$ parcel ./index.html

ブラウザで http://localhost:1234 を開くと「Hello my-app!」と表示されます。 ビルドからサーバー起動までたったの1秒!速いですね。

待ち受け用のコード

// my-app/index.js

const es = new EventSource(
  'http://localhost:3000/.well-known/mercure?topic=topic1'
);

es.onmessage = e => {
    console.log(e.data);
}

JavaScript のコードはたったこれだけです。

ブラウザが EventSource に対応している必要がありますのでご注意ください。
MDN EventSource
https://developer.mozilla.org/ja/docs/Web/API/EventSource

Symfony のプッシュ通知を JavaScript でキャッチする

ここまでの手順で準備は全て完了しています。 Symfony のコマンドを叩いて JavaScript がキャッチする事をブラウザで確認してみてください。

終わりに

手順の紹介は以上です。 Symfony4+ であれば簡単に SSE が利用できるのでポーリングに代わる新しい方法として、ぜひプロダクション環境でも使ってみたいなと思います。

明日は kojirock5260 さんの記事です。 よろしくお願いします!


このエントリーをはてなブックマークに追加

こんにちは!Symfonyアドベントカレンダー 21日目です :christmas_tree::crescent_moon:

今回は、Symfony4.4から新しく使えるようになったWeekType Fieldを使ってみようと思います。

WeekType Fieldとは?

<input type="week">に対応したFormTypeです。

そもそも、<input type="week">とは?

<input> 要素の week 型は、年と、その年の ISO 8601 週番号 (つまり、第1週から第52または53週) を簡単に入力することができる入力欄を生成します。

詳細は、MDNのドキュメントをご参照ください。https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input/week

WeekType Fieldを使ってみる

<input type="week"> 自体よくわかっていないですが、とりあえず使ってみます。

デフォルトで使ってみる

  • src/Form/TaskType.php
class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('dueDate', WeekType::class, [
                'input' => 'array',         //default
                'widget' => 'single_text'   //default
            ])
            ->add('submit', SubmitType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Task::class
        ]);
    }
}

こんな感じで、カレンダーから選択できます!便利〜 :drooling_face:

input type="week"となっています!!!(GoogleChrome)

スクリーンショット 2019-12-20 10 06 49

オプションをいろいろ使ってみる

  • 'widget' => 'choice'<select>になります。(GoogleChrome)

スクリーンショット 2019-12-20 10 52 34

  • 'widget' => 'text'input type="number"になります。(GoogleChrome)

スクリーンショット 2019-12-20 10 37 47

最後に

使い所はパッと思いつきませんが、使えるところには使えそうですね :sweat_smile:(週単位の検索など)

ただ、ブラウザの互換性についてSafariやFirefoxに未対応だったりするので、'widget' => 'choice''widget' => 'text'を使うのが良さそうです。

参考

  • https://symfony.com/blog/new-in-symfony-4-4-week-form-type