वीर

25 เมษายน 2008

CakePHP + timestamp

Filed under: Uncategorized — वीर @ 6:12 pm
Tags: , , , , , ,

เวลา save แล้วอยากให้ CakePHP ใส่ timestamp ให้ด้วยเวลาที่ row ที่ add เข้าไปถูก create เป็นครั้งแรก. ประมาณว่าต้องการเก็บว่า row นั้นสร้างมาแต่เมื่อไหร่. ผมใช้ callback function ใน model เอา เขียนสั้นๆ ก็เป็นอันใช้ได้.

class Corpus extends AppModel {
    function afterSave($created) {
        if($this->id && $created) {
            $data = $this->read();
            $data[$this->name]["created"] = date("Y-m-d H:i:s");
            $this->save($data);
        }
    }
}

เวลาสั่ง save จาก controller, afterSave ก็จะถูกเรียกแล้วก็ไปแก้ column: created ให้เป็นเวลาปัจจุบัน. ถ้า row นั้นถูกสร้างขึ้นใหม่ (ตรวจสอบได้จาก $created).

update #1: แต่จะให้ดีใช้ beforeSave ดีกว่า. จะได้ save ครั้งเดียวไปเลย (ตาม comment ของ พี่ป๊อก) แล้วก็ดูเป็นธรรมชาติดีด้วย :-).

class Corpus extends AppModel {
    function beforeSave() {
        $data[$this->name]["created"] = date("Y-m-d H:i:s");
        return true;
    }
}

แต่ถ้า edit แต่ไม่ได้ create corpus ก็คงต้องวิธีมาตรวจสอบเอา.

update #2: ลองดูอีกนิด จริงๆ แล้วไม่ต้องใช้ before/after save เลย. CakePHP ทำ timestamp ให้เองแบบที่ต้องการเลย โดยที่ไม่ต้องทำอะไร … แป่ว

24 เมษายน 2008

CakePHP 1.2.0.6311b: Group + ACL + AuthComponent

หลังจากที่ใช้ ACL กับ AuthComponent ได้แล้ว … ก็จะลองเปลี่ยนตัวอย่างเก่าให้ใช้ Group ได้ดู ก็แก้เยอะเหมือน :-P.

ผมเริ่มจากการแก้ database schema ก่อน. หลังจากที่ลองใช้ migration อยู่พักใหญ่แล้วผมก็งงๆ เลยตัดสินใจสละ table: aros และ users ไป.

เริ่มจากลบของใน table: aros และ aros_acos ก่อน ด้วยคำสั่ง:

mysql -u<your_username> -p<your_password> my_project -e 'delete from aros;'
mysql -u<your_username> -p<your_password> my_project -e 'delete from aros_acos;'

แล้วก็แก้ CakeSchema ของ user ใน folder: app/config/sql, ไฟล์: users.php แก้เป็นแบบนี้:

class UsersSchema extends CakeSchema {

    var $name = 'users';

    function before($event = array()) {
        return true;
    }

    function after($event = array()) {
    }

    var $users =
        array('id' => array('type'=>'integer',
                            'null' => false,
                            'key' => 'primary',
                            'extra' => 'auto_increment'),
              'username' =>  array('type'=>'string',
                                   'null' => false,
                                   'length' => 255),
              'password' =>  array('type'=>'string',
                                   'null' => false,
                                   'length' => 255),
              'group_id' => array('type' => 'integer',
                                  'null' => false),
              'indexes' => array('PRIMARY' => array('column' => 'id',
                                                    'unique' => 1)));
    var $groups =
        array('id' => array('type' => 'integer',
                            'null' => false,
                            'key' => 'primary',
                            'extra' => 'auto_increment'),
              'name' => array('type' => 'string',
                              'null' => 'false',
                              'length' => 255,
                              'unique' => true),
              'parent_id' => array('type' => 'integer'),
              'indexes' => array('PRIMARY' => array('column' => 'id',
                                                    'unique' => true),
                                 'GRP_NAME_KEY' => array('column' => 'name',
                                                         'unique' => true)));

ของใหม่อีกอย่างหนึ่งในตัวอย่างนี้คือ AclBehavior ที่จะช่วย sync ระหว่าง users, groups และ acl. แต่เท่าที่ใช้มา AclBehavior ไม่แก้ค่า alias ใน Aro ก็เลยสร้าง AclaBehavior จากการดัดแปลง AclBehavior นิดๆ หน่อยๆ โดยเพิ่มไฟล์ acla.php ลงใน folder: app/model/behaviors แบบข้างล่าง (จริงแล้วต้องเปิด <?php และปิดด้วย ?> ด้วย … แต่ว่าใส่ใน wordpress แล้วเหมือนเพี้ยนๆ ผมก็แก้ไม่เป็นด้วย.)

/* SVN FILE: $Id: acl.php 6311 2008-01-02 06:33:52Z phpnut $ */
/**
 * ACL behavior class.
 *
 * Enables objects to easily tie into an ACL system
 *
 * PHP versions 4 and 5
 *
 * CakePHP :  Rapid Development Framework <http ://www.cakephp.org/>
 * Copyright 2006-2008, Cake Software Foundation, Inc.
 *                                1785 E. Sahara Avenue, Suite 490-204
 *                                Las Vegas, Nevada 89104
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright 2006-2008, Cake Software Foundation, Inc.
 * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
 * @package            cake
 * @subpackage        cake.cake.libs.model.behaviors
 * @since            CakePHP v 1.2.0.4487
 * @version            $Revision: 6311 $
 * @modifiedby        $LastChangedBy: phpnut $
 * @lastmodified    $Date: 2008-01-02 00:33:52 -0600 (Wed, 02 Jan 2008) $
 * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
 */
/**
 * Short description for file
 *
 * Long description for file
 *
 * @package        cake
 * @subpackage    cake.cake.libs.model.behaviors
 */
class AclaBehavior extends ModelBehavior {

/**
 * Maps ACL type options to ACL models
 *
 * @var array
 * @access protected
 */
    var $__typeMaps = array('requester' => 'Aro', 'controlled' => 'Aco');
/**
 * Sets up the configuation for the model, and loads ACL models if they haven't been already
 *
 * @param mixed $config
 */
    function setup(&$model, $config = array()) {
        if (is_string($config)) {
            $config = array('type' => $config);
        }
        $this->settings[$model->alias] = array_merge(array('type' => 'requester'), (array)$config);
        $type = $this->__typeMaps[$this->settings[$model->alias]['type']];

        if (!ClassRegistry::isKeySet($type)) {
            uses('model' . DS . 'db_acl');
            $object =& new $type();
        } else {
            $object =& ClassRegistry::getObject($type);
        }
        $model->{$type} =& $object;
        if (!method_exists($model, 'parentNode')) {
            trigger_error("Callback parentNode() not defined in {$model->alias}", E_USER_WARNING);
        }
    }
/**
 * Retrieves the Aro/Aco node for this model
 *
 * @param mixed $ref
 * @return array
 */
    function node(&$model, $ref = null) {
        $type = $this->__typeMaps[strtolower($this->settings[$model->alias]['type'])];
        if (empty($ref)) {
            $ref = array('model' => $model->alias, 'foreign_key' => $model->id);
        }

        return $model->{$type}->node($ref);
    }
/**
 * Creates a new ARO/ACO node bound to this record
 *
 * @param boolean $created True if this is a new record
 */
    function afterSave(&$model, $created) {
        if ($created) {
            $type = $this->__typeMaps[strtolower($this->settings[$model->alias]['type'])];
            $parent = $model->parentNode();

            if (!empty($parent)) {
                $parent = $this->node($model, $parent);
            } else {
                $parent = null;
            }

            $acl_node_alias = null;

            if(method_exists($model, "aclNodeAlias"))
                $acl_node_alias = $model->aclNodeAlias();

            $model->{$type}->create();
            $model->{$type}->save(array(
                'parent_id'        => Set::extract($parent, "0.{$type}.id"),
                'model'            => $model->alias,
                'foreign_key'    => $model->id,
                'alias'         => $acl_node_alias
            ));
        }
    }
/**
 * Destroys the ARO/ACO node bound to the deleted record
 *
 */
    function afterDelete(&$model) {
        $type = $this->__typeMaps[strtolower($this->settings[$model->alias]['type'])];
        $node = Set::extract($this->node($model), "0.{$type}.id");
        if (!empty($node)) {
            $model->{$type}->delete($node);
        }
    }
}

ไม่พอก็ยังต้องแก้ CakeShell ต่อใน folder: app/vendors/shells

แก้ user.php:

uses ('controller'.DS.'components'.DS.'auth');

class UserShell extends Shell {
    var $uses = array("User", "Group");
    function add() {
        $auth = new AuthComponent();
        $username = $this->args[0];
        $password = $auth->password($this->args[1]);
        $grp_name = $this->args[2];
        $grp = $this->get_group($grp_name);
        $data = array("User" =>
                      array("username" => $username,
                            "password" => $password,
                            "group_id" => $grp["id"]));
        if($this->User->save($data))
            print "Success: $username was added\n";
        else
            print "Fail\n";
    }

