Archive for Zend Framework 累积

Zend_Db_Table Relationships 翻译和自己的注释

// January 10th, 2010 // No Comments » // Zend Framework 累积

下面是在读Zend手册9.8 Zend_Db_Table Relationships时,对重要的地方的摘录,翻译,还有很多自己的解释。由于时间有限,就不全部翻译了:

介绍:

在RDBMS中,表之间有着各种关系,有一多对应,多多对应等等。
Zend框架提供了一些方法来方便我们实现这些关系。

定义关系:

下面是本文用的例子的关系定义:

<?php
class Accounts extends Zend_Db_Table_Abstract
{
protected $_name            = ‘accounts’;
protected $_dependentTables = array(‘Bugs’);
}

class
Products extends Zend_Db_Table_Abstract
{
protected $_name            = ‘products’;
protected $_dependentTables = array(‘BugsProducts’);
}

class
Bugs extends Zend_Db_Table_Abstract
{
protected $_name            = ‘bugs’;

protected
$_dependentTables = array(‘BugsProducts’);

protected
$_referenceMap    = array(
‘Reporter’ => array(
‘columns’           => ‘reported_by’,
‘refTableClass’     => ‘Accounts’,
‘refColumns’        => ‘account_name’
),
‘Engineer’ => array(
‘columns’           => ‘assigned_to’,
‘refTableClass’     => ‘Accounts’,
‘refColumns’        => ‘account_name’
),
‘Verifier’ => array(
‘columns’           => array(‘verified_by’),
‘refTableClass’     => ‘Accounts’,
‘refColumns’        => array(‘account_name’)
)
);
}

class
BugsProducts extends Zend_Db_Table_Abstract
{
protected $_name = ‘bugs_products’;

protected
$_referenceMap    = array(
‘Bug’ => array(
‘columns’           => array(‘bug_id’),
‘refTableClass’     => ‘Bugs’,
‘refColumns’        => array(‘bug_id’)
),
‘Product’ => array(
‘columns’           => array(‘product_id’),
‘refTableClass’     => ‘Products’,
‘refColumns’        => array(‘product_id’)
)
);

}

我们看到例子中定义了四个类:Accounts,Products,Bugs,BugsProducts。其中Accounts,Products和Bugs是三个实体表,而BugsProducts是关系表。
我们再来分析一下这三个实体,一个Account有多个Bug,他们之间是一对多的关系,而Bug和Product是多对多的关系。
$_dependentTables是一个与该对象关联的对象名,这里注意,要写对象名而不是关联的数据库名。
$_referenceMap 数组用来定义和其他表的关系,在这里可以设置和那些表有关系,有什么样的关系。第一个设置的是Rule Key,也就是上面例子的’Reporter’, ‘Engineer’之类的。Rule Key的作用其实就是一个关系的名字,并不需要和其他数据库表名或者其他对象名的名字一样。只是为了标记的,在后面的时候,我们可以看到这个Rule Key的作用。

每一个Rule下面都有如下的一些定义:(没有特殊说明,都以如上’Reporter’关系进行说明)

* columns=> 设置和别的表关联的字段名,如上的’report_by’就是数据库中表Bugs的report_by字段。这里只有一个字段,也可以设置多个字段。
* refTableClass=>用于设置与这个表发生关系的表。这里要注意,一定使用目标表的对象的名字而不是表名字,例子中就和’Account’对象发生了关联。
* refColumns =>设置发生联系的表的字段。可以写多个,如果和多个字段发生联系的话,这里要和columns对应。这个设置其实是可选的,如果为空,关联字段自动被设置成为关联表的主键。上面例子中并没有使用主键作为关联字段,所以手动设置。
* onDelete=>可选字段,设置当删除是的动作。
* onUpdate=>可选字段,设置当更新表时的动作。

以上定义关系。

从关联表中取数据:

如果我们已经得到了一个查询结果,我们可以通过一下语句去取得这个结果相关联的表的查询结果:

$row->findDependentRowset($table, [$rule]);

这个方法一般使用与一多对应的两个实体表中,在多多对应的两个实体表和一个关系表如何从一个实体表取出另一个实体表的数据,我们会在下面叙述。
第一个字段$table是指和这个表想相联系的表对应的类名。第二个字段是可选的,是我们刚刚说到的rule key,就是这个关系的名字,如果省略,则默认为这个表中的第一个关系。下面是例子:

<?php
$accountsTable      = new Accounts();
$accountsRowset     = $accountsTable->find(1234);
$user1234           = $accountsRowset->current();

$bugsReportedByUser = $user1234->findDependentRowset(‘Bugs’);

例子中,我们先读取了一个编号为1234的用户,然后去查找这个家伙报了什么bug,由于zend默认是第一个关联,所以这里和Account发生关联的第一个就是’Reporter,所以就取出了Reporter的记录。

如果我们想取出其他的记录,比如Engineer,可以按照下面的办法:

<?php
$accountsTable      = new Accounts();
$accountsRowset     = $accountsTable->find(1234);
$user1234           = $accountsRowset->current();

$bugsAssignedToUser = $user1234->findDependentRowset(‘Bugs’, ‘Engineer’);

除了使用findDependentRowset之外,我们还可以使用叫做“魔术方法”(Magic Method)的机制。之所以这么叫,就是因为好像是在变魔术一样。所以方法findDependentRowset(‘<TableClass>’, ‘<Rule>’)就可以等价于如下:
- $row->find<TableClass>()
- $row->find<TableClass>By<Rule>()

注:这个机制是我们最一开始是在Ruby on Rails里面看到的。这里的<TableClass>和<Rule>一定要使用和相关联的类名以及关联名(Rule Key)完全一样的名字,才可以生效。下面是例子:

<?php
$accountsTable    = new Accounts();
$accountsRowset   = $accountsTable->find(1234);
$user1234         = $accountsRowset->current();

// Use the default reference rule
$bugsReportedBy   = $user1234->findBugs();

// Specify the reference rule
$bugsAssignedTo   = $user1234->findBugsByEngineer();

从父表取得字段:

刚刚我们介绍了一多关系中的从一去多的方法,现在我们反过来,从多取一,其实是从多中的一个取他相对应的那个记录。
类似的我们有这样的语句:

$row->findParentRow($table, [$rule]);

类似的,$table为类名,而可选参数$rule填入对应的Rule Key。下面是例子:

<?php
$bugsTable         = new Bugs();
$bugsRowset        = $bugsTable->fetchAll(array(‘bug_status = ?’ => ‘NEW’));
$bug1              = $bugsRowset->current();

$reporter          = $bug1->findParentRow(‘Accounts’);

和上面不太一样的是,上面返回的是一个多个记录的集合,而这次返回的必然是一条记录。下面的例子是设置Rule:

<?php
$bugsTable         = new Bugs();
$bugsRowset        = $bugsTable->fetchAll(‘bug_status = ?’, ‘NEW’);
$bug1              = $bugsRowset->current();

$engineer          = $bug1->findParentRow(‘Accounts’, ‘Engineer’);

只需要吧Rule填入就好了。相似的,这个方法也有“魔术字段”。findParentRow(‘<TableClass>’, ‘<Rule>’)对应:
- $row->findParent<TableClass>()
- $row->findParent<TableClass>By<Rule>()
例子:

<?php
$bugsTable         = new Bugs();
$bugsRowset        = $bugsTable->fetchAll(‘bug_status = ?’, ‘NEW’);
$bug1              = $bugsRowset->current();

// Use the default reference rule
$reporter          = $bug1->findParentAccounts();

// Specify the reference rule
$engineer          = $bug1->findParentAccountsByEngineer();

取得多对多关系表的字段:

上面两个方法讲述了一对多的使用,下面就是多对多了。我们使用如下方法取得多对多关系表的数据:

$row->findManyToManyRowset($table, $intersectionTable, [$rule1, [$rule2]]);

这里参数变成了4个,因为需要增加一个关系表来存储多对多的关系。
$table是与之发生多对多关系的表的类名,$intersectionTable是中间存储关系的关系表的类名。$rule1和$rule2是上面两个数据表的Rule Key。省略Rule Key的例子如下:

<?php
$bugsTable        = new Bugs();
$bugsRowset       = $bugsTable->find(1234);
$bug1234          = $bugsRowset->current();

$productsRowset   = $bug1234->findManyToManyRowset(‘Products’, ‘BugsProducts’);

下面是该方法的全部参数调用例子:

<?php
$bugsTable        = new Bugs();
$bugsRowset       = $bugsTable->find(1234);
$bug1234          = $bugsRowset->current();

$productsRowset   = $bug1234->findManyToManyRowset(‘Products’, ‘BugsProducts’, ‘Bug’);

这次的“魔术方法”是,对应 findManyToManyRowset(‘<TableClass>’, ‘<IntersectionTableClass>’, ‘<Rule1>’, ‘<Rule2>’)
- $row->find<TableClass>Via<IntersectionTableClass>()
- $row->find<TableClass>Via<IntersectionTableClass>By<Rule1>()
- $row->find<TableClass>Via<IntersectionTableClass>By<Rule1>And<Rule2>()

例子:

<?php
$bugsTable        = new Bugs();
$bugsRowset       = $bugsTable->find(1234);
$bug1234          = $bugsRowset->current();

// Use the default reference rule
$products          = $bug1234->findProductsViaBugsProducts();

// Specify the reference rule
$products          = $bug1234->findProductsViaBugsProductsByBug();

如何使用Zend_Form组件来实现表单提交,并且让错误提示信息为中文显示.

// January 6th, 2010 // No Comments » // Zend Framework 累积

今天因为在这里看到一篇帖子, 我也就找了一下怎样实现..没想到还真被我给搞事实上了….同时公司又要开发一个群组功能..我也就想运用一下Zend_Form来实现创建群组的功能.主要还是看中Zend_Form可以在写Form时候.实现服务器端的验证功能..省得我们在把数据提交到数据库的时候再验证一次..所以呢.我就看了一下这方面的手册..通过Zend Framework手册找到了相关的使用说明…最简单的使用方式就是在控制器(Controller)里写一个现成的Action,这样..在这个控制器里就可以直接使用这个Action…代码可以如下:

<?php
public    function   formAction()
{
$form=new Zend_Form();
$form->setName(‘group’)
$title = new Zend_Form_Element_Select(‘title’);
$title ->setLabel(‘性别’)
->setMultiOptions(array(‘mr’=>’Mr’, ‘mrs’=>’Mrs’))
->setRequired(true)
->addValidator(‘NotEmpty’, true);
$yourName = new Zend_Form_Element_Text(‘firstName’);
$yourName->setLabel(‘姓名’)
->setRequired(true)
->addValidator(‘NotEmpty’, true) ;
$email = new Zend_Form_Element_Text(‘email’);
$email->setLabel(‘电子邮件地址’)
->addFilter(‘StringToLower’)
->setRequired(false)
->addValidator(‘NotEmpty’);

$submit = new Zend_Form_Element_Submit(‘submit’);
$submit->setLabel(‘group’);
$form->addElements(array($title, $yourName,$email,$submit));
}
?>

当然..我也可以把这个Form专门写成一个类…存放在一个forms共同的目录下.这样就方便我们管理我们所有的Form表单..我的实现方式就是把它放在和控制器(Controller)的同一级别的目录下…这样管理起来也方便..当然不同的朋友..有不同的想法…另一种方式..就是可以把它写成View Helper…这个方式实现起来,,也很方便..这里我就不想多写了…Zend Framework实现起来很方便…只要你想的到…无论你怎样完成你的任务,,都是可以的..在这里我就不多说其它的…我只想谈一下怎样让 Zend_Form实现中文的提示信息功能…我这里有二种方法..
第一:比较笨的方式就是:如果你的网站不要做成多国语言的网站..同时你的Zend Framework版本不是经常更换的话…你就可以找到相关提示信息的源码…更改成中文的提示.
这个笨方法..实在是没有办法的办法…呵呵…
第二:我也是在英文站…看到的一个比较好的方式,就是通过重写这个提示信息.把它换成我们想要的语言…这样…就算我们会去换语言..或是换Zend Framework的版本..
对我们的影响也不是很大…我们只要更改一下我们的Form的表单就可以搞定了..现在这种方式的代码如下(我这里只写了Email提示信息..其它的不要多写出):

<?php
$email = new Zend_Form_Element_Text(‘email’);
$email->setLabel(‘电子邮件地址’)
->addFilter(‘StringToLower’)
->setRequired(false)
->addValidator(‘NotEmpty’)
->addValidator(‘EmailAddress’,true,array(‘messages’ => array(
‘emailAddressInvalid’ => ‘这不是一个可用的电子邮件!’,
‘emailAddressInvalidHostname’ => ‘这不是一个有效的主机名!’,
‘emailAddressInvalidMxRecord’ => ‘这不是一个有效的电子邮件地址!’,
‘emailAddressDotAtom’ => ‘这不是一个有效的电子邮件地址!’,
‘emailAddressQuotedString’ => ‘这不是一个有效的电子邮件地址!’,
‘emailAddressInvalidLocalPart’ => ‘这不是一个有效的电子邮件地址!’,)));

?>

到这里..Zend_Form这个组件还有一个比较重要的功能..就是Zend_Form_Decorator..手册上称为装饰器,也就是说你可以自己写你想要的装饰器..比如说..你要把你的Form用Table包含起来..我们要怎样实现呢?这个时候..我们就要用到比如说 HtmlTag,Label这些装饰器来达到我们想要的功能…这里是一个比较重要的概念了..有兴趣的朋友可以去去看一下…因为如果你想要用 Zend_Form这个组件..不会装饰器因该用起来会很困难..所以必须要会这个东西..才可以创建你自己想要的表单功能..最后..就是一点装饰器的小运用
我只是实现一个小的功能…如下代码:

<?php
$email = new Zend_Form_Element_Text(‘email’);
$email->setLabel(‘电子邮件地址’)
->addFilter(‘StringToLower’)
->setRequired(false)
//利用装饰器来增加td标签
->addDecorator(‘HtmlTag’, array(‘tag’ => ‘td’))
->addDecorator(‘Label’, array(‘tag’ => ‘td’))
//重复利用HtmlTag装饰器来增加tr标签
->addDecorator(array(‘FooTr’ => ‘HtmlTag’), array(‘tag’ => ‘tr’))
->addValidator(‘NotEmpty’);
?>

哈哈….大致的运用就是这样了…最后..就是验证提交的数据了…看如何验检验用户提交的数据….这里就不多说了…OK…

是快一个星期没有更新我的这个博客了..今天因为在这里看到一篇帖子, 我也就找了一下怎样实现..没想到还真被我给搞事实上了….同时公司又要开发一个群组功能..我也就想运用一下Zend_Form来实现创建群组的功 能.主要还是看中Zend_Form可以在写Form时候.实现服务器端的验证功能..省得我们在把数据提交到数据库的时候再验证一次..所以呢.我就看 了一下这方面的手册..通过Zend Framework手册找到了相关的使用说明…最简单的使用方式就是在控制器(Controller)里写一个现成的Action,这样..在这个控 制器里就可以直接使用这个Action…代码可以如下:

<?php
public	function   formAction()
{
 $form=new Zend_Form();
 $form->setName('group')
 $title = new Zend_Form_Element_Select('title');
 $title ->setLabel('性别')
        ->setMultiOptions(array('mr'=>'Mr', 'mrs'=>'Mrs'))
	->setRequired(true)
        ->addValidator('NotEmpty', true);
 $yourName = new Zend_Form_Element_Text('firstName');
 $yourName->setLabel('姓名')
          ->setRequired(true)
          ->addValidator('NotEmpty', true) ;
 $email = new Zend_Form_Element_Text('email');
 $email->setLabel('电子邮件地址')
       ->addFilter('StringToLower')
       ->setRequired(false)
       ->addValidator('NotEmpty');
 
 $submit = new Zend_Form_Element_Submit('submit');
 $submit->setLabel('group');
 $form->addElements(array($title, $yourName,$email,$submit));
}
?>

当然..我也可以把这个Form专门写成一个类…存放在一个forms共同的目录下.这样就方便我们管理我们所有的Form表单..我的实现方式就是 把它放在和控制器(Controller)的同一级别的目录下…这样管理起来也方便..当然不同的朋友..有不同的想法…另一种方式..就是可以 把它写成View Helper…这个方式实现起来,,也很方便..这里我就不想多写了…Zend Framework实现起来很方便…只要你想的到…无论你怎样完成你的任务,,都是可以的..在这里我就不多说其它的…我只想谈一下怎样让 Zend_Form实现中文的提示信息功能…我这里有二种方法..
第一:比较笨的方式就是:如果你的网站不要做成多国语言的网站..同时你的Zend Framework版本不是经常更换的话…你就可以找到相关提示信息的源码…更改成中文的提示.
这个笨方法..实在是没有办法的办法…呵呵…
第二:我也是在英文站…看到的一个比较好的方式,就是通过重写这个提示信息.把它换成我们想要的语言…这样…就算我们会去换语言..或是换Zend Framework的版本..
对我们的影响也不是很大…我们只要更改一下我们的Form的表单就可以搞定了..现在这种方式的代码如下(我这里只写了Email提示信息..其它的不要多写出):

<?php
$email = new Zend_Form_Element_Text('email');
$email->setLabel('电子邮件地址')
    ->addFilter('StringToLower')
    ->setRequired(false)
    ->addValidator('NotEmpty')
    ->addValidator('EmailAddress',true,array('messages' => array(
       'emailAddressInvalid' => '这不是一个可用的电子邮件!',
       'emailAddressInvalidHostname' => '这不是一个有效的主机名!',
       'emailAddressInvalidMxRecord' => '这不是一个有效的电子邮件地址!',
       'emailAddressDotAtom' => '这不是一个有效的电子邮件地址!',
       'emailAddressQuotedString' => '这不是一个有效的电子邮件地址!',
       'emailAddressInvalidLocalPart' => '这不是一个有效的电子邮件地址!',)));
 
?>

到这里..Zend_Form这个组件还有一个比较重要的功能..就是Zend_Form_Decorator..手册上称为装饰器,也就是说你可以自己 写你想要的装饰器..比如说..你要把你的Form用Table包含起来..我们要怎样实现呢?这个时候..我们就要用到比如说 HtmlTag,Label这些装饰器来达到我们想要的功能…这里是一个比较重要的概念了..有兴趣的朋友可以去去看一下…因为如果你想要用 Zend_Form这个组件..不会装饰器因该用起来会很困难..所以必须要会这个东西..才可以创建你自己想要的表单功能..最后..就是一点装饰器的 小运用
我只是实现一个小的功能…如下代码:

<?php
$email = new Zend_Form_Element_Text('email');
$email->setLabel('电子邮件地址')
   ->addFilter('StringToLower')
   ->setRequired(false)
   //利用装饰器来增加td标签
   ->addDecorator('HtmlTag', array('tag' => 'td'))
   ->addDecorator('Label', array('tag' => 'td'))
   //重复利用HtmlTag装饰器来增加tr标签
   ->addDecorator(array('FooTr' => 'HtmlTag'), array('tag' => 'tr'))
   ->addValidator('NotEmpty');
?>

哈哈….大致的运用就是这样了…最后..就是验证提交的数据了…看如何验检验用户提交的数据….这里就不多说了…OK…

用Zend_Auth实现Session身份持久认证

// January 5th, 2010 // No Comments » // Zend Framework 累积

今天用Zend Framework搞了一下身份持久认证…

手册里头提供了四种认证方式,一个是session,一个是数据库表,一个摘要式,最后一愕是HTTP认证适配器。我在实现session认证的时候(按照手册上的代码),起初发现了一个问题,无法验证…后来仔细看了一下,手册上的代码缺少了点东西…

Zend_Auth结合Zend_Auth_Storage_Session实现session的持久身份认证,主要有一下几点:

1、先做个适配器,也就是自己用来验证用户的类,如:

class AuthAdapter implements Zend_Auth_Adapter_Interface{
var $username;
var $password;
/**
* Sets username andpassword for authentication
* @return void
*/
public function __construct($username=”, $password=”){//
$this->username = $username;
$this->password = $password;
}
/**
* Performs an authentication attempt
* @throws Zend_Auth_Adapter_Exception If authentication cannot be performed
* @return Zend_Auth_Result
* Zend_Auth_Result::SUCCESS
* Zend_Auth_Result::FAILURE
* Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND
* Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS
* Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID
* Zend_Auth_Result::FAILURE_UNCATEGORIZED
*/
public function authenticate(){
$aryInfo;
if (isset($this->username) && (isset($this->password))){
$aryInfo[0] = _e(’验证正确’);
return new Zend_Auth_Result(1,$aryInfo);
}else{
$aryInfo[0] = _e(’验证失败’);
return new Zend_Auth_Result(-1,$aryInfo);
}
}
}

