Archive for the ‘symfony’ category

Symfony mongodb logger

January 9th, 2010

Thanks for the elegant design of symfony framework, we can easily create a new logger by extending sfLogger.

/**
 * opMongoLogger logs messages in a mongo database.
 *
 * @author     Jayson Xu(http://www.doupie.com) 
*/
class opMongoLogger extends sfLogger
{
  protected
    $timeFormat = '%b %d %H:%M:%S';
  /**
   * Initializes this logger.
   *
   * Available options:
   *
   * - database:      a database name to store the cache in
   * - host:         the host of database
   * - port:         the host of port
   * - collection:   the host of collection
   *
   * @param  sfEventDispatcher $dispatcher  A sfEventDispatcher instance
   * @param  array             $options     An array of options.
   *
   * @return Boolean      true, if initialization completes successfully, otherwise false.
   */
  public function initialize(sfEventDispatcher $dispatcher, $options = array())
  {

    if (!class_exists('Mongo'))
    {
      throw new sfInitializationException(sprintf('the mongodb extension is not installed or enable, cannot use opMongoLogger.'));
    }
    parent::initialize($dispatcher, $options);

    if (!$this->hasOption('database'))
    {
      throw new sfInitializationException(sprintf('You must provide a database name to store the cache in'));
    }
    $this->type = $this->getOption('type','m');
    $this->database = $this->getOption('database');
    $this->host = $this->getOption('host','127.0.0.1');
    $this->port = $this->getOption('port','27017');
    $this->collection = $this->getOption('collection','logs');
    try
    {
      $this->handler = new mongo($this->host.":".$this->port);
      $this->coll_db = $this->handler->selectDB($this->database)->selectCollection($this->collection);
    }
    catch (Exception $e)
    {
      throw new sfInitializationException(sprintf('Could not connect to mongo database : ', $e->getMessage()));
    }

    if (isset($options['time_format']))
    {
      $this->timeFormat = $options['time_format'];
    }
    return true;
  }

  /**
   * Logs a message.
   *
   * @param string $message   Message
   * @param string $priority  Message priority
   */
  protected function doLog($message, $priority)
  {
    $log = array(
      'type'      => $this->type,
      'message'   => $message,
      'time'      => strftime($this->timeFormat),
      'priority'  => $this->getPriority($priority),
      );
    if(sfContext::getInstance())
    {
      $log['module'] = sfContext::getInstance()->getRequest()->getParameter('module');
      $log['action'] = sfContext::getInstance()->getRequest()->getParameter('action');
    }
    $this->coll_db->insert($log);
  }

  /**
   * Returns the priority string to use in log messages.
   *
   * @param  string $priority The priority constant
   *
   * @return string The priority to use in log messages
   */
  protected function getPriority($priority)
  {
    return sfLogger::getPriorityName($priority);
  }

  /**
   * Executes the shutdown method.
   */
  public function shutdown()
  {
    $this->handler->close();
  }

  public function hasOption($option)
  {
    return array_key_exists($option, $this->options);
  }

    /**
   * Gets an option value.
   *
   * @param string $name    The option name
   * @param mixed  $default The default value
   *
   * @return mixed The option value
   */
  public function getOption($name, $default = null)
  {
    return isset($this->options[$name]) ? $this->options[$name] : $default;
  }
}

in opMongoLogger, if the logger is called from action, it will log the module and action too.

More with symfony 1.3 & 1.4-提高你的生产力

January 6th, 2010

提高你的生产力

作者: Fabien Potencier 翻译:逗派

使用symfony本身是web开发人员提高生产力的好方法。当然,每个知道symfony的异常细节和web 调试工具条的人都可以很大程度的提高生产力。 本章将告诉你一些提示和技巧,通过使用一些新的或很少被人所知的功能来更高的提高你的生产力。

启动速度更快:自定义项目创建过程

通过symfony中CLI的工具,创建一个新的symfony的项目,是快速,简单:

$ php /path/to/symfony generate:project foo --orm=Doctrine

generate:project 任务为您生成新项目的默认目录结构,并创建设置合理的默认值的配置文件。然后,您可以使用symfony的任务创造应用系统,安装插件,配置你的模型等等。

但是,创建一个新项目的第一个步骤通常总是不尽相同:您创建一个主应用程序,安装一堆的插件,根据你的需求调整一些默认配置等等。

随着symfony 1.3构架,该项目创建过程可以自定义和自动化。

由于所有symfony的任务都是类,所以可以很容易定制和扩展他们。但是generate:project任务并不能很容易的自定义,因为当执行这个任务的时候,项目还没有存在。

generate:project 任务有一个--installer选项,它可以指定在创建项目的过程中执行一个php脚本 :

$ php /path/to/symfony generate:project --installer=/somewhere/my_installer.php

/somewhere/my_installer.php脚本在 sfGenerateProjectTask实例的环境下运行,所以可以通过使用 $this对象来使用该任务的方法。 以下各节描述所有你可以用来自定义您的项目创建过程的方法。

如果你在php.ini中开启了include()方法的url file-access功能, 你甚至可以可以把URL作为参数传给–installer(当然当你使用了一个你完全不知道内容的脚本你需要非常小心):

$ symfony generate:project
 --installer=http://example.com/sf_installer.php

installDir()

installDir() 方法映射一个目录结构(包括子目录和文件)到新建的项目中:

$this->installDir(dirname(__FILE__).'/skeleton');

runTask()

runTask() 方法执行一个symfony任务。 它需要一个任务名称,一个作为任务的参数和选项的字符串两个参数:

$this->runTask('configure:author', "'Fabien Potencier'");

参数和选项也可以使用数组传递:

$this->runTask('configure:author', array('author' => 'Fabien Potencier'));

正如期望的,symfony任务的简写也可以使用:

$this->runTask('cc');

这个方法当然可以用来安装插件:

$this->runTask('plugin:install', 'sfDoctrineGuardPlugin');

为了安装特定版本的插件,只需要传递必要的选项就可以了:

$this->runTask('plugin:install', 'sfDoctrineGuardPlugin', array('release' => '10.0.0', 'stability' => beta'));

为了执行一个新安装的插件里的任务,首先需要重新加载任务:

$this->reloadTasks();

如果你创建了一个新的应用程序,想使用一个依赖特定应用程序的任务,像generate:module,你一定要自己手动变换配置环境:

$this->setConfiguration($this->createConfiguration('frontend', 'dev'));

日志

在安装脚本运行时为了给开发者回馈信息,您可以将一些东西很容易的写入日志中:

// 一个简单日志
$this->log('some installation message');

// 记录一个日志块
$this->logBlock('Fabien\'s Crazy Installer', 'ERROR_LARGE');

// 记录一个日志模块
$this->logSection('install', 'install some crazy files');

用户交互

askConfirmation(), askAndValidate()ask() 方法允许你问问题,使得你的安装过程可以动态配置。

如果你只需要确认,请使用 askConfirmation() 方法:

if (!$this->askConfirmation('Are you sure you want to run this crazy installer?'))
{
  $this->logSection('install', 'You made the right choice!');

  return;
}

通过使用ask()方法你也可以问任何问题,并获得一个字符串代表的用户答案:

$secret = $this->ask('Give a unique string for the CSRF secret:');

如果你想验证答案,使用askAndValidate() 方法:

$validator = new sfValidatorEmail(array(), array('invalid' => 'hmmm, it does not look like an email!'));
$email = $this->askAndValidate('Please, give me your email:', $validator);

文件系统操作

如果你想改变文件系统,你可以通过symfony的文件系统对象:

$this->getFilesystem()->...();

Sandbox应用的创建过程

symfony sandbox是一个预先打包的symfony项目,包括一个现成的应用程序和预先设定的SQLite数据库。任何人可以通过使用安装脚本创建一个sandbox :

$ php symfony generate:project --installer=/path/to/symfony/data/bin/sandbox_installer.php

symfony/data/bin/sandbox_installer.php 脚本是一个安装脚本的实际例子,值得一读。

安装脚本只是一个php脚本文件。所以,你可以做任何你想做的事情 。不象创建一个新项目时一而在再而三执行相同的任务,您可以创建自己的安装程序脚本,调整成您想要的symfony项目安装方式。 通过安装程序创建一个新的项目要快得多,并防止丢失步骤。您甚至可以与他人分享您的安装脚本!

第六章 , 我们将使用一个自定义的安装脚本。 你可以附件 B中找到源代码。

更快的开发

从PHP代码到CLI任务,编程意味着无尽的敲打键盘。让我们看看如何把输入降低至最低限度。

选择你的IDE

使用IDE能够帮助开发者在从多种的途径提高效率。

首先,绝大多数的现代化IDE提供PHP的自动完成功能。这意味着你只要打一个方法名字的开始几个字母。这也意味这即使你不记得方法的名字,你也不用去查询API文档,因为IDE会提示当前对象的所有可用方法。

此外,有的像PHPEdit或NetBeans的IDE,了解了更多的Symfony,能够对symfony的项目提供更具体的集成。

文本编辑器

有些用户更喜欢文本编辑器来编写代码,主要原因是文本编辑器比IDE快很多。 当然,文本编辑器没有提供IDE那么多的功能。但是绝大多数流行的编辑器,也提供插件或扩展用于增强用户体验,使得编辑PHP和symfony项目更加的有效。

比如,很多Linux用户倾向于使用VIM来工作。对于这些开发者,可以使用vim-symfony 扩展。 VIM-symfony 是一系列VIM脚本把symony集成到你喜爱的编辑器中。使用vim-symfony, 你可以容易的创建vim宏和命令来简化你的symfony开发。它还捆绑了默认的命令集,许多配置文件(模式,路由等)也是触手可及,使您可以轻松地从action切换到template。

有些MacOS X用户喜欢使用TextMate。这些开发者可以安装 symfony bundle, 它添加了很多日常开发中省时的宏和快捷方式。

使用支持symfony的IDE

有些IDE,比如 PHPEdit 3.4NetBeans 6.8, 本身支持symfony,所以给框架提供了精细粒度的集成。看看它们的文档,了解他们的symfony中的具体支持,以及如何可以帮助你更快地发展。

帮助IDE

在IDE自动完成PHP的只能是明确的,在PHP代码中定义的方法。但是如果你的代码使用了 __call()__get() 魔法方法,IDE就没有办法猜测可用的方法或属性。好消息是,你可以在PHPDoc块中提供方法和/或属性来帮助IDE(通过使用@method和@propterty标签)

比如我们有一个Message 类拥有一个动态属性  (message) 和一个动态方法 (getMessage())。 下面的代码向你展示怎样让IDE知道这些信息,而不需要在PHP代码中明确申明:

/**
 * @property clob $message
 *
 * @method clob getMessage() Returns the current message value
 */
class Message
{
  public function __get()
  {
    // ...
  }

  public function __call()
  {
    // ...
  }
}

即使 实际的getMessage() 方法不存在, 由于 @method 标签IDE还是能识别这个方法。 对于message属性也是同样的原理,因为我们添加了 @property 标签。

这个技术已经在doctrine:build-model 任务中使用了。比如,一个有两列(messagepriority)的Doctrine MailMessage 类看起来像下面代码:

/**
 * BaseMailMessage
 *
 * This class has been auto-generated by the Doctrine ORM Framework
 *
 * @property clob $message
 * @property integer $priority
 *
 * @method clob        getMessage()  Returns the current record's "message" value
 * @method integer     getPriority() Returns the current record's "priority" value
 * @method MailMessage setMessage()  Sets the current record's "message" value
 * @method MailMessage setPriority() Sets the current record's "priority" value
 *
 * @package    ##PACKAGE##
 * @subpackage ##SUBPACKAGE##
 * @author     ##NAME## <##EMAIL##>
 * @version    SVN: $Id: Builder.php 6508 2009-10-14 06:28:49Z jwage $
 */
abstract class BaseMailMessage extends sfDoctrineRecord
{
    public function setTableDefinition()
    {
        $this->setTableName('mail_message');
        $this->hasColumn('message', 'clob', null, array(
             'type' => 'clob',
             'notnull' => true,
             ));
        $this->hasColumn('priority', 'integer', null, array(
             'type' => 'integer',
             ));
    }

    public function setUp()
    {
        parent::setUp();
        $timestampable0 = new Doctrine_Template_Timestampable();
        $this->actAs($timestampable0);
    }
}

更快的查询文档

由于symfony是一个有丰富功能的大框架,记得所有可能的配置,或所有你要使用的类和方法是非常困难的。正如我们已经看到过,使用IDE可以提供你非常强大的自动完成功能。让我们探索如何利用现有的工具来尽可能快地找到可能的答案。

在线API

最快的方式找到一个类或方法的文档是在线浏览 API.

更有趣的是系统内建的API搜索引擎。搜索允许你只用几个字母就可以快速找到一个类和方法。在API页面的搜索框输入几个字母,一个带有有用提示的快速搜索框会实时出现。

你可以通过输入类名的开头来搜索:

API Search

或者一个方法的名称:

API Search

或者一个类名加::来列出所有可用方法:

API Search

或者再添加方法名的开头来提炼可能结果:

API Search

如果你想列出一个包所有的类,只要输入包名,提交请求就可以了。

你甚至可以集成symfony API搜索到你的浏览器种。这样,你甚至不需要访问symfony网站就能查找。这是因为symfony API搜索本身提供了 OpenSearch 支持.

如果你使用Firefox, symfony API搜索引擎会在搜索引擎菜单自动出现。你也可以点击在API文档上的  “API OpenSearch” 链接来添加到你的浏览器搜索框。

你可以在这个博客上查看截图,展示symfony api搜索引擎使如何很好的集成到Firefox中的。

备忘单

如果你想快速查看框架主要部分的信息,很多备忘单可供你选择:

有些备忘单还没有更新到symfony1.3

离线文档

关于配置的问题最好的答案是symfony参考指南。这是一本任何时候你用symfony开发时都需要放在身边的书。感谢非常详细的目录,术语的索引,夸章节引用等,这本书是找到每个可用配置的最快方法。你可以浏览在线版本,买一本打印版本,设置下载一个PDF 版本

在线工具

正如本章开始看到的,symfony中提供了一个很好的工具集来帮助您更快得开始。 最后,您将完成您的项目,并在一段时间后将其部署到生产。

要检查您的项目是否已经部署就绪,可以使用在线部署清单。 这个网站涵盖了你在产品上线前需要全都检查得重点。

更快的调试

当在开发环境下出现错误的时候,symfony展现一个包涵了有用信息的异常页面。例如你可以查看堆栈跟踪信息和已执行的文件,如果你设了在settings.yml(看下面的代码) 配置文件中设置了sf_file_link_format, 你甚至可以点击文件名,对应的文件就会在你喜欢的文本编辑器或IDE中打开。虽然这是一个在你调试问题时可以帮你节约很多时间的小功能,是一个非常棒的例子,。

当设置了sf_file_link_format参数,在Web调试工具条上的日志和可视面板显示的文件名(尤其当启用了XDebug)也可以点击。

默认情况下, sf_file_link_format 是空的。symfony将把php配置文件中 xdebug.file_link_format的值重新设为默认值。(在php.ini中设置 xdebug.file_link_format 允许现在的XDebug版本在堆栈跟踪添加所有文件的链接)。

sf_file_link_format的值根据你的IDE和操作系统不同而不同。比如,如果你想在TextMate中打开,添加下面的代码到settings.yml中:

dev:
  .settings:
    file_link_format: txmt://open?url=file://%f&line=%l

%f 占位符会由symfony替换位文件的绝对路径, %l 占位符由行号取代。

如果你使用VIM,在网上有更多的关于 symfonyXDebug的描述。

使用搜索引擎来学习配置你的IDE。你也可以通过同样的方式来查找关于sf_file_link_formatxdebug.file_link_format的配置。

更快的测试

记录你的功能测试

功能测试模拟用户的交互行为,以便全面测试集成的所有应用程序块。写功能测试虽然简单,但非常耗时。不过,每一个功能测试文件就是模拟用户浏览您的网站的一个场景,因为浏览网站比写php代码要快的多,如果你能够记录浏览的过程,并自动转换成php代码呢?谢天谢地,symfony中有这样的插件。它被称为swFunctionalTestGenerationPlugin,它让你在几分钟内产生一个现成的可自定义的测试框架。当然,你仍然需要添加适当的测试使之有用,但这仍然节省了大量时间。

该插件通过注册symfony过滤器(filter),拦截所有请求,并将其转换为功能测试代码。按通常的方式安装插件后,你需要启用它。打开您应用程序的filters.yml文件并在注释行后添加下面的内容:

functional_test:
  class: swFilterFunctionalTest

接下来,在你ProjectConfiguration类中启用插件:

// config/ProjectConfiguration.class.php
class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    // ...

    $this->enablePlugin('swFunctionalTestGenerationPlugin');
  }
}

