@blog.justoneplanet.info

日々勉強

crontabとCakePHPでバッチ処理をする

バッチ処理したい時などに使う技である。

■実装

app/vendors/shells/calc.php

以下のようにしてShellクラスを継承する。基本的にはコントローラと同様にモデルなどが使用できるがコンポーネントについては注意が必要である。

date_default_timezone_set('Asia/Tokyo');

//Configure::write('Config.environment', isset($_SERVER['CAKE_ENV']) ? $_SERVER['CAKE_ENV'] : "development");// コマンドラインから叩いている場合、$_SERVERによる環境分岐ができない
Configure::write('Config.environment', "production");

class RankShell extends Shell
{
    public $uses = array(
        'Logs',
        'Ranks'
    );
    
    /**
     * 処理を実行する前に読み込むコンポーネントなどを記述する
     */
    public function initialize()
    {
        parent::initialize();
        //$this->Email = new EmailComponent($this);// コンポーネント名に注意
    }
    
    /**
     * ここに記述した処理が実行される
     */
    public function main()
    {
        $this->out("start");
        if($result = $this->Log->getRank()){
            $this->Ranks->deleteAll();
            if($this->Ranks->saveAll($result)){
                $this->out("success");
            }
            else{
                $this->out("failed to save");
            }
        }
        else{
            $this->out("failed to calc");
        }
    }
}

■実行

以下のようにして実行できるかどうか確認する。

/usr/bin/php /var/www/hogehoge.justoneplanet.info/cake/console/cake.php calc

■crontab

以下のコマンドを実行してcrontabを編集する。

crontab -e

例えば10時のオヤツを忘れないよう10:15に実行するようにするには以下のようにする。

15 10 * * * /usr/bin/php /var/www/hogehoge.justoneplanet.info/cake/console/cake.php calc

15分ごとに処理をするには以下のように記述する。

*/15 * * * * /usr/bin/php /var/www/hogehoge.justoneplanet.info/cake/console/cake.php calc

以上のようにCakePHPでは非常に簡単にバッチ処理を書くことができる。個人的にはZendFrameworkよりも簡単に感じる。

CakePHPのrenderメソッドでViewファイルを指定する

以下のように指定することも多々ある。

$this->render('/ajax/json');

Cake内部でファイルを探索する際、open_basedirの制限に引っかかることがあるので以下のように記述すると良い。

$this->render(null, null, VIEWS . 'ajax' . DS . 'json.ctp');

MeCabをインストールする

■インストール

以下のコマンドでインストールできる。

wget https://mecab.googlecode.com/files/mecab-0.994.tar.gz
tar xvzf mecab-0.994.tar.gz
cd mecab-0.994
./configure
make
make install

辞書

utf-8で使用したいので以下のconfigureオプションを使用する。

wget https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz
tar xvzf mecab-ipadic-2.7.0-20070801.tar.gz 
cd mecab-ipadic-2.7.0-20070801
./configure --with-charset=utf8
make
make install

libiconvが必要になる。

macにインストールする

以下のコマンドでmacにインストールできる。

brew install mecab
brew install mecab-ipadic

■実行

以下のようにして使用する。

mecab
にわにはにわにわとりがいる
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
わに	名詞,一般,*,*,*,*,わに,ワニ,ワニ
はにわ	名詞,一般,*,*,*,*,はにわ,ハニワ,ハニワ
にわとり	名詞,一般,*,*,*,*,にわとり,ニワトリ,ニワトリ
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
いる	動詞,自立,*,*,一段,基本形,いる,イル,イル
EOS

ちょっと意地悪すぎたので入力を漢字にする。

庭には二羽鶏がいる
庭	名詞,一般,*,*,*,*,庭,ニワ,ニワ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
二	名詞,数,*,*,*,*,二,ニ,ニ
羽	名詞,接尾,助数詞,*,*,*,羽,ワ,ワ
鶏	名詞,一般,*,*,*,*,鶏,ニワトリ,ニワトリ
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
いる	動詞,自立,*,*,一段,基本形,いる,イル,イル
EOS

