2017年10月23日月曜日

webdevopsのdockerテンプレートを活用する

AWS, Google, Azure各サービス、コンテナのサポートが進むなか、遅ればせながらSymfonyのアプリケーションをdockerで動かすにはどうすればいいのか検討することにしました。

とりあえず動かす程度であればそれほど苦労はしないものの、本番アプリケーションを稼働させようとすると、いろいろ課題が見えてきました。

  • データベースはどこに用意しよう?
  • ログの永続化しないと消えちゃうね
  • バッチでファイルを生成してるけど、これも消えちゃうね
  • cronは?
  • メール送信は?

以前dockerをテストしていた時はubuntuのイメージをベースにapache入れて、設定して、をDockerfileにつらつら書いていましたが、最近のdocker普及によって様々なDockerfileが公開されています。これを使う手はない!と思い、検索してヒットして見つかったのが、webdevopsdockerリポジトリ です。

このリポジトリの特徴は、base と呼ばれるベースイメージを元に、alpine, centos, debian, ubuntu という複数のディストリビューション向けのlamp環境を提供しているところです。

base で用意されているコンテナ起動時のスクリプト(entrypoint.sh)からの複数サービス起動が整っているのも便利です。Dockerfile本体に手をいれずに、COPYするconfディレクトリに設定を追加しておけば、必要があれば複数のサービスを起動できます。

Dockerfileをいじらずにさまざまな設定を行うことができる反面、ディレクトリ構造やentrypoint.shからのスクリプト実行の流れはわかりづらい印象があります。自分の覚え書きも兼ねて、しばらく webdevops でいろいろ遊んで(?)みようと思います。

2014年4月22日火曜日

Symfony2.3へのアップグレード

4/19の土曜日に、Symfony勉強会 #9に参加してきました。#8に引き続き2回目の参加の新参者ですが、毎回参考になる発表が多く、かつ自分のSymfony愛(?)を大幅にupさせてくれる。すてきな勉強会です。

SymfonyはLong Term Support Releaseである2.3がリリースされてほぼ1年経過しました。また、2.2系は2014/5にサポート終了となることから、新規に使い始めるプロジェクトでは、2.4で導入された新機能をどうしても使いたい!というケース以外は2.3を選択することになるかと思います。 いままで〜2.2を使っていた方は2.3にアップグレードすることになります。2.2で非推奨になり、2.3で削除されるクラスやメソッドが存在するので注意が必要です。

UPGRADE-2.2.md

さらに、こちらのFabienの投稿にも有用な情報があります。

New in Symfony 2.2: Logging of deprecated calls

Symfony2.2以降では、非推奨なメソッドを呼んだり、非推奨なクラスのインスタンスを生成したときに、ログにdeprecation.warningとして記録されて、デバッグツールバーでもすぐわかるようになりました。

ここで実際私が体験した2.3へのアップグレードで気づいた点のお話です。 2.1 => 2.2 => 2.3とアップグレードしていったのですが、タイミングの関係で2.2を使用した期間が短かったのです。 2.2での開発や検証時にデバッグツールバーやログでのdeprecationを見落としてしまい、結果として2.3に上げた時点で、非推奨なメソッドを呼び出した時点でメソッドが存在しなくてエラーになってしまいました。

たとえばExecutionContext::addViolationAtPath()などです。 @okapon_ponさんもおっしゃっていましたが、フォームやバリデーションに互換性がなくなる変更が多かった印象です。 まだSymfony2.3にアップグレードしていないプロジェクトは、それなりに対応に時間をとっておいたほうが良いかと思います。

でも、これを乗り越えてしまえば、Fabienが「2.3以降はやむをえない理由以外は後方互換を保ちたい」と言っているように、今後のアップグレードは楽になると思います。

2013年6月6日木曜日

SwiftMailerでsubjectが文字化け

Symfony2でアプリケーションからメールを送信する際、標準のSwift Mailerを使うのが一般的でしょう。 PHPメンターズさんの記事「Practical Symfony #15: Swift Mailerによる日本語メールの作成」がとても参考になります。

私もこの記事のコードを参考にしてsubject, from, 本文に日本語を使ったメールが問題なく送信できることを確認していたのですが、この送信ロジックを複数のコントローラ内に散乱させてしまっていたので、サービスにして共通化しようとリファクタリングを始めました。