因为插件使用web调试工具条作为它的主要用户界面,请务必将它激活(在默认开发环境情况下是激活的)当启用时,一个新的菜单名为“Functional Test”在工具条上出现。在这个面板上,您就可以点击“激活(Activate)”链接来开始录制,点击“重置(Reset)”按钮重新开始录制。当您完成了,复制并粘贴textarea中代码到一个测试文件,并开始对其进行自定义。

更快的运行测试套件

当你有一个大的测试套件,每次您更改后运行所有测试将是非常耗时的,尤其是当一些测试失败了。每次您修复一个测试,你应该运行整个测试套件以确保你没有破坏其他测试。但是直到失败的测试被修复了,否则就没有必要重新执行所有的测试。为了加速这个过程, test:all 任务有一个 --only-failed (可以简写为-f )选项,可以强制只执行上次测试失败的用例:

$ php symfony test:all --only-failed

在第一次执行时,所有的测试都正常运行。但随后的测试,只运行上次失败的测试。当您修复您的代码时,一些测试会通过,并从随后的测试中删除。当所有的测试都通过的时候,所有的测试会被重新运行…然后你就重复这样的过程。

More with symfony 1.3 & 1.4-高级路由 (第二部分)

December 31st, 2009

高级路由 (第二部分)

作者:Ryan Weaver 翻译:逗派

路由集合(Route Collections)

要完成Sympal Builder应用,我们需要创建一个管理平台,使得每个 Client 能管理他们的页面。 要做到这一点,我们需要一系列的action让我们能够创建,更新和删除 Page对象。 由于这些类型的模块(module)非常通用,symfony可以自动生存这些模块。从命令行执行下面的任务(task)在backend应用中生成一个 pageAdmin模块:

$ php symfony doctrine:generate-module backend pageAdmin Page --with-doctrine-route --with-show

上面的命令产生一个包含action和templates的管理模块,它能完成所有对Page对象的修改需求。有很多自定义选项可以修改自动生存的CRUD,但这超出了本章要讨论的范围。

虽然上述命令为我们准备了需要的module,但是我们还是需要为每个action创建路由。 通过附加给命令的--with-doctrine-route 选项,每个action都会有生存一个相应的对象路由。这样减少了在每个action中的代码。比如在 edit action 中只有简单的一行:

public function executeEdit(sfWebRequest $request)
{
  $this->form = new PageForm($this->getRoute()->getObject());
}

总共,我们需要给index, new, create, edit, updatedelete actions都分配一个路由。一般创建这些具有RESTful风格的路由需要在routing.yml做非常多的配置。

pageAdmin:
  url:         /pages
  class:       sfDoctrineRoute
  options:     { model: Page, type: list }
  params:      { module: page, action: index }
  requirements:
    sf_method: [get]
pageAdmin_new:
  url:        /pages/new
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: new }
  requirements:
    sf_method: [get]
pageAdmin_create:
  url:        /pages
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: create }
  requirements:
    sf_method: [post]
pageAdmin_edit:
  url:        /pages/:id/edit
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: edit }
  requirements:
    sf_method: [get]
pageAdmin_update:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: update }
  requirements:
    sf_method: [put]
pageAdmin_delete:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: delete }
  requirements:
    sf_method: [delete]
pageAdmin_show:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: show }
  requirements:
    sf_method: [get]

想要查看这些路由,可以使用 app:routes 命令, 它能展示一个特定应用程序的所有路由的摘要。

$ php symfony app:routes backend

>> app       Current routes for application "backend"
Name             Method Pattern
pageAdmin        GET    /pages
pageAdmin_new    GET    /pages/new
pageAdmin_create POST   /pages
pageAdmin_edit   GET    /pages/:id/edit
pageAdmin_update PUT    /pages/:id
pageAdmin_delete DELETE /pages/:id
pageAdmin_show   GET    /pages/:id

使用路由集合代替路由

幸运的是,symfony提供了一个更加方便的方法来指定包含传统的CRUD功能的所有路由。在routing.yml中用一个简单的路由替换上面所有的内容。

pageAdmin:
  class:   sfDoctrineRouteCollection
  options:
    model:        Page
    prefix_path:  /pages
    module:       pageAdmin

我们再次运行app:routes命令来查看所有的路由,正如你所看到的,前面的七个路由仍然存在。

$ php symfony app:routes backend

>> app       Current routes for application "backend"
Name             Method Pattern
pageAdmin        GET    /pages.:sf_format
pageAdmin_new    GET    /pages/new.:sf_format
pageAdmin_create POST   /pages.:sf_format
pageAdmin_edit   GET    /pages/:id/edit.:sf_format
pageAdmin_update PUT    /pages/:id.:sf_format
pageAdmin_delete DELETE /pages/:id.:sf_format
pageAdmin_show   GET    /pages/:id.:sf_format