2、在获取一个Zend_Auth的实例

$auth = Zend_Auth::getInstance();
3、创建认证session的命名空间,并放到Zend_Auth的实例的存储器中
$auth->setStorage(new Zend_Auth_Storage_Session('someNamespace'));
4、加入上面的认证适配器实例:

include (’../classes/authadapter.class.php’);
$authAdapter = new AuthAdapter(’usrname’, ‘pwd’);
$result = $this->_auth->authenticate($authAdapter);

5、判断验证结果:

if (!$result->isValid()) {
foreach ($result->getMessages() as $message){
echo “$message\n”;
}
}else{

foreach ($result->getIdentity() as $message){
echo “$message\n”;
}
}

如果在其他页面去验证的时候直接调用一个方法就可以验证了:

$auth = Zend_Auth::getInstance();
$auth->setStorage(new Zend_Auth_Storage_Session(’sessionAuth’));
if ($auth->hasIdentity()){
$identity = $auth->getIdentity();
echo ‘认证’;
}else{
echo ‘未认证’;
foreach ($auth->getIdentity() as $message){
echo “$message\n”;
}

其实按照我的理解,采用什么样的方式去验证,只要在适配器验证方法中去决定就可以了…

对于身份持久,最好与ACL配合好使用才好…否则对于权限和资源的访问控制还是会有很多问题…

还有一个重要的问题,从根本上来讲,无论那种验证方式,还都是和session有很大关系的,除非在每次验证的时候都去调用验证的原始方法,而不是只根据验证session中取出来的值,当然,session中的值会作为验证信息的依据。

另外几种验证方法,再看看,也许会有更好更合适的,到时候再分享。

PS:有个重要需要搞清楚的问题,用这样的认证方式是否能够判断用户在线时长…

Zend Framework之Zend_ACL

// December 24th, 2009 // No Comments » // Zend Framework 累积

在Zend_Acl當中有兩個重要的部份:Resource(資源) 與 Role(角色),其中「角色」存取「資源」,舉個白話的例子,在一個進出受管制的電梯大樓,小明可以進入101樓的辦公室,表示小明這個「角色」,他的 通行證可以刷卡進入101樓的電梯,進入101樓這個「資源」。

比較常見的是網站管理系統的權限實作,例如編輯部的同仁只能用網站的內容編輯系統,廣告部則可以使用廣告刊播系統以及報表系統,但沒有使用內容編輯的權限。那在ZF中,Zend_Acl如何實作這樣的概念呢?

一、建立資源:
Zend_Acl裡有個Zend_Acl_Resource_Interface這個介面可以方便的來建立資源。

實作自這個介面的類別,會有一個getResourceId()這個方法,代表它是一個資源

另外,Zend_Acl_Resource類別中也包含一些Zend_Acl的一些實作,讓開發者可以直接繼承來使用。

Zend_Acl使用樹狀結構的方式來管理資源,透過這個樹狀結構,可以新增新的資源進來,並儲存在結構中。

對樹狀結構上層的修改,亦可影響至下層,不過即使下層不想繼承上層的規則,也可以簡單的加上一些例外規則,在這結構中,Resource只能一次繼承自一個Parent,不過每個Parent都可以有各自的Parent,以此類推。

二、建立角色:
透 過Zend_Acl_Role_Interface的實作,建立角色也是相當容易的。如同資源的建立,角色可透過實作的getRoleId()方法來建 立。同Zend_Acl_Resource,Zend_Acl裡也有個Zend_Acl_Role的類別可以直接繼承來使用。

不過與資源不同的,一個角色可以一次繼承自一個或多個角色。

以上面那個網站管理系統的例子來說,假設eddie可能只是編輯部的同事(單一角色),而joanne是企劃部門的主管,她除了管企劃部門的案子之外,亦可能需要看一下網站的新聞是不是有錯字,則可以說她的角色同時屬於”企劃部”及”編輯部”(多重角色)。

其 實透過角色繼承的方式,可以在設計系統時不用指定特定某個人的使用權限。例如設定”編輯部”的權限是可以用內容管理系統,然後只要讓eddie”屬於”這 個部門,eddie這個帳號也可以有使用的權限了;若同像上面joanne的例子,只要讓她同時”屬於”企劃部跟編輯部即可,在設定上相當簡潔且方便。

我們以上面這個例子來實作一小段程式碼,設定三種角色:編輯部editor、企劃部planner以及網站系統管理員administrator。

<?php
require_once ‘Zend/Acl.php’;
require_once ‘Zend/Acl/Role.php’;
require_once ‘Zend/Acl/Resource.php’;

//建立一個Access Control List(ACL)物件
$acl = new Zend_Acl();

//在ACL中建立三個角色:editor、planner及administrator
$acl->addRole(new Zend_Acl_Role(‘editor’))
->addRole(new Zend_Acl_Role(‘planner’))
->addRole(new Zend_Acl_Role(‘administrator’));

//建立joanne角色,並設定此角色同時屬於editor及planner兩個角色
$group1 = array(‘editor’, ‘planner’);
$acl->addRole(new Zend_Acl_Role(‘joanne’), $group1);

//在ACL中建立三個資源:內容管理系統ContentManager、廣告系統ADManager及系統管理SystemAdmin
$acl->add(new Zend_Acl_Resource(‘ContentManager’))
->add(new Zend_Acl_Resource(‘ADManager’))
->add(new Zend_Acl_Resource(‘SystemAdmin’));

//設定角色與資源之關係
$acl->allow(‘editor’, ‘ContentManager’);     //editor角色允許使用ContentManager資源
$acl->allow(‘planner’, ‘ADManager’);          //planner角色允許使用ADManager資源
$acl->allow(‘administrator’, ‘SystemAdmin’); //administrator角色允許使用SystemAdmin資源

//測試權限
//joanne不屬administrator角色….
echo $acl->isAllowed(‘joanne’, ‘SystemAdmin’)?’allowed’:'denined’;     //得到denined

//joanne屬於editor角色…
echo $acl->isAllowed(‘joanne’, ‘ContentManager’)?’allowed’:'denined’;   //得到allowed

上面這段程式碼中,因為joanne這個角色沒有SystemAdmin資源的使用權限,所以以isAllowed方法測試時會得到false。

Zend_Acl 在判斷isAllowed的時候,除了會去看被查的那個角色本身(上例中則為joanne)對資源的存取權限,也會從該角色所屬的角色群來詢問該角色是否 有足夠權限。上例中,其實只是將joanne繼承了editor角色,並沒有直接的定義joanne與ContentManager之間的關係,但因為 editor是有權限可以使用ContentManager,所以joanne也跟著有相同的權限。

不過要特別注意的是,上面的例子中,$group1的前後順序是有差別的,在某些情況下,可能會造成非預期的答案,舉例如下:


$group2 = array(‘editor’, ‘planner’, ‘administrator’);
$acl->addRole(new Zend_Acl_Role(‘eddie’), $group2);
$acl->allow(‘editor’, ‘ContentManager’);  //設定editor可以使用ContentManager
$acl->deny(‘planner’, ‘ContentManager’);   //設定planner不可以使用ContentManager

//測試結果
echo $acl->isAllowed(‘eddie’, ‘ContentManager’)?’allowed’:'denined’;  //得到denined

但如果改一下$group2的順序則會得到不同的結果:

$group2 = array(‘planner’, ‘editor’, ‘administrator’);
echo $acl->isAllowed(‘eddie’, ‘ContentManager’)?’allowed’:'denined’;  //得到allowed

第 一個例子中,因為eddie並沒有”直接”的被定義規則,所以Zend_Acl往它所繼承的上層角色搜尋,它會先搜尋”administrator”角 色,結果沒符合的規則,它會再往下一個”planner”搜尋,發現了”deny”的規則,搜尋結束,回傳false;而第二個例子中,先搜 尋”administrator”角色,然後再搜尋”editor”,發現”allow”的定義,搜尋結束,回傳true。

Zend_Acl在搜尋角色規則時,會在搜尋到第一個有定義規則時回傳答案。

所以如果直接定義如下:

$acl->allow(“eddie”, “ContentManager”);
echo $acl->isAllowed(‘eddie’, ‘ContentManager’)?’allowed’:'denined’;  //得到allowed

如同上面我們規納出來的規則,它在遇到第一個定義時(就是它本身的角色)就回傳true了。

存取控制清單(Access Control List – ACL)的建立
我們在上一段已經可以設定某個帳號有權限可以使用網站的內容管理系統了,但如果還需要更細部的設定,例如企劃部的同仁帳號只能”檢視”文章,編輯部的帳號可以”檢視”及”編輯”文章,主管則有全部的功能。

我們假設把內容管理系統的權限再細分為三種:view、edit、delete

角色 權限
planner view
editor view, edit
manager view, edit, delete

<?php
require_once ‘Zend/Acl.php’;
require_once ‘Zend/Acl/Role.php’;

//建立ACL
$acl = new Zend_Acl();

//建立三個角色 planner、editor及manager
$acl->addRole(new Zend_Acl_Role(‘planner’));
$acl->addRole(new Zend_Acl_Role(‘editor’));
$acl->addRole(new Zend_Acl_Role(‘manager’));

$acl->allow(‘planner’, null, ‘view’);
$acl->allow(‘editor’, null, array(‘view’,'edit’));
$acl->allow(‘manager’, null , array(‘view’, ‘edit’, ‘delete’));

//測試:企劃部能檢視文章?
echo $acl->isAllowed(‘planner’, null, ‘view’) ? “allowed” : “denied”;  //allowed
//測試:企劃部能編輯文章?
echo $acl->isAllowed(‘planner’, null, ‘edit’) ? “allowed” : “denied”;  //denined
//測試:編輯部能編文章?
echo $acl->isAllowed(‘editor’, null, ‘edit’) ?  “allowed” : “denied”;  //allowed
//測試:編輯部能刪除文章?
echo $acl->isAllowed(‘editor’, null, ‘delete’) ?  “allowed” : “denied”; //denined
//測試:主管可刪除文章?
echo $acl->isAllowed(‘manager’, null, ‘delete’) ?  “allowed” : “denied”; //allowed

上面這段程式碼分別對三個角色(planner、editor及manager)定義了使用的權限。如果仔細觀察權限列表,用角色繼承的方式可以更有彈性的完成實作。


//建立三個角色,manager繼承editor,editor繼承planner
$rolePlanner = $acl->addRole(new Zend_Acl_Role(‘planner’);
$roleEditor = $acl->addRole(new Zend_Acl_Role(‘editor’), $rolePlanner);
$roleManager = $acl->addRole(new Zend_Acl_Role(‘manager’), $roleEditor);

//對各別角色定義權限
$acl->allow($rolePlanner, null, ‘view’);
$acl->allow($roleEditor, null, ‘edit’);
$acl->allow($roleManager, null, ‘delete’);

如此一來,雖然editor只有定義了”edit”的權限,但因為它繼承自planner角色,所以它也有”view”的功能,同理,manager則同時擁有繼承別的角色的”view”、”edit”及自己定義的”delete”權限。

更複雜的權限設定
前 面的章節說明了如何定義角色與資源之間的關係,除了指定某帳號可使用某資源外,也可以定義某帳號可以使用該資源的特定功能,如檢視、修改、刪除等。不過我 們可能會遇到更複雜的例子,例如同樣是編輯部的三個同事:eddie、lindsay及jessica,其中eddie負責的是電子報的工作,還偶爾幫忙 lindsay看一下她的電子報有沒有錯字,lindsay負責的是網站最新消息的發布,jessica則是負責網站一般文章的撰寫,所以接下來我們要來 針對內容管理系統這個資源做更細部的權限設定。

我們將上述的角色及工作列一個簡單的表格:

角色 功能 權限
eddie epaper view, edit, send
news view
lindsay news view, edit, publish
jessica content view, edit, publish

程式碼如下:

<?php
require_once ‘Zend/Acl.php’;
require_once ‘Zend/Acl/Role.php’;
require_once ‘Zend/Acl/Resource.php’;
$acl = new Zend_Acl();

//定義三個角色
$acl->addRole(new Zend_Acl_Role(‘eddie’));
$acl->addRole(new Zend_Acl_Role(‘lindsay’));
$acl->addRole(new Zend_Acl_Role(‘jessica’));

//定義三個資源
$acl->add(new Zend_Acl_Resource(‘epaper’));
$acl->add(new Zend_Acl_Resource(‘news’));
$acl->add(new Zend_Acl_Resource(‘content’));

//定義規則
$acl->allow(‘eddie’, ‘epaper’, array(‘view’, ‘edit’, ‘send’));
$acl->allow(‘eddie’, ‘news’, ‘view’);
$acl->allow(‘lindsay’, ‘news’, array(‘view’, ‘edit’, ‘publish’));
$acl->allow(‘jessica’, ‘content’, array(‘view’, ‘edit’, ‘publish’));

//測試:eddie是否可編輯電子報?
echo $acl->isAllowed(‘eddie’, ‘epaper’, ‘edit’) ? “allowed” : “denied”; // allowed
//測試:lindsay是否可編輯網站文章?
echo $acl->isAllowed(‘lindsay’, ‘content’, ‘edit’) ? “allowed” : “denied”; // denied
//測試:jessica是否可發布網站文章?
echo $acl->isAllowed(‘jessica’, ‘content’, ‘publish’) ? “allowed” : “denied”; // allowed

上面這段,我們是針對各別角色來做資源及權限的設定,不過只要人數一多,這樣的做法顯然不切實際,建議就改用之前提到的角色繼承的方式讓它更有彈性。

移除權限
要移除權限設定也是相當容易的,只要用removeAllow()或removeDeny()這兩個方法就行了。

<?php
//..延用上一章節的程式碼..
//移除eddie對電子報的編輯權限
$acl->removeDeny(‘eddie’, ‘epaper’, ‘edit’);
進階應用
一、以資料庫管理ACL
Zend_Acl在角色及資源的權限設定上相當容易,但如果能配合資料庫系統做管理,則可更方便管理每個角色及資源之間之關係。

二、進階權限設定
除了原本Zend_Acl裡定義的角色、資源、權限的關係外,也可以再結合其它程式功能(例如IP白名單),或是只能在限定時間內使用該權限等等設定,使整個權限管控更加安全。

本文主要是參照Zend Framework的官方文件自己試寫的,新手上路難免有誤,若有謬誤還請指教

官方網站上也有附了一段程式碼,有興趣的可以上去看看 :)
http://framework.zend.com/manual/en/zend.acl.advanced.html

http://blog.eddie.com.tw/2008/04/30/zend-framework-zend-acl

ZendFramework之Acl+Auth+Dispatcher数据库表认证和授权,分发器简单完整实例

// December 23rd, 2009 // No Comments » // PHP知识累计, Zend Framework 累积

ZF 的版本跟新实在太快了,一个多月前开始用1.84做自己的项目,就自己一个人专研的,实在是学的太慢,网上最新版本实例几乎没有,遇到问题,google 翻烂都找不到方法,都说英文资料比较丰富,但这一个月google下来感觉也不咋样啊,特别是1.8x新版的。没办法,就只有分析源码来理解喽。。。

如果只是针对 Acl + Auth 的话,网上找的资料也能拼凑出个大概来,虽然都是老版本的,但也知道是怎么回事的,关键是这个Dispatcher,1.8x在哪里,怎样注册这个
My_Plugin_Auth 完全不明白,实在是把我弄晕了,研究了三天,今天终于成功了,赶紧趁热打铁,把整个过程写在这儿,跟大家分享下,我也好巩固下思路,当然不足之处肯定是很多的,也希望大家能不吝赐教,谢谢!

Dispatcher 中的 preDispatch 是分发前被调用的方法,通过重载这个方法,实现对行为的过滤,个人理解也就是在这儿监视用户当前行为是否被授权并进行相应调控吧。
另外,preDispatch 授权的验证应该是只能是在 frontController 里面重载这个方法才行,我之前稀里糊涂的,在controller_action里面重载这个方法,一直死循环,郁闷的要死!
今天上午看了一个 dispatch 的模型图,才恍然大悟的。大家也可以看看这个pdf,我在国外网站上找到的,结构非常清晰了,大家看看就知道了
下载地址:zendframeworkdispatchworkflow-090508180623-phpapp02.pdf 223KB

Bootstrap.php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initControllers()
{
$this->bootstrap(‘FrontController’);
//print_r(get_class_methods($this->frontController));
//exit;
require_once realpath(APPLICATION_CURR . ‘/acl/Acl.php’);
require_once realpath(APPLICATION_CURR . ‘/acl/Auth.php’);

$acl = new MyAcl;
$auth = Zend_Auth::getInstance();
$this->frontController->registerPlugin(new My_Plugin_Auth($auth, $acl));
}

Acl.php

class MyAcl extends Zend_Acl
{
public function __construct()
{
$this->add(new Zend_Acl_Resource(‘System_Category’));
$this->add(new Zend_Acl_Resource(‘System_Global’));
$this->add(new Zend_Acl_Resource(‘Article_Category’));
$this->add(new Zend_Acl_Resource(‘Article_Page’));
$this->add(new Zend_Acl_Resource(‘index’));
$this->add(new Zend_Acl_Resource(‘login’));
$this->add(new Zend_Acl_Resource(‘error’));

$this->addRole(new Zend_Acl_Role(‘guest’));
$this->addRole(new Zend_Acl_Role(‘administrator’));

$this->allow(‘guest’, ‘login’, null);
$this->allow(‘administrator’, null, null);
}
}

Auth.php

class My_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
private $_auth;
private $_acl;

// 认证没有通过(没有登陆)转向登录页面
private $_noauth = array(
‘module’ => ‘default’,
‘controller’ => ‘login’,
‘action’ => ‘index’);

// 没有权限转向错误页面
private $_noacl = array(
‘module’ => ‘default’,
‘controller’ => ‘error’,
‘action’ => ‘index’);

public function __construct($auth, $acl)
{
$this->_auth = $auth;
$this->_acl = $acl;
}

// 重载 preDispatch() 方法,认证用户
public function preDispatch($request)
{
if ($this->_auth->hasIdentity()) {
// 获取当前用户角色
$role = $this->_auth->getIdentity()->roleName;
}
else {
$role = ‘guest’;
}

$controller = $request->controller;
$action = $request->action;
$module = $request->module;
$resource = $controller;

if (!$this->_acl->has($resource)) {
$resource = null;
}

// Acl 配置该角色不能执行 $resource $action 的行为时
if (!$this->_acl->isAllowed($role, $resource, $action)) {
// 用户是由于没有通过认证 则 跳转到登录页面
if (!$this->_auth->hasIdentity()) {
$module = $this->_noauth['module'];
$controller = $this->_noauth['controller'];
$action = $this->_noauth['action'];
}
else {
// 用户没有权限,提示错误
$module = $this->_noacl['module'];
$controller = $this->_noacl['controller'];
$action = $this->_noacl['action'];
}
}

// 设置转向参数
$request->setModuleName($module);
$request->setControllerName($controller);
$request->setActionName($action);
}
}

LoginController.php

class LoginController extends Abstract_Controller
{
public function indexAction()
{
if ($this->_auth->hasIdentity()) {
// Already authenticated? Navigate away
$this->_redirect->myRedirect(‘/app/admin/index’);
}
$this->_view->display(‘login.html’);
}

public function checkAction()
{
if($this->_request->isPost()) {
// 过滤用户输入
$filter = new Zend_Filter_Striptags();
$username = trim($filter->filter($this->_request->getPost(‘account’)));
$password = trim($filter->filter($this->_request->getPost(‘password’)));

$bootstrap = $this->getInvokeArg(‘bootstrap’);
$db = $bootstrap->getResource(‘db’);
// 数据库表认证
$authAdapter = new Zend_Auth_Adapter_DbTable($db, ‘blog_system_user’, ‘account’, ‘password’);
$authAdapter->setIdentity($username)->setCredential(md5($password));
$result = $this->_auth->authenticate($authAdapter);

if($result->isValid()) {
// 将当前用户于 user 表中的记录除去 password 字段内容,其他的字段数据全部取出来
$data = $authAdapter->getResultRowObject(null, ‘password’);
// 这里添加当前用户的角色信息,由于 $data->roleID , 只记录了 id ,需要在 role 表里面查询 roleName
// 添加当前用户的角色名
$data->roleName = ‘administrator’;

// 将当前用户信息存入 Zend_Session
$this->_auth->getStorage()->write($data);
//$this->_redirect->myRedirect(‘/app/admin/index’);
echo $result->getCode();
}
else {
//$this->_view->assign(‘message’, $result->getMessages());
//print_r($result->getMessages());
//$this->_redirect->myRedirect(‘/app/admin/login’);
// 我用的 Ajax 判断登陆的所以直接 echo
echo $result->getCode();
}
}
}
}

上面就是4个文件的代码就是整个 认证+授权+分发 所涉及到的,都是最基础的,比较简单了,没想到用了三天才理解,这部分做完,我的整个项目也差不多完成了 嘿嘿!
大家看到有不足,或者有问题的地方 还麻烦你给指出来哦 谢谢啊!
欢迎转载,但请注明出生地哦!http://www.163fly.com/node/313

Zend Framework 剖析之MVC

// December 23rd, 2009 // No Comments » // PHP知识累计, Zend Framework 累积

【开篇】
在Web开发中,除了ASP.NET的Page Controller之外,MVC是其他开发语言中一个非常重要和常用的架构模式,本文就Zend Framework中的 MVC处理流程做一下浅显的分析。

【结构】
这里是一个Zend Framework 开发项目的目录结构,可以做为参考。具体的Front Controller 设计模式、MVC等可以参阅相关资料。目录结构如下:

/app/controllers       所有的controller
/app/models              所有的model
/app/views/scripts  view的模板文件夹
/config                        存放config文件
/db                               数据库相关脚步、sql存放
/lib                               库文件,如/lib/Zend存放Zend Framework,/lib/YourProj可以存放自己项目的库文件
/log                             日志文件
/root                            根目录,该文件夹下只有一个文件index.php即前端控制器,.htaccess是apache配置文件,将所有的请求转发到index.php。

一个典型的前端控制器代码:
$ctrl = Zend_Controller_Front::getInstance();
$ctrl ->throwExceptions(true);
$ctrl ->dispatch();
OK,一个dispatch就处理完了所有的请求,将处理结果传给了客户端,那么我们就从 Zend_Front_Controller来开始我们的Zend Framework之旅吧。

【流程分析】
首先,打开文件 /lib/Zend/Controller/Front.php文件。

getInstance 方法:
很简单的  Singleton 设计模式。下面进入构造函数。

__construct 方法:
$this->_plugins = new Zend_Controller_Plugin_Broker();
初始化当前 _plugins 字段。

在 实际开发中,本人觉得前端控制器的构造方法不应该是private,而应改成protected,理由很简单,如果要继承该Front Controller,那么就可以在构造函数中调用父类的构造函数,private的则没有办法实现,如果你要继承Front Controller的话,那必须在你的构造函数中加上一句: $this->_plugins = new Zend_Controller_Plugin_Broker();否则,当前的_plugins会因为没有初始化而出错。

?在实际开发中,本人觉得前端控制器的构造方法不应该是private,而应改成protected,理由很简单,如果要继承该Front Controller,那么就可以在构造函数中调用父类的构造函数,private的则没有办法实现,如果你要继承Front Controller的话,那必须在你的构造函数中加上一句:
$this->_plugins = new Zend_Controller_Plugin_Broker();否则,当前的_plugins会因为没有初始化而出错。

<!–[if !vml]–>
构造完之后,我们来重点分析一下dispatch方法。

dispatch方法:
dispatch方法是整个Zend MVC处理过程的核心,由于代码比较多,这里采用注释加评说的方法来讲解。
dispatch主要包括以下几个阶段:
(1)初始化阶段
/**
* 判断当前参数中是否设定了noErrorHandler参数,如果有或者已经有
* Plugin_ErrorHandler就不加载 这个plugin,否则就加载这个Plugin
*/
if (!$this->getParam(”noErrorHandler”) && !$this->_plugins->hasPlugin(”Zend_Controller_Plugin_ErrorHandler”)) {
// Register with stack index of 100
$this->_plugins->registerPlugin(new     Zend_Controller_Plugin_ErrorHandler(), 100
);
}
/**
*判断当前参数是否设定了noViewRenderer参数,如果有或者已经有了一个viewRenderer
*就不添加 Helper_ViewRenderer,否则就添加
*Zend_Controller_Action_Helper_ViewRenderer 实例作为默认ViewRenderer
*/
if (!$this->getParam(”noViewRenderer”)  && !Zend_Controller_Action_HelperBroker::hasHelper(”viewRenderer”)) {
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_ViewRenderer()
);
}

Zend Framework MVC中plugin机制类似于ASP.NET中的HTTPModule,也就是说你添加的所有的plugin都会在处理具体的Action之前和之后触 发相应的事件。一次请求只能有一个具体的action来处理(可以通过setDispatched的方法来让一个请求处理多个action),但是可以有 多个plugin。这个和ASP.NET中的HTTPHandler和HTTPModule完全类似。 一个plugin必须继承Zend_Controller_Plugin_Abstract 类,重载某些方法来实现相应的功能。 主要的事件有:

public function preDispatch(Zend_Controller_Request_Abstract $request){} public function postDispatch(Zend_Controller_Request_Abstract $request){}
preDispatch是在调用具体action之前触发,所以在这个地方,我们可以添加权限认证,URL转发等动作。
postDispatch 是在调用action之后触发,在这个地方,可以做输出缓存等。 如何注册一个plugin呢?前端控制器中有一个方法registerPlugin,所以最简单的办法就是在index.php中初始化一个 plugin,然后调用 $ctrl->registerPlugin(myPlugin);

?Zend Framework MVC中plugin机制类似于ASP.NET中的HTTPModule,也就是说你添加的所有的plugin都会在处理具体的Action之前和之后触 发相应的事件。一次请求只能有一个具体的action来处理(可以通过setDispatched的方法来让一个请求处理多个action,我们将这个做 为一个小技巧 J ),但是可以有多个plugin。这个和ASP.NET中的HTTPHandler和HTTPModule完全类似。

一个plugin必须继承Zend_Controller_Plugin_Abstract 类,重载某些方法来实现相应的功能。主要的事件有:
??? public function preDispatch(Zend_Controller_Request_Abstract $request){}
public function postDispatch(Zend_Controller_Request_Abstract $request){}
preDispatch是在调用具体action之前触发,所以在这个地方,我们可以添加权限认证,URL转发等动作。
postDispatch是在调用action之后触发,在这个地方,可以做输出缓存等。

如何注册一个plugin呢?前端控制器中有一个方法registerPlugin,所以最简单的办法就是在index.php中初始化一个plugin,然后调用 $ctrl->registerPlugin(myPlugin);

<!–[if !vml]–>
/**
* 如果没有提供Request对象,则用默认的Request对象
*/
if (null !== $request) {
$this->setRequest($request);
} elseif ((null === $request)
&& (null === ($request = $this->getRequest()))) {
require_once ”Zend/Controller/Request/Http.php”;
$request = new Zend_Controller_Request_Http();
$this->setRequest($request);
}
Response 对象的处理类似,在此略过。
/**
* 给所有注册的plugin添加request和repsonse对象
*/
$this->_plugins
->setRequest($this->_request)
->setResponse($this->_response);
/**
*初始化一个Router,默认的路由是用 Zend_Controller_Router_Rewrite
*/
$router = $this->getRouter();
$router->setParams($this->getParams());
/**
*初始化一个dispatcher,默认的dispatcher是
*Zend_Controller_Dispatcher_Standard
*/
$dispatcher = $this->getDispatcher();
$dispatcher->setParams($this->getParams())

?如何去改变默认提供的这些路由、dispatcher以及其他参数对象呢?在Zend_Controller_Front中都有类似 setXYZ的方法,以供设置这些对象,当然也有 对应的getXYZ来获取这些对象。所以一个典型的前端控制器的初始化也通常有如下写法:
$front->setRouter($router)
???? ??->setDispatcher(new Zend_Controller_ModuleDispatcher())
???? ??->registerPlugin(new My_Plugin_Auth($auth, $acl))
???? ??->registerPlugin(new My_Plugin_View($view))
??->setControllerDirectory(array(
”default” => realpath(”../app/controllers/default”),
”admin” => realpath(”../app/controllers/admin”)
))
???? ??->setParam(”auth”, $auth)
???? ??->setParam(”view”, $view)
???? ??->setParam(”config”, $config)
???? ??->setParam(”sitemap”, $sitemap)
???? ??->dispatch();???????????????? ???????????????????

<!–[if !vml]–>          ->setResponse($this->_response);
如何去改变默认提供的这些路由、dispatcher以及其他参数对象呢?在Zend_Controller_Front中都有类似 setXYZ的方法,以供设置这些对象,当然也有 对应的getXYZ来获取这些对象。所以一个典型的前端控制器的初始化也通常有如下写法:

$front   ->setRouter($router)
->setDispatcher(new Zend_Controller_ModuleDispatcher())
->registerPlugin(new My_Plugin_Auth($auth, $acl))
->registerPlugin(new My_Plugin_View($view))
->setControllerDirectory(
array( ”default” => realpath(”../app/controllers/default”),
&nbs

p;         ”admin” => realpath(”../app/controllers/admin”)
))
->setParam(”auth”, $auth)
->setParam(”view”, $view)
->setParam(”config”, $config)
->setParam(”sitemap”, $sitemap)
->dispatch();

(2)路由阶段
/**
* 触发所有plugin的routeStartup事件
*/
$this->_plugins->routeStartup($this->_request);
/**
* 路由当前请求,给request对象添加一些重要参数:module、controller、action,
* 表示当前的请求由哪一个module的 那一个controller的那个action处理
*/
$router->route($this->_request);
/**
* 触发所有plugin的routeShutdown事件
*/
$this->_plugins->routeShutdown($this->_request);
/**
* 触发所有plugin的dispatchLoopStartup事件
*/
$this->_plugins->dispatchLoopStartup($this->_request);

(3)分发处理
/**
* 设置当前已经被dispatch
*/
$this->_request->setDispatched(true);
/**
* 触发所有plugin的preDispatch
*/
$this->_plugins->preDispatch($this->_request);
/**
* 最重要的方法,该方法处理了以下动作:从request中找到当前的module、controller、
* action,然后实例化相应的类,最后执行action方法
*/
$dispatcher->dispatch($this->_request, $this->_response);
/**
* 触发所有plugin的  postDispatch
*/
$this->_plugins->postDispatch($this->_request);
/**
* 触发所有plugin的  dispatchLoopShutdown
*/
$this->_plugins->dispatchLoopShutdown();

(4)收尾阶段
如果要返回response对象,那么就返回当前的Response,否则就将response对象中的内容直接输出。
可以在这里返回response对象,然后对response对象做一些自定义的处理。

if ($this->returnResponse()) {
return $this->_response;
}
$this->_response->sendResponse();

?Zend_Controller_Action_HelperBroker 类实现了Helper类到Action类的代理,是Zend 构架中一个很优秀的机制,这种模式有点类似注册工厂,也就是向HelperBroker注册对象,然后再从HelperBroker中取回对象,下面是注 册工厂参考图

但是又不完全和注册工厂一样,在获取Helper类的时候是实例化了一个HelperBroker,又和MonoState模式做法一样,如果要和模式挂上钩的话,那么可以暂且称之为 “RegistryMonoState” 。
不足之处是PluginBroker类,并且在前端控制器的构造函数中实例化PluginBroker是非常不好的做法,PluginBroker也完全可以用HelperBroker类的做法,这样可以大大降低耦合性。

<!–[if !vml]–><!–[endif]–>

【参考】
Zend Framework 开发Jira地址: http://framework.zend.com/issues/secure/Dashboard.jspa
SVN仓库:http://framework.zend.com/svn/framework/trunk

http://www.diybl.com/course/4_webprogram/php/phpjs/20090307/159362.html

初级篇:Zend Framework 环境配置以及第一个Hello World 输出!

// December 21st, 2009 // No Comments » // PHP知识累计, Zend Framework 累积

Zend Framework教程列表出来也有几天了,只有少许PHPer给本人提出意见以及建议..那没办法..我现在只有按照我自己的想法去写这一系列的教程..
我答应过PHPer会在这周出最少一篇的教程..今天,我有点时间我就来写第一篇吧.
OK!开工了…
第一步:确认你的PHP环境:
1.请PHPer确认你的PHP版本是否在5.2.0以上..如果不是的话..请更新到5.2.0,否则.Zend Framework 好像用不了..我自己有试过.
遇到过这样的问题..所以请你们自己测试一下..PHP源码最新版下载地址为:http://www.php.net/downloads.php.
2.你的PHP环境配置好了之后,请打开php.ini文件,确认PDO扩展是否打开.如果没有请把extension=php_pdo.dll之前的;号给去掉.
3.打开APACHE文件夹里面的httpd.conf文件.查找到apache的mod_rewrite模块,确认LoadModule rewrite_module modules/mod_rewrite.so是否打开.如果没有请去掉
它前面的#号.
4. 查找到httpd.conf文件,如果AllowOverride为None的话..请一定把None都改成all.这样你写.htaccess这样的文件才会起到作用..
5.重新启动你的APACHE服务器..这样我们的PHP环境就可以运用Zend Framewrok了.

第二步:获取Zend Framework源码:
1.下载最新版的Zend Framework源码.现在最新版好像是1.7.0.但是怕不稳定..所以请PHPer们自己决定用什么版本的.
大家可以在这里下载http://www.zendframework.com/download/latest最新版本的源码.

第三步:建立项目目录:
我也不想去多说什么..我把图片给展示出来..是我的这个教程的项目目录…我在上面都有说明..大家可以按照下面的方式来建立目录..当然下面我会提供源码下载.
不过建议朋友们一定要自己动手..才可以学到更多..我给也源码也只是给大家做为一个参考.

第四步:程序说明:这里我不多说什么.因为每个文件里面都有注解.我想不会有太难.要是有的朋友不懂请在博客上给我留言.我会关注这个博客..尽量回答你们的问题..谢谢..
index.php(网站入口)文件及说明:

<?php
error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Asia/Shanghai');
 
set_include_path('.' .PATH_SEPARATOR .'./library'
.PATH_SEPARATOR .'./application/models/'.PATH_SEPARATOR .get_include_path());
 require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();//设置Zend Framework 自动载入类文件
$registry = Zend_Registry::getInstance();
 //设置模板显示路径
$view = new Zend_View();
$view->setScriptPath('./application/views/scripts/');
$registry['view'] = $view;//注册View
//设置控制器
$frontController =Zend_Controller_Front::getInstance();
$frontController->setBaseUrl('/zendframework')//设置基本路径
		->setParam('noViewRenderer', true)
		->setControllerDirectory('./application/controllers')
		->throwExceptions(true)
		->dispatch();

IndexController.php文件及说明:

<?php
class IndexController extends Zend_Controller_Action
{
    function init()
    {
        $this->registry = Zend_Registry::getInstance();
        $this->view = $this->registry['view'];
        $this->view->baseUrl = $this->_request->getBaseUrl();
 
    }
 
   function indexAction()
    {
      	//这里给变量赋值,在index.phtml模板里显示
        $this->view->bodyTitle = '<h1>Hello World!</h1>';
		echo $this->view->render('index.phtml');//显示模版
    }
 
}

index.phtml模板文件说明:

<?=$this->bodyTitle; ?> <!-- 这里输出控制器里Action传过来的值:hello world -->

整个文件源码下载…地址.在这里..不过我的library里面没有加上Zend. 请各位PHPer自己加上..因为空间有限…哈哈…好了…这个教程就算是搞定了..要是有什么问题..请朋友给我留言…有空就常关注册一下我的博客…..现在这个博客还没什么人气….呵呵…

源码下载地址:  源码下载

Zend Framework实例教程

// December 19th, 2009 // No Comments » // PHP知识累计, Zend Framework 累积

Zend Framework发布了!虽然仍处于开发初期,这个教程仍突出讲解目前几个最好的功能,并指导你完成一个简单程序的构建。

Zend最早在社区里发布了ZF。基于同样的想法,这个教程写来用于展示ZF现有的功能。由于这个教程是在线发布,我将在ZF变化时对其进行更新,以便尽可能有效。

要求

Zend Framework要求PHP5。为了更好利用本教程的代码,你还需要Apache网页服务器。因为示范程序(一个新闻管理系统)用到了mod_rewrite

这个教程的代码可以自由下载,所以你可以自己试一下。你可以从Brain Buld的网站下载到代码:http://brainbulb.com/zend-framework-tutorial.tar.gz

下载ZF

当你开始这篇教程时,你需要下载ZF的最新版本。你可以用浏览器手工从http://framework.zend.com/download选择tar.gzzip文件进行下载,或者使用下列命令:

$ wget http://framework.zend.com/download/tgz
$ tar -xvzf ZendFramework-0.1.2.tar.gz
提示:Zend计划提供自有PEAR通道简化下载。

一旦你下载了预览版,把library目录放到方便的地方。在这个教程,我把library重命名为lib以便有个简洁的目录结构:

app/
views/
controllers/
www/
.htaccess
index.php
lib/

www目录是文档根目录,controllersviews目录是以后会用到的空目录,而lib目录来自你下载的预览版。

开始

我要介绍的第一个组件是Zend_Controller。从很多方面看,它为你开发的程序提供了基础,同时也部分决定了Zend Framework不只是个组件的集合。但是,你在用之前需要将所有的得到的请求都放到一个简单的PHP脚本。本教程用的是mod_rewrite

mod_rewrite自身是一种艺术,但幸运的是,这个特殊的任务特别简单。如果你对mod_rewrite或Apache的一般配置不熟悉,在文档根目录下创建一个.htaccess文件,并添加以下内容:

RewriteEngine on
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php
提示: Zend_Controller的一个TODO项目就是取消对mod_rewrite的依赖。为了提供一个预览版的范例,本教程用了mod_rewrite

如果你直接把这些内容添加到httpd.conf,你必须重启网页服务器。但如果你用.htaccess文件,则什么都不必做。你可以放一些具体的文本到index.php并访问任意路径如/foo/bar做一下快速测试。如你的域名为example.org,则访问http://example.org/foo/bar

你还要设置ZF库的路径到include_path。你可以在php.ini设置,也可以直接在你的.htaccess文件放下列内容:

php_value include_path "/path/to/lib"

Zend

Zend类包含了一些经常使用的静态方法的集合。下面是唯一一个你要手工添加的类:

<?php

include 'Zend.php';

?>

一旦你包含了Zend.php,你就已经包含了Zend类的所有的类方法。用loadClass()就可以简单地加载其它类。例如,加载Zend_Controller_Front类:

<?php

include 'Zend.php';

Zend::loadClass('Zend_Controller_Front');

?>

include_path能理解loadclass()及ZF的组织和目录结构。我用它加载所有其它类。

Zend_Controller

使用这个controller非常直观。事实上,我写本教程时并没有用到它丰富的文档。

提示:文档目前已经可以在http://framework.zend.com/manual/zend.controller.html看到。

我一开始是用一个叫Zend_Controller_Front的front controller。为了理解它是怎么工作的,请把下列代码放在你的index.php文件:

<?php

include 'Zend.php';

Zend::loadClass('Zend_Controller_Front');

$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('/path/to/controllers');
$controller->dispatch();

?>

如果你更喜欢对象链结,可以用以下代码代替:

<?php

include 'Zend.php';

Zend::loadClass('Zend_Controller_Front');

$controller = Zend_Controller_Front::getInstance()
->
setControllerDirectory('/path/to/controllers')
->
dispatch();

?>

现在如果你访问/foo/bar,会有错误发生。没错!它让你知道发生了什么事。主要的问题是找不到IndexController.php文件。

在你创建这个文件之前,应先理解一下ZF想让你怎样组织这些事情。ZF把访问请求给拆分开来。假如访问的是/foo/bar,则foo是controller,而bar是action。它们的默认值都是index.

如果foo是controller,ZF就会去查找controllers目录下的FooController.php文件。因为这个文件不存在,ZF就退回到IndexController.php。结果都没有找到,就报错了。

接下来,在controllers目录创建IndexController.php文件(可以用setControllerDirectory()设置):

<?php

Zend::loadClass('Zend_Controller_Action');

class IndexController extends Zend_Controller_Action
{
public function
indexAction()
{
echo
'IndexController::indexAction()';
}
}

?>

就如刚才说明的,IndexController类处理来自index controller或controller不存在的请求。indexAction()方法处理action为index的访问。要记住的是index是controller和action的默认值。如果你访问//index/index/indexindexAction()方法就会被执行。 (最后面的斜杠并不会改变这个行为。) 而访问其他任何资源只会导致出错。

在继续做之前,还要在IndexController加上另外一个有用的类方法。不管什么时候访问一个不存在的控制器,都要调用noRouteAction()类方法。例如,在FooController.php不存在的条件下,访问/foo/bar就会执行noRouteAction()。但是访问/index/foo仍会出错,因为foo是action,而不是controller.

noRouteAction()添加到IndexController.php:

<?php

Zend::loadClass('Zend_Controller_Action');

class IndexController extends Zend_Controller_Action
{
public function
indexAction()
{
echo
'IndexController::indexAction()';
}

public function noRouteAction()
{
$this->_redirect('/');
}
}

?>

例子中使用$this->_redirect('/')来描述执行noRouteAction()时,可能发生的行为。这会将对不存在controllers的访问重定向到根文档(首页)。

现在创建FooController.php

<?php

Zend::loadClass('Zend_Controller_Action');

class FooController extends Zend_Controller_Action
{
public function
indexAction()
{
echo
'FooController::indexAction()';
}

public function barAction()
{
echo
'FooController::barAction()';
}
}

?>

如果你再次访问/foo/bar,你会发现执行了barAction(),因为bar是action。现在你不只支持了友好的URL,还可以只用几行代码就做得这么有条理。酷吧!

你也可以创建一个__call()类方法来处理像/foo/baz这样未定义的action。

<?php

Zend::loadClass('Zend_Controller_Action');

class FooController extends Zend_Controller_Action
{
public function
indexAction()
{
echo
'FooController::indexAction()';
}

public function barAction()
{
echo
'FooController::barAction()';
}

public function __call($action, $arguments)
{
echo
'FooController:__call()';
}
}

?>

现在你只要几行代码就可以很好地处理用户的访问了,准备好继续。

Zend_View

Zend_View是一个用来帮助你组织好你的view逻辑的类。这对于模板-系统是不可知的,为了简单起见,本教程不使用模板。如果你喜欢的话,不妨用一下。

记住,现在所有的访问都是由front controller进行处理。因此应用框架已经存在了,另外也必须遵守它。为了展示Zend_View的一个基本应用,将IndexController.php修改如下:

<?php

Zend::loadClass('Zend_Controller_Action');
Zend::loadClass('Zend_View');

class IndexController extends Zend_Controller_Action
{
public function
indexAction()
{
$view = new Zend_View();
$view->setScriptPath('/path/to/views');
echo
$view->render('example.php');
}

public function noRouteAction()
{
$this->_redirect('/');
}
}

?>

views目录创建example.php文件:

<html>
<head>
<title>This Is an Example</title>
</head>
<body>
<p>This is an example.</p>
</body>
</html>

现在,如果你访问自己网站的根资源,你会看到example.php的内容。这仍没什么用,但你要清楚你要在以一种结构和组织非常清楚的方式在开发网络应用。

为了让Zend_View的应用更清楚一点,,修改你的模板(example.php)包含以下内容:

<html>
<head>
<title><?php echo $this->escape($this->title); ?></title>
</head>
<body>
<?php echo $this->escape($this->body); ?>
</body>
</html>

现在已经添加了两个功能。$this->escape()类方法用于所有的输出。即使你自己创建输出,就像这个例子一样。避开所有输出也是一个很好的习惯,它可以在默认情况下帮助你防止跨站脚本攻击(XSS)。

$this->title$this->body属性用来展示动态数据。这些也可以在controller中定义,所以我们修改IndexController.php以指定它们:

<?php

Zend::loadClass('Zend_Controller_Action');
Zend::loadClass('Zend_View');

class IndexController extends Zend_Controller_Action
{
public function
indexAction()
{
$view = new Zend_View();
$view->setScriptPath('/path/to/views');
$view->title = 'Dynamic Title';
$view->body = 'This is a dynamic body.';
echo
$view->render('example.php');
}

public function noRouteAction()
{
$this->_redirect('/');
}
}

?>

现在你再次访问根目录,应该就可以看到模板所使用的这些值了。因为你在模板中使用的$this就是在Zend_View范围内所执行的实例。

要记住example.php只是一个普通的PHP脚本,所以你完全可以做你想做的。只是应努力只在要求显示数据时才使用模板。你的controller (controller分发的模块)应处理你全部的业务逻辑。

在继续之前,我想做最后一个关于Zend_View的提示。在controller的每个类方法内初始化$view对象需要额外输入一些内容,而我们的主要目标是让快速开发网络应用更简单。如果所有模板都放在一个目录下,是否要在每个例子中都调用setScriptPath()也存在争议。

幸运的是,Zend类包含了一个寄存器来帮助减少工作量。你可以用register()方法把你的$view对象存储在寄存器:

<?php

Zend::register('view', $view);

?>

registry()方法进行检索:

<?php

$view = Zend::registry('view');

?>

基于这点,本教程使用寄存器。

Zend_InputFilter

本教程讨论的最后一个组件是Zend_InputFilter。这个类提供了一种简单而有效的输入过滤方法。你可以通过提供一组待过滤数据来进行初始化。

<?php

$filterPost = new Zend_InputFilter($_POST);

?>

这会将($_POST)设置为NULL,所以就不能直接进入了。Zend_InputFilter提供了一个简单、集中的根据特定规则过滤数据的类方法集。例如,你可以用getAlpha()来获取$_POST['name']中的字母:

<?php

/* $_POST['name'] = 'John123Doe'; */

$filterPost = new Zend_InputFilter($_POST);

/* $_POST = NULL; */

$alphaName = $filterPost->getAlpha('name');

/* $alphaName = 'JohnDoe'; */

?>

每一个类方法的参数都是对应要过滤的元素的关键词。对象(例子中的$filterPost)可以保护数据不被篡改,并能更好地控制对数据的操作及一致性。因此,当你操纵输入数据,应始终使用Zend_InputFilter

提示:Zend_Filter提供与Zend_InputFilter方法一样的静态方法。

构建新闻管理系统

虽然预览版提供了许多组件(甚至许多已经被开发),我们已经讨论了构建一个简单程序所需要的全部组件。在这里,你会对ZF的基本结构和设计有更清楚的理解。

每个人开发的程序都会有所不同,而Zend Framework试图包容这些差异。同样,这个教程是根据我的喜好写的,请根据自己的偏好自行调整。

当我开发程序时,我会先做界面。这并不意味着我把时间都花在标签、样式表和图片上,而是我从一个用户的角度去考虑问题。因此我把程序看成是页面的集合,每一页都是一个独立的网址。这个新闻系统就是由以下网址组成的:

/
/add/news
/add/comment
/admin
/admin/approve
/view/{id}

你可以直接把这些网址和controller联系起来。IndexController列出新闻,AddController添加新闻和评论,AdminController处理一些如批准新闻之类的管理,ViewController特定新闻和对应评论的显示。

如果你的FooController.php还在,把它删除。修改IndexController.php,为业务逻辑以添加相应的action和一些注释:

<?php

Zend::loadClass('Zend_Controller_Action');

class IndexController extends Zend_Controller_Action
{
public function
indexAction()
{
/* List the news. */
}

public function noRouteAction()
{
$this->_redirect('/');
}
}

?>

接下来,创建AddController.php文件:

<?php

Zend::loadClass('Zend_Controller_Action');

class AddController extends Zend_Controller_Action
{
function
indexAction()
{
$this->_redirect('/');
}

function commentAction()
{
/* Add a comment. */
}

function newsAction()
{
/* Add news. */
}

function __call($action, $arguments)
{
$this->_redirect('/');
}
}

?>

记住AddControllerindexAction()方法不能调用。当访问/add时会执行这个类方法。因为用户可以手工访问这个网址,这是有可能的,所以你要把用户重定向到主页、显示错误或你认为合适的行为。

接下来,创建AdminController.php文件:

<?php

Zend::loadClass('Zend_Controller_Action');

class AdminController extends Zend_Controller_Action
{
function
indexAction()
{
/* Display admin interface. */
}

function approveAction()
{
/* Approve news. */
}

function __call($action, $arguments)
{
$this->_redirect('/');
}
}

?>

最后,创建ViewController.php文件:

<?php

Zend::loadClass('Zend_Controller_Action');

class ViewController extends Zend_Controller_Action
{
function
indexAction()
{
$this->_redirect('/');
}

function __call($id, $arguments)
{
/* Display news and comments for $id. */
}
}

?>

AddController一样,index()方法不能调用,所以你可以使用你认为合适的action。ViewController和其它的有点不同,因为你不知道什么才是有效的action。为了支持像/view/23这样的网址,你要使用__call()来支持动态action。

数据库操作

因为Zend Framework的数据库组件还不稳定,而我希望这个演示可以做得简单一点。我使用了一个简单的类,用SQLite进行新闻条目和评论的存储和查询。

<?php

class Database
{
private
$_db;

public function __construct($filename)
{
$this->_db = new SQLiteDatabase($filename);
}

public function addComment($name, $comment, $newsId)
{
$name = sqlite_escape_string($name);
$comment = sqlite_escape_string($comment);
$newsId = sqlite_escape_string($newsId);

$sql = "INSERT
INTO   comments (name, comment, newsId)
VALUES ('$name', '$comment', '$newsId')"
;

return $this->_db->query($sql);
}

public function addNews($title, $content)
{
$title = sqlite_escape_string($title);
$content = sqlite_escape_string($content);

$sql = "INSERT
INTO   news (title, content)
VALUES ('$title', '$content')"
;

return $this->_db->query($sql);
}

public function approveNews($ids)
{
foreach (
$ids as $id) {
$id = sqlite_escape_string($id);

$sql = "UPDATE news
SET    approval = 'T'
WHERE  id = '$id'"
;

if (!$this->_db->query($sql)) {
return
FALSE;
}
}

return TRUE;
}

public function getComments($newsId)
{
$newsId = sqlite_escape_string($newsId);

$sql = "SELECT name, comment
FROM   comments
WHERE  newsId = '$newsId'"
;

if ($result = $this->_db->query($sql)) {
return
$result->fetchAll();
}

return FALSE;
}

public function getNews($id = 'ALL')
{
$id = sqlite_escape_string($id);

switch ($id) {
case
'ALL':
$sql = "SELECT id,
title
FROM   news
WHERE  approval = 'T'"
;
break;
case
'NEW':
$sql = "SELECT *
FROM   news
WHERE  approval != 'T'"
;
break;
default:
$sql = "SELECT *
FROM   news
WHERE  id = '$id'"
;
break;
}

if ($result = $this->_db->query($sql)) {
if (
$result->numRows() != 1) {
return
$result->fetchAll();
} else {
return
$result->fetch();
}
}

return FALSE;
}
}

?>

(你可以用自己的解决方案随意替换这个类。这里只是为你提供一个完整示例的介绍,并非建议要这么实现。)

这个类的构造器需要SQLite数据库的完整路径和文件名,你必须自己进行创建。

<?php

$db = new SQLiteDatabase('/path/to/db.sqlite');

$db->query("CREATE TABLE news (
id       INTEGER PRIMARY KEY,
title    VARCHAR(255),
content  TEXT,
approval CHAR(1) DEFAULT 'F'
)"
);

