文章列表
 
您正在查看 "cakephp / zendframework" 分类下的文章

2009年10月24日 星期六 下午 8:08
作者:老王

欢迎访问我的新主页:http://huoding.com/

说点题外话,单就框架而言,对我而言使用CakePHP要比使用Zend Framework顺手得多,不过最近CakePHP陷入了多事之秋,Phpnut,Gwoo,Nate彻底闹僵,Gwoo和Nate选择离开,据坊间传闻他们已经开始另立门户,稍后会释出一个CakePHP的衍生框架:Lithium。虽然CakePHP官方已经表态会一如既往的继续前进,但毫无疑问在众多开发者眼中,CakePHP的前景已经不再明朗,其是否能继续保证PHP框架领头羊的地位也越发充满疑问。

之所以说了这么多题外话,就是为了说明一点,对于广大PHPer而言,过多局限于一种框架之上本身就是一种强耦合,未来是不可预期的。所以我打算在继续使用CakePHP的同时,适当向Zend Framework倾斜,如果Lithium不错的话,也会在我未来学习计划之中占有一席之地。

言归正传,新版的Zend Framework引入了Zend_Application,把Bootstrap进行的规范化的封装,一个典型的Application的Bootstrap文件大致如下:

01 class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
02 {
03     protected function _initAutoloader()
04     {
05         $autoloader = new Zend_Application_Module_Autoloader(array(
06             'namespace' => 'Application',
07             'basePath' => APPLICATION_PATH,
08         ));
09
10         return $autoloader;
11     }
12 }


在Bootstrap中,所有以_init开头的方法被当做资源方法自动运行,如果方法有返回值的话,还会被作为资源保存。资源方法不是唯一配置资源的方式,为了避免重复编码,框架包含了若干现成的资源插件,可以在配置文件里通过简单设置就可以达到相应的功能,一个典型的配置文件如下所示:

01 return array(
02     'bootstrap' => array(
03         'path' => APPLICATION_PATH . '/Bootstrap.php',
04     ),
05     'resources' => array(
06         'modules' => array(),
07         'frontController' => array(
08             'controllerDirectory' => APPLICATION_PATH . '/controllers',
09             'moduledirectory' => APPLICATION_PATH . '/modules',
10         ),
11     ),
12 );


resources部分的modules和frontController就是资源插件。其中modules资源开启了系统的模块功能。比如说你有一个admin模块的话,那么对应的也可以有一个它自己的Bootstrap类:

01 class Admin_Bootstrap extends Zend_Application_Module_Bootstrap
02 {
03 }


需要说明的一点是Modules的Bootstrap里一般不再需要类似前面的_initAutoloader资源方法,这是因为从父类Zend_Application_Module_Bootstrap里已经通过继承自动执行了以模块名为命名空间的Autoloader操作。

在使用Modules的Bootstrap时,有一点需要注意的是系统会把所有模块的所有资源方法统统初始化一遍,打个比方来说,如果你有一个Blog模块,还有一个Forum模块,每个模块使用各自独立的数据库配置,都有一个自己的_initDb方法,那么即便是我们单独访问Blog或者Forum中一个模块,系统还是会把两个数据库都连接一遍,这无疑是低效的。但也不能就此认为这是Zend Framework的设计错误,问题的根源在于我们划分模块的粒度大小,如上所示,Blog和Forum不应该以Module的形式存在,而应该以Application的形式存在。如此一来仅需要在Application层次的Bootstrap中执行一次数据库连接即可。

Zend Framework的文档虽然看似详细,但很多细节都没有说明,比如说View助手缺省就会加载,这一点可以在Zend_Controller_Front中看到对Zend_Controller_Action_Helper_ViewRenderer的调用,所以最好在使用时自己扎到源代码里查阅。
 
2009年09月30日 星期三 下午 3:15
作者:老王

欢迎访问我的新主页:http://huoding.com/

对于PHP程序员来说,发邮件之类的功能使用PHPMailer或者SwiftMailer或许更好一点,不过由于CakePHP本身包含了Email组件,所以我还是希望能通过它来调用Gmail的SMTP,实现发邮件的功能。

由于Gmail要求ssl,所以你得先确保PHP编译了openssl模块,并且在CakePHP的Email组件里通过https的方式声明:

$this->Email->smtpOptions = array(
'request' => array('uri' => array('scheme' => 'https')),
'port' => '465',
'host' => 'smtp.gmail.com',
'username' => '用户名',
'password' => '密码'
);

$this->Email->delivery = 'smtp';
$this->Email->from = "username@gmail.com";
$this->Email->to = "someone@foobar.com";
$this->Email->subject = "subject";
$this->Email->send("content");


看上去代码简单明了,可惜当你按上面代码运行时估计不会成功,通过$this->Email->smtpError你能得到错误信息:

555 5.5.2 Syntax error.

