まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:cakephp

PHP の MVC フレームワークの1つである CakePHP は、デフォルト設定の場合はその内部キャッシュにファイルシステムが使われています。

もちろんこれでも動くのですが、このキャッシュを memcached(メモリサーバー)にすることで、比較的簡単に高速化を実現することができます。以下は CakePHP 2.x を使う前提ですが、キャッシュに memcached を利用する方法を紹介します。


まずは memcached と、PHP から memcached を利用するための pecl-memcache をインストールしておきます:
# yum install memcached php-pecl-memcache -y

このコマンドが失敗する場合は yum リポジトリが足りていない可能性が高いので、以下のコマンドを実行してから再度 yum install してみてください(RHEL 6.x x86_64 の場合):
# rpm -Uhv http://ftp.riken.jp/Linux/repoforge/redhat/el6/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm

memcached と pecl-memcache がインストールできたら memcached を起動し、httpd を再起動します:
# /etc/init.d/memcached start
# chkconfig memcached on
# /etc/init.d/httpd restart

これで memcached の用意ができました。では CakePHP 側の設定を変更して、この memcached を利用するようにしてみましょう。app/Config/core.php ファイルを編集し、以下の設定を加えます:
# vi app/Config/core.php

  :
  :
//. デフォルトのキャッシュ設定(Cache::config で始まっている設定)が有効になっていたらコメントで無効にする //. Cache::config( "default", array( "engine" => "File" ) );
:
: //. デフォルトでは memcached にキャッシュするよう設定を追加 Cache::config( "default", array( "engine" => "Memcache", // 保存先は memcache "duration" => 3600, // キャッシュの有効時間は3600秒 "probability" => 100, // キャッシュ再作成率は100%(全て) "prefix" => Inflector::slug( APP_DIR ) . "_", // キャッシュ名のプレフィクスは定数 APP_DIR + "_" "servers" => array( "127.0.0.1:11211" ), // memcached サーバーとポート番号 "compress" => false // キャッシュは圧縮しない ));
: :

では現在までに作成されたキャッシュを全てクリアします:
# cd app/tmp/cache
# rf -rf ./*

この後に CakePHP を使ってみて、app/tmp/cache 以下にフォルダが作成されていないことが確認できれば成功です。

前回、LAMP 環境を構築した IBM LinuxONE サーバーを使って、PHP のメジャーな MVC フレームワークの1つであるCakePHP の環境を整えてみます:
IBM LinuxONE コミュニティクラウド上で LAMP 環境を作る