$db->query("CREATE TABLE comments (
id       INTEGER PRIMARY KEY,
name     VARCHAR(255),
comment  TEXT,
newsId   INTEGER
)"
);

?>

你只需要做一次,以后直接给出Database类构造器的完整路径和文件名即可:

<?php

$db = new Database('/path/to/db.sqlite');

?>

整合

为了进行整合,在lib目录下创建Database.phploadClass()就可以找到它。你的index.php文件现在就会初始化$view$db并存储到寄存器。你也可以创建__autoload()函数自动加载你所需要的类:

<?php

include 'Zend.php';

function __autoload($class)
{
Zend::loadClass($class);
}

$db = new Database('/path/to/db.sqlite');
Zend::register('db', $db);

$view = new Zend_View;
$view->setScriptPath('/path/to/views');
Zend::register('view', $view);

$controller = Zend_Controller_Front::getInstance()
->
setControllerDirectory('/path/to/controllers')
->
dispatch();

?>

接下来,在views目录创建一些简单的模板。index.php可以用来显示index视图:

<html>
<head>
<title>News</title>
</head>
<body>
<h1>News</h1>
<?php foreach ($this->news as $entry) { ?>
<p>
<a href="/view/<?php echo $this->escape($entry['id']); ?>">
<?php echo $this->escape($entry['title']); ?>
</a>
</p>
<?php } ?>
<h1>Add News</h1>
<form action="/add/news" method="POST">
<p>Title:<br /><input type="text" name="title" /></p>
<p>Content:<br /><textarea name="content"></textarea></p>
<p><input type="submit" value="Add News" /></p>
</form>
</body>
</html>