让人不得其解,解决方法则让人无言以对,修改发信人,收信人的格式,改成带尖括号的形式:

// $this->Email->from = "username<username@gmail.com>";
// $this->Email->to = "someone<someone@foobar.com>";
$this->Email->from = "<username@gmail.com>";
$this->Email->to = "<someone@foobar.com>";

这样就能成功发邮件了,不过没兴趣查阅RFC协议一探究竟了,如果你也遇到了类似的错误,不妨试试。
 
2009年07月06日 星期一 下午 4:42
作者:老王

CakePHP的控制器组件还是蛮丰富的。网上随便搜索一下,结果就是一大把,如此一来,我再写一个图片缩略图控制器组件,似乎有重复制造轮子的嫌疑。不过以前的轮子要么是方形的,要么是三角形的,用着都不太方便,这次我造了一个圆形的轮子,应该不算是什么罪过。

<?php
class ImageComponent extends Object {
    var $file;

    function initialize(&$controller, $settings=array()) {
        foreach ($controller->params['form'] as $key => $value) {
            if (isset($settings['name']) && $key != $settings['name']) {
                continue;
            }

            if (isset($value['error']) && isset($value['type'])) {
                if ($value['error'] != 0) {
                    continue;
                }

                if (substr($value['type'], 0, 5) != 'image') {
                    continue;
                }

                $this->file = $value['tmp_name'];
                return;
            }
        }
    }

    function thumbnail($file, $options) {
        if (!file_exists($this->file)) {
            return false;
        }

        list($srcWidth, $srcHeight, $typeCode) = getimagesize($this->file);

        if (!isset($options['width']) || !isset($options['height'])) {
            return false;
        }

        $dstWidth = $options['width'];
        $dstHeight = $options['height'];

        switch ($typeCode) {
            case 1:
                $typeName = 'gif';
                break;
            case 2:
                $typeName = 'jpeg';
                break;
            case 3:
                $typeName = 'png';
                break;
            default:
                return false;
        }

        $srcX = 0;
        $srcY = 0;

        $ratioX = $dstWidth / $srcWidth;
        $ratioY = $dstHeight / $srcHeight;

        if ($ratioX < $ratioY) {
            $srcX = round(($srcWidth - ($dstWidth / $ratioY)) / 2);
            $srcWidth = round($dstWidth / $ratioY);
        } else {
            $srcY = round(($srcHeight - ($dstHeight / $ratioX)) / 2);
            $srcHeight = round($dstHeight / $ratioX);
        }

        $srcImg = call_user_func('imagecreatefrom' . $typeName, $this->file);
        $dstImg = imagecreatetruecolor($dstWidth, $dstHeight);

        imagecopyresampled(
            $dstImg, $srcImg,
            0, 0, $srcX, $srcY,
            $dstWidth, $dstHeight, $srcWidth, $srcHeight
        );

        switch ($extension = strtolower(substr(strrchr($file, '.'), 1))) {
            case 'jpg':
                $typeName = 'jpeg';
                break;
            default:
                if (function_exists('image' . $extension)) {
                    $typeName = $extension;
                }
        }

        if (call_user_func('image' . $typeName, $dstImg, $file)) {
            return true;
        }

        return false;
    }
}
?>



使用方法:

比如做一个用户头像缩略图,把上述代码命名为image.php文件,保存到app/controllers/components目录。

在视图里做如下声明:

echo $form->input('avatar', array('type' => 'file', 'name' => 'avatar'));

注意:表单声明成file类型的:echo $form->create('User', array('type' => 'file'));

在控制器里做如下声明:

var $components = array('Image' => array('name' => 'avatar'));

可选:如果你不想使用上传文件,也可以手动指定:
$this->Image->file = '/path/to/file.jpg';

$this->Image->thumbnail('/path/to/image.jpg', array('width' => 100, 'height' => 100));


搞定了,使用非常方便,以后有空再加个水印功能。

补充:有条件的还是直接装Imagick吧,现成的Imagick::thumbnailImage方法,会让操作方便很多。

补充:看到一个写的很不错的Uploader插件,写得相当不错,如果早几天发现还真就不用自己发明轮子了。
 
2009年04月02日 星期四 下午 4:44
作者:老王

下午用CakePHP做了几个页面,发现自己还是不太熟,记录一下:

如何禁止布局?

如果需要禁止布局功能的动作或控制器比较多的话,那么可以在控制器中统一设置autoLayout为false,如果只是个别的模板的话,那么最好是直接在模板文件里设定$this->autoLayout = false,而不要在控制器里设定,毕竟使用布局与否是一个视图逻辑。

视图助手的一些注意事项?

