如何编写一个驱动行为
概述
当您碰巧为Propel对象模型的两个类编写了两次相同的方法时,是时候考虑一下了行为.行为提供了一种简单的方法,通过改变现有的方法或添加新的方法,以同样的方式扩展几个模型类。使用已有的行为非常简单:阅读本书第8章的相关部分,并遵循手册中所写的说明自述
与每个行为捆绑的文件。但是如果您想创建一个新的行为,您需要了解它们是如何工作的。
基地的例子
为了说明创建行为的过程,让我们从一个已经扩展的模型开始。例如,假设出于安全原因,数据库的记录文章
表不能从数据库中删除。的文章- > delete ()
方法仍然必须标记记录,以便它们不会被调用返回ArticlePeer: doSelect ()
,但底层数据不能被擦除。这就是你如何扩展Propel模型来实现这条规则:
// lib/model/Article.php类文章扩展BaseArticle(){公共函数删除(反对美元=零){这个美元->setDeletedAt(时间());这个美元->保存(反对美元);}}// lib/model/ArticlePeer.php类ArticlePeer扩展BaseArticlePeer(){公共函数doSelectStmt(标准美元标准,反对美元=零){美元标准->添加(自我::DELETED_AT,零标准::ISNULL);返回父::doSelectStmt(美元标准,反对美元);}}
当然,这意味着添加一个新的时间戳字段deleted_at
到文章
表格
请注意
方法扩展适用于的原因doSelectStmt ()
而不是doSelect ()
是不是因为前者不是只用来的doSelect ()
,但也由doCount ()
.
一个新的领域和改变的方法的结合给文章
反对“偏执”行为。目前,“行为”一词只是指一组方法。
进入混合
的已删除记录评论
表格而不是复制上面的两个方法评论
而且CommentPeer
类,这不是D.R.Y.时,应该重构在新类中使用过不止一次的代码,并通过mixin系统。您应该熟悉mixin的概念和sfMixer
类要理解以下内容,请参考第十七章symfonyob娱乐下载 1.0的书,如果你想知道这是关于什么的。
第一步是从模型类中删除任何代码,并添加钩子以允许对它们进行扩展。
//步骤1// lib/model/Article.php类文章扩展BaseArticle(){公共函数删除(反对美元=零){foreach(sfMixer::getCallables(的文章:删除:前)作为美元可调用的){美元受潮湿腐烂=call_user_func(美元可调用的,这个美元,反对美元);如果(美元受潮湿腐烂){返回;}}返回父::删除(反对美元);}// lib/model/ArticlePeer.php类ArticlePeer扩展BaseArticlePeer(){公共函数doSelectStmt(标准美元标准,反对美元=零){foreach(sfMixer::getCallables(“ArticlePeer: doSelectStmt: doSelectStmt”)作为美元可调用的){call_user_func(美元可调用的,“ArticlePeer”,美元标准,反对美元);}返回父::doSelectStmt(美元标准,反对美元);}}// lib/model/Comment.php类评论扩展BaseComment(){公共函数删除(反对美元=零){foreach(sfMixer::getCallables(评论:删除前的)作为美元可调用的){美元受潮湿腐烂=call_user_func(美元可调用的,这个美元,反对美元);如果(美元受潮湿腐烂){返回;}}返回父::删除(反对美元);}// lib/model/CommentPeer.php类CommentPeer扩展BaseCommentPeer(){公共函数doSelectStmt(标准美元标准,反对美元=零){foreach(sfMixer::getCallables(“CommentPeer: doSelectStmt: doSelectStmt”)作为美元可调用的){call_user_func(美元可调用的,“CommentPeer”,美元标准,反对美元);}返回父::doSelectStmt(美元标准,反对美元);}}
接下来,你必须把行为代码放在一个新类中,将这个类保存在一个可以自动加载的目录中:
//步骤2//在lib/ParanoidBehavior.php中类ParanoidBehavior{公共函数preDelete(美元的对象,反对美元){美元的对象->setDeletedAt(时间());美元的对象->保存(反对美元);返回真正的;}公共函数doSelectStmt(美元的类,标准美元标准,反对美元=零){美元标准->添加(常数(“美元class:: DELETED_AT”),零标准::ISNULL);}}
最后,必须注册new的方法ParanoidBehavior
类的钩子上文章
而且评论
类:
//步骤3//在config/config.phpsfMixer::注册(的文章:删除:前,数组(“ParanoidBehavior”,“preDelete”));sfMixer::注册(“ArticlePeer: doSelectStmt: doSelectStmt”,数组(“ParanoidBehavior”,“doSelectStmt”));sfMixer::注册(评论:删除前的,数组(“ParanoidBehavior”,“preDelete”));sfMixer::注册(“CommentPeer: doSelectStmt: doSelectStmt”,数组(“ParanoidBehavior”,“doSelectStmt”));
通过Mixins的强大功能,行为代码可以跨多个模型对象重用。
但是将钩子添加到模型类和注册方法的任务使得这个过程比简单的代码复制要长……这就是交响乐的地方ob娱乐下载行为来帮帮忙吧。
自动添加模型钩子
ob娱乐下载Symfony可以自动向模型添加钩子。要启用这些钩子,请设置AddBehaviors
财产真正的
在propel.ini
文件,如下:
propel.builder.AddBehaviors =真正的//默认值为false
你需要重新构建模型,以便将钩子插入到生成的模型类中:
symfonob娱乐下载y推进-构建模型
钩子被添加到基地
类,在lib /模型/ om /
目录中。例如,这里是生成的BaseArticlePeer
启用行为钩子的类:
公共静态函数doSelectStmt(标准美元标准,反对美元=零){foreach(sfMixer::getCallables(“BaseArticlePeer: doSelectStmt: doSelectStmt”)作为美元可调用的){call_user_func(美元可调用的,“BaseArticlePeer”,美元标准,反对美元);}//其余的代码}
这与手工添加到定制中的钩子几乎完全相同ArticlePeer
在步骤1中。不同之处在于注册的钩子名是BaseArticlePeer: doSelectStmt: doSelectStmt
而不是ArticlePeer: doSelectStmt: doSelectStmt
.因此,您可以删除在步骤1中添加到自定义类中的代码。方法中启用行为时propel.ini
,您不再需要在模型类中手动添加钩子。
由于钩子的名称发生了变化(它们现在都以基地
),必须更改步骤3中偏执行为的方法的注册方式。但是在这样做之前,看看添加的钩子的完整列表:
/ /挂钩添加到基本对象类[名字]:删除:pre / /删除之前[名字]:删除:post / /删除后[名字]:保存:前/ /之前保存[名字]:保存:post / /保存后[名字]:[methodName] / /内部__call()(新方法允许)/ /挂钩添加到基本对等类[PeerClassName]: doSelectStmt: doSelectStmt [PeerClassName]: doSelectJoin: doSelectJoin [PeerClassName]: doSelectJoinAll: doSelectJoinAll [PeerClassName]: doSelectJoinAllExcept: doSelectJoinAllExcept[PeerClassName]:doUpdate:pre [PeerClassName]:doUpdate:post [PeerClassName]:doInsert:pre [PeerClassName]:doInsert:post [PeerClassName]:doCount:doCount
请注意
从symfoob娱乐下载ny 1.0开始,只有一个钩子与doSelect
方法,而不是上面描述的四个钩子。这解释了为什么一些行为只能在symfony 1.1中工作,而不能在symfony 1.0中工作,ob娱乐下载因为symfony 1.0对行为的支持是不完整的。
添加新方法
应该仔细看看其中一个钩子:允许在对象类中使用新方法的钩子。当行为被启用时propel.ini
,所有生成的基对象类都包含一个__call ()
与此类似的方法:
//在lib/model/om/BaseArticle.php公共函数__call(美元的方法,美元的参数){如果(!美元可调用的= sfMixer::getCallable(“BaseArticle:”.美元的方法)){扔新sfException(sprintf(调用未定义方法BaseArticle::%s,美元的方法));}函数(美元的参数,这个美元);返回中的call_user_func_array(美元可调用的,美元的参数);}
如在第十七章交响乐的书,一ob娱乐下载个钩子放在一个__call ()
可以在运行时添加新方法。例如,如果你想添加一个恢复删除()
方法。文章
类来允许重置deleted_at
标志,首先将它添加到Behavior类中:
//在lib/ParanoidBehavior.php中公共函数恢复删除(美元的对象,反对美元){美元的对象->setDeletedAt(零);美元的对象->保存(反对美元);}
然后,按如下方式注册新方法:
//在config/config.phpsfMixer::注册(“BaseArticle:恢复”,数组(“ParanoidBehavior”,“恢复”));//其他钩子
现在,每一个呼叫文章- >恢复删除()
会打个电话给ParanoidBehavior::恢复(美元)
.
请注意
不幸的是,从PHP 5开始,静态方法调用不能被捕获__call ()
.这意味着symfony行为不能向ob娱乐下载Peer类添加新方法。
在一个步骤中注册钩子
方法使用时,仍然需要重写其他钩子注册基地
钩子名,都用于文章
而且评论
类。这将给出如下内容:
//步骤3//在config/config.phpsfMixer::注册(“BaseArticle:恢复”,数组(“ParanoidBehavior”,“恢复”));sfMixer::注册(“BaseArticle:删除:pre”,数组(“ParanoidBehavior”,“preDelete”));sfMixer::注册(“BaseArticlePeer: doSelectStmt: doSelectStmt”,数组(“ParanoidBehavior”,“doSelectStmt”));sfMixer::注册(“BaseComment:恢复”,数组(“ParanoidBehavior”,“恢复”));sfMixer::注册(“BaseComment:删除:pre”,数组(“ParanoidBehavior”,“preDelete”));sfMixer::注册(“BaseCommentPeer: doSelectStmt: doSelectStmt”,数组(“ParanoidBehavior”,“doSelectStmt”));
但是这段代码不是很D.R.Y,因为您需要为每个类重复整个方法列表。想象一下,如果行为提供了几十种方法,那该有多痛苦!如果你能把注册过程分成两个阶段,会更有效率:
- 将行为的方法注册到类未知钩子列表中。
- 对于每个类,将与类无关的钩子转换为真正的钩子,并使用mixins系统注册它们。
ob娱乐下载Symfony提供了一个实用程序类sfPropelBehavior
,它可以帮你完成这项工作。下面是如何重写步骤3来利用这个类:
//阶段1//在config/config.phpsfPropelBehavior::registerMethods(“偏执”,数组(数组(“ParanoidBehavior”,“恢复”)));sfPropelBehavior::registerHooks(“偏执”,数组(”:删除:前= >数组(“ParanoidBehavior”,“preDelete”),“同行:doSelectStmt: doSelectStmt”= >数组(“ParanoidBehavior”,“doSelectStmt”)));//第二阶段// lib/model/Article.phpsfPropelBehavior::添加(“文章”,数组(“偏执”));// lib/model/Comment.phpsfPropelBehavior::添加(“评论”,数组(“偏执”));
这两个registerMethods
而且registerHooks
方法期望钩子列表名称作为第一个参数。当行为方法被添加到模型类中时,这个名称将被用作快捷方式。注意调用时如何使用钩子名registerHooks
不要包含任何对特定模型类的引用BaseArticle
部分钩子名称被删除)。
此外,您不需要为通过的方式添加的方法指定方法名registerMethods
.默认情况下使用行为类中的方法名称。
只有当sfPropelBehavior: add ()
语句执行时,钩子就真的针对对象注册sfMixer
类……用一个真正的钩子名。由于此调用的第一个参数是模型类名,因此sfPropelBehavior
是否所有元素都可以重新创建完整的钩子名称(在本例中,通过连接字符串基地
使用模型类名和行为钩子名)。
将行为打包到插件中
要将行为打包成真正可重用的代码段,最好的方法是创建一个插件。
有一个关于行为插件名称的非书面约定。它们必须以“Propel”作为前缀,因为它们只适用于这个ORM,并且必须以“BehaviorPlugin”结束。所以我们的偏执行为的一个好名字可以是“myPropelParanoidBehaviorPlugin”。
到目前为止,插件中只有两个文件:ParanoidBehavior
类,以及其中编写的代码配置/ config。
来注册行为方法和钩子。第十七章解释如何在插件树结构中组织这些文件:
plugins/ myPropelParanoidBehaviorPlugin/ lib/ ParanoidBehavior.php //包含方法的类被混合在config/ config.php //行为方法的注册
的config。
在项目中安装的每个插件文件都会在每次请求时执行,所以这是注册行为方法的完美地方。
要完成该插件,必须添加一个自述
插件目录的根文件,包含安装和使用说明。最好的行为也捆绑单元测试。
最后,添加一个package.xml
(手动或通过sfPackageMakerPlugin),用PEAR打包插件,然后就可以重用它了。你也可以把它贴在symfony网站上。ob娱乐下载
将参数传递给行为
设计良好的行为不依赖于硬编码的值。在上面的偏执行为的例子中,deleted_at
列名是硬编码的,应该转换为参数。
要将参数传递给行为,请使用关联数组作为调用的第二个参数sfPropelBehavior: add ()
而不是常规数组,如下所示:
sfPropelBehavior::添加(“文章”,数组(“偏执”= >数组(“列”= >“deleted_at”)));
然后,要在行为类中获取此参数的值,必须使用sfConfig
注册表。参数存储在sfConfig
琴键是这样组成的:
“propel_behavior_”.[BehaviorName].“_”.[类名称].“_”.[ParameterName]//在上面的例子中,通过调用deleted_at来获取'deleted_at'值sfConfig::得到(“propel_behavior_paranoid_Article_column”)
问题在于行为方法不仅仅使用列名。他们使用这些名称的各种版本根据操作来实现:
格式名称| |用于示例 ----------------------------|---------------|----------- ` BasePeer: TYPE_FIELDNAME”|“deleted_at”|模式。yml ' BasePeer::TYPE_PHPNAME ' | ' DeletedAt ' |方法名称' BasePeer::TYPE_COLNAME ' | ' DELETED_AT ' |标准参数
因此,行为类需要一种将字段名从一种格式转换为另一种格式的方法。幸运的是,每个模型生成的Base Peer类都提供了一个静态的translateFieldName ()
方法。它的语法非常简单:
// translateFieldName($name, $origin_format, $dest_format)//实例美元的名字= ArticlePeer::translateFieldName(“deleted_at”, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME);
现在可以重写ParanoidBehavior
类。列
考虑参数:
类sfPropelParanoidBehavior{公共函数preDelete(美元的对象,反对美元=零){美元的类=get_class(美元的对象);peerClass美元=get_class(美元的对象->getPeer());columnName美元= sfConfig::得到(“propel_behavior_paranoid_”.美元的类.“_column”,“deleted_at”);美元的方法=“设置”.call_user_func(数组(peerClass美元,“translateFieldName”),columnName美元, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_PHPNAME);美元的对象->美元的方法(时间());美元的对象->保存();返回真正的;}公共函数doSelectStmt(美元的类,美元标准,反对美元=零){columnName美元= sfConfig::得到(“propel_behavior_paranoid_”.美元的类.“_column”,“deleted_at”);美元标准->添加(call_user_func(数组(美元的类,“translateFieldName”),columnName美元, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME),零标准::ISNULL);}}
结论
Propel Behaviors不过是一组预定义的钩子,以及一个helper类,旨在促进在一条语句中注册多个钩子。如果您理解Mixins,那么编写自己的行为应该不会太难。确保你检查了现有的行为插件在开始自己的之前:它们是行为语法的实际示例。
本作品采用创作共用署名-非商业性-禁止派生作品3.0未移植许可协议授权。