view.php模板可以用来显示选定的新闻条目:

<html>
<head>
<title>
<?php echo $this->escape($this->news['title']); ?>
</title>
</head>
<body>
<h1>
<?php echo $this->escape($this->news['title']); ?>
</h1>
<p>
<?php echo $this->escape($this->news['content']); ?>
</p>
<h1>Comments</h1>
<?php foreach ($this->comments as $comment) { ?>
<p>
<?php echo $this->escape($comment['name']); ?> writes:
</p>
<blockquote>
<?php echo $this->escape($comment['comment']); ?>
</blockquote>
<?php } ?>
<h1>Add a Comment</h1>
<form action="/add/comment" method="POST">
<input type="hidden" name="newsId"
value="<?php echo $this->escape($this->id); ?>" />
<p>Name:<br /><input type="text" name="name" /></p>
<p>Comment:<br /><textarea name="comment"></textarea></p>
<p><input type="submit" value="Add Comment" /></p>
</form>
</body>
</html>

最后,admin.php模板可以用来批准新闻条目:

<html>
<head>
<title>News Admin</title>
</head>
<body>
<form action="/admin/approve" method="POST">
<?php foreach ($this->news as $entry) { ?>
<p>
<input type="checkbox" name="ids[]"
value="<?php echo $this->escape($entry['id']); ?>" />
<?php echo $this->escape($entry['title']); ?>
<?php echo $this->escape($entry['content']); ?>
</p>
<?php } ?>
<p>
Password:<br /><input type="password" name="password" />
</p>
<p><input type="submit" value="Approve" /></p>
</form>
</body>
</html>
提示:为了保持简单,这个表单用密码作为验证机制。