    function get_group($name) {
        $data = $this->Group->findByName($name);
        if(!$data)
            return null;
        else
            return $data["Group"];

    }
}

สร้าง group.php ขึ้นมาใหม่:

class GroupShell extends Shell {

    var $uses = array("Group", "Aro");

    function add() {
        $name = $this->args[1];
        $parent_name = $this->args[0];
        if($parent_name == '/')
            $parent_id = null;
        else
            $parent_id = $this->get_parent($parent_name);
        $data = array("Group" =>
                      array("parent_id" => $parent_id,
                            "name" => $name));
        if($this->Group->save($data))
            print "Success: group: $name has been added\n";
        else
            print "Fail\n";
    }

    function get_parent($name) {
        $data = $this->Group->findByName($name, null, null, False);
        if(!$data['Group']['id'])
            return null;
        else
            return $data['Group']['id'];
    }
}

ยังไม่หมดต้องมาแก้ model ต่อใน folder: app/model

แก้ user.php:

class User extends AppModel {
    var $actsAs = array('Acla');
    var $belongsTo = array('Group');

    function parentNode() {
        if(!$this->id)
            return null;

        $data = $this->read();
        if(!$data['Group']['id'])
            return null;
        else
            return array('model' => 'Group',
                         'foreign_key' => $data['Group']['id']); 
    }
}

ตามด้วยเขียน group.php ขึ้นมาใหม่:

class Group extends AppModel {
    var $actsAs = array('Acla');
    var $hasMany = array('User');
    function parentNode() {
        if(!$this->id)
            return null;
        $data = $this->read();
        if(!$data['Group']['parent_id'])
            return null;
        else
            return array('model' => 'Group',
                         'foreign_key' => $data['Group']['parent_id']);
    }

    function aclNodeAlias() {
        if(!$this->id)
            return null;
        $data = $this->read();
        if(!$data['Group']['name'])
            return null;
        else
            return $data['Group']['name'];
    }

}

จาก code ที่แก้มามากมายก็จะเริ่มเรียกใช้แล้ว. โดย cd เข้าไปใน cake/console สั่ง: ./cake schema run create users แล้วก็กด y ไปเรื่อยๆ. เพื่อที่จะ drop table: users เก่าทิ้งไป แล้วสร้าง table: users และ groups ขึ้นมาใหม่.

ตามด้วยสร้าง group ขึ้นมาใหม่แบ่งเป็น admins กับ users ตามนี้:

./cake group add ‘/’ users

./cake group add ‘/’ admins

แล้วก็ add user ไว้ทดลอง group ละ 1 user:

./cake user add myadmin mypassword admins
./cake user add myuser mypassword users

grant ให้ admins ดูได้ทั้ง site (ทุกหน้า, ทุก action):

./cake acl grant admins site ‘*’

grant ให้ users ดู Pages/display (เอาไว้ redirect มาเวลาดูหน้าอื่นไม่ได้). และ ดู Books/display1:

./cake acl grant users ‘Pages/display’ ‘*’

./cake acl grant users ‘Books/display1′ ‘*’

ถ้าลอง login ด้วย myadmin ก็จะดูได้ทุกหน้า. แต่ถ้า login ด้วย myuser ก็จะดู http://localhost/my_project/books/display2 ไม่ได้.

ก็เป็นอันว่า users แต่ละกลุ่ม ก็ดูหน้าเว็บได้ต่างๆ กันตามที่กำหมดไว้ :-).

อ้างอิง:

http://lemoncake.wordpress.com/2007/07/19/acl-with-groups/

23 เมษายน 2008

เตรียม model (+ db) สำหรับ AuthComponent ใน CakePHP 1.2.x