路由集合是一种特殊类型的路由对象,它内部包含了许多个路由。 比如, sfDoctrineRouteCollection 路由自动生成做CRUD操作时七个最常用的路由。  sfDoctrineRouteCollection 幕后所做的无非就是创建我们先前在routing.yml配置的七个路由。路由集合主要是为创建一个通过路由组合而存在的快捷方式。

创建一个自定义的路由集合

在这点上,每个Client能够通过URL/pages在他的Page对象上做正常CRUD修改。 不幸的是,现在每个Client 能够看到和修改所有的Page对象-包括属于和不属于他的。比如,如,http://pete.sympalbuilder.com/backend.php/pages 会展现所有在fixtures.yml定义的Page的列表- Pete’s Pet Shop的 location页面和City Pub的 menu 页面。

要解决这个问题,我们需要重用在frontend创建的acClientObjectRoute。 sfDoctrineRouteCollection 类将生成一组sfDoctrineRoute 对象。但在这个应用中,我们需要生成一组 acClientObjectRoute 对象。

要做到这一点,我们将需要使用一个自定义的路由集合类。创建一个文件 acClientObjectRouteCollection.class.php ,并把它放到 lib/routing 目录。它的内容是难以置信的简单:

// lib/routing/acClientObjectRouteCollection.class.php
class acClientObjectRouteCollection extends sfObjectRouteCollection
{
  protected
    $routeClass = 'acClientObjectRoute';
}

$routeClass 属性定义了创建每个基本路由时使用的类型。现在每个基本路由都是使用acClientObjectRoute了,工作实际已经完成了。比如 , http://pete.sympalbuilder.com/backend.php/pages 现在指挥展现一个页面:Pete’s Pet Shop的 location页面。 感谢自定义路由类型,index action才能根据请求中的子域名展现只与正确的Client相关的Page对象。 只用了几行代码,我们就已经创建了整个可以被多个用户安全的使用的后台模块。

遗漏的部分:创建新页面

现在,当创建或编辑一个Page对象时会显示一个Client 选择框。替换允许用户选择Client(存在安全隐患)的功能,让我们根据请求中的子域名自动设置Client,

首先, 更新在 lib/form/PageForm.class.php文件中的PageForm对象

public function configure()
{
  $this->useFields(array(
    'title',
    'content',
  ));
}

现在这个选择框已经从Page的表单中消失了。但是,当创建了一个新的Page对象后,其client_id从没被设置过。为了解决这个问题,手动在new和create action中设置关联的 Client 信息。

public function executeNew(sfWebRequest $request)
{
  $page = new Page();
  $page->Client = $this->getRoute()->getClient();
  $this->form = new PageForm($page);
}

这里引入了的新方法getClient(),现在在acClientObjectRoute 类型中还没有 。 让我们通过一点小小的修改来把它加到类型中:

// lib/routing/acClientObjectRoute.class.php
class acClientObjectRoute extends sfDoctrineRoute
{
  // ...
 
  protected $client = null;
 
  public function matchesUrl($url, $context = array())
  {
    // ...
 
    $this->client = $client;
 
    return array_merge(array('client_id' => $client->id), $parameters);
  }
 
  public function getClient()
  {
    return $this->client;
  }
}

通过添加 $clien类属性,并在matchesUrl()方法中初始化,我们可以通过路由简单的获得这个Client对象。 现在新Page对象的client_id列会根据现在host中的子域名被自动正确的初始化。

自定义一个对象路由集合

通过使用路由框架,我们现在已经轻松的解决了创建Sympal Builder应用时提出的问题。随着应用的增长,  开发者能够在管理后台给其他模块重用自定义的路由(例如, 每个 Client 可以管理自己的图片相册).

创建自定义路由集合的另一个常见原因是添加额外的,常用的路由。比如,假设一个项目使用了很多model,每个model都有一个 is_active列。 在管理方面,需要有一个简单的方法来切换任何特定对象的is_active值。首先,修改 acClientObjectRouteCollection 指示它添加一个新的路由到集合中:

// lib/routing/acClientObjectRouteCollection.class.php
protected function generateRoutes()
{
  parent::generateRoutes();
 
  if (isset($this->options['with_is_active']) && $this->options['with_is_active'])
  {
    $routeName = $this->options['name'].'_toggleActive';
 
    $this->routes[$routeName] = $this->getRouteForToggleActive();
  }
}

当集合对象被初始化时调用sfObjectRouteCollection::generateRoutes() 方法,该方法负责创建所有需要的路由并把它们添加到$routes类属性中。在这个例子中,我们把实际创建路由的工作放到了新创建的getRouteForToggleActive()方法中:

protected function getRouteForToggleActive()
{
  $url = sprintf(
    '%s/:%s/toggleActive.:sf_format',
    $this->options['prefix_path'],
    $this->options['column']
  );
 
  $params = array(
    'module' => $this->options['module'],
    'action' => 'toggleActive',
    'sf_format' => 'html'
  );
 
  $requirements = array('sf_method' => 'put');
 
  $options = array(
    'model' => $this->options['model'],
    'type' => 'object',
    'method' => $this->options['model_methods']['object']
  );
 
  return new $this->routeClass(
    $url,
    $params,
    $requirements,
    $options
  );
}

唯一剩下的步骤是在routing.yml中设定这个路由集合。注意generateRoutes()在添加新的路由前需要查看 lwith_is_active参数。 添加这样的逻辑让我们在以后使用acClientObjectRouteCollection但不需要toggleActive的情况下有更多的控制能力:

# apps/frontend/config/routing.yml
pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    model:          Page
    prefix_path:    /pages
    module:         pageAdmin
    with_is_active: true

运行app:routes命令,验证新的 toggleActive路由已经存在。唯一剩下要做的是创建一个action来完成实际的工作。因为你可能需要在多个模块中使用这个路由集合和相应的action。在apps/backend/lib/action(你需要创建这个目录)目录中新建backendActions.class.php:

# apps/backend/lib/action/backendActions.class.php
class backendActions extends sfActions
{
  public function executeToggleActive(sfWebRequest $request)
  {
    $obj = $this->getRoute()->getObject();
 
    $obj->is_active = !$obj->is_active;
 
    $obj->save();
 
    $this->redirect($this->getModuleName().'/index');
  }
}

最后,把pageAdminActions类的基类换成backendActions类。

class pageAdminActions extends backendActions
{
  // ...
}

我们刚才完成了什么呢? 通过添加路由到路由集合和一个关联的基类action,任何新的模块只要使用acClientObjectRouteCollection和扩展backendActions类就可以自动使用这些功能。这样,通用的功能可以很容易地在许多模块共享。

路由集合的配置选项

对象路由包含了一系列的选项,允许它可以高度自定义。在很多情况下,开发人员可以使用这些选项配置路由集合而不需要创建一个新的自定义路由集合类。一份详细的路由集合选项列表可以在《symfony参考指南》中查到。

Action路由