使用到模板的地方,你只需要把注释替换成几行代码。如IndexController.php就变成下面这样:

<?php

class IndexController extends Zend_Controller_Action
{
public function
indexAction()
{
/* List the news. */
$db = Zend::registry('db');
$view = Zend::registry('view');
$view->news = $db->getNews();
echo
$view->render('index.php');
}

public function noRouteAction()
{
$this->_redirect('/');
}
}

?>

因为条理比较清楚,这个程序首页的整个业务逻辑只有四行代码。AddController.php更复杂一点,它需要更多的代码:

<?php

class AddController extends Zend_Controller_Action
{
function
indexAction()
{
$this->_redirect('/');
}

function commentAction()
{
/* Add a comment. */
$filterPost = new Zend_InputFilter($_POST);
$db = Zend::registry('db');
$name = $filterPost->getAlpha('name');
$comment = $filterPost->noTags('comment');
$newsId = $filterPost->getDigits('newsId');
$db->addComment($name, $comment, $newsId);
$this->_redirect("/view/$newsId");
}

function newsAction()
{
/* Add news. */
$filterPost = new Zend_InputFilter($_POST);
$db = Zend::registry('db');
$title = $filterPost->noTags('title');
$content = $filterPost->noTags('content');
$db->addNews($title, $content);
$this->_redirect('/');
}

function __call($action, $arguments)
{
$this->_redirect('/');
}
}