mimeが効かない

services:
    my_mailer:
        class:     MyBundle\Service\Mailer
        arguments:
            - @mailer
            - @templating
            - @translator
            - @logger
とサービスを定義して、Switf_Mailerをコンストラクタ内で注入して、sendメソッドでは$this->mailer->send()で送信。コントローラ内のロジックをサービスに集約する、ごくごく普通のリファクタリングをしたつもりでした。

ところが、このリファクタリングを入れてからsubjectのmimeエンコーディングがされなくなり、いわゆる「文字化け」が発生するようになりました。

原因はわかっているものの

mimeが効いていないので、おそらくは
Swift::init(function () {
    Swift_DependencyContainer::getInstance()
        ->register('mime.qpheaderencoder')
        ->asAliasOf('mime.base64headerencoder');

    Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
});
の設定がうまく反映されていないんだろうという見当はすぐつきました。ただ、なぜサービス内に移しただけで反映しないのかが釈然としません。

シングルトン

さきほどの設定部分のコードを眺めていてわかるのは、
  • Swift::init()はstaticなメソッドである。
  • Swift_DependencyContainerとSwift_Preferencesはシングルトンである。
ここまで来て、ようやく理解できました。私はメールの送信のメソッド内に上記設定を書いていました(PHPメンターズさんの記事ではアプリケーションのバンドルに配置するのを推奨しているにもかかわらず)。Swift::init()はSwift_Mailerのオブジェジェクトが生成される前に実行できていないといけなのですが、サービスで定義してコンストラクタでSwift_Mailerを注入するようになってからSwift::init()を呼んだ時点ではすでにSwift_Mailerのオブジェクトは生成されているため、mimeの設定が反映されない状態になっていました。

解決

mimeの設定をSwiht::init()で行うことを公式ページでも推奨しているのですが、別にsend()毎に実行してもよいのではないか?ということで、
Swift_DependencyContainer::getInstance()
    ->register('mime.qpheaderencoder')
    ->asAliasOf('mime.base64headerencoder');

Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
(snip)
とSwift::init()内から外に出して解決しました。mimeの設定コストがどのくらいなのか計測していませんが、バッチで大量に配信するものでなく、ウェブアプリケーションからなんらかのクライアントのリクエストからキックされてメールを送る用途では、気にしないでいいのではないでしょうか。 このやり方であれば、メール毎にmime設定のオンオフができます(する機会があるかは疑問ですが)

SwiftMailerネタはまだあります

メール本文のテキストの折り返しが意図しないところで行われる挙動にもだいぶ悩まされました。いずれ記事にします。

2013年6月4日火曜日

Symfony2.1から2.2へのアップデート

Symfony2.3.0のリリースおめでとうございます

2.3系はSymfony2はじめてのLTS(Long Term Support Release)で、今後3年間のメンテナンス期間が設定されています。 LTSは魅力的なのですが、βのころからコミットを追いかけていたわけではない私は、まだ公開サービスを2.3に移行するには早いと判断して、 ひとまず2.1のプロジェクトを2.2にアップデートしてみようとやってみました。

基本的な作業は

  1. composerでcreate-project
  2. composer.jsonにプロジェクト独自のパッケージを追加して、composer updateを通す
  3. 既存プロジェクトのvendor参照先をできあがったvendorに変更する
  4. app/config/config*.ymlの差分を確認、反映
  5. app/AppKernel.phpの差分を確認、反映

ここまでやって、php app/consoleを実行すればデバッグ環境なのでapp/cache以下がごそっと作成されます。 Symfonyをお使いの方であれば、php app/consoleでヘルプが表示されればひと安心!という気持ち、わかっていただけるかと思います。

すんなり移行できると思いきや

ところが、エラーハンドリングのカスタマイズをしていたところでエラー発生。 src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.phpを継承してクラスを作成して、それをconfig.ymlの
twig:
    exception_controller: Foo\Bundle\Controller\ExceptionController::showAction
    ...
で指定していたのですが、showActionのプロトタイプが変わっているようでした。

githubの差分を見てみたところ、ContainerAwareを継承しないController、つまりController as a Serviceに変更されていました。