まず CakePHP を動かす場合の PHP 設定を行います。/etc/php.ini を適宜変更するのですが、最低限やっておかないといけないのが、タイムゾーンの設定です。/etc/php.ini を編集して、以下の設定を加えます([Date] カテゴリ内でコメントアウトされている Date.timezone の設定からコメントを外し、"Asia/Tokyo" に設定して保存します:
$ sudo vi /etc/php.ini

  :
  :
[Date]
Date.timezone = "Asia/Tokyo"
  :
  :

また CakePHP では PHPUnit というモジュールを使うことになるので、これもインストールしておきます。前回の LAMP 環境構築時に pear もインストールしているので、pear を使って PHPUnit を導入します:
$ sudo pear install pear/PHPUnit

次に MySQL の設定を行います。まずは文字コードの設定を変更して、デフォルトで UTF-8 を使えるようにします:
$ sudo vi /etc/my.cnf

[mysqld]
character-set-server=utf8

[mysql]
default-character-set=utf8



今回は CakePHP 専用のデータベース(cakedb)を新たに作成し、その中に商品情報を格納するマスターテーブル(items)と、商品カテゴリを格納するマスターテーブル(categories)を定義することにします:
$ mysql -u root -p

mysql> create database cakedb default character set utf8;
mysql> use cakedb
mysql> create table items( id int primary key auto_increment, name varchar(50), category_id int, created datetime default null, modified datetime default null );
mysql> create table categories( id int primary key auto_increment, name varchar(50), created datetime default null, modified datetime default null );
mysql> quit

1つのデータベースと、2つのテーブルを作りました。必要に応じてこれらにアクセスできるユーザーなどを追加しましょう。

これら2つのマスターテーブルを操作できるような環境を CakePHP で作ることにしましょう。ここは必須ではありませんが、データベースを操作する上では phpMyAdmin があると便利です。IBM LinuxONE コミュニティクラウドのサーバーに phpMyAdmin 環境を導入する場合の手順はこちらの記事を参考にしてください:
IBM LinuxONE コミュニティクラウド上に phpMyAdmin を導入する


ではいよいよメインディッシュの CakePHP 環境を構築します。PHP 5.3 でも動くよう、CakePHP のバージョンは 2.x を使うことにします。またこれも Apache HTTPD のドキュメントルートがデフォルトのままの /var/www/html であるとして作業を紹介します:
$ https://github.com/cakephp/cakephp/archive/2.9.4.zip
$ sudo /bin/bash
# cd /var/www/html
# unzip ~linux1/2.9.4.zip
# mv cakephp-2.9.4 cakephp
# chmod 777 -R cakephp/app/tmp
# chmod 755 cakephp/lib/Cake/Console/cake
# cd cakephp/app/Config
# vi core.php

  :
  :
/**
 * A random string used in security hashing methods.
 */
        Configure::write('Security.salt', 'ABCDabcd1234');

/**
 * A random numeric string (digits only) used to encrypt/decrypt strings.
 */
        Configure::write('Security.cipherSeed', '1234567890');
  :
  :

最後の cakephp/app/Config/core.php の編集作業では 'Security.salt' の値と、'Security.cipherSeed' の値はデフォルトの(既知の)値のままだと危ないのでランダムな値に書き換えました。

続けて(カレントディレクトリが cake/app/Config/ の状態で)、更にデータベースの設定ファイルを用意して、自分の環境に合わせた設定を行います:
# cp database.php.default database.php
# vi database.php

  :
  :
class DATABASE_CONFIG {

        public $default = array(
                'datasource' => 'Database/Mysql',
                'persistent' => false,
                'host' => 'localhost',
                'login' => 'root',
                'password' => 'P@ssw0rd',
                'database' => 'cakedb',
                'prefix' => '',
                //'encoding' => 'utf8',
        );
  :
  :

↑具体的にはユーザー名(login)、パスワード(password)、データベース(database)の値を書き換えます。


CakePHP 自体はここまでの設定で動くはずです。必要に応じて DebugKit などの便利なプラグインを cakephp/app/Plugin/ 以下に追加で導入してください。DebugKit の導入に関してはこちらを参照ください:
https://github.com/cakephp/debug_kit/tree/2.2


ブラウザで http://(IPアドレス)/cakephp/ にアクセスすると、CakePHP のホーム画面が表示されます。緑と黄色のバーが並んでいる状態であれば、少なくとも設定は間違っていないことになります。下図では DebugKit まで導入して、全て緑になっている状態です:
2017010603

 

謎だった Power 版 RHEL(RedHat Enterprise Linux) での MongoDB の動かし方が分かりました!

これまでは「CPU のエンディアンの違いにより MongoDB が動かなかった」と思っていたのですが、IBM のダウンロードサイトで Power 版 RHEL 向けビルド済み rpm パッケージの存在を確認しました。これを使います。


まず準備段階として Power 版 RHEL 環境を用意します。・・・といっても普通の人はそんな環境持ってないですよね(苦笑)。 2週間のお試し程度であれば、開発者向けサービスである IBM Power Development Cloud を利用して環境を構築することも可能です。 その場合の手順はこちらのブログエントリを参照ください:


Power 版 RHEL の準備ができたら早速 MongoDB を導入しましょう。といっても特別な作業ではなく、必要な rpm パッケージを IBM からダウンロードしてインストールするだけです。Intel 版 RHEL と異なるのはファイル名のアーキテクチャ部分が i686 とか x86_64 とかではなく、ppc64 になっていることくらいです。あと現在用意されている MongoDB はバージョン 2.4.9(RC0) のようです:
# cd /tmp
# yum install wget boost-devel
# wget ftp://ftp.software.ibm.com/linux/rpms/redhat/6.5/v8-3.14.5.10-2.el6.ppc64.rpm
# wget ftp://ftp.software.ibm.com/linux/rpms/redhat/6.5/libmongodb-2.4.9-1.el6.ppc64.rpm
# wget ftp://ftp.software.ibm.com/linux/rpms/redhat/6.5/mongodb-server-2.4.9-1.el6.ppc64.rpm
# wget ftp://ftp.software.ibm.com/linux/rpms/redhat/6.5/mongodb-2.4.9-1.el6.ppc64.rpm
# rpm -ivh v8-3.14.5.10-2.el6.ppc64.rpm
# rpm -ivh libmongodb-2.4.9-1.el6.ppc64.rpm
# rpm -ivh mongodb-server-2.4.9-1.el6.ppc64.rpm
# rpm -ivh mongodb-2.4.9-1.el6.ppc64.rpm

これで MongoDB のインストールは完了です。念のため、導入先を確認しておきましょう:
# which mongod
/usr/bin/mongod
# which mongo
/usr/bin/mongo

MongoDB の起動はコマンドラインから、以下の内容を実行します:
# mongod --dbpath /var/lib/mongodb

実行するとサーバーコンソールのようにメッセージがずらずらと・・・出てくればサービスの起動成功です。ちなみにサービスの終了は CTRL + C です。
2014121001


Mongo クライアントは何の違いも感じずに使えます:
2014121002


この Power 版 RHEL 向け MongoDB はいつから用意されてたんだろう? ともあれ、これで Power 版の RHEL でも MongoDB が動くことが確認できました。本当に Intel 版との違いがなくなりつつあることを実感します。


そして、これはつまり MongoDB に対応したフレームワーク(cakePHPとか)を Power 版 RHEL で動かすことができるようになったのかな?? とも思っているのですが、実際 cakePHP で使ってみると Intel 版では見たことのないエラーメッセージに遭遇したりして、まだもう少し待った方がいいのかな、とも思ってたりします。要はちゃんと動かして調べましょう、ということで。


最後に、自分が業務で開発に携わっている公開済みサービスがこの Power 版 RHEL 環境でもある程度動いたので記念アップ!
2014121003

 

cakePHP には Validation という便利な機能が標準で搭載されています。


これは例えば、
 users テーブルの name フィールドの値は2文字以上30文字以内
というルールをアプリケーションのレベルで付与する、というものです。「アプリケーションのレベルで」付与するので、例えば入力の際のエラーメッセージも併せて定義することができます。

具体的にはこんな感じでモデルにルールを記述することになります。上記例では users テーブルを対象にしているので、モデルは Model/User.php ファイルとなり、その中に以下の様な Validation ルールを加えます:
<?php
App:uses('AppModel', 'Model' );
class User extends AppModel{
  :
  :
  
  public $validate = array(
    'name' => array(
      'between' => array(
        'rule' => array('between',2,30),
        'message' => '名前は2文字以上30文字以内で入力してください。'
      )
    )
  );
}
?%gt;

これで User モデルに Validation ルールが適用され、この条件に合わないデータ配列で save() メソッドを実行しても保存できなくなります。その際に定義されているエラーメッセージを取得することもできるので、それを画面に表示して再入力を促すこともできます。


これはこれで便利なのですが、アプリケーションを運用しているとこのルールを変えたくなることがあります。例えば name フィールドの値を5文字以上50文字以内に変更したい、というケースを考えます。

モデルのルールとしては上記の $validation 変数の該当部分を array('between',2,30) から array('between',5,50) に変え、エラーメッセージも新しいものに変更するだけで済みます。しかしこれだけでは問題が起こることもあります。


例えば元の Validation ルール運用時にユーザーがデータを作り、その後新しい Validation ルールに変えたとします。そしてユーザーや管理者が、現在の users データのあるレコードの、name フィールドではない別のフィールドの値を変えたいと思っている、という状況を考えてみてください:

(例) 管理者が id = 10 のユーザーの admin_flag 列の値を 0 から 1 に変えたい、と思っている。。。

idnameadmin_flag
10ABC0


ロジックそのものは単純で、こんな感じになります:
    :
//. id = 10 の User を検索 $user = $this->User->find('first', array('conditions'=>array('id'=>'10'));
//. admin_flag の値を 1 に変更 $user['User']['admin_flag'] = 1;
//. 更新して保存 $this->User->save($user['User']);
:

しかしこのロジックには問題があり、実行しても admin_flag の値は更新されません。 その理由は今は新しい Validation ルールで運用されているため、name 値は5文字以上になっている必要がある、というルールが適用されるためです。現在の "ABC" という name 値は以前のルールでは問題なく作れていても今のルールでは認められません。そのため save に失敗してしまいます。

その一方で、これは管理・運用上の処置であるため、ユーザーの意志とは関係なく管理者が行う必要のある作業であるケースもありえます。name 値が現行の Validation ルールにはそぐわないものであったとしても、この変更を保存できるようにしたいのですが、何か方法はないでしょうか?


で、その答がこのブログエントリのテーマでもある「Validation ルールを一時的に無効にする」です。実はすごく簡単で、最後の save メソッド実行時に第二パラメータを指定します:
    :
//. id = 10 の User を検索 $user = $this->User->find('first', array('conditions'=>array('id'=>'10'));
//. admin_flag の値を 1 に変更 $user['User']['admin_flag'] = 1;
//. 更新して保存 $this->User->save($user['User'], false);
:

これで Validation ルールを一時的に無効にした上で save メソッドを実行することができます。


 

cakePHP を使って業務アプリを開発しています。

ある程度使っていると色々なトラブルに遭遇しますが、タチが悪いのが「特定の環境でのみ障害が再現する」というパターン。開発環境やテスト環境では動くのに、本番環境では動かない、というやつです。

そんな現象の1つに遭遇しました。現象の特徴は以下の2つでした:
(1) DB の設計を変更(alter table)した後の内容が Model に反映されない
(2) 本番環境でのみ再現する

(1) の内容は、例えば以下の SQL で作成したテーブルがあったとします:
create table users( id int primary key auto_increment, name varchar(100), deleted int default 0, created datetime, updated datetime );

この時点で id, name, deleted, created, updated という5つの列が含まれたテーブル users が作成されます。

このテーブルのための User モデルを cakePHP に(bake コマンドなどで)用意して、アプリを開発します。


その後しばらくしてから、この SQL でテーブルに列を追加します:
alter table users add column email varchar(256) after name;

最終的には id, name, email, deleted, created, updated という6つの列が含まれたテーブル users が定義されていることになります。

モデル(Model/User.php)は変更する必要がないので、このまま使っていれば変更後の6列の値が含まれる配列変数のモデルとして使えるはずです。


が、この alter table が反映されず、Controller の中で取得したこの User モデルの配列変数には5つの値しか入っていない、というものです。


(2) の内容は、この現象がローカル環境やテスト環境などでは再現せず、よりによって本番環境でのみ再現する、という現象を意味しています。 テスト中には全く気づくことができず、本番環境を使っている利用者からの報告で気づく、という最悪のパターン。。。



今回、このような現象に遭遇した上で最終的に解決することができました。原因とその対処法を紹介します。

結論を先に言うと、キャッシュが原因でした。話をややこしくしているのは(多くの本番環境がこの設定だと思いますが)cakePHP ではデバッグモードが 0 の場合にモデルのキャッシュが長い時間(999日間)有効になる、ということです。キャッシュが有効になっている間はモデルの設計変更が反映されません。その結果、デバッグモードを無効にした本番環境でのみモデルは古い設計のまま動いてしまい、他と異なる挙動になる、という発生パターンになったのでした。

解決策としてはキャッシュを削除します。今回の例であれば users テーブルのキャッシュを削除することで強制的に更新させます。問題のファイルは app/tmp/cache/models 内の myapp_cake_model_*****_users というファイルです。まずこのファイルを削除します。

続いて、app/tmp/cache/persistent/myapp_cake_core_method_cache ファイルも削除します。これで新しいテーブルの内容をモデルに反映させることができました。


いや~、こんなトラブル、事前に想定するのが難しいですよね。でもこれを経験したことで自分の cakePHP レベルが1つ上がった気がします。


(参考サイト)
http://culdesac.verse.jp/?p=19

 

このページのトップヘ