在模板里使用图片的时候,最好是使用助手的方式,如:echo $html->image('pic.gif'),而不是直接手动编写<img>标签,因为如果硬编码的话,一旦网站目录结构出现变化,问题就严重了。同样的情况还出现在链接上,应该使用助手的方式,如:echo $html->url(array('action' => 'foobar'),原因同上。

分页助手一般使用的都是文字链接,但实际上也可以使用图片链接,方法如下:
echo $paginator->prev($html->image('prev.gif', array('border' => 0)), array('escape' => false));
类似的,表单提交按钮如果想使用图片的话,方法如下:
$form->submit(‘pic.gif’)
注意所有文件都被认为是存放在webroot目录下的img文件夹内的。

有时候分页时需要通过URL传递一些查询参数,此时应该在模板文件里按如下设置:
$paginator->options(array('url' => $this->passedArgs));
具体的可以参考文档

表单助手缺省创建的input会有div包裹,还有label等等修饰,很多时候这和美工设计的页面结构不同,这时:
$form->input('password', array('div' => false, 'label' => false, 'error' => false))

输出记得转义,CakePHP提供了h()方法,很简单,但是总容易忘。

Configure最多支持三级设置。

如果超过三级,比如说Configure::write('A.B.C.D', '...');这样是无效的,做多A.B.C三级。

通过HTTP_X_FORWARDED_FOR得到客户端IP。

如果想通过HTTP_X_FORWARDED_FOR得到客户端IP,必须传递一个false参数:

$this->RequestHandler->getClientIP(false);

也就是说要传递一个false参数,不过这样做仅适合安全性要求不高的应用。

钩子方法afterFilter并不一定会执行。

这是CakePHP的一个硬伤,因为redirect会直接跳转,所以afterFilter有可能会被忽略,这点要注意。

Auth组件缺省设置的弊端?

缺省实现里,提交表单时,如果有password字段,会自动哈希处理,但是有的时候反倒麻烦,比如说添加用户时,如果验证未通过,再显示出错表单时,密码字段已经被哈希了,已经不是初始值了,再提交就不对了,所以为了屏蔽问题,就不应该再使用password字段名,而应该改名,以注册表单为例:
原始密码:<?php echo $form->input('encrypt_password'); ?>
确认密码:<?php echo $form->input('confirm_password'); ?>
这样的话,字段值在提交前后始终保持是初始值,就不会再有问题了。

顺手送几张CakePHP非官方壁纸:http://www.jotlab.com/2009/03/24/cakephp-wallpapers-not-official/

再唠叨一句,CakePHP1.3的开发工作已经开始:http://thechaw.com/cakephp/wiki/1.3

顺手记录一个好网址:

http://github.com/mcurry/html_cache
http://wiki.github.com/davidpersson/media/
 
2009年03月22日 星期日 下午 6:51
作者:老王

最近,随着CakePHP 1.2.2.8120 Stable的发布,我又把这个框架捡起来复习了一下,下面记录两个知识点:

一:Form助手

在CakePHP里,Form助手可以说是所有助手里最重要的,也是最复杂的,其中,尤以input方法为甚,比如说文章属于一个目录,那么在录入文章的表单里我们可以这样生成一个目录的下拉菜单:

$form->input('category_id');

当然,仅仅这样还不够,还需要在控制器里提供数据才OK:

$this->set('categories', $this->Article->Category->find('list'));

相应表有一个name字段用于显示,当然你也可以在模型里设定var $displayField属性。

总体来说非常简单,甚至有点奇幻的感觉,这是因为CakePHP在这里做了点小动作,参考form.php文件:

if (!isset($options['options']) && in_array($options['type'], $types)) {
    $view =& ClassRegistry::getObject('view');
    $varName = Inflector::variable(
        Inflector::pluralize(preg_replace('/_id$/', '', $this->field()))
    );
    $varOptions = $view->getVar($varName);
    if (is_array($varOptions)) {
        if ($options['type'] !== 'radio') {
            $options['type'] = 'select';
        }
        $options['options'] = $varOptions;
    }
}

CakePHP会把表单元素名字中的_id部分去掉,并转换成复数形式,然后在当前视图变量里查找是否已经有了同名的变量,如果有,且未设置成radio单选标签,就把它当做select下拉菜单。

这里还有一个技巧,就是在渲染select的时候,如果原来的数据库表里有一个名为name或者title的字段,那么会自动被当做下拉菜单的显示部分。

详细介绍参考官方文档

二:saveAll方法

saveAll方法可以同时保存一对一,一对多的关系。比如说一个question有多个answer,那么在question的表单里,我们就可以这样设定:

echo $form->input('Question.title');
echo $form->input('Answer.0.content');
echo $form->input('Answer.1.content');
echo $form->input('Answer.2.content');
echo $form->input('Answer.3.content');

经过这样简单的设定后,在question控制器里就可以使用saveAll方法一次性保存所有的数据了:

详细介绍参考官方文档
 
   
 
 
文章存档
 
     
 
最新文章评论
  

[表情]
 

不错!
 

linux大师之路,www.linuxmr.com
 

引导一直没有整明白说。
 

[表情]
   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu