ぷぎがぽぎ このページをアンテナに追加 RSSフィード

2015-12-20

[][] Loggerクラスをテストのたびにmockを用意しなくて済むための工夫

この記事は、Symfonyアドベントカレンダー2015の19日目の記事です。でも今日は20日ですね。(ごめんなさい。。忘れてました。。

18日はnaoyesさんのSymfonyプロジェクトのテストにCodeceptionを使ってみるでした。

今日は11/24 に開催された PHP BLT #1 でLTしてきた内容ですが、まだ発表内容をまとめていなかったので書きます。

Symfony では yaml でサービス構成の定義ができる

Symfony で 何かサービスを作るとき、必要な外部サービスを次のようにyamlで定義することができ、DI コンポーネントによって hoge_manager が用意されます。

    hoge_manager:
        class: HogeBundle\Service\HogeManager
        arguments:
            - @doctrine.orm.entity_manager
            - @report
            - @slack_notification
            - @loger

そして、HogeManager クラスはargumentsで定義した何かをコンストラクタで受け取るようにすればいいわけです。

<?php
/**
 * Hoge Manager
 */
class HogeManager
{
    public function __construct($em, $reporter, $slack, $logger)
    {
        $this->em = $em;
        $this->reporter = $reporter;
        $this->slack = $slack;
	$this->logger = $logger;
    }

コントローラーからは InjectParams が便利

また、このように名前をつけた何かは、コントロラーからなどは `InjectParams` を使えばコンストラクタなどで利用することもできます。

<?php

namespace HogeBundle\Controller\Hoge;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use JMS\DiExtraBundle\Annotation as DI;

/** @Route("/hoge") */
class HogeController extends Controller
{
    private $hoge;

    /**
     * @DI\InjectParams({
     *     "hoge" = @DI\Inject("hoge_manager")
     * })
     */
    public function __construct($hoge)
    {
        $this->hoge = $hoge;
    }

外部の何かを利用したクラスのテストはmockが活躍

さて、このHogeManager クラスのテストを書いてみましょう。このマネージャのテストを書きたいので、

外部の何かはmockにすることになります。Phakeを使うとこんな感じでしょうか

<?php
namespace HogeBundle\Tests\Service;

use HogeBundle\Service\HogeManager;

class HogeManagerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * setup
     */
    public function setUp()
    {
        $this->logger = \Phake::mock('Psr\Log\LoggerInterface');
    	...

ここで `$this->logger` を毎回用意しないといけなくなります。

もはや mock を毎回書かなくてもテスト書けるように慣れると幸せですよね。というわけで Loggable Trait を作ってみます。

Loggable Trait

<?php
namespace Hoge\HelperBundle\Logger;

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

trait Loggable
{
    /** @var LoggerInterface */
    private $logger;

    public function setLogger($logger)
    {
        $this->logger = $logger;
    }

    protected function logger()
    {
        if (is_null($this->logger)) {
            $this->logger = new NullLogger();
        }

        return $this->logger;
    }
}

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

loggerのsetterを用意しているのと、getterではsetされていなければNullLoggerを返しています。

なので、setされていなくてもエラーを履かずに動きます。

なので、yaml定義を以下のようにします。

    hoge_manager:
        class: HogeBundle\Service\HogeManager
        arguments:
            - @doctrine.orm.entity_manager
            - @report
            - @slack_notification
            - @loger
		calls:
			- [setLogger, ["@logger"]]

コンストラクタではなくsetLoggerをコールするように指定します。

HogeManager は Trait を使うので以下のようなコードに修正します

<?php
/**
 * Hoge Manager
 */
class HogeManager
{
use Loggable;
    public function __construct($em, $reporter, $slack)
    {
        $this->em = $em;
        $this->reporter = $reporter;
        $this->slack = $slack;
    }

コンストラクタでloggerを渡さなくてもよくなりましたし、logger関連のメソッドは全部Traitにあるのでuse Loggable するだけです。

これで、テストもmock作らなくてもNullLoggerが勝手に作成されるのでテストもすっきりします。

コンストラクタで渡す何かが増えすぎたら設計を考えなおす時期かもしれない

あと、このLoggableを使うとわかることは、そのサービス全体が依存しているものはコンストラクタで渡すのは自然で良いのですが、

サービスの一部だけが依存しているようなものまでもコンストラクタで渡すようにするとテストが辛いものになりやすくなります。

サービスの一部が利用するような外部の何かは、その一部で利用するメソッドで渡すなど別の手段を考えてみたほうが良いです。

Symfony では簡単に依存関係をコンストラクタで渡せるのでついついなんでも書いてしまいがちですが、

コンストラクタに大量に何か渡すような設計になってきたら、本当にそれはコンストラクタで渡すべきなのか、またはクラスの仕事が多すぎるなど設計を再度考える時期なのかもしれませんね。

というわけで、20日はimunewさんで「Doctrine DBALで取得したデータをキャッシュする」です!(既に公開されていますね!すばらしい!いや、ほんとごめんなさい。。

DBの問い合わせがWebアプリケーションで一番重くなることはよくあるので、キャッシュ大事ですよね!

2015-02-25

[][] CRUDっぽいルーティングとアクションを追加できるTraitコントローラーを作ってみた

管理画面で欲しいCRUD処理

いわゆる管理画面作ってると CRUDができるAdmin Gegeneratorっぽいものが欲しくなりますよね。

色々アプローチはあると思います。

  • テンプレートからひな形のコードを生成してしまう Generetor 方式
    • 必要があれば直接編集したり、直接編集しない場合はカスケード機能みたいなものがあったり
    • Symfony だと `generate:doctrine:crud` コマンドっぽいの
  • ymlなどで定義を書けばDSLで管理画面っぽいCRUDをある程度好きにできる方式

そして、これらはWebアプリケーションフレームワークが標準で用意していたり、別途ライブラリ(バンドル)を入れたりすることで実現可能だったりします。

個人的に、完全generateの場合は再generateすると死ねるので、「最初の一発だけ生成させて後は頑張る」になりますし、必要以上に生成されるコードが多すぎたりするのであまり選択したくありません。

ymlで定義できる方式は「DSLで表現できるかぎりは良いけど、複雑なものは無理ゲー」なので積極的に使いたいとは思えません。

というわけで、「おれがかんがえたミニマムなCRUDバンドル」が今回のお話です。

普通のコントローラークラスにTraitで CRUDなルーティングと機能が生える

出落ちですが、そういうことです。

Symfony2のバンドルとして作りました。

Traitを使うというのと、array()は[]で書いてるので、PHP5.4以上じゃないと動きません。

まさか、今どき5.3でry

Traitに慣れていないという方は下記の記事あたりを読んでみてください。

ちょっと語弊ありそうですが、「クラスに機能(メソッド)があたかも最初からあったかのように追加できる」ものです。

scalaのTraitと名前は一緒ですが、できることが違うのでそれはそれで注意が必要だったりします。


つまり方向性としては

  • 既に用意しているコントローラークラスに Trait を use するだけで CRUDな機能を追加できる。です。

つまり、自動生成する部分はありません。Traitで書かれたとおりにしかデフォルトでは動きません。

シンプルですね。

@Crud アノテーションで Entity, Repository, 各テンプレートファイルを指定

とはいえ、どの Entity をどの Repository を使ってどのテンプレートファイルで。というのは指定する必要があります。

Traitなので既存のコントローラークラスにプロパティを生やすことも考えたのですが、Trait を使わなくなったときに use 行を削除するだけでなく無駄なプロパティも削除する必要があるので、今回は Doctrine にあるアノテーション Reader を使ってこれらを書けるようにしました。CRUD機能が要らなくなったらuseを外せばアノテーションは残るけど無視されるだけなんでまだましかなと。(ちょっと微妙な感はあります)


最終的な利用方法は以下のような感じ

<?php
namespace Brtriver\SimpleCrudBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

use Brtriver\SimpleCrudBundle\Controller\SimpleCrudTraitController;
use Brtriver\SimpleCrudBundle\Annotation\Crud;

/**
 * @Route("/simple_crud", name="simple_crud_demo")
 * @Crud(
 *   entity="Brtriver\SimpleCrudBundle\Entity\Demo",
 *   repository="BrtriverSimpleCrudBundle:Demo",
 *   template_list="BrtriverSimpleCrudBundle:Demo:list.html.twig",
 *   template_new="BrtriverSimpleCrudBundle:Demo:new.html.twig",
 *   template_edit="BrtriverSimpleCrudBundle:Demo:edit.html.twig",
 *   template_show="BrtriverSimpleCrudBundle:Demo:show.html.twig",
 *   form="Brtriver\SimpleCrudBundle\Form\DemoType"
 * )
 */
class DemoController extends Controller
{
    use SimpleCrudTraitController;

    ....

動きを変えたいとき

テンプレートなんかは何も書かれていないのに等しいのでがんばって好きなように書いて指定してください。

アクションメソッドはTrait なので、Traitクラスに書かれたメソッドと同じメソッドを自分のコントローラーで定義すればおk。

install 方法や詳しいことは?

github の README 参照。

no title

わかったこと

  • CRUDのためだけに BaseCrudController みたいなの作ると辛いよね
  • CRUD っていいながら Delete 作ってないことに気づいた
  • Annotation がこんなに簡単に使えるとは知らなかった
  • オレオレ Trait 作って利用すると色々幸せになれる場面は多そう

2012-07-02

[][] Symfony勉強会 #6 が無事終了!!

f:id:brtRiver:20120703000119j:image:left

無事開催できました。

2012/6/30に VOYAGE GROUP にて Symfony勉強会#6 が開催されました。

レポートはユーザー会にもアップされる予定です。アップされました。

http://www.symfony.gr.jp/blog/20120630-symfony2-workshop6-report

それに、参加者のみなさんのブログ記事もアップされはじめているので、詳細はそちらをみていただくとして。。。


今回は自分が発起人として動いていたということもあり疲労感はかなりなものでした。

勉強会後のSymfony ミッドナイトでは最後まで起きていることすらできませんでした。。

なんといっても、勉強会は参加者も含めて皆の協力があってこそなのでこの場をお借りしてスタッフの皆さん、参加者の皆さんに本当にお礼申し上げます。

Symfony勉強会は回を増すごとに濃くなっているという事実

ユーザー会主催としてのSymfony勉強会は今回で6回目だったのですが、おそらく今回は過去最高の濃い内容でした。

f:id:brtRiver:20120703000121j:image:left

なんといっても、開催時間が長かった。。10:30から懇親会(と言うなの特別セッションやLT)で11:30という13時間コースでした。

ただセッションを聞くだけでなく、ワークショップも3時間ありましたし、参加された皆さんも気を抜く時間はほとんどなかったんじゃないでしょうか。。

学ぶということ

また、ただ勉強会をやるというのではなく、今回はテーマを決めました。

"フレームワークに縛られない技術とそれを実践している一歩先ゆくエンジニアたちの声"

そして、実際に参加者の人たちにはこのテーマを体感できたんじゃないかと思います。

あの勉強会では自分自身も多くのことを学びました。正直、勉強会の内容を100%なんかとてもじゃないですが理解できてません。

考えてみれば当たり前ですよね。たった1日話を聞いただけで身につくわけがないので。なので、これをきっかけに見えた色々な自分の足らない部分を把握し前に一歩踏み出し、学び続けることが大事じゃないかなと思っています。

ポシャった幻のセッション

今月フランスで Symfony Live Paris 2012 が開催されていました。去年のSymfony Live後はファビアンさんのKeynoteの動画がアップされたりしたので今年もあるだろうと推測し、その動画に某スタッフTシャツをデザインしてくれた広島人にappleのアレっぽく吹き替えしてもらおうと考えてました。

これは残念ながら動画が公開されずボツに....

BEAR.Sunday勉強会!?

f:id:brtRiver:20120703000120j:image:left

当日強引に@nekogetさんにLTを振るという結果、BEAR.Sundayの勉強会の話が進みそうです。よかったよかった。

懇親会セッションに参加された方は@koriymのあのホワイトボードの白熱教室の凄さを覚えてますよね。(油性ペンだったという話はまた違う凄さですが)

スライドは11枚。

http://www.slideshare.net/akihito.koriyama/bearsunday-offline-talk-1

このスライドで1時間以上使って進んだ枚数が5枚!!

あの続きが気になってしょうがないですし、参加するしかありません。

もういまからwktkです。

最後に

勉強会終了時に「参加してよかった人」との質問に、皆が元気よく手を挙げてくれたのに感動しました。

次回の開催は未定ですが、次回は皆が「LTしたい!!」ぐらいの勉強会にしたいですね!

おまけ

今日ファビアンさんが色々つぶやいていました。

phpBB, Drupal が Symfony Componentを使うことは周知の事実として...

eZPublishとPHP-NukeのフォークのZikulaも Symfony2ベースに。

また、eZが Symfony2 を選んだ理由として

After benchmarking the available open-source PHP frameworks, the indisputable winner was the Symfony framework & community.

色々なPHPのフレームワークのベンチマークを行った結果、議論の余地もなく勝者は Symfony とそのコミュニティーだった

via: http://symfony.com/blog/symfony2-meets-ez-publish-5

Symfony の世界的な勢いがすごいですね。

2011-12-01

[][] Symfony のこれまで 〜 Symfony Advent Calender 2011 JP - 初日 -

しわっす。

今日から Symfony Advent Calender 2011 ということで、symfony/Symfony に関するブログを24日間お届けします。

初日は 日本Symfonyユーザー会の前田@brtriver が担当します。

Symfonyとは

初日ということで、Symfonyそのものについて簡単にお話しようかと思います。

SymfonyはPHPのフルスタックのWebアプリケーションフレームワークの1つで、Fabienさんが中心となり活発に開発が行われています。

名前空間やクロージャーを活用しているためPHP5.3以上の環境が必要というのがとんがった特徴でしたが、今となってはごく自然に最新のPHPを最大限に活用できているフレームワークと言えると思います。

そして、今年の7月に待望のSymfony2系がリリースされ、ようやくSymfony2系で開発を行うことも問題ないレベルになってきました。

また、2011/12/1現在で、githubで最もウォッチされているPHPのプロジェクトであり、プロジェクトへの貢献者(コントリビュータ)は289人にも及びます。

日本国内には、日本Symfonyユーザー会という組織があるのが特徴的です。勉強会の開催やUstでのオンライン居酒屋座談会、そしてメンバーによって多くのドキュメントの翻訳作業などが行われています。

このようなSymfonyですが、一番最初に公開されたのは2005年なので6年も前のことです。

symfony1.0がリリースされるまで

Symfonyが最初に公開された2005年ごろはPHP4がまだまだバリバリ現役で開発現場で利用されている時代であり、PHPだとMojaviというフレームワークがかなり有名だったころだと思います。そして、SymfonyはPHP5用のMojaviであるMojavi3のforkとしてスタートしました。つまりはMojaviの親戚です。そして、Agaviとも親戚です。

なので、symfony1系のときは"return sfView::SUCCESS"のようなMojaviらしいコードが随所にありました。

私はこのころにsymfonyと出会い、大量の英語のドキュメントを読むついでにひどい日本語に置き換えて野良公開するという作業をしていました。

また、Symfonyという名前ですが、wikipediaによると

Symfonyのスポンサーはフランスの Sensio Labs である。
このため、初期の名称は「Sensio Framework」であった[4]。
そして、各クラス名には「Sensio Framework」の頭文字である"sf" というプレフィックスが付いているが、
オープンソースのフレームワークにすることが決まり、
議論の結果プレフィックスをそのまま生かせる 「Symfony」 に改称された

と解説があります。そういえば、2年前にFabienさんが東京にこられたときに、聞いた気がします。

symfony 1.0のリリースと日本語書籍出現

symfony1.0がリリースされたのは2007年1月でした。そしてこのころに日本語の書籍"symfony×PHP"(アシアルさん)と"symfony徹底攻略"が発刊されました。

symfony×PHP [LLフレームワークBooks] (LLフレームワークBOOKS # 3)

symfony×PHP [LLフレームワークBooks] (LLフレームワークBOOKS # 3)

symfony徹底攻略 [PHP徹底攻略シリーズ]

symfony徹底攻略 [PHP徹底攻略シリーズ]

私は"symfony徹底攻略"を執筆したのですが、執筆中はまだバージョンが0.8ぐらいで試行錯誤しながら書いていたのを覚えています。もう4年も前のことなんですね。

このころは今以上に検索しても日本語情報は無かった時代でした。

symfony 1.1 そして sfFormの登場。 symfony勉強会の開催

2008年3月にディノさんの会場で初めてのsymfony勉強会が行われました。

http://events.php.gr.jp/events/show/40

ちなみに、このころはまだ1.0のバージョンがメインで1.1はリリースされていませんでした。この後に1.1がリリースされ、理解が大変だったsfFormが登場します。

そして、2008年10月に第2回symfony勉強会が行われ、発表内容を見てみると1.2がリリースされる直前で注目は1.2の内容になっていることがわかります。

http://events.php.gr.jp/events/show/56

また、発表者名にkatsuhiroとあったのですが、小川さんの名前ですね。このころはアカウント名ではなかったんですね。

symfony 1.2 Doctrineが標準ORMに

この1.2へのバージョンアップでsymfonyは大きく進化しました。

1.1まではPropelが標準のORMだったのですが、人気におされDoctrineが標準ORMになりました。

ルーティング機能が強化され、objectRougint機能が追加されました。これでアクションの記述がよりすっきりすることができるようになりました。

アドミンジェネレータも書き換えられ上記ルーティングの機能を活用できるように書き換えられました。

そして、1.2のメンテナンス期間が1.0よりも短いということが判明したり色々あったり。

http://d.hatena.ne.jp/brtRiver/20081211/1229029487

このころに"こんな短い期間だとsymfony2.0がリリースされるまでにsymfony1.5ぐらいまで行きそうなのですね。。"とか書いてますがあながち間違いではなかった。。。

この頃に開催された勉強会を振り返ってみると...

* 2009年5月 symfony勉強会/懇親会
** sfWebDebugToolbarを拡張してみる (fivestar)
** symfony1.2のイベントを使ってみた (brtriver)
** ルーティングを使ってシンプルなアプリケーション開発を (海老原昂輔)

タイトルみただけでどういった内容か推測できる人は立派なsymfonyanです。

Fabienさんの来日と More With Symfony

この年の9月にFabienさんがPHPカンファレンスのゲストとして初来日され、生Fabienさんに会うことができました。

また、翌日に東京観光ツアーが開催され、夜に中華料理屋でFabienさんからSymfony2のコンセプトについて話を聞いたのをはっきりと覚えています。

http://akimoto.jp/blog/2009/09/02/tokyo-one-day-sightseeing-with-symfony-fabien-potencier/

たしか、このときに@ganchikuさんや@hidenorigotoさんと初めて会った記憶が。

また、このときにFabienさんから"今年もアドベントカレンダーをやるんだけど、翻訳して同日公開したいから協力してくれないか?"との話があり、

その場にいたメンバーで"やろう!"ということで本家のMLとも絡みながら作業を行いました。

これが"More With Symfony"でした。

More with Symfony 1.3 & 1.4

More with Symfony 1.3 & 1.4


symfony 1.3, 1.4 そして Symfony2の開発開始

2009年12月に 新しい機能を詰め込んだ後方互換機能を含む1.3と後方互換を含まない1.4がリリースされました。

このころか一部の熱狂的ファンの間では興味はSymfony2に移っていきます。

また、symfony勉強会はファーストロジックさんのところで開催されるようになります。

http://events.php.gr.jp/events/show/89

日本Symfonyユーザー会設立

2010年6月 More With Symfonyの翻訳活動から始まった有志の集まりからユーザー会を正式に設立しました。

http://www.symfony.gr.jp/

PHPMatsuri 2010 で Kris さん来日

2010年10月にアメリカからsymfony1系のメンテナである Kris さんが来日されSymfony2ミニワークショップを開催しました。

Textmateで名前空間を補完なしにがっつり神コーディングしていく姿はとても印象的でした。

http://www.symfony.gr.jp/blog/20101005-php-matsuri-2010

クレイジーなSymfony2勉強会

2010年11月にまだstableが出ていないのに、Symfony2勉強会をジンガジャパンさんで開催しました。

このときから@hidenorigotoさんのスパルタワークショップ伝説が始まりました。

また、ほんとうはこの頃にSymfony2がリリースされると予想していたのが見事に外れたというのもありました。

Symfony1.4本の発刊

"Symfony2がもうすぐリリースされるからこそ、このタイミングで良いsymfony1系の本を!"ということで

ユーザー会のメンバーで"オープンソース徹底活用 symfony1.4によるWebアプリケーション開発"を執筆しました。

Symfony2 リリース

2011年7月にようやく、よーやく、よーーーやくSymfony2がリリースされました。

PHPMatsuri 2011 で Fabienさん再来日

2年ぶりにFabienさんが来日し、PHPMatsuriでSymfony2についてお話いただきました。やっぱり男前でした。

今年もあらためて More With Symfony

そして、今週末(12/4(日))にも Symfony勉強会/忘年会/前夜祭/ミニハッカソンが開催されます。

まだ席には余裕があらいますので、興味がある方は是非参加してみてください。

http://www.symfony.gr.jp/events/20111114-symfony-study5

あらためて振り返ってみると、色々ありました。そして、なかなか濃い歴史でもありますね!


明日は koyhogeさんです!

お楽しみに!

2011-08-02

[][] Symfony2のパフォーマンスをアップする簡単な方法

[追記とお詫び]

APC有効の場合の値が正しく測定できていませんでした。

当初は20倍という結果になってましたが、再測定の結果4倍になりました。

すんごい悪くなったような気がしますが、4倍"も"速くなったと前向きに捉えてます。

なにわともあれ、正しくない情報で最初にレポートしてしまい申し訳ありませんでした。



PHPフレームワークの比較が流行っていますね。

Symfony2のベンチマークが当初はCake2の半分程度しかなかったのですが、今のようにより速くなったのは、@hidenorigotoさんが不要なファイルの読み込みをコメントアウトしたりというごくごく普通のアプローチで修正を加えた結果です。(とはいえ、自分も気づかなかったですがw Good Job! @hidenorigoto)

APCを使わないはずがないよね。

あと、Symfony2を動かすのにconfig.phpでチェックしていればAPCはもちろん入ってますよね。

であれば、以下の公式サイトのドキュメントを参考にしてみましょう。

要するに、「file_existsが呼ばれまくって遅くなってるからAPC対応のローダー使うと速いよ!」ってことのようです。

なので、公式ドキュメントに従って以下のようにApcUniversalClassLoaderメソッドを使うように修正します。

元のソースは最初に紹介したベンチマークで利用されているSymfony2のサンプルアプリを使います。

  • app/autoload.php
<?php
...
require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php';
use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
$loader = new ApcUniversalClassLoader();

ab結果

brew install で最初に紹介したベンチマークと同じコマンドを使おうかと思ったのですがエラーになるので素直にabを手元のmac book air(1世代前) で簡易的にやってみました。

$ ab -n1000 -c10 http://localhost/symfony2-sample-application.git/web/app.php/blog/1
APC無し (UniversalClassLoader)
  • 結果: 10.75 [#/sec]
Concurrency Level:      10
Time taken for tests:   93.019 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      816000 bytes
HTML transferred:       496000 bytes
Requests per second:    10.75 [#/sec] (mean)
Time per request:       930.193 [ms] (mean)
Time per request:       93.019 [ms] (mean, across all concurrent requests)
Transfer rate:          8.57 [Kbytes/sec] received
APC有効 (ApcUniversalClassLoader)
  • 結果: 217.10 42.11 [#/sec]
Concurrency Level:      10
Time taken for tests:   23.748 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      816000 bytes
HTML transferred:       496000 bytes
Requests per second:    42.11 [#/sec] (mean)
Time per request:       237.484 [ms] (mean)
Time per request:       23.748 [ms] (mean, across all concurrent requests)
Transfer rate:          33.55 [Kbytes/sec] received

20倍?? 4倍でした。ごめんなさい。

APCを使っていないときが遅すぎる気もしたり、どれほど速くなるかの詳しい検証は別としてもAPCは標準で使うぐらいでないと駄目ですね。

2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |