Code snippets for symfony 1.x

Navigation

Popular Tags

action admin ajax batch cache cli criteria css culture custom database date debug doctrine email filter form forms generator helper i18n javascript model navigation object pager pagination pake propel query routing session sql symfony template test user validation validator view widget
This is a public source code repository where you can find, rate and comment code snippets published by others, or store you owns and categorize them with tags for easy retrieval.

Latest snippets

How to generate routes (links) from the model or from a task

The symfony routing system is tied to the sfContext resource via the factory handler.

It might an issue if you need to build routes in the cli mode, where sfContext has no instance. Sample: one of your cron jobs sends emails that contain links.

The best way I've found is to provide an access to the routing system linked to the ProjectConfiguration class. Add a static protected "$routing" variable and a static public method "getRouting()" :

class ProjectConfiguration extends sfProjectConfiguration
{
  static protected
    $routing = null # Symfony routing system
    ;
 
 
  /**
   * Returns the routing resource for the active application
   */
  static public function getRouting()
  {
    if (null !== self::$routing)
    {
      return self::$routing;
    }
 
    // If sfContext has an instance, returns the loaded routing resource
    if (sfContext::hasInstance() && sfContext::getInstance()->getRouting())
    {
      self::$routing = sfContext::getInstance()->getRouting();
    }
    else
    {
      // Initialization
      if (!self::hasActive())
      {
        throw new sfException('No sfApplicationConfiguration loaded');
      }
      $appConfig = self::getActive();
      $config = sfFactoryConfigHandler::getConfiguration($appConfig->getConfigPaths('config/factories.yml'));
      $params = array_merge(
        $config['routing']['param'],
        array(
          'load_configuration' => false,
          'logging'            => false,
          'context'            => array(
            'host'      => sfConfig::get('app_host',   'localhost'),
            'prefix'    => sfConfig::get('app_prefix', sfConfig::get('sf_no_script_name') ? '' : '/'.$appConfig->getApplication().'_'.$appConfig->getEnvironment().'.php'),
            'is_secure' => sfConfig::get('app_host',   false),
          ),
        )
      );
      $handler = new sfRoutingConfigHandler();
      $routes = $handler->evaluate($appConfig->getConfigPaths('config/routing.yml'));
      $routeClass = $config['routing']['class'];
      self::$routing = new $routeClass($appConfig->getEventDispatcher(), null, $params);
      self::$routing->setRoutes($routes);
    }
 
    return self::$routing;
  }
}
 

In the method, the 'context' parameter needs further explanations.

In a classic context, symfony is loaded after a web request. Sample: https://www.example.com/frontend_dev.php/products

This request provides data that are reused when sf needs to generate a new dynamic url:

In the cli mode, as Symfony is not loaded by a request, you have to provide these parameters (prefix can be guessed).

In app.yml, add:

all:
  host: www.example.com
  is_secure: false
 

Now, let's see how to generate the link. If your routing.yml file looks like it:

product_show:
  url:     /products/:category_slug/:slug
  class:   sfDoctrineRoute
  options: { model: Product, type: object }
  # ...
 

Then building the link is as simple as:

class Product extends BaseProduct
{
  /**
   * Returns the public url
   */
  public function getUrl()
  {
    return ProjectConfiguration::getRouting()->generate('product', $this, true);
  }
 
 
  /* ... */
}
 

Regards.

by Éric Rogé on 2009-12-11, tagged cli  routing 
(2 comments)

How to send Emails from the model or from a task

For sf1.3/1.4 only.

