php -r メモ。このエントリをはてなブックマークに追加
1 月 21, 2009

なんかたまにphp -rを使って、うまく書けないときがあるのでメモっておく。

  • php -rって?
  • php –helpの説明

    -r  <code>        Run PHP <code> without using script tags <?..?>

    <?..?>コードなしでphpコードを実行できますってことですね。
    ってことで実行する。 とりあえずechoで。

    $ php -r echo "test"
    
    Parse error: syntax error, unexpected $end in Command line code on line 1

    \(^o^)/
    なにが駄目かっていうと、コードの部分は文字列で与えないといけない
    なので、これならOK

    $ php -r 'echo "test";'
    test

    俺みたいな阿呆だとたまにこんなコードを書いて意味わからん!となげく。

    $ php -r "echo "test";"
    Notice: Use of undefined constant test - assumed 'test' in Command line code on line 1

    これは普通のPHPでもエラーっていう!

    で、今回一番よくわからなかったのがforeach

    $ php -r "foreach(array('a','b') as $val) { echo $val; }"
    
    Parse error: syntax error, unexpected ')', expecting T_STRING or T_VARIABLE or '$' in Command line code on line 1

    これがなんでエラーになるのかよくわからんかった。実は$がキー。なかでは使えないらしいから、エスケープしてやる。

    $ php -r "foreach(array('a','b') as \$val) { echo \$val; }"
    ab

    というようにできる。

    まぁちょっと確認したいときとかには便利だけど、こんなんではまって時間とられるのも時間の無駄ですからね!
    ささっと済ませるがよろし。

    cakephpのfindをmemcacheするこのエントリをはてなブックマークに追加
    1 月 12, 2009

    [お詫び]間違えがありましたので修正しました。getのとこの条件間違えてました。
    新年初投稿で。よろしくお願いします。

    今回はmemcacheとfindをからめたお話です。
    Webサイトでキャッシュを使うことはパフォーマンスを高めるのにも有効です。
    特にWebサイトのほとんどはDBも使うでしょう。

    最近そんな私もDBを使用したサイトをcakeで作ってて、うまくキャッシュ使えないかなーと思ってて若干(?)むりやりですが、findメソッドをオーバーライドしてfindの結果をmemcacheに入れるようなプログラムを書いてみた。

    cakephpのfind系のメソッド(findAllとか、findCountなど)はすべてfindを通しているので、findの部分をちょっと変更する。

    app_model.php
    ちと長いですが勘弁してください><findAllとfindCountはよく使うので一緒に書きました

    <?php
    uses("cache/Memcache");
    class AppModel extends Model{
    
        var $_mem_obj = null;
        var $_memcache_key = null;
        var $_mem_server = null;
        var $_mem_expire = null;
        var $_mem_timeout = null;
    
        /**
         * memcacheKey用のコントローラ名とアクション名を登録する
         */
        function setMemcacheKey($controller, $action) {
            if(empty($controller)) return false;
            if(empty($action)) return false;
    
            $this->_memcache_key = $controller . "_" . $action;
            return true;
        }
    
        /**
         * memcache keyの作成
         *
         * @param string $memkey ユニークなmemcache key
         * @return string
         */
        function createMemKey($memkey) {
            if(empty($memkey)) return "";
            return sha1($this->_memcache_key."_".$memkey);
        }
    
        function find($conditions = null, $fields = array(), $order = null, $recursive = null, $memcache=false) {
    
            // get
            if(!empty($memcache)) {
                $this->_mem_obj = $this->getMemcacheObj();
                $memcache_key = $this->createMemKey($memcache);
                if(!empty($memcache_key)) {
                    $result = $this->_mem_obj->read($memcache_key);
                    if($result!==false) return $result;
                }else{
                    $this->log("Memcache Read key Error: key -> " . $memcache);
                }
            }
    
            $result = parent::find($conditions, $fields, $order, $recursive);
            // regist
            if(!empty($memcache)) {
                if(!empty($memcache_key)) {
                    $mem_result = $this->_mem_obj->write($memcache_key, $result, $this->_mem_expire);
                    if(!$mem_result) {
                        $this->log("Memcache write Error: key -> " . $memcache);
                    }
                }else{
                    $this->log("Memcache key Error: key -> " . $memcache);
                }
                //$this->_mem_obj->clear(); // memcache all clear
            }
            return $result;
        }
    
    
        /**
         * findAllオーバーライド
         */
        function findAll($conditions = null, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null, $memcache=false) {
            return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'), null, null, $memcache);
        }
    
        /**
         * findCountオーバーライド
         */
        function findCount($conditions = null, $recursive = 0, $memcache=false) {
            return $this->find('count', compact('conditions', 'recursive'), null, null, $memcache);
        }
        /**
         * memcacheオブジェクトの生成と設定する
         */
        function getMemcacheObj() {
            // memcache設定
            $this->decideMemInfo();
            if(empty($this->_memcache)) {
                $this->_memcache = new MemcacheEngine();
                $setting = array(
                            "servers" => array($this->_mem_server),
                            );
                $this->_memcache->init($setting);
            }
            return $this->_memcache;
        }
        /**
         * 開発環境を見て、つなぐmemcacheサーバを変える。
         */
        function decideMemInfo() {
    
            switch(php_uname('n')) {
                default:
                    $server_name = 'localhost:11211';
                    $connect_timeout = 1;
                    $cache_expire = 3;
            }
    
            $this->_mem_server = $server_name;
            $this->_mem_expire = $cache_expire;
            $this->_mem_timeout =$connect_timeout;
    
            return true;
        }
    }
    ?>

    app_controller.php

    <?php
        /**
         * memcacheを使う準備
          *
         * @param array $models memcacheを使うモデル名
          * @return boolean
         */
        function setMemkey($models) {
            if(empty($models)) return false;
            foreach($models as $val) {
                $this->$val->setMemcacheKey($this->name, $this->action);
            }
            return true;
        }
    ?>

    使い方。

    <?php
    class UsersController extends AppController {
        function index() {
             $this->setMemkey(array("User"));
           $result = $this->User->find("all", null, null, null, "index");
        }
    ?>

    findの最後の変数に、文字列を入れるとmemcacheに登録して、ある場合はmemcacheから返します。

    keyの形はapp_model.phpのcreateMemKeyで生成していて
    sha1(コントローラ名_アクション名_ユニークな値)
    としています。かぶらないような値を考えた結果こんな感じになりました・・・。(memcacheのkeyの設計って皆さんどうしてるんですかね?><)
    keyの長さが255文字までなので、sha1でhash値にしてます。そうすればユニークな値が長すぎてエラーになるってこともないので。

    ↑の例だと、controllerでfindを発行していますが、自分は基本controllerでfindを発行するようなことはなく、model内で整形して、controllerに返すようにしています。

    そのため、modelからも使えるようにするため、modelからcontroller名やアクション名を登録することができないので、controllerからコントローラ名とアクション名を渡してます。
    modelからfindする場合は、そのfunction名とかをmemcacheのkeyにするかとよいと思います。

    こんな感じ(実際のコードではなく適当に参考程度に)
    user.php

    <?php
    function getaUserFromID($id) {
        if(empty($id)) return false;
        
        $conditions = array("User.id"=>$id);
        $result = $this->find($conditions, null, null, null, "getaUserFromID_{$id}");
    
        if(empty($result)) return false;
    
        return $result;
    }
    ?>

    とか書いてて思ったけど、idから取得するならreadでもいいのか。まぁいいか。

    以下は随時追加していきます。
    ○メリット
    - 最初の導入が手間だけど、そのあとはお手軽にfindの結果をmemcacheできる。
    ○デメリット
    - ある程度app_modelをいじってからだとめんどいかも。
    - expireを各findで設定できない。
    - memcacheオブジェクトが各モデル内に生成されるのでちと微妙。
    - findBy***では使えない。まぁそこは普通にconditionsで設定するようにすればいいかなーとか思うけど。

    使うのはかまいませんが、自己責任でお願いします。
    memcacheだとこんな使い方もあるよーってのがあったら教えてくださいませ。><

    Cakephpで普通にINNER JOINするこのエントリをはてなブックマークに追加
    12 月 28, 2008

    タイトルが微妙すぎて困る!

    CakePHPのアソシエーションを使うと、基本LEFTでテーブルをJOINしてfindなどされると思います。
    これをINNER JOINにしたい!といっても、hasOneとbelongsToでtype=>”INNER”を指定すればINNER JOINできるのは周知の事実でございますが、hasManyでできねーのかよ!って思って触ってたら案の定できたのでメモ。

    まぁSum limitedさんところで書いてある方法にほとんど近いのですが、beforeFindに書くと別のfindでも使ってしまうし、うーんと思ってたんだけど、findAllじゃなくてfindを使えば大丈夫そうなんですよ。

    テーブル

    mysql> desc users;
    +----------+------------------+------+-----+---------+----------------+
    | Field    | Type             | Null | Key | Default | Extra          |
    +----------+------------------+------+-----+---------+----------------+
    | id       | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
    | name     | varchar(255)     | NO   |     |         |                |
    | modified | datetime         | YES  |     | NULL    |                |
    | created  | datetime         | YES  |     | NULL    |                |
    +----------+------------------+------+-----+---------+----------------+
    4 rows in set (0.00 sec)
    
    mysql> select * from users;
    +----+------+---------------------+---------------------+
    | id | name | modified            | created             |
    +----+------+---------------------+---------------------+
    |  1 | a1   | 2008-11-17 08:33:12 | 2008-11-17 08:33:12 |
    |  2 | b1   | 2008-11-17 08:33:12 | 2008-11-17 08:33:12 |
    +----+------+---------------------+---------------------+
    2 rows in set (0.00 sec)
    
    mysql> desc user_comments;
    +----------+------------------+------+-----+---------+----------------+
    | Field    | Type             | Null | Key | Default | Extra          |
    +----------+------------------+------+-----+---------+----------------+
    | id       | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
    | user_id  | int(11) unsigned | NO   | MUL | 0       |                |
    | comment  | varchar(255)     | NO   |     |         |                |
    | modified | datetime         | YES  |     | NULL    |                |
    | created  | datetime         | YES  |     | NULL    |                |
    +----------+------------------+------+-----+---------+----------------+
    5 rows in set (0.00 sec)
    
    mysql> select * from user_comments;
    +----+---------+---------+---------------------+---------------------+
    | id | user_id | comment | modified            | created             |
    +----+---------+---------+---------------------+---------------------+
    |  1 |       1 | aee     | 2008-11-17 08:34:02 | 2008-11-17 08:34:02 |
    |  2 |       1 | addd    | 2008-11-17 08:34:02 | 2008-11-17 08:34:02 |
    |  3 |       1 | accdd   | 2008-11-17 08:34:02 | 2008-11-17 08:34:02 |
    |  4 |       2 | brree   | 2008-11-17 08:34:02 | 2008-11-17 08:34:02 |
    |  5 |       2 | bbawwaa | 2008-11-17 08:34:02 | 2008-11-17 08:34:02 |
    |  6 |       2 | bqwedf  | 2008-11-17 08:34:02 | 2008-11-17 08:34:02 |
    +----+---------+---------+---------------------+---------------------+
    6 rows in set (0.00 sec)

    今回使うのは2つ。usersとuser_commentsテーブル。
    こいつをuserのモデルでuser_commentsをINNER JOINしてデータをひっぱってくる。

    <?php
            $fields = array(
                        "conditions" => array("User.id=1"),
                        "fields" => null,
                        "limit" => 5,
                        "order" => null,
                        "joins" => array(
                                         array("type" => "INNER",
                                               "table" => "`user_comments`",
                                               "alias" => "UserComment",
                                               "conditions" => "`User`.`id`=`UserComment`.`user_id`",
                                              ),
                                   ),
                      );
            $result = $this->User->find("all", $fields);
    ?>

    まぁ前回のコードの使いまわしですが気にしない。
    fieldsにjoinsがあるので、そこで配列の中に配列でJOINするテーブルを指定すればOK。

    まーわかると思うけど
    ・type → INNER / LEFT
    ・table → 接続したいテーブル名
    ・alias → テーブル名のエイリアス。as ~の~にあたる部分。
    ・conditions → JOINのONにあたる部分を記述。
    です。

    結果はこちら。

    Array
    (
        [0] => Array
            (
                [User] => Array
                    (
                        [id] => 1
                        [name] => a1
                        [modified] => 2008-11-17 08:33:12
                        [created] => 2008-11-17 08:33:12
                    )
    
            )
    
        [1] => Array
            (
                [User] => Array
                    (
                        [id] => 1
                        [name] => a1
                        [modified] => 2008-11-17 08:33:12
                        [created] => 2008-11-17 08:33:12
                    )
    
            )
    
        [2] => Array
            (
                [User] => Array
                    (
                        [id] => 1
                        [name] => a1
                        [modified] => 2008-11-17 08:33:12
                        [created] => 2008-11-17 08:33:12
                    )
    
            )
    
    )

    JOINはできてる!・・・けど、user_commentsがとれてないじゃん!ってことです。
    これが注意点ですね。fields(取得するパラメータ)をnullにしてしまうと、JOINの元となるテーブルしかひっぱってこないのです!
    ちなみにクエリはこんなの

    SELECT `User`.`id`, `User`.`name`, `User`.`modified`, `User`.`created` FROM `users` AS `User` INNER JOIN `user_comments` AS `UserComment` ON (`User`.`id`=`UserComment`.`user_id`) WHERE `User`.`id`=1 LIMIT 5

    Userの部分しかひっぱってきてないのがわかりますね。なので、joins使って指定するときはちゃんと「*」なり、「User.*」など、普通のクエリを書くように注意しましょう。

    あとちなみに、joinsの中のjoinテーブルを追加すると、多重接続もできるよ!!

    <?php
      "joins" => array(
                     array("type" => "INNER",
                           "table" => "`user_comments`",
                           "alias" => "UserComment",
                           "conditions" => "`User`.`id`=`UserComment`.`user_id`",
                     ),
                     array("type" => "INNER",
                           "table" => "`user_skills`",
                           "alias" => "UserSkill",
                           "conditions" => "`User`.`id`=`UserSkill`.`user_id`",
                     ),
                ),
    ?>

    こんな感じってことです。

    今年の更新はこれで終わり!
    あまり更新できなかったけど、まぁこんなもんかなー!
    来年はMySQLクラスターとかで遊びたいな!

    それでは皆様よいお年を!

    CakePHPでgroup by構文を使うこのエントリをはてなブックマークに追加
    12 月 14, 2008

    CakePHPのmodelでgroup by使おうとしたのだけども、findAllじゃどうやらできないらしいのでソースをちらほら読んでたら機能はあるみたいなので試してみた(そりゃあるよな
    Cakeのバージョンはcake_1.2.0.7692-rc3でございます。ちょっと古いな。

    とりあえず↑にも書いたけど、findAllでやると無理っぽい。ので、findでfindAllのように実装すると細かいところまで設定できるようになってる。

    cake/libs/model/model.php
    Line.1767~

    <?php
    function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
      if (!is_string($conditions) || (is_string($conditions) && !array_key_exists($conditions, $this->_findMethods))) {
        $type = 'first';
        $query = array_merge(compact('conditions', 'fields', 'order', 'recursive'), array('limit' => 1));
      } else {
        list($type, $query) = array($conditions, $fields);
      }
    
      $db =& ConnectionManager::getDataSource($this->useDbConfig);
      $this->findQueryType = $type;
      $this->id = $this->getID();
    
      $query = array_merge(
          array(
             'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
             'offset' => null, 'order' => null, 'page' => null, 'group' => null, 'callbacks' => true
          ),
          (array)$query
      );

    としていて、$fieldsをarray_mergeしているのがわかる。で、findAllはっつーと

    <?php
      function findAll($conditions = null, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) {
        //trigger_error(__('(Model::findAll) Deprecated, use Model::find("all")', true), E_USER_WARNING);
        return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
      }
    ?>

    としていて、findでallをしてやると全部検索になるっていう。
    まぁ今回findAll使わないけど、こんな感じになってるよっていう感じで。

    今回のデータ。

    create table groups (
     id INT(11) unsigned NOT NULL auto_increment,
     sex enum("male","female") NOT NULL default "male",
     age int(11) unsigned NOT NULL default 0,
     modified datetime,
     created datetime,
     PRIMARY KEY (id)
    );
    
    insert into groups (sex, age) values
    ("male",10),
    ("female",10),
    ("female",30),
    ("female",20),
    ("male",30),
    ("male",20),
    ("female",20),
    ("male",20),
    ("female",10),
    ("male",10);
    
    mysql> select * from groups;
    +----+--------+-----+----------+---------+
    | id | sex    | age | modified | created |
    +----+--------+-----+----------+---------+
    |  1 | male   |  10 | NULL     | NULL    |
    |  2 | female |  10 | NULL     | NULL    |
    |  3 | female |  30 | NULL     | NULL    |
    |  4 | female |  20 | NULL     | NULL    |
    |  5 | male   |  30 | NULL     | NULL    |
    |  6 | male   |  20 | NULL     | NULL    |
    |  7 | female |  20 | NULL     | NULL    |
    |  8 | male   |  20 | NULL     | NULL    |
    |  9 | female |  10 | NULL     | NULL    |
    | 10 | male   |  10 | NULL     | NULL    |
    +----+--------+-----+----------+---------+
    10 rows in set (0.00 sec)
    mysql> SELECT  age, count(age) as cnt  FROM groups GROUP BY age ORDER BY cnt DESC LIMIT 3 ;
    +-----+-----+
    | age | cnt |
    +-----+-----+
    |  10 |   4 |
    |  20 |   4 |
    |  30 |   2 |
    +-----+-----+
    3 rows in set (0.00 sec)

    適当に。↑のクエリを発行したいとします。

    先ほどのプログラムも見たとおりだけど、array_mergeの中に、groupというのがあるので、これに値を渡せばよいことがわかります。

    ってことで、こんな感じにfindのfiledsにあたる部分の配列を作成します。

    <?php
      $fields = array(
                        "conditions" => array(),
                        "fields" => "age, count(Group.age) as cnt",
                        "limit" => 3,
                        "group" => "age",
                        "order" => "ORDER BY cnt DESC",
                      );
      $result = $this->Group->find("all", $fields);
    ?>

    まー見ればわかると思うけど、filedsの中身は、array_mergeでマージされるようにパラメータをうまく調整しただけです。
    ちなみに単品テーブルで、アソシエーションはなしです。

    これの結果が以下。

    Array
    (
        [0] => Array
          (
          [Group] => Array([age] => 10)
          [0] => Array([cnt] => 4)
          )
        [1] => Array
          (
          [Group] => Array([age] => 20)
          [0] => Array([cnt] => 4)
          )
        [2] => Array
          (
          [Group] => Array([age] => 30)
          [0] => Array([cnt] => 2)
          )
    )

    こんな感じ。
    知らなかったのですが、モデル名にあたる部分(この場合だとage)はGroupの配列はいってくるけど、それ以外は素の配列で戻ってくるのね。ちょっと手間だな。
    「count(Group.age) as cnt」の部分を「count(Group.age) as Group.cnt」っていうこともしてみたけど、SQLエラーになった\(^o^)/そりゃそうだけどって感じだけども。

    実践で使うなら、modelに書いてデータを整理しなおしてから返したほうがよさそうね。

    MySQL High Availabilityこのエントリをはてなブックマークに追加
    12 月 10, 2008

    というMySQL公式のオフィシャルトレーニング受けてきたよ!
    その前にいつの間にか久々だな。ネタねぇんだよ。反省。

    左がテキストで、右が認定証?
    ちなみにテキストは全部英語だよー\(^o^)/

    ちなみに内容はこんな感じ

    1. introduction
    2. introduction to MySQL High Availability
    3. MySQL Replication
    4. MySQL Cluster
    5. Shared Disk Clustering
    6. Other Clustering
    7. System Maintenance Impacts
    8. High Availability Conclusions
    9. Conclusion

    個人的にReplicationとClusterあたりのお話と実習ができたので満足。もうちょっと時間とってじっくり実習というか試してみかったのぅ。

    MySQL Replication

    Replicationのところは

  • Master→Slave(Basic)
  • Master←→Master(two-way)
  • Master1→Master2→Master3→Master1(Circlular)
  • と3つ実習をしました。

    two-wayいいなーとか思ったんだけど、制約多すぎw
    primary keyのauto_incrementの設定変更強く推奨とか。しかもデータベース単位とかテーブル単位で設定できなくて、MySQLのグローバス設定かーってちょっと残念。

    ちなみに設定項目はauto_increment_increment(auto_incrementnの増分)とauto_increment_offset(auto_incrementの初期値)。
    この2つを設定して、お互いかぶらないprimary_keyを設定する必要があるっていう。っていうか、auto_increment_incrementはネーミングもうちょっとがんばれw

    あれ?今ふと思ったんだけど、auto_increment_offsetでauto_incrementの初期値設定できるけど、create tableのときもできなかったっけか。あとでちょっと試してみっか。

    MySQL Cluster

    Clustertについては、なんかすごい大きな勘違いのようなことをしてた。
    DataNodeってなんかすごい(いい加減な日本語)機能で、いままでにない特殊な形でデータ持ってるんだろうな!とか思ってたら、engine=ndbにするだけだったとか。

    通常のMysqlと混同できるのかなーとか思ってたら、engine=ndbするだけだし、普通に共存できるね。
    my.cnfの設定を変更するからReplicationも一緒にできるのかな?

    あと、Replicaの意味をようやく把握した。ようは同じデータをいくつに持つようにするかってことね。
    例えば、
    DataNode=4
    Replica=2
    とすると、(DataNode*2)=(DataNode*2)となって、
    DataNode=4
    Replica=4
    とすると、(DataNode)=(DataNode)=(DataNode)=(DataNode)ってことだよね。
    ちなみにReplicaは4以下の数字推奨。

    Clusterは使いどころが難しそうだ。
    使いどころとしては・・・更新が多くて、データ数が無限でないだよねーという話をしてた。まさにそんな感じだな。
    joinに弱いし、まだ実践的じゃないかなーとか思った。楽なんだけどねー。
    あとでvmwareで遊ぶか。

    その他にも、1つのサーバで複数mysqldを立ち上げるけど、共有ストレージを使う設定とか、
    DRBDとHeartBeat使った生存確認のもやった。
    DRBDはEnterpriseの上位版でないと入ってないらしいけど・・・。

    研修も3人だったので、気軽にできました。
    あとはこれを業務に生かさないと・・・

    とりあえず、来週会社が引越すから、ランチ共有サイトみたの作ろうかなー
    また個人的にclusterとかreplicationとか触ったら書きますわん。

    Pages: Prev 1 2 3 4 5 Next