新版のshowActionにプロトタイプをあわせて、いままでContainerAwareを継承していたため使えた$this->get('xxxx')をすべてコンストラクタで注入するようにして動作するようになりました。ここいらの設定は普通のサービスと変わりません。

ライブラリやフレームワークをアップデートしてアタリがある箇所ってたいてい、既存のインターフェースをimplementsしてたり、クラスをextendsしていたりすることが多いですね。

まとめ

  • Symfony2.2以降、標準のExceptionControllerはController as a serviceとなり、リクエストを注入しレスポンスを返すサービスになった

2011年11月8日火曜日

Zend Framerowkでデフォルトと異なるビュースクリプトでレンダリングする

何回使っても覚えられずにいつもgoogleにお世話になってしまいます。 forwardと違って表示しようとするスクリプトのactionは実行されません。
return $this->getHelper('viewRenderer')->setNoController()->setScriptAction("controller/action");

2011年10月11日火曜日

短縮リンク先取得であらためて、KISSの原則について考えた

Twitterの短縮がリンクがすべて t.co/... になってからもう数ヶ月たちますが、関連アプリケーションを開発している人たち以外にはほとんど意識する必要はないでしょう。

いっぽう、Twitterクライアントを開発している人たちは、t.co/...の短縮リンクを展開するにはどうしたらいいか、必ず直面する課題だと思います。bit.lyにあるようなAPIは提供されていないのです。

bit.lyをいままで使っていた私は、APIがないから展開できないと考えていたのです。しかし、よくよく考えてみると実際にt.coのリンクにアクセスして、リダイレクトをたどっていけば最終的には求めるURLにたどり着けるはずです。

開発の現場でスケジュールに追われていると、このような発想の転換はなかなかできません。後になって冷静に考えると、問題を解決しようとしてかえって問題を複雑にとらえてしまっていることが多々あります。

詰まってしまったら、まず以下を意識すると解決の糸口になるかもしれません。

  • 大きな問題は細かく分割する
  • 難しくかんがえない。自分だけだとなかなか気づかないことも多いので、第三者のアドバイスをあおぐのも手です

(おまけ)短縮リンク取得(Zend Framework版)

        $config = array(
            'adapter'   => 'Zend_Http_Client_Adapter_Curl',
        );
        $client = new Zend_Http_Client(短縮リンクのURL, $config);
        $client->setMethod(Zend_Http_Client::GET);
        $response = $client->request();
        if ($response->getStatus() == "200") {
            $url = $client->getUri(true);
        }

2011年9月18日日曜日

phpのエラーハンドリング

エラーを適切にキャッチし、記録し、対策することはどんな言語を使っていても重要です。 phpでこれらの処理を行うのによく使う関数やディレクティブを並べてみました。


関数

error_reporting
出力する PHP エラーの種類を設定する

ディレクティブ

error_reporting
error_reporting()関数同様の機能。ただし演算子の記述に制限あり
display_errors
エラーをHTML出力の一部として画面に出力するかどうかを定義する
log_errors
エラーメッセージを、サーバーのエラーログまたはerror_logに記録するかどうかを指定する
error_log
スクリプトエラーが記録されるファイル名を指定する

それぞれのおすすめ設定

error_reporting

E_NOTICEもログ出力しておくとバグに気づくことがあります。

display_errors

開発環境ではtrue、本番環境ではfalse(これは必須)

log_errors

開発環境、本番環境問わずtrue

error_log

省略するとapacheのエラーログに記録されます。アプリケーション個別のエラーログファイル名を決めて指定することを推奨。

まとめ

  • 本番環境、開発環境問わずerror_reportingにはE_NOTICEも記録する
  • 本番環境ではdisplay_errorsは必ずfalseを指定。開発環境ではお好みですが、trueを設定しておくと画面に表示されるので対応漏れを減らすことができます。
  • エラーログはアプリケーションごとにファイルを分ける。これにより、監視するときもアプリケーション単位で行うことができます。

注意

error_reportingに設定するE_ALLの値がphpのバージョンで変わります。 このせいでphpのバージョンアップをしたとたんに大量のエラーログに見舞われることがあります。これについてはまた別の機会に。