It seems that the symfony mailer system is tied to the sfContext resource via the factory handler (correct me if I'm wrong).

It might be a problem when you want to send an email from one of your model methods because sfContext has no instance in cli mode.

It can also be an issue if you want to build a task that send emails. Sample: a nightly cron job that alert users by email when their free trial period is over.

The best way I've found is to provide an access to the mailer system linked to the ProjectConfiguration class.

Add a static protected "$mailer" variable and a static public method "getMailer()" :

class ProjectConfiguration extends sfProjectConfiguration
{
  static protected
    $mailer  = null # Symfony mailer system
    ;
 
  /**
   * Returns the project mailer
   */
  static public function getMailer()
  {
    if (null !== self::$mailer)
    {
      return self::$mailer;
    }
 
    // If sfContext has instance, returns the classic mailer resource
    if (sfContext::hasInstance() && sfContext::getInstance()->getMailer())
    {
      self::$mailer = sfContext::getInstance()->getMailer();
    }
    else
    {
      // Else, initialization
      if (!self::hasActive())
      {
        throw new sfException('No sfApplicationConfiguration loaded');
      }
      require_once sfConfig::get('sf_symfony_lib_dir').'/vendor/swiftmailer/classes/Swift.php';
      Swift::registerAutoload();
      sfMailer::initialize();
      $applicationConfiguration = self::getActive();
 
      $config = sfFactoryConfigHandler::getConfiguration($applicationConfiguration->getConfigPaths('config/factories.yml'));
 
      self::$mailer = new $config['mailer']['class']($applicationConfiguration->getEventDispatcher(), $config['mailer']['param']);
    }
 
    return self::$mailer;
  }
}
 

Now if you want to send an email from a task or from a model class:

ProjectConfiguration::getMailer()->composeAndSend(/* your email params*/);
 

In your unit tests, if you want to test which emails have been sent:

$sentEmails = ProjectConfiguration::getMailer()->getLogger()->getMessages();
 

Regards.

by Éric Rogé on 2009-12-11, tagged mailer  swift 
(2 comments)

Fetch a Random Record with Doctrine

To fetch a random user:

$userCount = Doctrine::getTable('User')->count();
$user = Doctrine::getTable('User')
  ->createQuery()
  ->limit(1)
  ->offset(rand(0, $userCount - 1))
  ->fetchOne();
 
by ericfreese on 2009-11-16, tagged doctrine  random  record 
(7 comments)

Adding stemming to Doctrine Searchable

Stemming reduces a word to its common stem, so if you search on Google for "Knit", you'll also be searching for "Knits" and "Knitting". Sadly, Doctrine's Searchable behaviour doesn't support stemming out of the box, but it's pretty easy to add.

I've chosen to use the PECL 'stem' extension in this implementation because it contains the excellent Porter2 stemmer. You may be able to find a free pure PHP implementation of the algorithm somewhere, or decide that the original Porter stemmer is good enough. I couldn't.

You'll need to compile and install the PECL stem extension, and make sure it's loaded.

The stemming analyzer class

Create lib/MyAnalyzer.class.php:

    class MyAnalyzer extends Doctrine_Search_Analyzer_Standard
    {
 
      public function analyze($text)
      {
 
        // First run the standard analyzer. This will do a lot of tidying on 
        // the text and remove stopwords, so we only need to do the stemming
        $text = parent::analyze($text);
 
        foreach ($text as &$keyword) {
          $keyword = stem_english($keyword);
        }
 
        return $text;
      }
    }
 

Making your project use it

First, create a doctrine listener, lib/MyConnectionListener.class.php

    class MyConnectionListener extends Doctrine_EventListener
    {
 
      public function postConnect(Doctrine_Event $e)
      {
          $entity_table = Doctrine_Core::getTable('Entity');
          $entity_table->getTemplate('Doctrine_Template_Searchable')
                       ->getPlugin()
                       ->setOption('analyzer', new MyAnalyzer);
        }
      }
    }
 

Then edit your ProjectConfiguration class:

    public function configureDoctrineConnection(Doctrine_Connection $conn)
    {
      $conn->addListener(new MyConnectionListener)
    }
 

Finally, in your frontend code, make sure you stem your search terms before running a query.

    $terms = 'my search string';
 
    $stemmer = new MyAnalyzer();
    $terms = join($stemmer->analyze($terms), ' ');
 
    $results = Doctrine::getTable('Entity')->search($terms);
 

A couple of notes:

by Matt Robinson on 2009-11-14, tagged doctrine  search  searchable  stemming 

Master & Slave connections in Doctrine 1.2 and Symfony 1.3

Doctrine's built-in support for multiple connections follows a baffling use-case that allows different Model objects to be saved on different connections. A more common usage of multiple connections is to send read commands to a slave database and write commands to a master. Here's how to do it.

  1. Declare your connections in databases.yml as usual, name one of them master and one of them slave.

  2. Override Doctrine's default Query and Record objects. Edit config/ProjectConfiguration.class.php and add the following method:

    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. Create your MyQuery class in lib/MyQuery

    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. Create your MyRecord class in lib/MyRecord.class.php

    abstract class MyRecord extends sfDoctrineRecord
    {
        public function save(Doctrine_Connection $conn = null)
        {
          $conn = Doctrine_Manager::getInstance()->getConnection('master');
          parent::save($conn);
        }
    }
     
  5. If you're using a Doctrine behaviour that creates new tables, like the Versionable behaviour, tell it to use MyRecord too.

    Entity:
      ActAs:
        Versionable:
          builderOptions:
            baseClassName: MyRecord
     
  6. Finally, rebuild your model

    ./symfony doctrine:build --all-classes
     

If you have multiple slaves, modify the ProjectConfiguration to pick one on startup then change the method on your Query object to retrieve it.

by Matt Robinson on 2009-11-13, tagged database  doctrine 

Get module and action from an absolute url

I happened to come across this problem - parse referer to get its module and action. I thought there was an official way to do this in Symfony. But it turns out not(At least 1.0, maybe I didn't dig deep enough, then please inform me). I don't like to parse it by myself either, so after digging into symfony's source code. I come up with below solution:

function getModuleActionFrom($absurl) {
        $_uri = $_SERVER['REQUEST_URI'];
        if (isset($_SERVER['PATH_INFO'])) $_path = $_SERVER['PATH_INFO'];
        $_SERVER['REQUEST_URI'] = parse_url($absurl, PHP_URL_PATH);
        if (isset($_SERVER['PATH_INFO'])) $_SERVER['PATH_INFO'] = null;
        $tempReq = new sfWebRequest();
        $tempReq->initialize(sfContext::getInstance());
        $result  = array(
            'module' => $tempReq->getParameter('module'),
            'action' => $tempReq->getParameter('action')
        );
 
        $_SERVER['REQUEST_URI'] = $_uri;
        if (isset($_SERVER['PATH_INFO'])) $_SERVER['PATH_INFO'] = $_path;
        // I didn't notice sfRouting sets currentRouteName when it parses url, so ...
        $tempReq->initialize(sfContext::getInstance());
 
        return $result;
}
 

This function doesn't check whether the url belongs to the current application, as sfWebRequest doesn't do that.

by Kail Zhang on 2009-11-13, tagged url 

Doctrine custom grouping

This is how to create a complex grouping using doctrine query.

sample sql statement

    select ... from ... where x = 1 and (a in(1,2,3) or a is not null)
 

Extend Doctrine_Query

    class myDQ extends Doctrine_Query
    {
      public static function create($conn = null) {
        return new myDQ($conn);
      }
 
      public function toGroup()
      {
        $where = $this->_dqlParts['where'];
        if (count($where) > 0) {
          array_splice($where, count($where) - 1, 0, '(');
          $this->_dqlParts['where'] = $where;
        }
 
        return $this;
 
      }
 
      public function endGroup()
      {
        $where = $this->_dqlParts['where'];
        if (count($where) > 0) 
        {
          $where[] = ')';
          $this->_dqlParts['where'] = $where;
        }  
 
        return $this;
      }
 
    }
 

Usage

    $q = myDQ::create()
      ->from('Some s')
      ->where('s.x = ?', 1)
      ->andWhereIn('s.a', array(1,2,3))
      ->toGroup()
      ->orWhere('s.a is not null')
      ->endGroup();
 
by ken marfilla on 2009-11-11, tagged doctrine  grouping  parenthesis  query 

Tired to type "php symfony" in your terminal ?

Yeah, so am I.

Here's how to create a "sf" command in your terminal that will do exactly the same job than when you use the "php symfony xxx" command.

There's many ways to do it, here's mine, it should works on all unix systems.

In the "/usr/bin" directory, create an "sf" file with this content :

#!/usr/bin/env php
<?php
 
// This file provides the cli "sf" shortcut instead of "php symfony" in syfony projects
 
if (file_exists('config/ProjectConfiguration.class.php'))
{
  require_once('config/ProjectConfiguration.class.php');
  $dir = sfCoreAutoload::getInstance()->getBaseDir();
}
else
{
  die('You must be in a symfony project directory.'."\n");
}
include($dir.'/command/cli.php');
 

Be carefull, don't forget the first line !

Now, open the rights on execution for the file: "sudo chmod +x /usr/bin/sf"

That's it, you're done. Go in a symfony project and try your brand new "sf" command in your terminal.

May the laziness be with you !

[UPDATE]

Pedro Casado has reported in comments a cleaner method that uses the native bash configuration file :

It worked pretty great for me.

[/UPDATE]

by Éric Rogé on 2009-10-27, tagged cli  lazy  terminal 
(6 comments)

Globally format forms

If you are like me, you see the nice Forms chapter, but when you follow the instructions, your forms don't look nice like the examples.

I gleaned the following techniques from various forum posts. I take NO credit for this, it's heavily snatched from other sources.

First, create your format class, which lays out all forms. I use tables in my design, but you can find examples of div based versions elsewhere, )such as here).

