「mongoDB datasource for CakePHP」 を composerでインストールする

超久々のブログですが、小ネタです。

何周か遅れでいま開発中の環境をcomposerを使って構築してるんですが、ちょっとはまったのでメモ。

id:cakephper さんの作ったmongoDB datasource for CakePHPPackagistに登録されていないのでcomposer.jsonを以下の様な感じにするとgithubから直接インストールできます。

{
    "name": "cakephp",
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        },
        {
            "type":"package",
            "package": {
                "type" : "cakephp-plugin",
                "name": "ichikaway/cakephp-mongodb",
                "version": "2.2.x-dev",
                "dist": {
                    "url": "https://github.com/ichikaway/cakephp-mongodb/zipball/master",
                    "type": "zip"
                },
                "source": {
                    "url": "https://github.com/ichikaway/cakephp-mongodb",
                    "type": "git",
                    "reference": "cake2.2"
                },
                "require":{
                   "composer/installers": "*"
                },
                "extra": {
                    "branch-alias": {
                        "dev-cake2.0": "2.0.x-dev",
                        "dev-cake2.2": "2.2.x-dev"
                    },
                     "installer-name": "Mongodb"
                }
            }
        }
    ],
    "require": {
        "pear-cakephp/cakephp": "2.4.4",
        "cakedc/migrations": "*",
        "ichikaway/cakephp-mongodb": "2.2.x-dev"
    },
    "require-dev": {
        "cakephp/debug_kit": "2.2.*@dev"
    },
    "config": {
        "vendor-dir": "Vendor/"
    }
}

composer.jsonの書式がよくわからなかったので、このPRを参考にしました。

以上で。

2014/2/20 11:30 追記

id:cakephperさんが音速でこのPRをマージしてくれたので、以下の様な普通か方法でインストール出来るようになりました〜!

{
    "name": "cakephp",
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        }
    ],
    "require": {
        "pear-cakephp/cakephp": "2.4.4",
        "cakedc/migrations": "*",
        "ichikaway/cakephp-mongodb": "2.2.x-dev"
    },
    "require-dev": {
        "cakephp/debug_kit": "2.2.*@dev"
    },
    "config": {
        "vendor-dir": "Vendor/"
    }
}

「CIを半年間まわしてみて」というお題でLTをしてきました

大分時間も経ってしまい今更ではありますが、先日行われた第67回 PHP勉強会で「CIを半年間まわしてみて」というお題でLTをしてきました。

昨年の11/30に、当時ちょうど開発が始まった案件の開発環境に関して「今時なCakePHPでの開発環境!?」というエントリーを書いて、初のホッテントリ入りしました。4月末でこのプトジェクトが始まって半年という事で、実際にCIをまわしている中で起こった事や、試行錯誤しつつどうやって解決したかなどを簡単にまとめてお話ししました。

LT用に作った資料ではちょっと伝わりにくいので、以下にまとめ直しました。

成長の軌跡

Jenkinsサーバーを立ち上げた時は、UnitTestのテストケースが10個だけだったのですが、4/30現在 UnitTestのテストケースが467件、受入れテストのシナリオ数が292件とものすごい成長っぷりです。

f:id:kaz_29:20130430122241p:plain

f:id:kaz_29:20130430120730p:plain

この半年間に起こった事

テストコードはいつ書くか?

今回のプロジェクトはUnitTestやBDD Plugin(BeHat)を使用しての受入れテストは書きましたが、TDD/BDDで開発していた訳ではありません。私自身もプロダクションのコードを書きながらテストコードを書いて、一通りブラウザ上で動作する所まで進んでから、受入れテストを書いていました。

年末〜年始にかけて手伝ってくれるメンバーが3名増えたのですが、実際にコードを書き始めて起こったのが...

プロダクションコードとテストコードが一緒にコミットされない!

折角CIしているのにこれでは全く意味がありません。という事で「テストコードが無いコードはmasterにマージしません!」と宣言して実際にマージしませんでした。masterにマージされなければ当然ステージング環境にも反映されませんので、ストーリ(チケット)が完了しません。という事で(おそらく(^^;)渋々テストコードを書いてくれたんではないかと思います。

自動テストに40分かかる

開発も順調に進み、テストコードが増えてくると皆さん直面するスローテスト問題が発生しました。当初は、インスペクション(静的解析)/UnitTest/BDD(StoryTest)を順番に実行していたため、各処理を分割して、インスペクションとUnitTestは並列に実行する様にしました。また、ビルドパイプライン化してUnitTestが正常に完了した場合のみStoryTestを実行する様に変更しました。この変更ですべてが完了するのに20分台まで時間が短縮しました。

ステージングへの自動デプロイがエラー

今回のプロジェクトではビルドパイプラインを作って、masterブランチで受入れテストが正常に完了すると自動的にステージング環境にデプロイされる様になっています。もちろん、テーブルの変更/追加時も自動でデプロイする為に、Migrations Pluginを使って自動でマイグレーションしています。そして、自動デプロイの際にこのマイグレーションに失敗してしまいました...。

原因は単純なミスだったのですが、ステージング環境のDBがつじつまの合わない中途半端な状態になってしまいクライアントさんにごめんなさいして、再度構成し直しました。これがプロダクション環境で起きたら...と思うと冷や汗がでました。

では、なぜこんな問題が起こったかというと、Jenkins上のUnitTestやBDD環境でテストを実行する際のマイグレーション手順がステージング環境と違っていたためでした。

この辺りにも書かれている通り、原則なはずの「すべての環境で、同じ手順でデプロイするべし」をしっかり守る事で、開発環境やテスト環境などで繰り返しデプロイ(この場合はマイグレーションですが...)が実行され、問題がある場合にも事前に発見出来る様になりました。

selenium-standalone-serverが落ちる

githubのcommit時に自動でテストが実行されるので、CIサーバーはもちろん24時間動作しています。継続して運用をしていると、突然ストーリテストに失敗する様になってしまいました。原因を調査すると、selenium-standalone-serverが落ちていたり、メモリ不足が発生していました。メモリーリークが発生しているのだろうと予想して、ストーリテスト実行時に自動的にselenium-standalone-serverを再起動する様に設定したところ改善しました。

ストーリーテストで時々エラーになる

単独で実行したり、開発環境で実行すると問題なく動作するのにある特定のテストケースが時々エラーになるという謎現象が発生しました。色々と調べた結果原因は簡単で、とあるモデルのアソシエーション先のorderが指定されていないため、環境/状況により並び順が変わってうことが原因でした。

アソシエーション先のデータの並び順は通常の処理では問題にならない事もあるので、注意が必要かもしれません。

スローテスト再び...

ビルドパイプライン化して、30分を切っていたストーリーテストがまた40分程度かかる様になってしまいました。色々と悩んだ結果、ストーリーテストを2つに分割して、同時に実行する様に修正しました。今回利用しているBDD Plugin(BeHat)ではシナリオにタグを振る事ができるので、以下の様にJavaScriptを使っていてSeleniumServerを利用するシナリオと、それ以外のシナリオに分割しました。実際にタグで実行するストーリーを切り替えるには以下の様に実行します。

$ ./app/Console/cake Bdd.story --tags=javascript // javascriptタグが付いているもの
$ ./app/Console/cake Bdd.story --tags=~javascript // javascriptタグが付いていないもの


複数のタグを付ける事も出来るので、機能毎にタグを付けておくとテストの実行範囲を絞り込むことが出来てとても便利です。この修正で、実行時間はほぼ半分の20分程度まで改善しました。

UnitTestがセグフォ

通常のUnitTestを実行する分には、全件テストを実行しても256M程度割り当てていれば問題ないのですが、CodeCoverageを取得しようとすると、膨大なメモリが必要で、プロジェクト開始から半年経過した4月にはとうとう2G割り当ててもセグフォで落ちる様になってしまいました。CI用サーバーのメモリは4Gと立ち上げ当初は多めのものを用意したつもりだったのですが、厳しくなってきました。

試行錯誤した結果、全件一括のテストを諦めてControllerのテストとそれ以外のテストの2つに分割して順番に実行する様にしました。カバレッジ自体は、Jenkinsが複数の結果をまとめてくれるのですが、詳細なhtmlレポートは今のところ統合出来ていません。ちょっと不便なのでなんらか改善策を打ちたいなと思っています。

テストケースが増えてくるとこの問題は必ず打ち当たる問題だと思います。皆さんがどんな風に解決しているかとても興味のあるところです!

またしてもスローテスト…

プロジェクトも終盤にはいって、JavaScriptをガンガン使う大きめの機能が複数完成したのに伴ってまたしてもストーリーテストの実行時間が30分を超えて、40分に迫りつつあります。これに関してはまだ対策は実施していません。4月末で、ひとまず開発作業にめどがつくので、時間を見つつテスト用のデータベースをインメモリ化してみようと思っています。

今回のCIサーバーは、さくらのVPS 4Gを使用しているのですが、UnitTestにしても、ストーリーテストにしても、Fixtureなどを利用してデータベースへの操作が多いテストを実行するとIO負荷が高くなりがちです。VPSにしてもIaaSにしてもHDDのアクセス性能はオンプレミスのサーバーに比べてかなりおそいので、ここがボトルネックになる事がありそうです。最近はSSDを使えるサービスなどもある様なので、色々と試してみたいと思います。

まとめ

今回クライアントさんの理解のおかげで、開発初期からCI環境を用意して進める事が出来、とても貴重な経験を出来ました。CI(continuous integration)は、日本語では継続的インテグレーションと訳されます。半年間継続してきて実感するのは、まさに継続していく事が肝だなと思いました。

今後、このプロジェクトに限らずプロジェクトが進んでいく中で、色々と厳しい状況が発生する事があると思いますが、今回の取り組みを継続していきたいと思っています。

FileFixture Plugin for CakePHPを作りました

皆さんテスト書いてますか?

という事で、必要に迫られて「FileFixture Plugin for CakePHP」を作りました。

Fixtureのデータを外部ファイルから読込む

プロジェクトの規模が大きくなってくると、整合性の取れたテストデータを手動でFixture形式にするのは結構大変です。Excel等の表計算ソフトでテストデータをまとめて作成して、CSVで出力したファイルをFixtureから利用する事が出来ます。

FileFixture Plugin for CakePHP」で出来る事は以下の通りです。

  • CSVファイルからFixtureデータを読み込む
  • TSVファイルからFixtureデータを読み込む

今のところ、対応しているフォーマットはCSV/TSVの2つですが、一応複数のフォーマットに対応出来る様に作ったつもりなのでそのうちXMLとかYamlとかも作るかもしれません。

他にも幾つかアイデアがあるので、しばらくはメンテナンスしていく予定。

使い方

使い方はとても簡単です。

データファイルを準備

下記のディレクトリにCSVファイルを用意します。

app/Test/Fixture/Data/posts.csv

1行目のフィールド名は、実際のテーブルのフィールド名と一致している必要があります。

title,body,created,modified
"The title","This is the post body.","2011-06-20 23:10:57","2011-06-20 23:10:57"
"A title once again","And the post body follows.","2011-06-20 23:10:57","2011-06-20 23:10:57"
"Title strikes back","This is really exciting! Not.","2011-06-20 23:10:57","2011-06-20 23:10:57"

テストデータファイルをFixtureで指定

Fixtureを以下の様に、親クラスを変更し、テストデータファイルを指定するだけです。

<?php
App::uses('FileTestFixture', 'FileFixture.TestSuite/Fixture');
class PostFixture extends FileTestFixture {
	public $fields = array(
		'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 11, 'key' => 'primary'),
		'title' => array('type' => 'string', 'null' => true, 'length' => 50),
		'body' => array('type' => 'text', 'null' => true, 'length' => 1073741824),
		'created' => array('type' => 'datetime', 'null' => true),
		'modified' => array('type' => 'datetime', 'null' => true),
		'indexes' => array(
			'PRIMARY' => array('unique' => true, 'column' => 'id')
		),
		'tableParameters' => array()
	);

	public $importRecords = array(
		// 'path' => [path to Fixture File Path], // Optional(default is 'app/Test/Fixture/Data/')
		'file' => 'posts.csv',
	);
}

デフォルトでは、'app/Test/Fixture/Data/' にあるファイルを利用しますが、パラメータを指定する事で別のディレクトリに配置する事も可能です。

多分、来月くらいから実戦配備する事になると思うので必要な機能が出来たら随時アップデートする予定です。

「入門Chef Solo - Infrastructure as Code」のおかげでChefデビューできました!

入門Chef Solo - Infrastructure as Code

入門Chef Solo - Infrastructure as Code

 

去年、会社をやめて最初にサーバーを設定する時に、この本に書かれている理由そのまんまで挫折した経験があります。
その時はcapistrano+shellスクリプトでそこそこのレベルで自動化させてお茶を濁したのですが、ことあるごとにChefを調べては挫折...の繰り返しでした。
 
昨今のChefの盛り上がりを横目に眺めつつ、今月に今進んでいる案件のプロダクション環境を構築する予定があったので、SDの特集を読んでそろそろ手を付けようと思っていた矢先に「入門Chef Solo - Infrastructure as Code 」が発売されました!
 
速攻購入して、通勤中にiPad miniで読みふける事数日...。この週末にようやく纏まった時間が取れたので実際に手を動かしてみました。
 
手元の開発用MacのRuby環境がなんだかカオスな状態だったので、rvmを入れるところからはじめて、何とか一日がかりでいつも設定する基本的な設定までをChefで設定する事ができました。
 
普段、ニフティクラウドを使う事が多いのですが、ニフティクラウドはインスタンス立ち上げ直後は、rootでログインしないといけないのでひとまず以下の様な流れで設定出来る物をつくりました。
 
  1. Webコンソールからインスタンス立ち上げ
  2. capistranoでアカウント作成、sudo出来る様に設定
  3. chef-solo(knife solo)で諸々設定

 

1,2の部分も含め、どこまでをどのツールで設定するかは今後試行錯誤が必要そうです(サーバーの設定に関しては、出来るだけChefによせたいと思ってます)

 

しかし、コマンド一発でいつもの設定が終わって、なおかつ状態が管理されているのは感動ものです!

 

すばらしい本を書いてくれた id:naoyaに感謝!

「CakePHP2+Jenkinsで継続的インテグレーション」について話してきました

週末に行われた「(CakePHPとか)PHPのテストについての勉強会」@Co-Edoで、「CakePHP2+Jenkinsで継続的インテグレーション」について話してきました。

今回は、CIの概念とかメリットに関してはこことか、ここなど先人のすばらしい資料に丸投げし(^^;、実際にCakePHP2でCIする際の流れやビルドの設定方法などを中心に話しました。割と突発的に行われた勉強会だった印象なのに非常に中身の濃い勉強会で僕自身もとても勉強になりました!

僕の発表は資料だけみてもちょっと分かりにくそうなので以下補足です。

Jenkinsの設定

実際の設定は以下を見て、このページに紹介されている「Job Template」をコピーする方法を使えば、あとで紹介するbuildファイル用の設定が一通り完了しているJobを簡単に作れるかと思います。

CakePHP2用のBuildファイル サンプル(Ant)

以下が、Template for Jenkins Jobs for PHP Projectsで紹介されているBuildファイルをCakePHP2用に修正したものです。

Jenkinsで実行する際は、tools-parallel というターゲットを実行することになります。

<?xml version="1.0" encoding="UTF-8"?>

<project name="name-of-project" default="build">
 <target name="build" depends="prepare,lint,phploc,pdepend,phpcb,phpmd-ci,phpcs-ci,phpcpd,phpunit"/>
 <target name="build-parallel" depends="prepare,lint,tools-parallel,phpunit"/>

 <target name="tools-parallel" description="Run tools in parallel">
  <parallel threadCount="2">
   <sequential>
    <antcall target="pdepend"/>
    <antcall target="phpmd-ci"/>
   </sequential>
   <antcall target="phpcpd"/>
   <antcall target="phpcs-ci"/>
   <antcall target="phploc"/>
   <antcall target="phpcb"/>
  </parallel>
 </target>

 <target name="clean" description="Cleanup build artifacts">
  <delete dir="${basedir}/build/api"/>
  <delete dir="${basedir}/build/code-browser"/>
  <delete dir="${basedir}/build/coverage"/>
  <delete dir="${basedir}/build/logs"/>
  <delete dir="${basedir}/build/pdepend"/>
 </target>

 <target name="prepare" depends="clean" description="Prepare for build">
  <mkdir dir="${basedir}/build/api"/>
  <mkdir dir="${basedir}/build/code-browser"/>
  <mkdir dir="${basedir}/build/coverage"/>
  <mkdir dir="${basedir}/build/logs"/>
  <mkdir dir="${basedir}/build/pdepend"/>
  <mkdir dir="${basedir}/build/phpdox"/>
 </target>

 <target name="lint" description="Perform syntax check of sourcecode files">
  <apply executable="php" failonerror="true">
   <arg value="-l" />

   <fileset dir="${basedir}/app">
    <include name="**/*.php" />
    <include name="**/*.ctp" />
    <modified />
   </fileset>
  </apply>
 </target>

 <target name="phploc" description="Measure project size using PHPLOC">
  <exec executable="phploc">
   <arg value="--log-csv" />
   <arg value="${basedir}/build/logs/phploc.csv" />
   <arg value="--exclude"/>
   <arg value="Test" />
   <arg value="--exclude"/>
   <arg value="Config/Migration" />
   <arg value="--exclude"/>
   <arg value="Plugin" />
   <arg path="${basedir}/app" />
  </exec>
 </target>

 <target name="pdepend" description="Calculate software metrics using PHP_Depend">
  <exec executable="pdepend">
   <arg value="--jdepend-xml=${basedir}/build/logs/jdepend.xml" />
   <arg value="--jdepend-chart=${basedir}/build/pdepend/dependencies.svg" />
   <arg value="--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg" />
   <arg value="--exclude=${basedir}/app/Vendor,${basedir}/app/webroot" />
   <arg path="${basedir}/app" />
  </exec>
 </target>

 <target name="phpmd"
         description="Perform project mess detection using PHPMD and print human readable output. Intended for usage on the command line before committing.">
  <exec executable="phpmd">
   <arg path="${basedir}/app" />
   <arg value="text" />
   <arg value="${basedir}/build/phpmd.xml" />
  </exec>
 </target>

 <target name="phpmd-ci" description="Perform project mess detection using PHPMD creating a log file for the continuous integration server">
  <exec executable="phpmd">
   <arg path="${basedir}/app" />
   <arg value="xml" />
   <arg value="codesize,unusedcode,design,${basedir}/build/phpmd-naming.xml" />
   <arg value="--reportfile" />
   <arg value="${basedir}/build/logs/pmd.xml" />
   <arg value="--exclude" />
   <arg value="Test,Config,Vendor,Plugin" />
  </exec>
 </target>

 <target name="phpcs"
         description="Find coding standard violations using PHP_CodeSniffer and print human readable output. Intended for usage on the command line before committing.">
  <exec executable="phpcs">
   <arg value="--standard=${basedir}/build/phpcs.xml" />
   <arg path="${basedir}/app" />
  </exec>
 </target>

 <target name="phpcs-ci" description="Find coding standard violations using PHP_CodeSniffer creating a log file for the continuous integration server">
  <exec executable="phpcs">
   <arg value="--report=checkstyle" />
   <arg value="--report-file=${basedir}/build/logs/checkstyle.xml" />
   <arg value="--standard=${basedir}/build/phpcs.xml" />
   <arg value="--extensions=php"/>
   <arg value="-p"/>
   <arg path="${basedir}/app" />
  </exec>
 </target>

 <target name="phpcpd" description="Find duplicate code using PHPCPD">
  <exec executable="phpcpd">
   <arg value="--log-pmd" />
   <arg value="${basedir}/build/logs/pmd-cpd.xml" />
   <arg value="--exclude"/>
   <arg value="Test" />
   <arg value="--exclude"/>
   <arg value="Config/Migration" />
   <arg value="--exclude"/>
   <arg value="Plugin/" />
   <arg path="${basedir}/app" />
  </exec>
 </target>

 <target name="phpdox" description="Generate API documentation using phpDox">
  <exec executable="phpdox"/>
 </target>

 <target name="make-tmp-folders">
  <mkdir dir="${basedir}/app/tmp/"/>
  <mkdir dir="${basedir}/app/tmp/cache"/>
  <mkdir dir="${basedir}/app/tmp/cache/persistent"/>
  <mkdir dir="${basedir}/app/tmp/cache/models"/>
  <mkdir dir="${basedir}/app/tmp/cache/views"/>
  <mkdir dir="${basedir}/app/tmp/logs"/>
  <mkdir dir="${basedir}/app/tmp/tests"/>
  <mkdir dir="${basedir}/app/tmp/sessions"/>
 </target>

 <target name="migration">
  <exec executable="bash" failonerror="true" dir="${basedir}/app">
   <arg value="Console/cake"/>
   <arg value="Migrations.migration"/>
   <arg value="run"/>
   <arg value="all"/>
  </exec>
 </target>

 <target name="phpunit" depends="make-tmp-folders,migration" description="Run unit tests with PHPUnit">
  <exec executable="bash" failonerror="true" dir="${basedir}/app">
   <arg value="Console/cake"/>
   <arg value="test"/>
   <arg value="app"/>
   <arg value="AllTests"/>
   <arg value="--configuration"/>
   <arg path="${basedir}/build/phpunit.xml"/>
  </exec>
 </target>

 <target name="phpcb" description="Aggregate tool output with PHP_CodeBrowser">
  <exec executable="phpcb">
   <arg value="--log" />
   <arg path="${basedir}/build/logs" />
   <arg value="--source" />
   <arg path="${basedir}/app" />
   <arg value="--output" />
   <arg path="${basedir}/build/code-browser" />
   <arg value="--exclude=*/Config/*,*/Test/*,*/Vendor/*,*/View/*" />
  </exec>
 </target>
</project>

この設定を使用すると以下の処理が実行されます。

  1. lint - 文法チェック
  2. phploc(*) - プロジェクトの規模を測定
  3. pdpend (*) - パッケージ単位のメトリクスの測定(依存度とか)
  4. phpcb (*) - ソースコード毎の状態を解析
  5. phpmd - 静的解析(実装上の問題を検出)
  6. phpcs - コーディング規約チェック
  7. phpcpd - コピペコードの検出
  8. migration - DBのマイグレーション(cakedc/Migrations plugin)
  9. UnitTest - 単体テスト(CakePHP TestCase)

ツール毎に処理対象を除外する記述が異なってて以外と面倒なので、参考になれば嬉しいです。

おまけ

今進行中ののプロジェクトでは、以下の様なターゲットを作って、BDD Pluginでのストーリテストの実行やCapistranoでの自動デプロイもしてます。

<?xml version="1.0" encoding="UTF-8"?>

 <target name="migration-bdd">
  <exec executable="bash" failonerror="true" dir="${basedir}/app">
   <env key="APP_ENV" value="bdd" />
   <arg value="Console/cake"/>
   <arg value="Migrations.migration"/>
   <arg value="run"/>
   <arg value="all"/>
  </exec>
 </target>

 <target name="bdd-story" depends="make-tmp-folders,migration-bdd" description="Run Bdd Story tests">
  <exec executable="bash" failonerror="true" dir="${basedir}/app">
   <env key="APP_ENV" value="bdd" />
   <arg value="Console/cake"/>
   <arg value="Bdd.story"/>
   <arg value="--format=junit"/>
   <arg value="--out"/>
   <arg path="${basedir}/build/behat/"/>
  </exec>
 </target>

 <target name="deploy-staging" description="Deploy Appliction to Staging">
  <exec executable="cap" failonerror="true" dir="${basedir}">
   <arg value="staging"/>
   <arg value="deploy"/>
  </exec>
 </target>

ACL PluginでACL再入門

CakePHP Advent Calendar 2012 18日目の記事です。
昨日は、@msngさんのCakePHP の Configure クラスで最も悩ましい点を解決する方法 でした。確かにConfigureはちと長いですよね(^^;。

当初、BDD Pluginについて何か書こうと思っていたのですが、なかなか時間が取れず記事に出来るほどネタがたまっていないので、ストックしてあったネタからACL Pluginについて書きたいと思います。ACLに関しては、@ootatterさんの7日目の記事、ACL.phpとACL.iniについてでも取り上げられていますのであわせて読むと良いかと思います。

ACLは難しい

ACL難しい」という話は結構良く聞きますし、実際僕もそう思っていました。今まではあまり細かな権限管理を要求される事も無かった事もあって、ACLは使わずに簡易的な権限管理機能を自作していました。先日たまたまACL Plugin 2.2.0がリリースされているのを発見したので、今後の為にと試用してみました。実際試してみると、「難しい」というより「少し面倒」といった程度な感じでした。一度使い始めると本当に手放せない機能だと実感してます。ただ、実際に使うには結構準備が大変なので、簡単にACL Pluginを手元の環境で体験できるサンプルを作りました。

GitHub - ACL Plugin Sample

以下で実際の設置方法や注意点等を紹介します。

ACL Plugin sampleの設定

1. サンプルプロジェクトの配置
設置したいディレクトリで以下の手順で、サンプルプロジェクトを配置します。

$ git clone https://github.com/kaz29/acl_plugin_sample.git
$ cd acl_plugin_sample
$ chmod -R go+w ./app/tmp
$ cp ./app/Config/database.php.default ./app/Config/database.php
$ vim ./app/Config/database.php => データベースの設定

2. サンプル用テーブルの作成
以下の様な手順で、サンプルで使用するテーブルを作成します。私はPostgreSQL使いなので以下はPostgreSQLの例ですが、MySQL用のファイルも"schema_mysql.sql" という名前で用意してありますので、適宜読み替えてください。

$ psql データベース名 < ./app/Config/Schema/schema_postgres.sql

今回使用するテーブルは以下の2つです。

テーブル名 モデル名 説明
users User ユーザー情報テーブル(belongsTo:Group)
groups Group 権限情報テーブル(hasMany:User)

3. ACL用テーブルの作成
ACLで利用するテーブルはSchemaファイルが用意されているので以下の様に作成します。

$ ./app/Console/cake schema create DbAcl

ACL Plugin sampleを試す

設定が終わったら、http://localhost/admin/にアクセスしてみましょう。

1. ユーザーの作成

ユーザーが存在しない状態でアクセスをすると、ユーザー作成画面に遷移するのでユーザーを作成します。この時点でグループが存在しない場合、「サイト管理者」と「一般ユーザー」の2つのグループが自動的に作成されます。

2. ログイン

ユーザー作成が完了すると、ログインページに遷移するので先ほど作成したアカウントでログインします。

3. ACO(Access Control Object)の初期化(*1)

ログイン後、acosテーブルにデータが存在しない場合、ACL Pluginのaco同期処理(/admin/acl/acos/synchronize)に遷移します。現在定義されているコントローラ/アクションとacosテーブルの内容をチェックし、存在しない項目がリストアップされます。

画面の下の方にある"Synchronize"をクリックし、acoを作成します。

4. アクセス権限(ARO)の設定

次に、アクセス権限の設定をします。画面上の"Permissions" => "Roles permissions"をクリックしグループ毎の権限設定画面を表示します。

初期状態ではアクセス権限が設定されていないので、サイト管理者の横にある緑のチェックマークをクリックし、サイト管理者にすべての機能へのアクセス権を与えます。

設定が完了すると以下の様に、すべてのアクションへのアクセス権限が付与された事が分かります。

ACLが正常に機能しているかを確認する為に、Groups->admin_indexへのアクセス権を削除してみましょう。下記の画像の赤丸の部分をクリックします。

処理が完了すると、以下の様に表示が切り替わります。

5. 権限が無い機能にアクセスしてみる

この状態で、http://localhost/admin/groups/ にアクセスすると権限が無いため以下の様にエラーが表示されます。

ACL Pluginを使うととても簡単に権限の管理が可能です。グループ単位の設定の他、ユーザー毎にアクセスを許可したり、拒否したりといった事も簡単に出来ますので色々と試してみてください。

実際のカスタマイズ内容はGitHubのCommitログを見ていただければ参考になるかと思います。幾つかコメントも書いておいたので、気づいた点等あれば適宜追記していただければと思います。

ACL Plugin を使う上でのちょっとした注意点

adminルーティングの利用が必須

ACL Pluginはadminルーティングを利用する事を前提に実装されています。URL構造を検討する際には考慮しておきましょう。

Group,User共にモデル経由で作成する必要がある

ACLに関連するデータは、ACLビヘイビアが自動的に生成等の処理を行います。Group,Userのテーブルデータを作成しただけではエラーが発生して正常に動作しません。特に初期状態を作成する際には注意が必要かもしれません。

(*1)user_id:1は特権ユーザー

上の「ACL Plugin sampleを試す」の「ACO(Access Control Object)の初期化」で、まだアクセス権限が設定されていないのに、Acl関連のコントローラーにアクセスできるのを不思議に思った方もいるのではないでしょうか?ACL Pluginではデフォルトでuser_id=1のユーザーが特権ユーザーとして設定されています。下記の'acl.role.access_plugin_user_ids'に設定されているユーザーIDは特権ユーザーとして扱われ、ACLの設定がされていなくてもAcl関連のコントローラにアクセス出来ます。また、'acl.role.access_plugin_role_ids'にグループIDを設定すると設定したグループに所属するユーザーに特権が与えられます。

Acl/Config/bootstrap.php 47行目〜
<?php
...
/*
 * You can add here role id(s) that are always allowed to access the ACL plugin (by bypassing the ACL check)
 * (This may prevent a user from being rejected from the ACL plugin after a ACL permission update)
 */
Configure :: write('acl.role.access_plugin_role_ids', array());

/*
 * You can add here users id(s) that are always allowed to access the ACL plugin (by bypassing the ACL check)
 * (This may prevent a user from being rejected from the ACL plugin after a ACL permission update)
 */
Configure :: write('acl.role.access_plugin_user_ids', array(1));

まとめ

如何でしたでしょうか?ACL Pluginを使う事でかなり簡単にACLを利用出来る事がお分かりいただけたかと思います。是非一度試してみてください。

明日は、@konsanの「Chosen + Search + Collectionableの3つのプラグインの組み合わせ」です。楽しみ!

おまけ

ACL Plugin 2.2.0 の potファイルには漏れがある

ACL Plugin 2.2.0に付属のacl.potには一部の文言が含まれていないため、日本語化したい場合は注意が必要です。i18nコマンドを使って再抽出するか、手動で追加する必要があります。

Authコンポーネントのflashメッセージをカスタマイズ

これは私が実際に困って調べたのですが、TwitterBootstrapPlugin等を使用してデザインをカスタマイズしている場合Authプラグイン内で生成されるflashメッセージが表示されないという問題がありました。こんな場合には以下の様にAuthコンポーネントに設定をする事でカスタマイズ出来ました。

<?php
class AppController extends Controller {
	public $components = array(
            'Acl',
            'Auth' => array(
                .....
                'flash' => array(
                    'element' => 'alert',
                    'params' => array(
                        'plugin' => 'TwitterBootstrap',
                        'class' => 'alert-error'
                    ),
                    'key' => 'flash',
                ),
            ),
....
        );
....
}

今時なCakePHPでの開発環境!?

前職を退職してもう半年以上経ってしまいました。おかげさまで、レガシーなコードとのバトル等色々お仕事しています。

さて、先日のPHP Matsuri2012での@ryuzeeさんの発表を聞いて、開発環境や手法を改善するべく、今月から始まったプロジェクト用の環境を構築しています。

CakePHPは最新で!

CakePHPの最新stable版は 2.2.3 ですが、既に 2.3.0-beta が公開されています。幾つか2.3.0-betaの機能で使いたかったものがあったのと、今回のプロジェクトのリリース時期が来年の5月という事もあって、2.3.0-beta を使って開発をしています。来年の5月なら2.3.0Stable版がリリースされるんじゃないかとの読みです(^^;さて、どうなるでしょうかw?

出来る限り公開されているPluginを使う

前職の職場では、ある程度蓄積された自前のPluginがあった関係で公開されているPluginはあまり使っていませんでした。当然、前職の職場で開発した資産を今回使う事は出来ないので、既に公開されているものは積極的に利用しています。どれも定番ですが、今のところ使う予定のものは以下の通り。

これらのプラグインを上手く使って開発を進めていく予定です。幾つか初めて使ったものもあるのですがもう手放せません(^^。

既存のサービスを使う!

今まではソースコードは自前のgitリポジトリ(gitolite)、issue管理はcandycane(お客さんのredmineやbacklogって事も...)で管理はしていましたが、今回は以下の様なツールを使う事にしました。

今回のプロジェクトは、クライアントさんは別にして開発者全員がバラバラの場所で作業するのでオンライン会議はかなり重要です。他にも色々あるとは思いますが、最近はco-meeting+Facebookってパターンがほとんどです。
また、今回はお客さんにGithubとPivotal Trackerを契約していただけたのでありがたく使わせていただきます(^^。色々試していますが、今後は自前のgitリポジトリはバックアップにして、この構成で進める事が多くなりそうです。

本番環境のサーバーはまだ選定中ですが、NIFTY Cloudになる予定。

簡単デプロイ

今までは簡単なシェルスクリプトでデプロイをする事が多かったのですが、CakePHP2実践入門でデプロイの章を書いた事もあり、Capistrano+capcakeでデプロイする環境を作りました。詳しい事は以下に書いてありますので、まだの方は是非ご購入を検討してみてください(^^!

CakePHP2 実践入門 (WEB+DB PRESS plus)

CakePHP2 実践入門 (WEB+DB PRESS plus)

Jenkinsさんに協力してもらう

さていよいよ本題(^^;。いままではがっつりアジャイル開発をした経験が実はなく、今回アジャイル開発に挑戦という事で久々にJenkinsを使ってCI環境を作りました。Unitテストを実行してカバレッジをとりつつ以下のプラグインを使って色々とソースコード解析もしています。

また、GithubのService HookとJenkinsのJob Notificationsを使って以下の様な内容を自動実行しています。

  • commit時にJenkinsのビルドを自動実行
  • commit時にPivotal Trackerに自動連携 : 自動的にストーリーをFinishしたり、Activityを追加したり。
  • commit/Jenkinsのビルド失敗時にFacebookグループに自動投稿 : サンドボックスモードな自前アプリ(^^;
  • 毎朝作業前の時間にビルドを自動実行

これらが実際に動き始めたのは昨日なのですが、なかなかいい感じです。まだ本格的な開発はこれからなので、頑張って育てていくつもりです。今後は、受け入れテストの自動化や自動的にデプロイもせたいですね。

まとめ

こんな感じで、作業環境としてはかなり整ってきた感じですが、実際の開発は12月からなのでこれからが僕らの真価の発揮どころです。ツールに振り回されずに、地道に作業進めていこうと思っています。