每个对象路由集合接收三个不同的选项,它们决定在集合中需要生成的正确的路由。不做深入的探讨,下面配置的集合会生成7个默认的路由以及一个额外的集合路由和对象路由:

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    actions:      [list, new, create, edit, update, delete, show]
    collection_actions:
      indexAlt:   [get]
    object_actions:
      toggle:     [put]

默认情况下,使用model的主健来生成所有的url和查询对象。这个,当然,也可以简单的改变。 比如,下面的代码会使用slug列而不是主健:

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    column: slug

Model方法

默认情况下,路由会获取集合路由中所有关联的对象和查询对象路由中指定的列。如果你需要重写这些,在路由上添加 model_methods选项。在这个例子中,fetchAll()findForRoute() 方法 需要被添加到PageTable 类中。两个方法都会接收一个request参数数组作为参数。

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    model_methods:
      list:       fetchAll
      object:     findForRoute

默认参数

最后,假设你需要在请求中添加特定参数,并在集合中的每个路由可用。这个可以通过default_params选项简单的实现:

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    default_params:
      foo:   bar

最后的思考

传统的路由框架功能是-匹配和生成url- 已发展成为一个完全可定制的,能满足项目最复杂的URL需求的能力的系统。通过控制路由对象,特定的URL结构能够从业务逻辑中抽象出来,并完全独立存在在路由框架中。最终的结果是更可控制,更灵活和更易于管理的代码。

More with symfony 1.3 & 1.4-day02-高级路由 (第一部分)

December 29th, 2009

高级路由(第一部分)

作者: Ryan Weaver 翻译:逗派

在symfony内核中,路由框架是链接每个url到特定symfony项目路径的映射,反之亦然。它可以很容易地创造出优雅的url,同时应用程序逻辑保持完全独立。随着最近symfony版本的更新,现在的路由框架也进步了很多。

本章将说明如何创建一个简单的Web应用程序,其中每个客户端使用一个单独的子域名(比如 client1.mydomain.comclient2.mydomain.com). 通过扩展symfony路由框架,这会变得很容易。

本章节需要你的项目使用Doctrine作为ORM框架。

项目设置: 给许多用户使用的CMS系统

在这个项目中,一个虚构的公司 – Sympal Builder – 想创建一个CMS(内容管理系统),使他们的客户可以通过sympalbuilder.com子域名建立网站 。具体来说,客户XXX可以通过xxx.sympalbuilder.com查看他的网站,通过xxx.sympalbuilder.com/backend.php使用管理界面。

Sympal 的名字借用Jonathan Wage的Sympal, 一个使用symfony开发的内容管理框架(CMF)

这个项目有两个基本需求:

  • 用户应该能够创建网页,并可以指定这些网页的标题,内容,以及url网址。
  • 整个应用建立在一个symfony项目中,并且能通过子域名检测出客户和加载正确的数据来处理所有客户网站的前台和后台。

创建此应用程序,服务器将需要设置所有的*. sympalbuilder.com路由到相同的文件根目录-该symfony项目的web目录。

Schema和数据

该项目的数据库包括ClientPage对象,每个Client代表一个子域名的网站,每个网站有许多Page对象组成。

# config/doctrine/schema.yml
Client:
  columns:
    name:       string(255)
    subdomain:  string(50)
  indexes:
    subdomain_index:
      fields:   [subdomain]
      type:     unique

Page:
  columns:
    title:      string(255)
    slug:       string(255)
    content:    clob
    client_id:  integer
  relations:
    Client:
      alias:        Client
      foreignAlias: Pages
      onDelete:     CASCADE
  indexes:
    slug_index:
      fields:   [slug, client_id]
      type:     unique

虽然每个表的索引不是必需的,但是应用程序中很多查询需要经常使用这些列,所以加入索引是一个不错的主意。

为了使项目能运行,请将下面的测试数据放到 data/fixtures/fixtures.yml 文件:

# data/fixtures/fixtures.yml
Client:
  client_pete:
    name:      Pete's Pet Shop
    subdomain: pete
  client_pub:
    name:      City Pub and Grill
    subdomain: citypub

Page:
  page_pete_location_hours:
    title:     Location and Hours | Pete's Pet Shop
    content:   We're open Mon - Sat, 8 am - 7pm
    slug:      location
    Client:    client_pete
  page_pub_menu:
    title:     City Pub And Grill | Menu
    content:   Our menu consists of fish, Steak, salads, and more.
    slug:      menu
    Client:    client_pub

测试数据最初引进两个网站,每个网站有一个页面。每个网页的完整url由Client对象中的 subdomain字段和Page对象的 slug字段组成。



http://pete.sympalbuilder.com/location

http://citypub.sympalbuilder.com/menu

路由

每个Sympal Builder网站上的网页直接对应一个 Page模型对象, Page对象定义了网页输出的标题和内容。为了把url链接到特定的Page对象,需要创建一个使用slug字段的sfDoctrineRoute对象。下面的代码使用url匹配的slug字段从数据库中自动查询出一个Page对象:

# apps/frontend/config/routing.yml
page_show:
  url:        /:slug
  class:      sfDoctrineRoute
  options:
    model:    Page
    type:     object
  params:
    module:   page
    action:   show

上面的路由将自动匹配 http://pete.sympalbuilder.com/location页到正确的 Page 对象中. 不幸的是,上面的路由也会匹配 http://pete.sympalbuilder.com/menu, 这意味着餐馆的菜单也会在Pete的网站上显示。在这一点上,路由是不知道客户端子域名的重要性。

为了使应用能够使用,路由需要更加的智能。它应该同时基于 slug client_id来匹配正确的Page, 这个可以通过把子域名(比如pete.sympalbuilder.com)匹配到Client对象上的subdomain来决定。为实现这一目标,我们将充分利用路由框架来创建自定义的路由类。

但是,首先,我们需要了解一些路由系统是如何工作的背景。

路由是如何工作的

在symfony中一个路由就是指一个 sfRoute 对象,它有两个重要的工作:

  • 生成一个url:例如,如果您传递一个slug参数给page_show方法,它应该能够产生一个真正的url(例如,/location)。
  • 匹配传入的url:对于每个传入的请求URL,每个路由必须能够确定url是否“匹配”自己的路由要求。

每个路由的信息一般在每个应用程序的config目录下的app/yourappname/config/routing.yml文件设置。回想一下,每个路由就是“一个sfRoute类型的对象”。那么,这些简单YAML的记录是如何转化成为sfRoute对象的呢?

路由缓存配置处理器

尽管大多数路由在YAML文件上定义,在请求的时候,文件中的每个记录都会通过一个叫缓存配置处理器的特殊类型来转化成实际的对象。最终的结果是每一个应用程序中的路由都对应一个PHP代码。这一过程的细节超出了本章的范围,让我们直接看一下结果吧,编译后的page_show路由代码看起来像:

new sfDoctrineRoute('/:slug', array (
  'module' => 'page',
  'action' => 'show',
), array (
  'slug' => '[^/\\.]+',
), array (
  'model' => 'Page',
  'type' => 'object',
));

每个路由类的名称定义在routing.yml文件中的class字段。如果没有class字段,默认会使用sfRoute类。 另一个常用的路由类型是sfRequestRoute,它允许开发人员创建REST风格的路由。完整的路由类列表和可用选项可以通过《symfony参考指南》查询。

匹配传入的请求到特定的路由

路由框架的主要工作之一是将每个传入的URL匹配到正确的路由对象。sfPatternRouting类代表着核心路由引擎,由它负责上面的匹配任务。尽管它的重要性,开发人员还是很少会直接与sfPatternRouting打交道。

为了匹配正确的路由, sfPatternRouting遍历每个sfRoute,“询问”它是否匹配传入的请求。 在内部,这意味着sfPatternRouting调每个路由对象的sfRoute::matchesUrl()方法。 如果路由不匹配传入的网址,这个方法只返回false。

但是,如果路线匹配传入的URL,sfRoute::matchesUrl()不只是简单的返回trueHowever。 相反,路由对象返回一个参数数组并将其合并到request对象。例如,网址http://pete.sympalbuilder.com/location会匹配page_show路由,它地matchesUrl()方法将返回下面的数组:

array('slug' => 'location')

然后,此信息合并到request对象,这就是为什么我们可以从actions或其他地方获得路由中的变量(比如,slug)

$this->slug = $request->getParameter('slug');

正如您可能已经猜到的,覆盖sfRoute::matchesUrl()是扩展和定制的路由做几乎任何事情的很棒的方式。

创建一个自定义的Route类

为了扩展page_show路由以石塔能够基于Client对的subdomain匹配url, 我们将创建一个新的自定义route类。创建一个名叫 acClientObjectRoute.class.php文件,并把它放到项目的  lib/routing 文件夹 (你需要创建这个文件夹):

// lib/routing/acClientObjectRoute.class.php
class acClientObjectRoute extends sfDoctrineRoute
{
  public function matchesUrl($url, $context = array())
  {
    if (false === $parameters = parent::matchesUrl($url, $context))
    {
      return false;
    }

    return $parameters;
  }
}

唯一的其他步骤是指示page_show 路由使用这个路由类。在routing.yml中,更新这个路由的class 字段:

# apps/fo/config/routing.yml
page_show:
  url:        /:slug
  class:      acClientObjectRoute
  options:
    model:    Page
    type:     object
  params:
    module:   page
    action:   show

到目前为止, acClientObjectRoute 还没有添加额外的功能,但是所有的细节都已经准备到位了。 matchesUrl()方法由两个具体的工作。

在自定义route中添加逻辑

为了给自定义的路由所需的功能,使用下面的代码替换acClientObjectRoute.class.php文件中的内容:

class acClientObjectRoute extends sfDoctrineRoute
{
  protected $baseHost = '.sympalbuilder.com';

  public function matchesUrl($url, $context = array())
  {
    if (false === $parameters = parent::matchesUrl($url, $context))
    {
      return false;
    }

    // return false if the baseHost isn't found
    if (strpos($context['host'], $this->baseHost) === false)
    {
      return false;
    }

    $subdomain = str_replace($this->baseHost, '', $context['host']);

    $client = Doctrine_Core::getTable('Client')
      ->findOneBySubdomain($subdomain)
    ;

    if (!$client)
    {
      return false;
    }

    return array_merge(array('client_id' => $client->id), $parameters);
  }
}

开始的parent::matchesUrl() 方法调用很重要因为它运行了正常的路由匹配过程。在这个例子中,因为URL /location 匹配 page_show 路由, parent::matchesUrl() 将返回一个包含匹配的 slug 参数的数组。

array('slug' => 'location')

换句话说,所有路由匹配过程中的困难都已经为我们完成了。这使得方法剩余部分只要关注于匹配正确的 Client 子域名。

public function matchesUrl($url, $context = array())
{
  // ...

  $subdomain = str_replace($this->baseHost, '', $context['host']);

  $client = Doctrine_Core::getTable('Client')
    ->findOneBySubdomain($subdomain)
  ;

  if (!$client)
  {
    return false;
  }

  return array_merge(array('client_id' => $client->id), $parameters);
}

通过一个简单的字符串替换,我们可以隔离得到host中的子域名,然后查询数据库判断是否有 Client 对象包含该子域名。如果没有  Client 对象匹配该子域名,我们返回 false 表示传入的请求没有匹配路由。但是,如果有 Client 对象包含了正确的子域名,我们将一个额外的参数  client_id 合并到返回的数组中。

这个 传给matchesUrl()的$context数组预置了很多关于当前请求的很多有用信息,包括 host, is_secure 布尔值request_uri, HTTP请求方法等。

但是,自定义的路由真正完成的是什么呢? acClientObjectRoute 类现在做了下面两点:

  • 传入的 $url 现在只有当其 host 包含了属于一个 Client 对象的子域名时才会被匹配.
  • 如果路由匹配成功,会返回一个额外的Client对象中获得的client_id 参数并最终合并到reqeust参数中。

使用自定义路由

现在正确的client_id 参数通过acClientObjectRoute返回了, 我们可疑通过reqeust对象获得它了。For 比如,在 page/show action中可以使用client_id 来获得正确的 Page 对象:

public function executeShow(sfWebRequest $request)
{
  $this->page = Doctrine_Core::getTable('Page')->findOneBySlugAndClientId(
    $request->getParameter('slug'),
    $request->getParameter('client_id')
  );

  $this->forward404Unless($this->page);
}

这个 findOneBySlugAndClientId() 方法是在Doctrine1.2中新的 魔幻查找器 ,可以根据多个字段查询对象。

这已经很好了,但是路由框架提供你一个更加完美的解决方案。首先, 添加下面的方法到 acClientObjectRoute 类中:

protected function getRealVariables()
{
  return array_merge(array('client_id'), parent::getRealVariables());
}

根据这最后的代码,action中现在可疑完全依靠route就能获得正确的Page对象, page/show action可以精减到一行代码:

public function executeShow(sfWebRequest $request)
{
  $this->page = $this->getRoute()->getObject();
}

没有任何额外的工作,上面的代码将同时根据slugclient_id字段查询得到Page对象。 此外,像所有的对象路由一样,如果没有找到相应的对象,这个action会自动跳转到404页面。

但它是如何工作的呢?对象路由,比如 sfDoctrineRouteacClientObjectRoute 就是继承它的类, 会根据路由中的url关键字自动查询关联对象。比如这个 page_show 路由,在url中包含了 :slug 变量, 就会使用 slug字段来查询获得Page对象

然而在这个应用中,page_show路由也必需同时使用client_id 字段来查询Page对象。要做到这一点,我们需要重写 sfObjectRoute::getRealVariables()方法, 这个方法在内部调用来决定需要使用哪些列来查询对象。通过添加client_id字段到数组, 这个 acClientObjectRoute 会同时根据 slugclient_id 列来做查询。

对象路由自动忽略哪些没有真实列对应的变量。比如,如果url关键字中包含 :page变量,但是在相关的表中没有  page 列,这个变量就会被忽略。

目前为止,自定义路由类只做了一点修改就完成了所有所需的功能。在下一章中,我们会重用这个路由来创建一个特定客户的管理功能。

生成正确的路由

剩下的一个小问题是如何生成路由。假设使用下面的代码创建一个网页的链接:

<?php echo link_to('Locations', 'page_show', $page) ?>
Generated url: /location?client_id=1

正如你所看到的, 这个client_id 会自动添加到url。 这是因为路由尝试使用所有可用变量来生成的网址。 因为路由同时知道slugclient_id参数,当生成路由时会同时使用两者。

要解决这个问题,添加下面的方法到acClientObjectRoute 类中:

protected function doConvertObjectToArray($object)
{
  $parameters = parent::doConvertObjectToArray($object);

  unset($parameters['client_id']);

  return $parameters;
}

当生成对象路由时,它会通过调用doConvertObjectToArray()来试图获得所有需要的信息。默认情况下,  client_id$parameters 数组中返回。 但是通过unset, 我们防止它被包含在生成的url中。记住我们能删除它是因为子域名自身包含了Client 对象信息

你可以重写整个doConvertObjectToArray() 过程,只要在model类中添加toParams()就可以了,这个方法应该返回一个你想在生成路由时需要使用的参数数组

未完待续…



More with symfony 1.3 & 1.4-day01

December 28th, 2009

介绍

作者 Fabien Potencier 翻译 逗派

在撰写本文的同时,symfony项目已经庆祝了一个重要里程碑:它的第四个生日。在短短的4年中,symfony框架已经发展成为全球最流行的PHP框架之一, 很多网站比如 Delicious, Yahoo BookmarksDaily Motion都在使用symfony。但是,随着最近一次symfony1.4版本的发布(2009年12月),我们即将结束一个周期(symfony1.x版本)。 这本书是结束这个周期最完美的方式,所以这将是最后一本你能阅读的由symfony项目团队发布的关于symfony 1.x版本的书。下一本书的主题最有可能是围绕在symfony2.0上,在2010年晚些时候发布

由于这个和其他许多我将在本章后面解释的原因,这本书对我们有说是非常特殊的。

为什么又一本书?

我们已经出版了两本关于symfony1.3和symfony1.4的书《symfony实践》 和 《symfony参考指南》. 前者是开始学习symfony框架的最好方式,它是根据一个实际项目一步一步的开发流程而编写的。后者是一本包含了几乎你每天开发时所需要查询的所有有关symfony配置的参考书。

《更多的symfony》是一本关于更多symfony高级主题的书。这不是第一本你该阅读的关于symfony的书,但是一本对已经有symfony项目开发经验的人非常有用的书。如果你想知道symfony底层是如何工作的,或者你想通过其他方法来扩展symfony以满足你特殊的需求,这本书就是为你写的。 《更多的symfony》是一本将你的symfony技巧提升一个档次的书。

因为这本书是各种主题的教程的集合,所以根据你要在框架中实现的功能来随意变换章节阅读顺序。

关于这本书

这是一本特殊的书因为它是一本由社区为社区写的一本书。 很多人对这本书有贡献:从作者到翻译,到校对员,这大量的努力使得这本书得以完成。

这本书至少同时用五种语言(英文,法语,意大利语,西班牙语和日语)发表. 如果没有我们翻译团队的慈善工作,这是不可能完成的。

由于开源精神这本书才得以实现,所以这本书的发行是基于开源协议的。 这一事实就改变了一切。这意味着写这本书的人没有人是支付报酬的:所有贡献者努力工作完成它只是因为他们想这么做。 每个人都想分享他们的知识,回馈给社区,帮助传播symfony,当然,也有一些乐趣在里面,同时也可以出名。

这本书由十个每天使用symfony的开发人员和项目经理完成。 他们对框架有深刻的理解,并试图在书本中分享他们的知识和经验。

致谢

在2009年8月,当我开始思考写另一本关于symfoy的书时,我马上有一个疯狂的想法:在两个月内完成并同时使用五种语言出版一本书会是怎样的情况!当然要让社区参与这么大的项目几乎是强制性的。我开始在日本的PHP会议讨论这个想法,经过几个小时努力,日本的翻译团队就准备工作了。这太惊人了! 从作者和翻译者来的反馈也是同样鼓舞人心,《更多的symfony》就由此诞生了。

我想感谢在编写这本书的过程以一种或其他多种方式参与进来的大家,这包括(不按照任何特定顺序):

Ryan Weaver, Geoffrey Bachelet, Hugo Hamon, Jonathan Wage, Thomas Rabaix, Fabrice Bernhard, Kris Wallsmith, Stefan Koopmanschap, Laurent Bonnet, Julien Madelin, Franck Bodiot, Javier Eguiluz, Nicolas Ricci, Fabrizio Pucci, Francesco Fullone, Massimiliano Arione, Daniel Londero, Xavier Briand, Guillaume Bretou, Akky Akimoto, Hidenori Goto, Hideki Suzuki, Katsuhiro Ogawa, Kousuke Ebihara, Masaki Kagaya, Masao Maeda, Shin Ohno, Tomohiro Mitsumune, and Yoshihiro Takahara.

在我们开始前

这本书是同时为symfony1.3和symfony1.4写的。为两个不同版本的软件写一本书是相当不寻常的。本节解释了两个版本间的最大不同点,以及如何为你的项目做最佳的选择。

symfony1.3和symfony1.4几乎是同时发布的(2009年底)。事实上,它们具有完全相同的功能 。 这两个版本间唯一的区别是对symfony老版本的兼容性支持。

如果你需要升级使用symfony老版本(1.o,1.1或者1.2)的项目,你应该选择symfony1.3。 它有一个向后兼容层,所有在symfony1.3中不推荐使用的功能仍然可用。这意味着升级容易,简单而且安全。

然而你如果现在开始一个新项目,你应该使用symfony1.4。这个版本和symfony1.3有完全相同的功能,但是所有不推荐使用的功能,包括整个向后兼容层,都已经删除。这个版本是清洁的,而且比symfony1.3快一点。 另一个使用symfony1.4的优点是它被支持的时间更长。 作为一个长期支持的版本,它将被symfony核心团队支持维护3年(至2012年11月)

当然你可以先将你的项目迁移到symfony1.3,然后慢慢的重构你的代码,删除不推荐使用的功能,最后升级到symfony1.4,以获得更长的symfony核心团队支持。你有足够的时间计划迁移工作,因为symfony1.3将被支持一年(至2010年11月)

由于这本书没有使用不推荐使用的功能,书中所有的例子都可以在两个版本上工作。

output download file in php

December 22nd, 2009

as easy as like this

header ("Content-type: $type");//text/plain
header ("Content-length: $size");// this line is optional
header ("Content-Disposition: attachment; filename=robots.txt"); //the filename you want to save as
readfile('realfile.txt');//the real file path

Minimal CSS pagination helper