/lib/sfWidgetFormSchemaFormatterCustom.class.php:

<?php
class sfWidgetFormSchemaFormatterCustom extends sfWidgetFormSchemaFormatterTable
{
  protected
    $rowFormat       = "<tr class='formrow'>\n  <th class='formheader'>%label%:</th>\n  <td class='forminput'>%error%%field%%help%%hidden_fields%</td>\n</tr>\n",
    $errorRowFormat  = "<tr><td colspan='formerror' colspan=\"2\">\n%errors%</td></tr>\n",
    $helpFormat      = '<br />%help%',
    $decoratorFormat = "<table class='form'>\n  %content%</table>";
}
 

The key here is to insert into each HTML element the global classes to format your code. Above, you can see I added the 'form' class to the table, 'formheader' to the header rows, and 'forminput' for each field.

Next, you need to globally enable this formatter for all forms:

/config/ProjectConfiguration.class.php:

<?php
 
require_once dirname(dirname(__FILE__)).'/lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();
 
class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    // for compatibility / remove and enable only the plugins you want
    $this->enableAllPluginsExcept(array('sfDoctrinePlugin', 'sfCompat10Plugin'));
    sfWidgetFormSchema::setDefaultFormFormatterName('Custom');
  }
}
 

Naturally, make sure you manage your plugins appropriately. They key line is the:

sfWidgetFormSchema::setDefaultFormFormatterName('Custom');
 

Now, customize your CSS files:

.form {
  padding-top:15px;
}
.formrow {
 
}
.formheader {
  text-align: right;
  font-weight: bold;
  padding-right: 10px;
  vertical-align: middle;
}
td.forminput input {
  width: 300px;
}
.formerror {
 
}
 

Hope this helps others.

by Mike Crowe on 2009-10-24, tagged form  formatter  sfform  widget 

Verify form field matches existing field: sfValidatorPropelExisting

This snippet may be used as a post-validator to verify that a column exists within a Propel table. It's a trivial derivative of the sfValidatorPropelUnique (so much so I left the copyrights in place). It's strictly a that validator with the logic reversed.

/*
 * This file is part of the symfony package.
 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
/**
 * sfValidatorPropelExisting validates that the uniqueness of a column.
 *
 * Warning: sfValidatorPropelExisting is susceptible to race conditions.
 * To avoid this issue, wrap the validation process and the model saving
 * inside a transaction.
 *
 * @package    symfony
 * @subpackage validator
 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
 * @version    SVN: $Id: sfValidatorPropelExisting.class.php 13249 2008-11-22 16:10:11Z fabien $
 */
class sfValidatorPropelExisting extends sfValidatorSchema
{
  /**
   * Constructor.
   *
   * @param array  $options   An array of options
   * @param array  $messages  An array of error messages
   *
   * @see sfValidatorSchema
   */
  public function __construct($options = array(), $messages = array())
  {
    parent::__construct(null, $options, $messages);
  }
 