正しく分類できた。

■Pythonで実行

久しぶりのPythonで遊ぶ。

python-devel

セットアップスクリプトを実行するのに必要になる。

yum insatll python-devel

mecab-python

以下のコマンドでインストールできる。

wget https://mecab.googlecode.com/files/mecab-python-0.996.tar.gz
tar xvzf mecab-python-0.98.tar.gz
cd mecab-python-0.98
python setup.py build
python setup.py install

テストスクリプトの文字コードを指定する。

vi test.py

2行目に以下のコードを加える。

# -*- coding: utf-8 -*-

以下のコマンドで実行する。

python test.py

■PHPで実行

php_mecabのインストール

su
pear channel-discover pecl.opendogs.org
pear install opendogs/mecab-beta
vim /private/etc/php.ini

Mountain LionにしたらPEARごと消滅してた

brew install autoconf

mecab.soが無いと言われて何もできなくなるので、php.iniを開いて一旦コメントアウトする。

su
vim /private/etc/php.ini

再度インストールする。

php /usr/lib/php/install-pear-nozlib.phar #PEARのインストール
pear channel-discover pecl.opendogs.org
pear install opendogs/mecab-beta #php-mecabのインストール
vim /private/etc/php.ini

ちなみに以下のようにパスを指定しないと

specify pathname to mecab-config [no] : /usr/local/bin

以下のようなエラーが出る。

