symfony 的config文件分层管理机制

5月 17th, 2008

symfony 将程序分为很多层,symfony, app, module 是最主要的三层,因此这三层下面都有一个 config 文件夹,用来管理自己的层的 config 文件,比如 view.yml 的作用是配置 View 层的数据,包括 javascript, css, meta 等等,分层管理的好处是:可以在 app 层中方便的给每一个 module 设置配置信息,同时也可以在每一个 module 层的配置文件中定义自己的配置信息。

symfony 通过 /symfony/lib/config/sfLoader.class.php 文件中的 getConfigPaths 方法采集路径信息,比如在处理 view.yml 的时候,会采集到三个 view.yml :

Array
(
    
[0] => /symfony/data/config/view.yml
    
[1] => /apps/app_name/config/view.yml
    
[2] => /apps/app_name/modules/module_name/config/view.yml
)

在 /symfony/lib/config/sfConfigCache.class.php 文件的 callHandler 方法的作为入口,访问 /symfony/lib/config/sfViewConfigHandler.class.php 文件的 execute() 方法,这是入口的代码:

$data = $handlerToCall->execute($configs);

在 execute() 方法中,调用 mergeConfig() 来合并这三个 view.yml:

$this->mergeConfig($this->parseYamls($configFiles));

合并的时候使用了 sfToolkit::arrayDeepMerge 方法,这个方法和php内置的 array_merge_recursive() 函数的区别是:sfToolkit::arrayDeepMerge,在主键重复的时候,后一个覆盖前一个;array_merge_recursive(),函数则会用重置后一个主键的形式强行合并两个数组。

所以 mergeConfig() 方法,先使用 array_merge() 函数合并 js, css 部分,然后使用 sfToolkit::arrayDeepMerge 方法合并的其他数据。

这个分层机制因为是在 cache 之前完成的,会生成一个数组文件在 cache 中,所以并不会对程序的效率产生影响,个人非常喜欢这样类似的设计,在整个 symfony 的很多配置文件中,都有使用。

symfony 的插件机制

5月 17th, 2008

symfony 的插件是有够神奇的,只要在命令行中使用下面的语句,插件就能自动安装:

symfony plugin-install http://plugins.symfony-project.com/sfGuardPlugin

然后在相关的 settings.yml 设置开启相关的插件,插件就可以使用了,非常的简单。

根据《symfony的命令行功能》,可以在 symfony/data/tasks/ 的 sfPakePlugins.php 文件中找到这个为 run_plugin_install() 的函数:

$cmd = PEAR_Command::factory($command, $config);
$ok   = $cmd->run($command, $opts, $params);

原来整个程序探测,下载,解压,安装的过程,全部转包给了 PEAR_Command 来实现(应该位于你PEAR目录的PEAR/Command/ 下),仅仅是将web部分,交给 _install_web_content() 函数做了自定义的处理。对 PEAR 了解有限,仅发现在插件被下载以后,有一个 package.xml 文件,将决定如何安装插件包中的内容到程序目录。

插件的调用也是由程序自动判定完成的,当用户输入一个特点的 module 和 action 的时候, symfony/lib/config/sfLoader.class.php 的 getControllerDirs 方法中的这些代码决定如何处理:

// 处理 app 中的 module
$dirs[sfConfig::get('sf_app_module_dir').'/'.$suffix] = false;
// 判断插件中是否有这个 module
if ($pluginDirs = glob(sfConfig::get('sf_plugins_dir').'/*/modules/'.$suffix))
{
 
$dirs = array_merge($dirs, array_combine($pluginDirs, array_fill(0, count($pluginDirs), true)));
}
// 添加核心程序中的 module
$dirs[sfConfig::get('sf_symfony_data_dir').'/modules/'.$suffix] = true;

这里对glob()函数的使用是插件调用机制的核心,非常有用的函数。

François Zaninotto 宣告离开 symfony 团队

5月 17th, 2008

昨天,François Zaninotto 在blog上面发表了告别的文章 《No one is irreplaceable》(小心防火墙),宣告了他将最终离开 symfony 的核心团队,François 的离开,不得不说是一件令人遗憾的事情,但是开源社区的特点,就是分裂,这是没有办法的事情……

促使 François 离开的原因,是 symfony 已经复杂到令他难以忍受撰写文档的痛苦,François 认为 symfony1.1需要的,是一个 API 手册,而不再是像 Askeet 24 tutorials 这样的“快乐”学习指南,François 最终选择了用行动捍卫自己的理念,所以,祝愿这位仁兄一路走好。

选择使用 symfony 而不是 zend framework 或者 cakephp 的原因,就是 François 带来的,详细得一塌糊涂的文档:《My First Project》《Askeet》《The Book》《The Cookbook》 当时看到这些文档的时候,就觉得眼前一亮,后来得知是这位 François 老兄,作为两位核心开发人员之一,居然是专职撰写 symfony 的文档,这让我很是新奇,也完全接受了这样的搭配,很多的开源项目,无论成熟与否,都有文档不健全的问题,比如说Python,Nginx等等,让新人很难学习,而相对于 symfony 的复杂程度来说,François 撰写的文档,真是一笔无比巨大的财富。

François 女儿的照片,可爱法国女孩呦:
girl.jpg

symfony的命令行功能

5月 7th, 2008

symfony的命令行解析非常有用,插件安装,代码生成,程序升级,甚至还有给插件留了的命令行接口。

命令行解析用了一个名为pake的包,位置在 \lib\symfony\vendor\pake,里面的 pakeGetopt.class.php 文件的 parse() 方法对命令行的参数进行解析:

// 处理 symfony init-module, cc, propel-build-sql 等
if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$this->long_options))
{
 
$this->arguments = array_merge($this->arguments, array($arg), $this->args);
}
// 处理  symfony --help 等
elseif (strlen($arg) > 1 && $arg{1} == '-')
{
 
$this->parse_long_option(substr($arg, 2));
}
// 处理 symfony -t 等
else
{
 
$this->parse_short_option(substr($arg, 1));
}

当执行 symfony cc 的时候,就会被定位到 \data\symfony\tasks\ 目录下 sfPakeMisc.php 文件中的run_clear_cache() 函数,同理,symfony init-module 会定位到 sfPakeGenerator.php 文件的 run_init_module() 函数。

命令行的功能:
clear-cache > 清除缓存
clear-controllers > 清除 web/ 目录下非 prod 环境运行的程序
disable > 在某运行环境下禁用某 app
downgrade > downgrade to a previous symfony release
enable > 在某运行环境下启用某 app
fix-perms > 修复目录权限,比如使用SVN的 checkout 的时候
freeze > freeze symfony libraries
init-app > 初始化一个 symfony 的 app
init-batch > 初始化一个 symfony 的 batch
init-controller > 初始化一个 symfony 的 controller
init-module > 初始化一个 symfony 的 module
init-project > 初始化一个 symfony 的 project
log-purge > 清除所有的日志文件
log-rotate > 清除某个环境或者 app 的日志文件
plugin-install > 安装插件
plugin-list > 显示插件列表
plugin-uninstall > 卸载一个插件
plugin-upgrade > 升级一个插件
promote-super-admin > 提升一个用户为超级管理员
propel-build-all > 建立 propel 模块,生成 sql 并将 sql 写入数据库
propel-build-all-load > 建立 propel 模块,生成 sql 并将 sql 和数据写入数据库
propel-build-db > 为当前模块建立数据库
propel-build-model > 为当前模块建立相关类
propel-build-schema > 将数据库结构转换成 schema.xml
propel-build-sql > 将 schema.xml 中的数据库结构转换为 sql
propel-convert-xml-schema > 将 schema.xml 转换为 schema.yml
propel-convert-yml-schema > 将 schema.yml 转换为 schema.xml
propel-dump-data > 将数据导出到 fixtures 目录
propel-generate-crud > 初始化一个 generate 类型的 propel CRUD 模块
propel-init-admin > 初始化一个 init 类型的 propel admin 模块
propel-init-crud > 初始化一个 init 类型的 propel CRUD 模块
propel-insert-sql > 将 schema.yml 生成的 sql 写入数据库
propel-load-data > 载入 fixtures 目录下的数据
sync > 远程同步 symfony 程序
test-all > 运行所有的测试程序
test-functional > 运行某个 app 的功能测试
test-unit > 运行单元测试
unfreeze > unfreeze symfony libraries
upgrade > 升级 symfony 程序
别名:
app = init-app
batch = init-batch
cc = clear-cache
controller = init-controller
module = pinit-module
new = init-project

symfony1.0的MVC

5月 7th, 2008

symfony1.0 的 controller 里面的过滤链(filter chain)比较有意思,位置在 \lib\symfony\filter,运行时会以栈的结构依次存放这些 filter :
sfRenderingFilter:取出时输出页面的header和html
sfWebDebugFilter:取出时添加调试信息给html
sfGuardBasicSecurityFilter:放入时检查用户权限相关信息
sfCommonFilter:取出时生成js和css的html
sfFlashFilter:放入时标记一个Flash,取出时销毁掉一个Flash
sfExecutionFilter:真正处理 M 和 V 的地方,也是栈的最上层

sfExecutionFilter 中的 M 部分在 execute 方法中:

// 在解析一个 action 前执行的 preExecute
$actionInstance->preExecute();
// 执行用户的 action
$viewName = $actionInstance->execute();
if ($viewName == '')
{
 
// 定义一个默认的 view
 
$viewName = sfView::SUCCESS;
}
// 在解析一个 action 后执行的 postExecute
$actionInstance->postExecute();

execute()方法的代码在 \lib\symfony\action\sfActions.class.php 中:

// 组装一个 action
$actionToRun = 'execute'.ucfirst($this->getActionName());
// 运行一个 action
$ret = $this->$actionToRun();

sfExecutionFilter 中的 V:

// 确定使用哪个View,默认是使用的 \lib\symfony\view\sfPHPView.class.php
$viewInstance = $controller->getView($moduleName, $actionName, $viewName);
// 初始化,在这里读取的view.yml
$viewInstance->initialize($context, $moduleName, $actionName, $viewName);
// 执行 默认是一个空方法
$viewInstance->execute();
// 生成一个 view 中所有的html
$viewData = $viewInstance->render();

symfony的作者解释symfony1.1中的MVC:
《symfony 1.1 form framework and the MVC pattern》

对symfony作者的采访,关于文档的观点非常赞成,像我这样的非英语母语国家的人,非常有同感:
《Interview with Francois Zaninotto and Fabien Potencier, authors of 'The Definitive Guide to symfony'》