プログラミング - THIS IS IT !

より良い開発をすべく日々奮闘しているプログラマーです。設計やDDDに興味があります。主にPHPネタを書いてます。 Crocos Inc./日本Symfonyユーザー会

Symfonyでloggerが呼ばれた箇所にPHPStormでファイルジャンプしやすくする

f:id:okapon_pon:20151205123050p:plain

この記事はSymfony Advent Calender 2016 23日目の記事です。

はじめに

今回はloggerについてのお話です。

何か不測の事態にそなえて logger で記録するということは、本番環境でアプリを運用していくにあたり取り組まれていると思います。

ただ、何かログが吐かれて「やばい調査しないと!」となった時に、ログのメッセージでgrepして、ファイル探して開いて、該当行探してっとなると結構面倒だったりします。

モチベーション

そのため今回のモチベーションは以下の通りです。

  • errorログが記録された(loggerが呼びだされた)箇所のコードを素早く調べたい
  • PHPStormで該当ファイル行を素早く開いて調査したい

f:id:okapon_pon:20161223215003p:plain

実はPHPStormのNavigationのfile検索は最後に「:行番行」と入れると該当行にジャンプすることができます。

なのでloggerで「呼び出し元のファイル名 +:番号」をログに出力するようにしれあげれば、ログをコピーしてPHPStormに入力してすぐ該当ファイルにアクセスできるわけです。

Symfonyのloggerを呼んだ箇所にPHPStormでファイルジャンプしやすくする」という内容についての記事ですが、PsrLoggerInterfaceを実装したLoggerを利用したその他フレームワークでも利用可能なテクニックとなっていると思います。

やったこと

PsrLoggerInterfaceのメソッドであるerror()関数は第2引数にcontextという配列を受け取ることができます。

contextはloggerが呼び出されたときの状況を記録するために利用することができます。

(例)ユーザー情報の更新に失敗した場合のロギング

$this->error('Update failed', ['user_id' => $user->getId());

今回の場合はloggerに行番号付きでファイルパスを渡します。 以下のイメージです。

$this->get('logger')->error('Error occurred!', ['caller' => 'path/to/SampleFile.php:100']);

loggerのメソッドが呼ばれた時に、この'caller' という情報がデフォルトで入るようにするのが今回の目的です。

ではどうやって呼び出し元を取得してくるか?

PHPには debug_backtrace() という関数があります。

PHP: debug_backtrace - Manual

<?php
function foo()
{
    $trace = debug_backtrace();
    var_dump($trace);
}
foo();

結果

/PhpstormProjects/test.php:5:
array(1) {
  [0] =>
  array(4) {
    'file' =>
    string(47) "/PhpstormProjects/test.php"
    'line' =>
    int(7)
    'function' =>
    string(3) "foo"
    'args' =>
    array(0) {
    }
  }
}

配列を見てみると 呼び出し元のファイル名や行番号が配列に入っていることが分かります。これを利用して呼び出し元のファイル行数を取得します。

詳細は後で掲載するサンプルコードを参照ください。

どうやって実装するか

既にloggerを利用している箇所が沢山あるのであれば、別のメソッドを足した新しいクラスを作るという選択肢はないでしょう。PsrLoggeerInterfaceを損なわない形でloggerクラスを差し替えたいところです。

幸いLoggerについてはPSR-3にて PsrLoggerInterfaceが定められており、十分安定したInterfaceであると考えられるのでこれに従うのが筋がいいと考えられるます。

1番思いつきそうな実装方法はサブクラスを作る方法

Interfaceの変更さえなければ既にloggerを利用している箇所も問題なく動きます。

悪くはないと思いますが、継承という手段を用いる以上親クラスの実装が置き換わったりすると壊れる可能性はあると思います。 (Loggerだとあまりないとは思いますが…)

そこでデコレータ

デコレータパターンはGoFデザインパターンの一つで、既存のインターフェースを変更することなくクラスに機能追加することができるようにする設計パターンです。

個人的にとても好きなデザインパターンの1つです。

今回やりたいことはloggerの機能はそのままで「'caller'という呼び出し元の付加情報をデフォルトで付与する」という機能の追加になります。 デコレーターパターンを使うのに向いているのではないでしょうか。

実装方法

実はSymfonyのDIには既存のServiceクラスをお手軽にデコレートする方法が提供されています。

http://symfony.com/doc/current/service_container/service_decoration.html

今回はloggerを置き換えてやればよいので以下の様な設定となります。

  app.traceable_logger:
    class: AppBundle\Logging\TraceableLogger
    decorates: logger
    arguments:
      - "@app.traceable_logger.inner"
      - "%kernel.root_dir%"

decorates オプションを指定することで元のlogger サービスを app.traceable_logger でデコレートすることをDIコンテナに伝えることができます。 その際元の logger サービスは @app.traceable_logger.inner としてリネームされ、引き続きコンテナ内に残り続けます。

今回は元のloggerを利用したいのでargumentsで@app.traceable_logger.inner を渡してあげます。

肝心のTraceableLoggerクラスですがPsr/Log/LoggerInterfaceSymfonyDebugLoggerInterfaceを実装します。

Symfonyの場合loggerの実体はSymfony\Bridge\Monolog\Logger なので、DebugLoggerInterface も実装してあげた方が良いでしょう。

コードは以下の通りとなります。

<?php

namespace AppBundle\Logging;

use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;

class TraceableLogger implements LoggerInterface, DebugLoggerInterface
{
    const NUM_OF_TRACE = 1;

    /**
     * @var LoggerInterface
     */
    private $logger;
    private $projectRootDir;

    /**
     * @param LoggerInterface $logger
     * @param string $kernelRootDir
     */
    public function __construct(LoggerInterface $logger, $kernelRootDir)
    {
        $this->logger = $logger;
        $this->projectRootDir = realpath($kernelRootDir . '/../');
    }

    /**
     * {@inheritdoc}
     */
    public function emergency($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->emergency($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function alert($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->alert($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function critical($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->critical($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function error($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->error($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function warning($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->warning($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function notice($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->notice($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function info($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->info($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function debug($message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->debug($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context = [])
    {
        $context['caller'] = $this->getCallerPath();
        $this->logger->log($level, $message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function getLogs()
    {
        if ($this->logger instanceof DebugLoggerInterface) {
            return $this->logger->getLogs();
        }

        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function countErrors()
    {
        if ($this->logger instanceof DebugLoggerInterface) {
            return $this->logger->countErrors();
        }

        return 0;
    }

    /**
     * @return string
     */
    private function getCallerPath()
    {
        $trace = debug_backtrace();
        $callerInfo = $trace[self::NUM_OF_TRACE];
        if($callerInfo['file'] && $callerInfo['line']) {
            // project root 以下のパスで返す
            return $this->normalizePath($callerInfo['file']) . ':' . $callerInfo['line'];
        }

        return '';
    }

    private function normalizePath($realPath)
    {
        return str_replace($this->projectRootDir . '/', '', $realPath);
    }
}

LoggerInterfaceには、emergency, alert, critical… etc. と沢山のメソッドがあり、デコレートしなければならないメソッドが多いのはちょっとあれですが…

実際に呼び出し元のファイルパスを取得しているのは getCallerPath() メソッドの部分になります。

少し工夫している点としてdebug_backtrace()で得られるファイルパスは絶対パスのためそれをプロジェクトルート起点のパスに変更しています。

絶対パスはproduction環境とdevelop環境で異なるでしょうし、PHPStormの検索ではプロジェクトルート以下のパスで十分です。

実際に動かしてみると以下のようなlogが得られます。

f:id:okapon_pon:20161224024356p:plain

ファイル検索を実際にしてみると、該当行に直接ジャンプすることができました。

f:id:okapon_pon:20161224025959p:plain

このようにしてデコレーターを活用してあげると既存の機能も損なわず、またコードの利用側にも影響を与えることなく機能追加をすることができます。

デコレーターパターンはインターフェースのパワーを利用した素晴らしい設計手法ですね。

まとめ

  • PHPStormではファイル名の後に「:行番号」でファイルの該当行にジャンプすることができる
  • 呼び出し元ファイルは debug_backtrace() で取得できる
  • SymfonyのDIにはDecorate 機能が用意されており、既存のサービスクラスを簡単に拡張することができる

今回は呼び出し元のファイルパスを取得して、それをloggerのcontextに付加情報として追加するという内容でした。

他にもloggerで常にアクセス元のURLを出すようにしたり、ログインユーザーのIDを出したりとかも簡単に実装できますね。

余談

余談ですが、Symfonyフレームワーク内ではデコレーターが普通に利用されています。

ControllerResolverはdev環境で動かした場合にはTraceableControllerResolverによってデコレートされ、コントローラがresolveされるまでの時間を計測する機能が追加されています。

【参考】

https://github.com/symfony/symfony/blob/5129c4cf7e294b1a5ea30d6fec6e89b75396dcd2/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php#L58

計測結果はプロファイラーでパフォーマンスとして見ることができます。

Symfonyでダイナミックにconfigリソースを読み込む方法

f:id:okapon_pon:20151205123050p:plain

この記事はSymfony Advent Calender 2016 7日目の記事です。

実は以前Symfony meetupのLTで話した内容(スライド作ってない)ですが、改めてご紹介してみたいと思います。


ここから少し宣伝

宣伝となりますがSymfony meetup ではLT発表枠がありまして、そこではネット上に(特に日本語では)あまり公開されてないノウハウなどが聞くことができるかもしれません。

↓興味がありましたら、meetupの方への参加もよろしくお願いします。

symfony.connpass.com

宣伝終わり


では本題

Symfonyのconfigリソース

Symfonyでは config.yml などの設定ファイルをresourceと呼んでいます。 assetやview、translationなどもresourceと呼びますがここではconfigリソースについて解説します。

Symfonyのコンフィグレーションドキュメント Configuring Symfony (and Environments) (current)

上記ドキュメントから引用&抜粋

Configuration Formats

Throughout the chapters, all configuration examples will be shown in three formats (YAML, XML and PHP). YAML is used by default, but you can choose whatever you like best. There is no performance difference:

The YAML Format: Simple, clean and readable; XML: More powerful than YAML at times & supports IDE autocompletion; PHP: Very powerful but less readable than standard configuration formats.

You can also load XML files or PHP files.

ドキュメントに記載の通り、Symfonyの設定ファイルはyml以外にもxmlphpファイル、またiniファイル(下記リンク参照)も読み込むことが可能です。

【参考】

Loading Resources (The Config Component - Symfony)

本記事で扱う内容

説明にもある通りymlはシンプルに書けるし、symfonyの標準フォーマットでもあるので、基本的にyamlで書くことがほとんどだと思いますが、ここではphpフォーマットについて解説したいと思います。

phpフォーマットだけ解説する理由としてはxmlやiniファイルはyamlとほぼ機能的に変わらないためです。

余談ですが、Symfony2.0の頃は parameters.yml ではなくparameters.ini が使われていました。

phpファイルをリソースとして読み込む方法

例えば、コンテナにパラメータを設定したい場合にはこのようになります。

# app/config/config.yml
imports:
    - { resource: 'parameters.php' }
<?php

$container->setParameter('key', 'value');

これだけです。

簡単ですがこれだけなら「yamlで十分じゃないか」「むしろ記述が面倒だ」という話になってしまうのでもう少し説明します。

phpファイルをリソースとして利用するメリット

yamlではできない動的(ダイナミック)な処理を行うことができる

これに尽きます。

phpファイルであるため、パラメーターの設定や置き換え、特定条件下で実行するなどどんな処理でも行うことができます。

いくつか実例

基本編

1. 環境変数から値を取得してパラメーターをセットする

サービスコンテナで外部パラメータをセットする方法 | Symfony2日本語ドキュメント

Symfony は、 SYMFONY__ の接頭辞の付いたあらゆる環境変数を読み取り、サービスコンテナのパラメータとしてセットすることができます。

こちらのドキュメントにも書いてありますが、元々の機能として SYMFONY__ というprefixがついた環境変数は自動でパラメーターとして設定されます。

しかし、環境変数がこういった命名規則に則っていない場合もあると思います。(複数アプリで同じ値を読まないといけない場合など) その場合もphpなら値を入れられますね。

この方法を用いれば環境変数以外にも外部から取得してきた値を利用することも可能です。

2. 特定ファイルが存在する時にだけ設定を読み込む

<?php
$file = __DIR__ . '/some_file.php';

if (file_exists($file)) {
    // overrideしたいパラメーターをセット
    $params = require $file;
    foreach ($params as $key => $value) {
        $container->setParameter($key, $value);
    }
}

単に設定がphpファイルで書かれている場合にも利用できます。 あまり実用的ではないですが、レガシーコードに依存している場合などどうしようもない状況下では選択肢としてはあり得るのかなと。

※ dev環境では通常コードを書き換えたらコンテナは再生されますが some_file.php を更新してもファイルの変更が検知されるcacheが再生成されないため注意

応用編

これまで parameterの設定にのみ絞って紹介しましたが、ここからは動的にconfigrationする方法を紹介します。

ここまでちゃんと説明してこなかったのですが、実はリソースファイルの読み込みはcontainerのコンパイル前に行われます。 そのため、リソースファイルに書いた設定でframeworkの動作を変更することもできます。 またコンパイルされてキャッシュされるということは、毎回ファイルを読み込むこともないのでパフォーマンス面も気にする必要はありません。

では、引き続き実例を紹介します。

3. 特定環境下でDBのconnectionを変えたい

開発サーバー上とlocalhost上で接続先を変えたい場合など

<?php
// どこかから設定を取ってくる
$connection = [
    'host' => 'localhost',
    'dbname' => 'blog_local',
    'user' => 'root',
    'password' => null,
];
$container->loadFromExtension('doctrine', [
    'dbal' => [
        'connections' => [
                'default' => $connection,
            ],
        ],
    ],
]);

少し応用すれば、DB名にtest_ prefixを付けたり、動的にslaveサーバーのhostを追加することもできます。

4. 自動でassetバージョンを付与する

これが今回紹介する中で一番簡単で実用的かもしれません。

assetバージョンとは?

<link rel="stylesheet" href="/asset/css/style.css?201612070000">

こういうやつです。 cssファイルなどの静的コンテンツにクエリーストリングとして、バージョン番号を付けてやることで、cssを更新した際に古いcssがブラウザにcacheされ続けてしまう問題を回避することができます。

<?php
// app/config/assets.php
$container->loadFromExtension('framework', [
    'assets' => [
        'version' => date('YmdHi'),
    ],
]);

phpリソースファイル中でコンテナが使えるのはなぜ?

リソースファイルが読み込まれる箇所

Kernel.php https://github.com/symfony/symfony/blob/a4edafbd7d82ccea983f04ade3dc220e72f340e2/src/Symfony/Component/HttpKernel/Kernel.php#L659

PhpFIleLoader https://github.com/symfony/symfony/blob/a4edafbd7d82ccea983f04ade3dc220e72f340e2/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php#L39

<?php
    public function load($resource, $type = null)
    {
        // the container and loader variables are exposed to the included file below
        $container = $this->container;
        $loader = $this;

        $path = $this->locator->locate($resource);
        $this->setCurrentDir(dirname($path));
        $this->container->addResource(new FileResource($path));

        include $path; // ここでphpファイルを読み込む
    }

load()メソッド内に$containerというローカル変数を作っているので phpのリソース中から$containerが参照できるのでした。

まとめ

  • configファイルにphpを利用したら黒魔術的ではあるがわりと何でも設定できる。
  • ただし、やり過ぎると何が設定されているか分からなくなってしまうので注意

Symfonyでdebug環境を最適化しコードを追いやすくする

この記事はSymfony Advent Calender 2015 5日目の記事になります。

f:id:okapon_pon:20151205123050p:plain

昨日は@clonedさんの「Symfonyから手早くYAMLのFixtureを読み込めるAliceFixturesBundle」でした。 空いていた12/4を埋めてくださりありがとうございます。私も救世主となるべく投稿することにしました。

実はこの記事が去年のSymfony Advent Calender以来1年ぶりの投稿となってしまいました。 中々アウトプットできてないですね。orz...

さて本題


Symfonyでdebug環境を最適化しコードを追いやすくする

対象環境

Symfony2系(ただしSymfony3系についても少し触れます)

前置き

Symfonyで開発時のデバッグやコードリーディングを行いたい場合、デバッガー立ち上げてコードを追うと、キャッシュファイルが読み込まれてしまって、Symfonyコアのコードが読みづらいということがあると思います。 エラーが発生した場合にも同様で、どのクラスのどの行なのか分かりづらいことがあります。

具体的には、app/bootstrap.php.cacheclasses.php というファイルのことで、このファイルにはAppKernelやRequestクラス等を始めフレームワークの多くのコアクラスが1ファイルに記述されています。

1ファイルに多数のクラスを詰め込むことで何度もファイルをrequireせずに済みパフォーマンスを向上させているわけですが、開発時のデバッグ&コードリーディングを行いたい場合にはしばしば邪魔になることがあります。

この記事ではその問題をなんとか解決するための方法を書きたいと思います。

設定方法

symfonyのcacheとautoloadの仕組みが分かってしまえばそんなに難しい話ではないのですが、意外と日本語情報もないのでご紹介したいと思います。

紹介したいといいつつ、実は公式ドキュメントに書いてあります。(最近気が付きました)

How to Optimize your Development Environment for Debugging (2.8version)

Symfonyはドキュメントが充実していてどんどん増えているのでよくチェックしておかないとですね。

やることとしては

  1. app/bootstrap.php.cache を読み込まずに、普通にautoloadを利用する
  2. 生成したcacheファイル(classes.php)を読み込まないの2つです。

app_dev.phpを以下のように編集するだけです。

変更前

<?php
// ...
$loader = require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';

$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$request = Request::createFromGlobals();

変更後

<?php
// ...
// $loader = require_once __DIR__.'/../app/bootstrap.php.cache';
$loader = require_once __DIR__.'/../app/autoload.php';
require_once __DIR__.'/../app/AppKernel.php';

$kernel = new AppKernel('dev', true);
// $kernel->loadClassCache();
$request = Request::createFromGlobals();

bootstrap.php.cache の中身ですが、先頭でautoload.phpを読み込んでいて、その後に続けてSymfonyのコアクラスが書かれています。 変更後のコードに書き換えることで、通常のautoloadによってSymfonyのコアクラスがきちんとロードされるようになります。 また、loadClassCache()コメントアウトすることでclasses.phpを読み込まなくなります。

以上で設定完了です。簡単ですね。

これでコードが読みやすくなりました。Symfonyのコードを読むのはとても参考になるので是非チャレンジしてください。

Symfony3の場合

実はSymfony3の場合も既にドキュメントが書いてありました。 こちらを参考に設定してみてくだい。

How to Optimize your Development Environment for Debugging (current)

補足すると、Symfony3では auloload.phpbootstrap.php.cache から分離されました。

【参考】 extract autoloading out from bootstrap by Tobion · Pull Request #854 · symfony/symfony-standard · GitHub f:id:okapon_pon:20151205123050p:plain そして bootstrap.php.cache/var/ディレクトリ直下に配置されるようになり、app_dev.phpではデフォルトで読み込まないようになりました。

app.phpではパフォーマンス向上のためbootstrap.php.cache利用しています。 https://github.com/symfony/symfony-standard/blob/3.0/web/app.php#L9

まとめ

  • ちょっと、書き換えるだけでdebugしやすいようになります。
  • Symfonyは(英語の)ドキュメントが充実しているのでよく見ておくといいです。

最期に

まだ「[Symfony Advent Calender 2015](http://qiita.com/advent-calendar/2015/symfony」の枠で空いているところがあります。 この記事のように、日本語化されていないドキュメントのちょっとした紹介や入門記事でも構いません。 是非この機会にSymfonyについて記事を書いてみてください。

明日は @hidenorigoto さんのあの本の話ですね。とても楽しみです。

2014年個人的Symfony振り返り、そして 2015年・・・

この記事はSymfony Advent Calender 2014 25日目最終日の記事になります。

昨日は qcmatsuokaさんの「Symfony2 カスタムバリデーションの作成」でした。

カスタムバリデータ作成は一応公式ドキュメントに書かれてはいるものの意外と良い記事がなくて苦戦しがちなところだと思います。 私も以前苦戦した記憶があります。

わりと詰まりがちなところですので、やってみました記事が出てくるのはいいですね。

emailのシステム制約の実装は実践的な内容ですし、Symfony入門者にとって嬉しい内容だと思いました。ご丁寧にスクショまでついています。

本題

さて本題です。タイトルから想像がつきますが、残念ながら技術的な内容ではありません。技術的内容を期待されていた方ごめんなさい。

本当は「レガシー 続・フラットなPHPからSymfonyへ」という誰得(?)な内容でブログを書こうと思っていたのですが、時間がなくて内容を変えました。

ごめんなさい。機会を作ってまた書こうと思います。

個人的2014Symfony振り返り

ここからは私個人の主観による2014年Symfonyの所感と、Symfonyコミュニティ発展のため小ネタも含めて書いていきたいと思います。 入門者向けの情報も書いてますので、最期まで読んでみてください。

その1 今年はSymfonyユーザーが増えてきているのを実感した

1-1

まず、今年のSymfony Advent Calender 2014はほとんどの人が2周目登録することもなく全枠埋まりました。

しかも皆さん期日通り記事をアップされていて、クオリティーもすごく高くてこういう日本語情報が欲しかった! という記事がたくさん集まりました。

良質な記事を書く人が増えたのは純粋に嬉しいですね。

Symfony Advent Calendar 2012 は本当に大変で、同じ人が何周もしてました(笑)

1-2

2つめは、Symfony2を採用している会社が増えてきて、勉強会に参加される中でも利用者の割合が増えてきました。

2013年ぐらいまでは勉強会を開催しても、あまり周りでSymfony2を使っているということを聞いたことがないですが、最近はちらほら聞くようになりました。

これまでSymfony勉強会は決まった人が前に出てはなすところがありましたが、今年は今まで発表したことない人の発表が多かったです。 よくよく考えたら、私も今年初めて発表した人の一人でした。

あの @t_wada さんがSymfonyを使っているというのは今年一番の衝撃だったかも知れません。

勉強会では様々な会社の事例・取り組みであったり、開発ノウハウ、Symfonyを使った開発スタイルなどリアルな情報を交換できたことはとてもよかったです。

Symfonyの使い方で悩んでいる、という方がもしいらっしゃいましたら勉強会へ参加されることをオススメします!

勉強会の開催情報を逃したくない方はこちらへ → 日本Symfonyユーザー会 | Doorkeeper

その2 Symfony入門者の増加を感じた

日本Symfonyユーザー会 メーリングリスト を見ていてもそうですが、最近Syomfony入門者の方が増えているのか、これまでほとんど動いていなかった日本Symfonyユーザー会のメーリングリストでしたが、最近はぼちぼちの頻度で質問が投稿されています。

メーリングリストや、#symfony_ja というハッシュタグをつけてtwitterに投稿すれば誰かしら回答してくれると思います。

入門者の方は是非活用してみてください。

ただし、ここで注意。

「今後はSymfonyに関する質問は 日本語版のstack overflow を使うこと」を推奨することになりましたのでそちらで質問されるのがよいと思います。

日本語版のstack overflow Symfony2タグ

その3Symfonyもくもく会が開催されるようになった

今年からと言っても、11月からですがSymfonyもくもく会というものが開催されるようになりました。

いつも勉強会に参加されている人から、Symfonyをほとんど触ったことのない人達まで参加しているゆるい会です。半分ぐらいはSymfony触ったことないという方でした。

もくもく会と称しつつ作業半分、議論半分ぐらいで、もくもくやってる時間の方が少ないかもしれません。

今のところ月1ぐらいのペースで開催していて、おそらく1月も開催すると思います。

こちらも参加者の募集はDoorkeeperでやっています。 日本Symfonyユーザー会 | Doorkeeper

2015年は?

2015年は 次期LTSであるSymfony2.7が5月に、そしてメジャーバージョンアップである Symfony3.0が11月にリリース予定となっています。

The Release Process (Contributing to Symfony)

来年は動きのある年となりそうで、今後のSymfonyの発展が楽しみですね。

来年の抱負

  • Symfony勉強会 #11も開催したいとろ。LTSや3.0も出るので、ワークショップ形式でもいいかも。
  • 次回も発表する
  • そろそろ日本語ドキュメントのFormの章の翻訳を終わらせたい(最近ユーザー会への質問も多い。。)
  • 来年もコミュニティへの貢献
  • Symfony温泉!(これ重要!!)

以上、Symfony Advent Calendar 2014 でした。

ここまでSymfony Advent Calendar 2014のバトンを繋いでこられた皆さま。本当にお疲れ様でした!!

今年も残すところあと僅かですね。それでは皆さま良いお年をお過ごしくださいませ!!

「BEAR.Sundayから学ぶテストプラクティス」という記事を書きました

タイトル通りなのですが、BEAR.Sunday Advent Calendar 2014 の21日目の記事として書きました。

BEAR.Sundayから学ぶテストプラクティス - Qiita

よろしければ読んでみてください。

これを書こうと思った経緯なども時間のあるときに書きたいなー