checking for mecab-config... configure: error: not found
ERROR: `/var/tmp/mecab/configure --with-mecab=/usr' failed

以下の一行をextensionに付加する。

extension=mecab.so

実行

$mecab = new MeCab_Tagger();
for($node = $mecab->parseToNode($tag); $node; $node = $node->getNext()){
    var_dump($node->getSurface());
    var_dump($node->getFeature());
}

参考

CakePHPでモデルを作る

前回に引き続き今更ではあるがメモっておく。

■モデルのファイル生成

bakeする

bakeの前に解説用に以下のテーブルを作る。

CREATE TABLE `users` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `is_public` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

以下のコマンドを実行する。

./cake/console/cake bake

以下のように表示される。

Welcome to CakePHP v1.3.10 Console
---------------------------------------------------------------
App : app
Path: /Applications/XAMPP/xamppfiles/htdocs/cake.sample.justoneplanet.info/app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> M

Mを選択すると以下の選択肢が表示されるのでdefaultを選択する。

---------------------------------------------------------------
Bake Model
Path: /Applications/XAMPP/xamppfiles/htdocs/cake.sample.justoneplanet.info/app/models/
---------------------------------------------------------------
Use Database Config: (default/production) 
[default] > default

この時点でDBにテーブルが存在していないと以下のように表示される。

Your database does not have any tables.

当然だけどデータストアの定義がされていないのにコードを書き始めてはいけない。

テーブルが存在している場合は以下のように表示される。

Possible Models based on your current database:
1. User
Enter a number from the list above,
type in the name of another model, or 'q' to exit 

1番を選択すると以下のように質問される。

Would you like to supply validation criteria 
for the fields in your model? (y/n) 

yを選択すると以下のように表示される。

Field: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - uuid
29 - Do not do any validation on this field.

どんな入力値チェックをするかという事ですな。ここで設定した値を元にソースを書きだしてくれる。但し、idはautoincrementで特にinsertするカラムではないので29を選択する。

Field: name
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - uuid
29 - Do not do any validation on this field.
... or enter in a valid regex validation string.

次に表示されるのはnameカラムに対するバリデーションだ。必須項目としたいので19を入力すると以下のように表示される。

Would you like to add another validation rule? (y/n) 

1つのカラムに複数のバリデーションを適用できるので必要な場合は番号を入力する。次はパスワードだ。

Field: password
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - uuid
29 - Do not do any validation on this field.
... or enter in a valid regex validation string.
  
[19] > 

1と19あたりを設定すると思う。次はemailだ。

Field: email
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - uuid
29 - Do not do any validation on this field.
... or enter in a valid regex validation string.
  
[10] > 

なんとカラム名から判断して10番を提示してくれるではないか。Cakeは気がきくのだがこれは時としてお節介にもなる。そんなこんなでバリデーションを設定していくと次に以下のような質問をされる。

Would you like to define model associations
(hasMany, hasOne, belongsTo, etc.)? (y/n) 

関連するテーブルを聞かれているのだが、まだ他のテーブル存在していないのでnを入力する。

---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       User
DB Table:   `users`
Validation: Array
(
    [name] => Array
        (
            [notempty] => notempty
        )

    [password] => Array
        (
            [notempty] => notempty
        )

    [email] => Array
        (
            [email] => email
        )

    [is_public] => Array
        (
            [boolean] => boolean
        )

)

-------------------------

上述のように生成されるファイルの確認をされるのでyを押す。

ファイルが存在している場合は上書きの確認メッセージが表示される。

Creating file /Applications/XAMPP/xamppfiles/htdocs/cake.sample.justoneplanet.info/app/models/user.php
File `/Applications/XAMPP/xamppfiles/htdocs/cake.sample.justoneplanet.info/app/models/user.php` exists, overwrite? (y/n/q) 
[n] >

次に単体テスト用のファイルをbakeするか聞かれる。

SimpleTest is not installed. Do you want to bake unit test files anyway? (y/n)

やっぱちゃんとたりたいのでyを選択する。ちなみにSimpleTestのインストールは後からでも全然構わない。ファイルは「./app/tests/cases/models/user.test.php」に自動的に生成されている。

■生成したファイル

./app/models/user.php

<?php
class User extends AppModel {
    var $name = 'User';
    var $displayField = 'name';
    var $validate = array(
        'name' => array(
            'notempty' => array(
                'rule' => array('notempty'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
        'password' => array(
            'notempty' => array(
                'rule' => array('notempty'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
        'email' => array(
            'email' => array(
                'rule' => array('email'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
        'is_public' => array(
            'boolean' => array(
                'rule' => array('boolean'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
    );
}

コメントアウトされている部分については一番上を参考にする。

./app/tests/cases/models/user.test.php

/* User Test cases generated on: 2011-07-20 02:35:04 : 1311096904*/
App::import('Model', 'User');

class UserTestCase extends CakeTestCase {
	var $fixtures = array('app.user');

	function startTest() {
		$this->User =& ClassRegistry::init('User');
	}

	function endTest() {
		unset($this->User);
		ClassRegistry::flush();
	}

}

しかし、PHP5の文法で書きだして欲しいかつインデントはスペースを使って欲しいものだ。

■SimpleTest

debugモードで動作しているサイトのtest.phpにアクセスするとSimpleTestがダウンロードされていない場合、リンクが表示されるのでそこからダウンロードする。。。のもいいんだが分かりにくいのでコマンドラインから操作する。

cd vendors
wget http://sourceforge.net/projects/simpletest/files/simpletest/simpletest_1.0.1/simpletest_1.0.1.tar.gz/download
tar xvzf simpletest_1.0.1.tar.gz
rm simpletest_1.0.1.tar.gz

上述の操作でSimpleTestが使用できるようになった。テストケースについてはまた別の機会に解説する。

eclipseプラグイン

eclipseプラグインもあるのだがデバッグの構成とかいまいち設定方法が分からん。。。

■メソッドの記述

モデルクラスができたので試しにidからユーザを取得するメソッドgetByIdを書いてみる。

class User extends AppModel {
    public $name = 'User';
    public $displayField = 'name';
    public $validate = array(
        'name' => array(
            'notempty' => array(
                'rule' => array('notempty'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
        'password' => array(
            'notempty' => array(
                'rule' => array('notempty'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
        'email' => array(
            'email' => array(
                'rule' => array('email'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
        'is_public' => array(
            'boolean' => array(
                'rule' => array('boolean'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
    );
    
    /**
     * getById
     * 
     * @param numeric $id
     */
    public function getById($id)
    {
        $id = (int) $id;
        $result = $this->find(
            'first',
            array(
                'conditions' => array(
                    'id' => $id
                )
            )
        );
        return $result;
    }
}

SQLは見当たらない。生のSQLを書かないのが流儀である。まぁ、サブクエリとか書くときは普通に生で書いちゃうんだけど・・・少なくとも通常のCRUDはORMを使うべきだと思う。

理由

Symfonyの記事を参考に読んだ。以下は抜粋である。

データベースはリレーショナルです。一方でPHP 5とsymfonyはオブジェクト指向です。オブジェクト指向のコンテキストでもっとも効果的にデータベースにアクセスするには、オブジェクトをリレーショナルなロジックに変換するインターフェイスが求められます。

リレーショナルから取り出すものは単なるデータの集合に過ぎないが、オブジェクトを取り出せるならコードとの相性もいいよね。

$data = "{'name' : 'pochi' , 'age' : 28}";
$data = new Dog('pochi', 28);
$data->cry();// こんな感じ

抽象化レイヤーの主な利点は、移植性です。これによって、プロジェクトの真っ最中でも、別のデータベースに切り替えることができます。

そう!DBに依存した部分の変換(泥臭い仕事)はフレームワークがこなす。しかし、プロジェクトの途中でDBが変わるなんてあるのか・・・?

詳しくはドキュメントを参照するのが良いんだが、まぁとりあえず書いてみる。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * getByAge
     * 年齢でユーザを検索する
     * @param numeric $age
     */
    public function getByAge($age)
    {
        $age = (int) $age;
        $result = $this->find(
            'all',
            array(
                'conditions' => array(
                    'age' => $age
                )
            )
        );
        return $result;
    }
}

前述と違うのは結果が複数あるのでallを使用した。条件が複数ある場合は以下のようにする。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * getByAgeAndGender
     * 年齢と性別でユーザを検索する
     * @param numeric $age
     * @param numeric $gender
     */
    public function getByAgeAndGender($age , $gender)
    {
        $age    = (int) $age;
        $gender = (int) $gender;
        $result = $this->find(
            'all',
            array(
                'conditions' => array(
                    'age'    => $age,
                    'gender' => $gender,
                )
            )
        );
        return $result;
    }
}

上述のように配列の要素を増やすだけで良い。

範囲

日付や数値などで等号ではなく大なり小なりを指定して特定の範囲に含まれるレコードが欲しい場合がある。以下のようにすると実現できる。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * getByAgeRangeAndGender
     * 年齢の範囲と性別でユーザを検索する
     * @param numeric $minAge
     * @param numeric $maxAge
     * @param numeric $gender
     */
    public function getByAgeRangeAndGender($minAge, $maxAge , $gender)
    {
        $minAge = (int) $minAge;
        $maxAge = (int) $maxAge;
        $gender = (int) $gender;
        $result = $this->find(
            'all',
            array(
                'conditions' => array(
                    'gender' => $gender,
                    'age <' => $maxAge,
                    'age >' => $minAge,
                )
            )
        );
        return $result;
    }
}

若干気持ち悪いが連想配列なのでまぁしょうがない。

OR

conditionsに追加すると全てANDで結合した条件となる。ORにする場合は以下のように記述する。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * getByAgeRangeAndGender
     * 年齢の範囲と性別でユーザを検索する
     * @param numeric $minAge
     * @param numeric $maxAge
     * @param numeric $gender
     */
    public function getByAgeAndGender($minAge, $maxAge , $gender)
    {
        $minAge = (int) $minAge;
        $maxAge = (int) $maxAge;
        $gender = (int) $gender;
        $result = $this->find(
            'all',
            array(
                'conditions' => array(
                    'gender' => $gender,
                    'or' => array(
                        'age >' => $maxAge,
                        'age <' => $minAge,
                    )
                )
            )
        );
        return $result;
    }
}
NOT

NOTにする場合は以下のように記述する。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * getTarget
     * 血液型が入力されているユーザをサービスのターゲットとして検索する
     * @param string $type
     */
    public function getTarget($type)
    {
        $result = $this->find(
            'all',
            array(
                'conditions' => array(
                    'not' => array(
                        'blood_type' => null
                    )
                )
            )
        );
        return $result;
    }
}
順序

以下のようにすることでORDER BYと同じになる。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * getByAgeAndGender
     * 年齢と性別でユーザを検索する
     * @param numeric $age
     * @param numeric $gender
     */
    public function getByAgeAndGender($age , $gender)
    {
        $age    = (int) $age;
        $gender = (int) $gender;
        $result = $this->find(
            'all',
            array(
                'conditions' => array(
                    'age'    => $age,
                    'gender' => $gender,
                ),
                'order' => array(
                    'age desc',
                    'gender asc',
                )
            )
        );
        return $result;
    }
}
結合

例えば職業テーブルが存在しユーザが職業IDを持つ場合に結合する必要もあるはずだ。以下のようにして結合する。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * getByOccupation
     * @param string $occupation
     */
    public function getByOccupation($occupation)
    {
        $alias = 'Occupation';
        $result = $this->find(
            'all',
            array(
                'fields' => array(
                    "`{$this->name}`.`id`",
                    "`{$this->name}`.`name`",
                    "`{$alias}`.`name`",
                ),
                'conditions' => array(
                    "`{$this->name}`.`gender`" => $gender,
                ),
                'joins' => array(
                    array(
                        'type'       => 'LEFT',
                        'table'      => 'occupations',
                        'alias'      => $alias,
                        'conditions' => "`{$this->name}`.`occupations_id` = `{$alias}`.`id`",
                    )
                ),
            )
        );
        return $result;
    }
}

fieldsを使用しない場合は結合したテーブルのカラムが取り出せない。結合しないクエリにおいて、fieldsを使用しない場合に発行されるクエリは以下のとおりとなる。

SELECT * FROM `user` WHERE `id` = ?;

fieldsを使用しないとアスタリスクとなり全てのカラムがfetchされるので注意が必要だ。

■保存

CakePHPでは明確にInsertとUpdateが分離されていない。プライマリキーがセットされている場合はUpdateの扱いとなる。

Insert

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * register
     * 登録してIDを返す
     * @param string $name
     * @param numeric $age
     */
    public function register($name, $age)
    {
        $age    = (int) $age;
        $result = $this->save(
            array(
                'name' => $name,
                'age'  => $age,
            )
        );
        return ($result)? $this->getLastInsertID() : $result;
    }
}

Update

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * register
     * 登録してIDを返す
     * @param string $name
     * @param numeric $age
     * @param numeric $id
     */
    public function register($name, $age, $id)
    {
        $id    = (int) $id;
        $result = $this->save(
            array(
                'id'   => $id,
                'name' => $name,
                'age'  => $age,
            )
        );
        return $result;
    }
}

■削除

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * del
     * @param numeric $id
     */
    public function del($id)
    {
        $this->delete((int) $id);
    }
}

第二引数でcascadeも指定できるけど、MySQLで直接定義してるよね・・・。以下のように条件で削除することもできる。

class User extends AppModel {
    public $name = 'User';
    // validationは省略
        
    /**
     * del
     * @param string $name
     */
    public function del($name)
    {
        $this->deleteAll(array(
            'name' => $name
        ));
    }
}

大体モデルはコンナ感じでいいかな。

CakePHPを使う準備をする

今更な感があるがメモとして残しておく。さらに面倒なのでローカルのXAMPPで説明する。

■ダウンロード

以下のようにダウンロードして解凍する。

mkdir cake.sample.justoneplanet.info
cd cake.sample.justoneplanet.info/
wget https://github.com/cakephp/cakephp/tarball/1.3.10
tar xvzf cakephp-cakephp-1.3.10-0-g8671aa3.tar.gz
rm cakephp-cakephp-1.3.10-0-g8671aa3.tar.gz
rm -fr cakephp-cakephp-b0abad1/

以下のような状態になっている。

  • cake.sample.justoneplanet.info/app
  • cake.sample.justoneplanet.info/cake
  • cake.sample.justoneplanet.info/plugins
  • cake.sample.justoneplanet.info/vendors
  • cake.sample.justoneplanet.info/index.php
  • cake.sample.justoneplanet.info/README

■Apacheの設定

まぁ、バーチャルホストの設定を変更する。

vi /Applications/XAMPP/etc/extra/httpd-vhosts.conf
<VirtualHost *:80>
    DocumentRoot "/Applications/XAMPP/xamppfiles/htdocs/cake.sample.justoneplanet.info/app/webroot"
    ServerName cake.sample.justoneplanet.info
    ServerAlias www.cake.sample.justoneplanet.info
#    SetEnv CAKE_ENV default
</VirtualHost>

hostsファイルを変更しApacheを再起動する。

■パーミッションの設定

起動すると大量のエラー文が表示されるので、以下のコマンドを実行してパーミッションを変更する。

chmod -R 0777 app/tmp/

また、以下のコマンドを実行して「Security.salt」と「Security.cipherSeed」を変更する。

vi app/config/core.php

■データベースの設定

ファイルを直接編集しても良いんだけど、せっかくなのでbakeを使う。

./cake/console/cake bake

以下のように表示されるので順に設定する。

Welcome to CakePHP v1.3.10 Console
---------------------------------------------------------------
App : app
Path: /Applications/XAMPP/xamppfiles/htdocs/cake.sample.justoneplanet.info/app
---------------------------------------------------------------
Your database configuration was not found. Take a moment to create one.
---------------------------------------------------------------
Database Configuration:
---------------------------------------------------------------
Name:
[default] > default
Driver: (db2/firebird/mssql/mysql/mysqli/odbc/oracle/postgres/sqlite/sybase) 
[mysql] > mysql
Persistent Connection? (y/n) 
[n] > n
Database Host:
[localhost] > localhost
Port?
[n] > n
User:
[root] > hogehoge
Password:
> **********
Database Name:
[cake] > sample
Table Prefix?
[n] > n
Table encoding?
[n] > utf8

設定が終わると確認メッセージが表示され他のデータベース設定をするか聞かれる。

Look okay? (y/n) 
[y] > y
Do you wish to add another database configuration?  
[n] > y

今回は以下のようにしてもう一つ設定する。

Welcome to CakePHP v1.3.10 Console
---------------------------------------------------------------
App : app
Path: /Applications/XAMPP/xamppfiles/htdocs/cake.sample.justoneplanet.info/app
---------------------------------------------------------------
Your database configuration was not found. Take a moment to create one.
---------------------------------------------------------------
Database Configuration:
---------------------------------------------------------------
Name:
[default] > production
Driver: (db2/firebird/mssql/mysql/mysqli/odbc/oracle/postgres/sqlite/sybase) 
[mysql] > mysql
Persistent Connection? (y/n) 
[n] > n
Database Host:
[localhost] > localhost
Port?
[n] > n
User:
[root] > hogehoge
Password:
> **********
Database Name:
[cake] > sample
Table Prefix?
[n] > n
Table encoding?
[n] > utf8

開発用と本番用で設定を用意した。開発上は関係ないと思われるが、以前に使用していたdevelopmentという名前だと配置した段階でdefaultという設定名が必要になるようなので、開発環境はdefaultという名前を用いることにした。

app/app_model.php

以下のように記述して本番環境用の設定も読み込めるようにする。

class AppModel extends Model {
    /**
     * __construct
     * @param mixed $id
     * @param mixed $table
     * @param mixed $ds
     */
    public function __construct($id = false, $table = null, $ds = null)
    {
        $this->useDbConfig = Configure::read('Config.environment');
        parent::__construct($id, $table, $ds);
    }
}

■開発環境と本番環境の設定の分離

以下のコマンドを実行する。

vi app/config/bootstrap.php
mkdir app/config/environment

以下の記述を最終行に付加する。

Configure::write('Config.environment', isset($_SERVER['CAKE_ENV']) ? $_SERVER['CAKE_ENV'] : "default");
require_once("environment/" . basename(Configure::read('Config.environment') . ".php"));

新しくできたディレクトリに以下の2ファイルを追加する。

default.php

開発用設定ファイル。

Configure::write('debug', 2);
Configure::write('Cache.disable', true);

エラーメッセージを表示するのと、キャッシュを無効にしておく。

production.php

本番用設定ファイル。

Configure::write('debug', 0);
Configure::write('Cache.disable', false);

エラーメッセージは見せないようにする。

■おまけ

PHP5.3以上を使っていると思うので、core.php以下の部分をコメントアウトし引数を変更する。

date_default_timezone_set('Asia/Tokyo');

CakePHPでCSVのダウンロードをする

ベタに書いてもいいんだけどアレなんでヘルパーを使う。

■ヘルパー

vi app/views/helpers/csv.php

CSV Helper (PHP 5)のコードをペーストする。

コントローラ

App::import('Helper', 'Csv');
var $helpers = array(
    'Csv'
);

■データ挿入

Configure::write('debug', 0);
$this->layout = false;
$line = array('hogehoge', 'fugafuga', 'piyopiyo'); 
$csv->addRow($line);

■出力

echo $csv->render('data.csv', 'sjis', 'utf-8');

CakePHPのページャーにクエリを付加する

以下のようにoptionsに記述する。

$this->paginate = array(
    'page'   => 1,// 初期選択ページ
    'limit'  => 100,// 1ページあたりの件数
    'order'  => array('created' => 'desc'),// 順序
    'fields' => 'id, name, birthday, created',
    'joins'  => array(
        array(
            'type'       => 'LEFT',
            'alias'      => 'Data',
            'table'      => 'datas',
            'conditions' => "User.id = Data.users_id"
        ),
    ),
    'options'  => array(
        '?' => array(
            'param' => 'hogehoge'
        )
    )
);

CakePHPでデータを保存する

CakePHPでデータをDBに保存するときは新規登録でも更新でもsaveメソッドを使用する。

■新規登録

新規登録の際は以下のようにcreateメソッドを用いる。

$this->User->create();
$this->User->save($this->data, true, array('name', 'password'));

■更新

更新する際は以下のようにidに更新するレコードの値をセットする。

$this->User->id = 32;
$this->User->save($this->data, true, array('name', 'password'));

CakePHPとMySQLのON UPDATE CURRENT_TIMESTAMPを使う

CakePHPにおいてON UPDATE CURRENT_TIMESTAMPを使用すると、文字列としてCURRENT_TIMESTAMPを格納しようとするので、MySQL側でTIMESTAMPとして処理できない。以下のようにすることで対処できる。

■CURRENT_TIMESTAMP

modifiedというカラム名をつければCakeが自動的に更新日時をセットするので必ずしもON UPDATE CURRENT_TIMESTAMPをセットする必要はない。

■saveメソッド

CakePHP 2系からどうも挙動が変わったようで、1.3系の手法では上手くいかなくなった。以下のように、アップデートして欲しくないカラムにfalseを指定することで対応できる。

$data['modified'] = false;

CakePHP 1.3系

付けたい場合、saveメソッドで保存するカラムを指定することで対処できる。

$this->User->save($this->data, true, array('name', 'birthday'));

上述のようにすることで$this->dataにあらゆるキーとそれに紐付く値が格納されていても、保存されるのはnameカラムとbirthdayカラムのみである。これはアプリケーションをセキュアにする目的でも使用できるので、常時使用することをお勧めする。