?>

因为用户在提交表单后被重定向,这个controller不需要视图。

AdminController.php,你要处理显示管理界面和批准新闻两个action:

<?php

class AdminController extends Zend_Controller_Action
{
function
indexAction()
{
/* Display admin interface. */
$db = Zend::registry('db');
$view = Zend::registry('view');
$view->news = $db->getNews('NEW');
echo
$view->render('admin.php');
}

function approveAction()
{
/* Approve news. */
$filterPost = new Zend_InputFilter($_POST);
$db = Zend::registry('db');
if (
$filterPost->getRaw('password') == 'mypass') {
$db->approveNews($filterPost->getRaw('ids'));
$this->_redirect('/');
} else {
echo
'The password is incorrect.';
}
}

function __call($action, $arguments)
{
$this->_redirect('/');
}
}

?>

最后是ViewController.php

<?php

class ViewController extends Zend_Controller_Action
{
function
indexAction()
{
$this->_redirect('/');
}

function __call($id, $arguments)
{
/* Display news and comments for $id. */
$id = Zend_Filter::getDigits($id);
$db = Zend::registry('db');
$view = Zend::registry('view');
$view->news = $db->getNews($id);
$view->comments = $db->getComments($id);
$view->id = $id;
echo
$view->render('view.php');
}
}