December 21st, 2009
function pagination($pager)
{
    $uri = sfRouting :: getInstance()->getCurrentInternalUri();
    $html = '';

    if ($pager->haveToPaginate())
    {
        $uri .= strstr($uri, '?') ? '&page=' : '?page=';
        if ($pager->getPage() != 1)
        {
            $html .= '
  • ' . link_to('first', $uri . '1') . '
  • '; $html .= '
  • ' . link_to('previous', $uri . $pager->getPreviousPage()) . '
  • '; } foreach ($pager->getLinks() as $page) { if ($page == $pager->getPage()) $html .= '
  • ' . link_to($page, $uri . $page) . '
  • '; else $html .= '
  • ' . link_to($page, $uri . $page) . '
  • '; } if ($pager->getPage() != $pager->getLastPage()) { $html .= '
  • ' . link_to('next', $uri . $pager->getNextPage()) . '
  • '; $html .= '
  • ' . link_to('last', $uri . $pager->getLastPage()) . '
  • '; } $html = '
      ' . $html . '
    '; } return $html; }

    minial css

    ul.pagination li {
        display: inline;
        list-style-type: none;
        padding-right: 1em;
    }

    In your template

    echo use_helper('Pagination')
     echo pagination($pager)

    Fetch a Random Record with Doctrine

    December 21st, 2009
    $userCount = Doctrine::getTable('User')->count();
    $user = Doctrine::getTable('User')
      ->createQuery()
      ->limit(1)
      ->offset(rand(0, $userCount - 1))
      ->fetchOne();
    

    Master & Slave connections in Doctrine 1.2 and Symfony 1.3

    December 21st, 2009

    http://snippets.symfony-project.org/snippet/373

    1. 配置database.yml,设置master,slave数据库连接

    2. 覆写Doctrine默认的Query 和Record

    public function configureDoctrine(Doctrine_Manager $manager)
    {
        // Configure custom query and custom record classes
        $manager->setAttribute(Doctrine::ATTR_QUERY_CLASS, 'MyQuery');
        $options = array('baseClassName' => 'MyRecord');
        sfConfig::set('doctrine_model_builder_options', $options);
    }
    

    3. 创建自定义的Query

    class MyQuery extends Doctrine_Query
    {
      public function preQuery()
      {
          // If this is a select query then set connection to the slave
          if (Doctrine_Query::SELECT == $this->getType()) {
              $this->_conn = Doctrine_Manager::getInstance()->getConnection('slave');
          // All other queries are writes so they need to go to the master
          } else {
              $this->_conn = Doctrine_Manager::getInstance()->getConnection('master');
          }
      }
    }
    

    4.创建自定义的Record

    abstract class MyRecord extends sfDoctrineRecord
    {
        public function save(Doctrine_Connection $conn = null)
        {
          $conn = Doctrine_Manager::getInstance()->getConnection('master');
          parent::save($conn);
        }
    }
    

    5. 如果你使用的Doctrine Behaviour(比如Versionable)需要创建表,也需要指明

    Entity:
      ActAs:
        Versionable:
          builderOptions:
            baseClassName: MyRecord
    

    如果你有多个slave服务器,就在ProjectConfiguration中配置在启动的时候选择一个,同时修改Query对象获得得connection名字

    symfony性能优化

    December 19th, 2009

    将symfony网站上的优化章节简略的翻译一下,
    原文在http://www.symfony-project.org/book/1_2/18-Performance

    1. 优化服务器

    关闭magic_quotes_gpc

    升级PHP版本到5.2以上,5.1比5.0快很多

    使用PHP加速器,比如 ( APC, XCache, or eAccelerator)

    2. 优化model

    model层是symfony框架中执行最慢的部分。

    2.1. 优化prople集成。

    关闭database,如果应用不需要database

    使用autoload,而不是include

    删除注释

    2.2. 限制hydrate对象的数量

    $c = new Criteria();
    $c->setOffset(10);  // Offset of the first record returned
    $c->setLimit(10);   // Number of records returned
    $articles = ArticlePeer::doSelect($c);
    

    2.3. 使用join来减少查询的次数

    2.4. 避免使用临时数组

    因为Propel已经hydrate了数组

    2.5 不要使用ORM

    class ArticlePeer extends BaseArticlePeer
    {
    public static function getArticleTitlesWithNbComments()
    {
    $connection = Propel::getConnection();
    $query =  'SELECT %s as title, COUNT(%s) AS nb FROM %s LEFT JOIN %s ON %s = %sGROUP BY %s';
    $query = sprintf($query,
    ArticlePeer::TITLE, CommentPeer::ID,
    ArticlePeer::TABLE_NAME, CommentPeer::TABLE_NAME,
    ArticlePeer::ID, CommentPeer::ARTICLE_ID,
    ArticlePeer::ID
    );
    $statement = $connection->prepare($query);
    $statement->execute();
    $results = array();
    while ($resultset = $statement->fetch(PDO::FETCH_OBJ))
    {
    $results[] = array($resultset->title, $resultset->nb);
    }
    return $results;
    }
    }
    

    2.6 优化数据库

    添加索引和建立持久连接。但两种方法都不能绝对保证提升数据库的性能

    3. 优化显示

    3.1 使用最快的代码

    include_compenent() > include_partial() > include

    slot: 很快,和初始化一个变量差不多

    component slot:性能根据具体view的配置

    上面是在没有考虑缓存的情况下。

    3.2 提升routing的性能

    symfony默认按照yml种配置的顺序一个一个match,最简单的优化方法是使用rule名词而不是url

    3.3 跳过模板 并不是所有请求都需要返回完整的页面,尤其是Ajax请求

    public function executeRefresh()
    {
    $output = '{"title":"My basic letter","name":"Mr Brown"}';
    $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');
     return sfView::HEADER_ONLY;
    }
    

    public function executeFastAction()
    {
    return $this->renderText("<html><body>Hello, World!</body></html>");
    }
    

    3.4 限制默认的Helper

    默认的Helper在每次请求的时候都会加载,过多的加载会影响性能

    3.5 压缩返回内容

    关闭PHP压缩功能,能够降低cpu使用率,但会提高带宽的使用

    ps: 我们也可以使用web server的压缩功能

    4 优化缓存

    缓存优化应该作为做主要的优化措施,它将获得的性能提升是非常显著的。

    4.1 清除部分缓存

     // Clear only the cache of the frontend application
    > php symfony cache:clear frontend
    // Clear only the HTML cache of the frontend application
    > php symfony cache:clear frontend template
    // Clear only the configuration cache of the frontend
    application > php symfony cache:clear frontend config
    

    4.2 生成缓存页面

    require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
    $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'staging', false); sfContext::createInstance($configuration);
    // Array of URLs to browse
    $uris = array(
     '/foo/index',
      '/foo/bar/id/1',
      '/foo/bar/id/2',
      ... );
    $b = new sfBrowser();
     foreach ($uris as $uri)
    {
     $b->get($uri);
    }
    

    4.3 使用数据库存储缓存内容

    比如SqlLite

    4.4 跳过symfony

    使用http1.1 head做proxy和浏览器缓存

    使用sfSuperCachePlugin

    4.5 缓存函数执行结果

    $cache = new sfFileCache(array('cache_dir' => sfConfig::get('sf_cache_dir').'/function'));
     $fc = new sfFunctionCache($cache);
    $result1 = $fc->call('cos', array(M_PI));
    $result2 = $fc->call('preg_replace', array('/\s\s+/', ' ', $input));
    

    4.6 在服务器上保存缓存

    $cache = new sfAPCCache();
    // Storing data in the cache
    $cache->set($name, $value, $lifetime);
    // Retrieving data
    $value = $cache->get($name);
    // Checking if a piece of data exists in the cache
    $value_exists = $cache->has($name);
    // Clear the cache
     $cache->clear();
    

    5. 禁用不需要的功能

    比如session,database,security等,

    禁用logger在prod服务器上,使用 sfErrorLoggerPlugin

    6. 优化你的代码

    core_compile.yml和sfOptimizer