  /**
   * Configures the current validator.
   *
   * Available options:
   *
   *  * model:              The model class (required)
   *  * column:             The unique column name in Propel field name format (required)
   *                        If the uniquess is for several columns, you can pass an array of field names
   *  * field               Field name used by the form, other than the column name
   *  * primary_key:        The primary key column name in Propel field name format (optional, will be introspected if not provided)
   *                        You can also pass an array if the table has several primary keys
   *  * connection:         The Propel connection to use (null by default)
   *  * throw_global_error: Whether to throw a global error (false by default) or an error tied to the first field related to the column option array
   *
   * @see sfValidatorBase
   */
  protected function configure($options = array(), $messages = array())
  {
    $this->addRequiredOption('model');
    $this->addRequiredOption('column');
    $this->addOption('field', null);
    $this->addOption('primary_key', null);
    $this->addOption('connection', null);
    $this->addOption('throw_global_error', false);
 
    $this->setMessage('invalid', 'An object with the same "%column%" already exist.');
  }
 
  /**
   * @see sfValidatorBase
   */
  protected function doClean($values)
  {
    if (!is_array($values))
    {
      throw new InvalidArgumentException('You must pass an array parameter to the clean() method (this validator can only be used as a post validator).');
    }
 
    if (!is_array($this->getOption('column')))
    {
      $this->setOption('column', array($this->getOption('column')));
    }
 
    if (!is_array($field = $this->getOption('field')))
    {
      $this->setOption('field', $field ? array($field) : array());
    }
    $fields = $this->getOption('field');
 
    $criteria = new Criteria();
    foreach ($this->getOption('column') as $i => $column)
    {
      $name = isset($fields[$i]) ? $fields[$i] : $column;
      if (!array_key_exists($name, $values))
      {
        // one of the column has be removed from the form
        return $values;
      }
 
      $colName = call_user_func(array(constant($this->getOption('model').'::PEER'), 'translateFieldName'), $column, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME);
 
      $criteria->add($colName, $values[$name]);
    }
 
    $object = call_user_func(array(constant($this->getOption('model').'::PEER'), 'doSelectOne'), $criteria, $this->getOption('connection'));
 
    // if no object or if we're updating the object, it's ok
    if (!is_null($object) && !$this->isUpdate($object, $values))
    {
      return $values;
    }
 
    $error = new sfValidatorError($this, 'invalid', array('column' => implode(', ', $this->getOption('column'))));
 
    if ($this->getOption('throw_global_error'))
    {
      throw $error;
    }
 
    $columns = $this->getOption('column');
 
    throw new sfValidatorErrorSchema($this, array($columns[0] => $error));
  }
 
  /**
   * Returns whether the object is being updated.
   *
   * @param BaseObject  $object   A Propel object
   * @param array       $values   An array of values
   *
   * @return Boolean     true if the object is being updated, false otherwise
   */
  protected function isUpdate(BaseObject $object, $values)
  {
    // check each primary key column
    foreach ($this->getPrimaryKeys() as $column)
    {
      $columnPhpName = call_user_func(array(constant($this->getOption('model').'::PEER'), 'translateFieldName'), $column, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_PHPNAME);
      $method = 'get'.$columnPhpName;
      if (!isset($values[$column]) or $object->$method() != $values[$column])
      {
        return false;
      }
    }
 
    return true;
  }
 
  /**
   * Returns the primary keys for the model.
   *
   * @return array An array of primary keys
   */
  protected function getPrimaryKeys()
  {
    if (is_null($this->getOption('primary_key')))
    {
      $primaryKeys = array();
      $tableMap = call_user_func(array(constant($this->getOption('model').'::PEER'), 'getTableMap'));
      foreach ($tableMap->getColumns() as $column)
      {
        if (!$column->isPrimaryKey())
        {
          continue;
        }
 
        $primaryKeys[] = call_user_func(array(constant($this->getOption('model').'::PEER'), 'translateFieldName'), $column->getPhpName(), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_FIELDNAME);
      }
 
      $this->setOption('primary_key', $primaryKeys);
    }
 
    if (!is_array($this->getOption('primary_key')))
    {
      $this->setOption('primary_key', array($this->getOption('primary_key')));
    }
 
    return $this->getOption('primary_key');
  }
}
 
by Mike Crowe on 2009-10-24, tagged propel  validator