?>

虽然很简单,但我们还是提供了一个功能较全的新闻和评论程序。最好的地方是由于有较好的设计,增加功能变得很简单。而且随着Zend Framework越来越成熟,只会变得更好。

Zend_Db_Table Relationships 关联查询实战(一)

// December 19th, 2009 // No Comments » // PHP知识累计, Zend Framework 累积

我们通过四个数据表articles(文章表),categories(分类表),tags(标签表),articles_tags(文章_标签对应表)
来学习使用Zend_Db_Table Relationships关联查询
以下是创建这四个表的SQL语句:

复制内容到剪贴板

PHP代码:

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

--

-- 表的结构 `articles`

--

CREATE TABLE IF NOT EXISTS `articles` (

`id` int(10) unsigned NOT NULL auto_increment,

`cat_id` tinyint(3) unsigned NOT NULL default '1',

`title` varchar(100) NOT NULL,

PRIMARY KEY (`id`),

KEY `title` (`title`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

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

--

-- 表的结构 `articles_tags`

--

CREATE TABLE IF NOT EXISTS `articles_tags` (

`article_id` int(10) unsigned NOT NULL,

`tag_id` int(10) unsigned NOT NULL,

PRIMARY KEY (`article_id`,`tag_id`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8;

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

--

-- 表的结构 `categories`

--

CREATE TABLE IF NOT EXISTS `categories` (

`cat_id` tinyint(3) unsigned NOT NULL auto_increment,

`parent_id` tinyint(3) unsigned NOT NULL default '0',

`name` varchar(50) NOT NULL,

PRIMARY KEY (`cat_id`),

KEY `name` (`name`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

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

--

-- 表的结构 `tags`

--

CREATE TABLE IF NOT EXISTS `tags` (

`tag_id` int(10) unsigned NOT NULL auto_increment,

`name` varchar(100) NOT NULL,

PRIMARY KEY (`tag_id`),

KEY `name` (`name`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

同时我们在文章表和分类表中插入一些测试数据

复制内容到剪贴板

PHP代码:

INSERT INTO `categories` (`cat_id`, `parent_id`, `name`) VALUES

(1, 0, '分类一'),

(2, 0, '分类二');

INSERT INTO `articles` (`id`, `cat_id`, `title`) VALUES

(1, 1, '这是一篇属于分类一的文章'),

(2, 1, '这是一篇属于分类一的文章'),

(3, 2, '这是一篇属于分类二的文章'),

(4, 2, '这是一篇属于分类二的文章');

下面分别定义文章模型文件(Article.php)和分类模型文件(Category.php):
models/Article.php

复制内容到剪贴板

PHP代码:

class Article extends Zend_Db_Table {

protected $_name = 'articles';

protected $_primary = 'id';

}

models/Category.php

复制内容到剪贴板

PHP代码:

class Category extends Zend_Db_Table {

protected $_name = 'categories';

protected $_primary = 'cat_id';

}

我们通过Zend_Db_Table的$_referenceMap属性来定义数据表的关联关系
models/Article.php

复制内容到剪贴板

PHP代码:

class Article extends Zend_Db_Table {

protected $_name = 'articles';

protected $_primary = 'id';

protected $_referenceMap = array(

'category' => array( //关联名称

'columns' => 'cat_id',

'refTableClass' => 'Category',

'refColumns' => 'cat_id',

),

);

}

$_referenceMap 的相关属性说明:
columns:指定当前数据表通过那个字段和外表关联,一般指外键(PK)
refTableClass:关联表所对应的类名称
refColumns:关联表使用那个字段和其它表关联,一般指主键(FK)

现在我们已经将文章数据表和分类数据表关联起来了,下面我们看来如何在查询某一篇文章的同时查询出相关的分类信息,
修改modles/Article.php,添加下面方法:

复制内容到剪贴板

PHP代码:

public function getById($id)

{

$where = $this -> select() -> where('id = ?', $id);

$article = $this -> fetchRow($where);

$select = $this-> select() -> from('categories', array('name'));

$category = $article -> findParentRow('Category', null, $select) -> toArray();

$row = $article -> toArray();

$row['category'] = $category;

return $row;

}

然后在任意controller里,比如IndexController.php里

复制内容到剪贴板

PHP代码:

class IndexController extends Zend_Controller_Action {

public function init()

{

}

public function indexAction()

{

$modelArticle = new Article();

$article = $modelArticle -> getById(3);

Zend_Debug::dump($article);

}

}

运行程序结果复制内容到剪贴板

PHP代码:

array(4) {

["id"] => string(1) "3"

["cat_id"] => string(1) "2"

["title"] => string(24) "这是一篇属于分类二的文章"

["category"] => array(1) {

["name"] => string(6) "分类二"

}

}