ก่อนที่จะใช้ AuthComponent ได้ก็ต้องมีการเตรียมการสร้าง database และ model กันนิดๆ หน่อยๆ หลังจากติดตั้ง CakePHP เรียบร้อยแล้ว. นอกจากนั้นเพื่อให้การทดลองเป็นได้โดยสะดวกผมคิดว่าควรจะสร้าง view กับ controller ง่ายๆ ไว้ใช้ในทดลองก่อน. จากนั้นก็สร้าง user table ผ่านทาง CakeSchema. แล้วก็ต่อด้วยสร้าง table ของ acl (access control list) โดยเข้าไปใน folder: cake/console แล้วสั่ง ./cake schema run create DbAcl แล้วตามด้วยการกด y ไปเรื่อยๆ เพื่อลบ table เก่าถ้ามี แล้วสร้างใหม่. ต่อด้วยการสร้าง model ของ user ใน app/models ในไฟล์ user.php:

< ?php
class User extends AppModel {
}
?>

ต่อจากนั้นก็ add user เข้าไปสักคนเพื่อเป็นการทดลอง เพื่อความสะดวกในการ add user ผมก็สร้าง CakeShell ไว้ใน folder: app/vendors/shells ชื่อไฟล์ว่า user.php:

< ?php
uses ('controller'.DS.'components'.DS.'auth');

class UserShell extends Shell {
    var $uses = array("User");
    function add() {
        $auth = new AuthComponent();
        $username = $this->args[0];
        $password = $auth->password($this->args[1]);       
        $data = array("User" =>
                      array("username" => $username,
                            "password" => $password));
        if($this->User->save($data)) {
            print "Success: $username was added\n";
        } else {
            print "Fail\n";
        }
    }
}

จากนั้นก็เข้าไปใน folder: cake/console แล้วก็เรียก ./cake user add myusername mypassword

อาจจะสั้นๆ ไปหน่อย นอกจากนั้นก็ปล่อยให้ CakePHP จัดการ.

CakeSchema

Filed under: Uncategorized — वीर @ 7:08 am
Tags: , , , , , ,

CakeSchema จริงๆ แล้วผมก็ไม่ค่อยเข้าใจเท่าไหร่ว่ามันเอาไว้ทำอะไรบ้าง. เข้าไปดูในเว็บต่างๆ[1] เขาบอกว่าเอาไปใช้ทำ Migration ได้แบบใน Ruby on Rails แต่ผมก็งงๆ อยู่ดีว่า migration เอาไว้ทำอะไรบ้าง. เท่าที่เห็นได้คร่าวๆ คือไม่ต้องเขียน SQL ตรงๆ แต่มาเขียน PHP แล้ว CakePHP จะไป generate SQL ให้แทน เวลาสร้าง database. แล้วก็เวลาที่จะเปลี่ยนแปลงโครงสร้างของ table ก็ทำผ่าน CakePHP ได้ด้วย … แบบนี้เขาเรียก migration หรือเปล่า?

ผมจะลองใช้ CakeSchema มาสร้าง table สำหรับเก็บ username และ password ดู. เริ่มแรกก็สร้างไฟล์ users.php ใน app/config/sql ดังนี้:

< ?php
class UsersSchema extends CakeSchema {

        var $name = 'users';

        function before($event = array()) {
                return true;
        }

        function after($event = array()) {
        }

        var $users =
        array('id' => array('type'=>'integer',
                            'null' => false,
                            'key' => 'primary',
                            'extra' => 'auto_increment'),
              'username' =>  array('type'=>'string',
                                   'null' => false,
                                   'length' => 255),
              'password' =>  array('type'=>'string',
                                   'null' => false,
                                   'length' => 255));

}

?>

จากนั้นเวลาจะสรั้าง database ก็ cd เข้าไปใน folder: cake/console แล้วก็สั่ง

./cake schema run create users เสร็จแล้วก็ตอบ y ไปเรื่อยๆ ก็เป็นอันใช้ได้. หน้าจะเป็นประมาณด้านล่างนี้

Hello vee,

Welcome to CakePHP v1.2.0.6311 beta Console
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------

The following tables will drop.
users

Are you sure you want to drop the tables? (y/n)
[n] > y
Dropping tables.
users updated.

The following tables will create.
users

Are you sure you want to create the tables? (y/n)
[y] > y
Creating tables.
users updated.
End create.

ส่วนเรื่อง migration หรืออื่นๆ ไว้ให้ผมทำเป็นก่อน คงจะได้เขียนต่อไป.

อ้างอิง

[1] http://cakebaker.42dh.com/2008/04/13/migrations-the-cakephp-way/

หน้าต่อไป »

บลอกที่ WordPress.com .