轻松、安全地调度由框架创建的任务
简介: 短短几年前,对 PHP 早期版本的常见批评是不支持 MVC 样式的架构。现在,开发人员可以在许多 PHP 框架中做出选择。“PHP 框架” 系列将介绍三个广泛使用的 PHP 框架 —— Zend、symfony 和 CakePHP —— 通过在三个框架中构建和扩展样例应用程序来检验这三个框架的类似之处和不同之处。在本文中,您将集成外部任务,创建可通过调度程序 cron 调用的简单任务。
本系列专门针对那些想要开始使用框架、但又没有机会详细检验可用框架的 PHP 开发人员。在学完本系列后,您将了解选择这三个框架的原因、如何安装每个框架,并且充分运用将在三个框架中扩展的测试应用程序。听起来要学习的内容很多,但是不必担心。内容虽然多,但是我们已经把内容细分为多个便于管理的部分。
本系列的 第 1 部分 将列出本系列涵盖的内容,介绍将进行考察的框架以及说明如何安装,并查看将构建的第一个测试应用程序。
第 2 部分 将指导您在三个框架中构建样例应用程序,着重说明了它们的类似之处和不同之处。
第 3 部分 从扩展测试应用程序开始,然后处理不符合一般规律的例外情况。所有框架都能很好地完成份内的任务。每个项目中都需要完成一些框架设定工作之外的事情。第 3 部分将介绍那些情况。
第 4 部分 主要介绍了 Ajax 支持。使用本机代码和第三方库检验了 Ajax 的使用 —— 特别介绍了每个框架如何运行及接受具体的常用库。
第 5 部分将处理如何在框架外部工作。设定一项任务(每晚更新脚本),并在每个框架中检验完成此项任务的过程。
当应用程序与数据库进行交互时,您一般都会发现需要设置一些自动执行的任务。通常,这些任务都面向基本的数据库管理,例如表删除、终止帐户等。根据以往的经验,一般都是用 Perl 编写这些任务。虽然 Perl 是一种优秀的语言,但是它把一些 PHP 没有的内容引入到表中,否则使用一种语言编写自动执行的任务和应用程序将更有意义。它更易于支持,您可以共享代码,并且在来回切换时不会混淆语法。
同样的道理,由于使用框架编写了 Blahg,因此,如果可以继续使用框架执行一般的自动执行任务将非常好。您可以使用已经建立的常用结构,也可以使用已经创建的方法,并且可以维护使用模型获得的数据库独立性,而不用编写特定数据库的数据库接口代码。如果要使用框架,那么全面使用它会十分有意义。
在本文中,您将看到如何为 Blahg 编写一个自动执行的任务,该任务将删除发布时间超过 30 天的所有 post。您将能够通过命令行调用这项任务,使您可以使用诸如 cron 之类的调度程序自动执行任务。您将在每个框架中构建应用程序,了解 Zend、symfony 和 CakePHP 各自如何处理自动执行的任务。
您应当已经阅读过第 1 部分至第 4 部分,这些部分介绍了安装、先决条件、初始应用程序构建和扩展,以及适用于每个框架的 Ajax。如果尚未阅读这几个部分,则应当立即阅读。
注:每篇文章都附带了包含在各篇文章中构建和讨论的所有代码的 ZIP 文件。本系列的各篇文章将此文件引用为 代码归档。
对于给定的框架结构,可以使用一种简便方法创建可以通过 cron 调用的自动执行任务:将任务作为现有控制器或者新控制器中的一项操作并使用 wget 调用该操作。这样做绝对有效,而且如果您不怎么注重执行任务的方法,也就是说不安全的方法,那么可以使用 wget。
但是应当注意这里所说的 “不安全” 一词。如果以这种方法编写自动执行的任务,那么这些任务可被外界使用。即使不以任何方式链接操作,使用名为 Mxyzptlk 的控制器,或者要求在执行前必须把 40 个字符的惟一字符串传递给操作,操作也仍然可用于执行。因此,隐藏式安全性(Security through obscurity)实际上根本不安全。由于许多自动执行的任务往往倾向于数据库密集型,因此用户不希望随机执行这些操作。
因此,虽然可以通过简单的方法实现自动执行任务,但是最好寻找一种更安全的方法。下面的每个示例将向您展示在每个框架中实现这一目标的一种方法。方法往往有很多种。在阅读完本文后,您可以亲自试验或者做一些其他研究。看看其他人是怎样做的。
另外,在继续之前请对表进行备份。您将要删除一些记录,而能够把表恢复到默认状态对于稍后的测试将十分有用。
序言到此为止。我们开始吧!
由于 Zend 框架挑剔的本性,因此使用框架创建自动执行的任务时允许灵活选择要使用的组件。由于不打算通过处理 Web 请求的现有前端控制器来访问 Web 应用程序,因此应当创建一个额外的控制器来控制脚本执行。该控制器看上去将类似前端控制器,因为您将注册自动装入器(autoloader)并为模型定义基本适配器。但是重要的差别可能在于 PHP 安装或配置。
与从 Web 服务器调用 PHP 相比,命令行 PHP 通常是使用不同的服务器应用程序编程接口(Server Application Programming Interface,SAPI)来执行的。根据安装和配置的不同,这可能意味着包含路径会有所不同。出于这个原因,您应当把 include_path ini
设置设为包括安装 Zend 框架文件的 /column/include 目录。
注意:不同操作系统中定义 include_path
的方法将会有所不同。下面的示例适用于 Linux®。
创建一个名为 /column/protected/zend/scripts 的新目录。此目录将用于保存脚本控制器和创建的其他与脚本相关的所有文件。由于这次将只创建一项任务,因此首先在新 scripts 目录中创建文件 prune.php。此文件的上半部分将包含脚本控制器的主要代码。
清单 1. 脚本控制器的代码
<?php ini_set('include_path', ini_get('include_path') . ':/column/include'); require_once("Zend/Loader.php"); Zend_Loader::registerAutoload(); $params = array( 'host' => 'localhost', 'username' => 'frameworks', 'password' => 'fwpw', 'dbname' => 'zend' ); $db = Zend_Db::factory('PDO_MYSQL', $params); Zend_Db_Table::setDefaultAdapter($db); |
此文件的其余部分将包含需要执行的简单任务。包括并实例化 posts 模型,创建 where
子句并运行标准的 delete 查询。
清单 2. 要执行的任务
require_once("/column/protected/zend/models/Posts.php"); $posts = new Posts(); $where = $posts->getAdapter()->quoteInto('modified < ?', date('Y-m-d H:i:s', strtotime('-30 days'))); $posts->delete($where); ?> |
这一切完成后,您可以在命令行中执行这个脚本:php /column/protected/zend/scripts/prune.php
。
如果可以从命令行调用脚本,则可以对脚本进行调度、批处理或 cron。要在午夜执行此脚本的 crontab
条目可能类似这段代码:00 00 * * * php /column/protected/zend/scripts/prune.php
。
在构建了脚本控制器后,您有许多选项可用于通过 Zend 框架执行脚本。您可以使用在应用程序的其余部分中使用的相同模型,在必要时创建复杂的脚本对象等等。
现在您已经很好地掌握了如何为 Zend 执行 cron 作业。接下来,让我们看看如何在 symfony 中执行相同操作。
当需要在 Zend 框架中执行命令行脚本时,您创建了可用于替代前端控制器的脚本控制器。您将对 symfony 执行类似的操作。
创建保存 symfony Blahg 脚本的目录 —— /column/protected/sf_column/apps/blahg/scripts/ —— 并创建一个名为 prune.php 的文件作为新脚本控制器。此文件的前半部分与您创建的 symfony 前端控制器看上去很相似。您将定义必要的常量并要求配置文件。本例的不同之处在于如何获得 symfony 实例。在前端控制器中,您将要求 symfony 查找控制器并发送请求。对于脚本控制器,却不是这样。
清单 3. 定义必要常量并要求配置文件
<?php define('SF_ROOT_DIR', '/column/protected/sf_column'); define('SF_APP', 'blahg'); define('SF_ENVIRONMENT', 'prod'); define('SF_DEBUG', false); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR .SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); sfContext::getInstance(); |
接下来,您将创建一个基本的 Criteria
来传递给 post 模型并运行 delete
。
清单 4. 创建基本的
Criteria
$c = new Criteria(); $c->add(PostPeer::MODIFIED, date("Y-m-d H:i:s", strtotime("-45 days")), Criteria::LESS_THAN); $posts = PostPeer::doDelete($c); |
保存文件。这就是与此有关的全部内容。现在可以在命令行中执行脚本,非常类似于您在 Zend 框架中所做的操作:php /column/protected/sf_column/apps/blahg/scripts/prune.php
。
从调度程序调用此任务就像为 Zend 框架执行的操作一样(当然,指定 symfony 脚本而不是 Zend 脚本)。对于 cron,它将类似这段代码:00 00 * * * php /column/protected/sf_column/apps/blahg/scripts/prune.php
。
最后,让我们看看如何在 CakePHP 中执行相同操作。
Cake 控制台是 CakePHP V1.2 中的新增功能,它将提供与 Cake 框架的命令行接口。要创建您自己的命令行任务,需要创建一个 shell。shell 看上去非常类似您已经创建的控制器。
首先在 /column/protected/cakephp/app/vendors/shells 目录中创建 prune.php 文件。这是名为 prune 的新 shell,它将删除发布日期超过 30 天的所有 post。定义一个新类 PruneShells
,该类将扩展 shell 类。由于要删除 post,因此 shell 将需要使用 post 模型,您可以使用 $uses
变量来指定。默认情况下,在告诉 Cake 执行 shell 并且不传递任何具体操作时,Cake 将查找一个名为 main
的方法,并且如果找到,则执行该方法。此时,空 shell 看上去将类似清单 5。
清单 5. 定义一个新
PruneShells
类
<?php class PruneShell extends Shell { var $uses = array('Post'); function main() { } } ?> |
现在只需要把代码添加到 main
方法中来删除发布超过 30 天的所有 post。
清单 6. 删除发布超过 30 天的 post
$conditions = array ( "Post.modified" => "< " . date("Y-m-d H:i:s", strtotime("-30 days")) ); $this->Post->deleteAll($conditions); |
要通过命令行执行此脚本,需要告诉 Cake 运行 prune shell。由于所有代码都在 main
方法中,因此它将默认执行。还应当把 app 目录位置告诉给 Cake。如果是从 app 目录运行命令则不必告知 app 目录位置,但是 cron 作业将不会从正确目录执行,除非您告诉它执行下列代码:/column/protected/cakephp/cake/console/cake prune -app /column/protected/cakephp/app/
。
将一项 cron 作业调度到午夜运行来执行此 shell 将类似下列代码:00 00 * * * /column/protected/cakephp/cake/console/cake prune -app /column/protected/cakephp/app/
。
注:如果把 /column/protected/cakephp/cake/console 目录添加到 PATH
中,则无需指定完整路径,这将使您可以更轻松地使用控制台。在批处理或 cron 作业中指定完整路径将帮助确保正确执行,而不管执行作业的是哪个用户。
现在可以通过命令行调用,您可以对脚本进行批处理、cron 或调度。
为应用程序编写自动执行的任务的优点非常明显。您应当能够在所选框架中编写这些自动执行的任务。通过使用框架编写任务,您将在代码中应用一致的方法并利用已有结构。
您在测试 prune 脚本时可能已经注意到,在删除 post 时,可能没有将与 post 关联的评论和等级解除关联。利用您已经掌握的关于框架的知识,修改 prune 脚本,以便在删除 post 时删除相关的评论和等级。尝试使用每个框架特有的方法与相关的表进行交互。
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
第 5 部分的样例代码 | os-php-fwk5-column_part5.zip | 32KB | HTTP |