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.

Popular snippets

Forward in filters

The regular use of forward is causing double content rendering and it took me a while to find the proper solution so here it is:

<?php
 
class SiteFilter extends sfFilter
{
 
  public function execute($filterChain)
  {
    $context = $this->getContext();
 
    if($this->isFirstCall()){
      if(true){ // put some condition in here
        $context->getController()->forward('module', 'action');
        throw new sfStopException(); // the solution
      }
    }
 
    $filterChain->execute();
  }
}
 

As you can see sfStopException() is the key.

by rozwell on 2012-06-08, tagged content  double  filter  filters  forward  rendering 

Thumbnails creation in view (like sorl in django)

function thumbnail($path, $maxWidth, $maxHeight, $params=null)
{
    $path = ($path{0} == '/' ? '' : '/') . $path;
 
    $maxWidth = (int) $maxWidth;
    $maxHeight = (int) $maxHeight;
 
    $fileName = @end(explode('/', $path));
 
    $thumbsDir = sfConfig::get('sf_web_dir').'/images/thumbs';
    if(!file_exists($thumbsDir)) {
        mkdir($thumbsDir);
        chmod($thumbsDir, 0777);
    }
 
    $thumbName = $fileName.'-'.$maxWidth.'x'.$maxHeight.'.png';
    $thumbPath = '/images/thumbs/'.$thumbName;
 
    $sourcePath = sfConfig::get('sf_web_dir').$path;
    $destPath = sfConfig::get('sf_web_dir').$thumbPath;
 
    if(!file_exists($destPath)) {
        $t = new sfThumbnail($maxWidth, $maxHeight);
        $t->loadFile($sourcePath);
        $t->save($destPath, 'image/png'); 
    }
 
    $path = $thumbPath;
    return image_tag($path, $params);
 
}
 

Usage similar to image_tag, directory web/images/thumbs with chmod 0777 need to be created. Depending on sfThumbnailPlugin

example:

echo thumbnail('bigPhoto.png', 100, 100, 'alt=myBigPhoto');
 
by Adam Zieliński on 2010-03-06, tagged thumbnail 

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 

Using timers to profile functions speed

This snippet allows you to evaluate functions speed and display them in symfony debug bar.

$timer = sfTimerManager::getTimer ('ldap_request'); // Create and start a new timer
 
// do some stuff
 
$timer->addTime (); // stop and store the timer value
 
by Arnaud Didry on 2009-06-22, tagged performance  profile  speed 
(2 comments)

Case-unsensitive filters with Symfony and Doctrine

Problematic

Environnement : Symfony 1.2 - sfDoctrinePlugin

Context : doctrine:generate-admin modules

Problème : filters case-sensitive

Objective : make filters case-unsensitive

Analysis

Every classes inherited from sfFormFilter alse inherite from BaseFormFilter, which is modifyable in every project. In the Doctrine case, sfFormFilterDoctrine and BaseFormFilterDoctrine. This class "Base" is here :

lib/filter/doctrine/BaseFormFilterDoctrine.class.php
 

The aimed method is the one which works on text fields. The line we want is the line 209 of the file symfony/lib/plugins/sfDoctrinePlugin/lib/form/sfFormFilterDoctrine.class.php :

$query->addWhere('r.' . $fieldName . ' LIKE ?', '%' . $values['text'] . '%');
 

So a solution should be the override this method replacing LIKE by ILIKE. Let's try :

Solution

Overriding the class sfFormFilterDoctrine's method addTextQuery() should give you this kind of stuff if there is no more work done on it :

# lib/filter/doctrine/BaseFormFilterDoctrine.class.php
 /**
  * Project filter form base class.
  *
  * @packagefilters
  *
  * @versionSVN: $Id: sfDoctrineFormFilterBaseTemplate.php 11675 2008-09-19 15:21:38Z fabien $
  */
 abstract class BaseFormFilterDoctrine extends sfFormFilterDoctrine
 {
   public function setup()
   {
   }
 
   // totally inspirated from lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/form/sfFormFilterDoctrine.class.php
   // function addTextQuery()
   // line modified : 209
   // be careful on symfony updates to update this method if there are changes
   protected function addTextQuery(Doctrine_Query $query, $field, $values)
   {
     $fieldName = $this->getFieldName($field);
 
     if (is_array($values) && isset($values['is_empty']) && $values['is_empty'])
     {
       $query->addWhere('r.' . $fieldName . ' IS NULL');
     }
     else if (is_array($values) && isset($values['text']) &&  != $values['text'])
     {
       // Here is the change, with "ILIKE" instead of "LIKE"
       $query->addWhere('r.' . $fieldName . ' ILIKE ?', '%' . $values['text'] . '%');
     }
   }
 }
 

References

The original documentation in french is available here :

http://www.e-glop.net/main/Filtres_case-unsensitive_dans_Symfony

by Baptiste SIMON on 2009-06-15, tagged caseinsensitive  casesensitive  filters  ilike 
(1 comment)

Auto-detect format (from URL and/or Acceptable content types)

As of Symfony 1.1, using alternative formats has become simple. However, automatic use of these formats is now disabled by default.

In the URL

Add support for sf_format in your routing factory :

/apps/myapp/config/factories.yml

all:
  routing:
    ...
    param:
      ...
      suffix: .:sf_format
 

This way, each time you go to http://mysite/module.format, the corresponding format will be detected and used.

Following URLs will automatically work and be rendered as XML : - http://mysite/module.xml # renders indexSuccess.xml.php - http://mysite/module/action.xml # renders actionSuccess.xml.php - http://mysite/module/action/param/value.xml # equals http://mysite/module/action.xml?param=value

If you want the suffix not to be mandatory (e.g. http://mysite/module/action == http://mysite/module/action.html) you'll have to add "sf_format: html" in you routing rules parameters. This is required if you want your old calls to link_to() and url_for() (who don't specifies sf_format parameter) still work.

/apps/myapp/config/routing.yml

default:
  url:   /:module/:action/*
 

myTemplate.php

link_to('hey !', 'module/action') // Produces an error !
link_to('hey !', 'module/action?sf_format=html') // Works, but hey, you'll have to add this to *all* your links :/
 

Add the default format :

/apps/myapp/config/routing.yml

default:
  url:   /:module/:action/*
  param: { sf_format: html }
 

myTemplate.php

link_to('hey !', 'module/action') // Works :)
link_to('hey !', 'module/action?sf_format=xml') // Works too, of course :)
 

Using the Accept HTTP header

We can use a filter, or plug to an even in the ProjectConfiguration class. My choice was a filter.

/apps/myapp/config/filters.yml

# insert your own filters here
detect_format:
  class: DetectFormatFilter
 

/lib/DetectFormatFilter.php

class DetectFormatFilter extends sfFilter
{
 
  public $default_formats = array(
    'html' => array('text/html', 'application/xhtml+xml'),
  );
 
  public function execute($filterChain)
  {
    $request = $this->context->getRequest();
 
    // Only if format is not already defined
    if (!$request->getRequestFormat()) {
 
      // Complete with the filter default formats
      foreach ($this->default_formats as $format => $mimeTypes) {
        foreach ($mimeTypes as $mimeType) {
          if (!$request->getFormat($mimeType)) {
            $request->setFormat($format, $mimeType);
          }
        }
      }
 
      // Detect format depending on acceptable contant types : first is prefered
      foreach ($request->getAcceptableContentTypes() as $mimeType) {
        if ($format = $request->getFormat($mimeType)) {
          $request->setRequestFormat($format);
          break;
        }
      }
 
    }
 
    // execute next filter
    $filterChain->execute();
  }
 
}
 

Take a look at the fact we automatically add "html" to the formats, attached to the two standard mime types. This could be done by adding the corresponding options to the request part of factories.yml, but I wanted this filter to be "ready-to-use" without touching other parts of configuration. "text/html" must be handled apart because as "html" is the default format, Symfony just does not handle it. So when we browse "Accept" headers, we will pass on "text/html", and just skip it because it's not configured. Without adding this part, "html" format would never been rendered, quite annoying isn't it ;)

With this configuration, you can just add the "Accept" header to say you want an XML content, and your ".xml.php" templates will then be rendered.

An interesting evolution of this filter would be to allow it to test if the format can be rendered, and if not, fall back to a default one.

Conclusion

It's quite easy to enable back auto-detection of the format. However, as Fabien says in the ticket 4920, Accept header is sometimes unreliable, and some badly configured browsers could make you think they prefer XML, which will not be the case of the user behind this browser ;)

That said, even if the URL suffix method is probably the best choice, this auto-detection method works and could be useful in some projects...

by Nicolas Chambrier on 2009-03-21, tagged accept  filter  format  routing 
(2 comments)

How to trigger an event from a Model Class

[UPDATE]

I've found a cleaner way to deal with it :

sfProjectConfiguration::getActive()->getEventDispatcher()->notify(new sfEvent($this, 'foo.bar'))
 

The old version of this snippet is now useless, you don't need to read it.

[/UPDATE]

This snippet is not a tutorial on symfony events, but you can find some documentation in the book and or in this good tutorial.

The event dispatcher is a very powerfull tool, but you might meet the same issue than me if you want to use it from a Model Class.

Let's take the classic Author/Article project sample.

When an article is added, it might be interesting to trigger an "article.new" event. This event coud be listened by different actors.

Like for instance :

The most obvious place to trigger the event is the Article save() method. Here is the code for Doctrine (the logic is exactly the same for Propel):

Article.class.php

public function save(Doctrine_Connection $conn = null)
{
  if ($this->isNew())
  {
    // Let's trigger the event
    sfContext::getInstance()->getEventDispatcher()->notify(new sfEvent($this, 'article.new'));
  }
  return parent::save($conn);
}
 

Unfortunatly, this code won't work. If you try the "symfony doctrine:build-all-reload" command, here is the error that will be displayed :

Cli

sfException: The "default" context does not exist.
 

Why ? Because symfony tasks don't initialise the sfContext class. And for the moment (symfony version 1.2.4) I don't think there's a clean way to access to the dispatcher object from a Doctrine Class.

Here is my solution : let's build a (very) little singleton class that will provide the dispatcher ressource everywhere in the project, and even when sfContext is not avaible

yourProjectRoot/lib/Fedex.class.php

class Fedex
{
  static protected $instance;
  protected $dispatcher;
  /**
   * The singleton logic http://en.wikipedia.org/wiki/Singleton_pattern#PHP_5
   */
  static public function getInstance()
  {
    if (!self::$instance instanceof self)
    {
      self::$instance = new self;
    }
    return self::$instance;
  }
 
  public function setEventDispatcher(sfEventDispatcher $dispatcher)
  {
    $this->dispatcher = $dispatcher;
  }
 
  public function getEventDispatcher()
  {
    return $this->dispatcher;
  }
}
 

Of course, you first have to give to the Fedex class a link the delivery ressource. Let's do it with the very first class called by the script of the project :

yourProjectRoot/config/ProjectConfiguration.class.php

public function setup()
{
  // add after all your setup stuffs
 
  require_once(dirname(__FILE__).'/../lib/Fedex.class.php');
  Fedex::getInstance()->setEventDispatcher($this->dispatcher);
}
 

That's it, you're done. The Fedex class will deliver the dispatcher ressource everywhere in your project through Fedex::getInstance()->getEventDispatcher() And it works even when the sfContext class is not avaible

Article.class.php

public function save(Doctrine_Connection $conn = null)
{
  if ($this->isNew())
  {
    Fedex::getInstance()->getEventDispatcher()->notify(new sfEvent($this, 'article.new'));
  }
  return parent::save($conn);
}
 

The important thing is that this little class doesn't modify any part of the symfony event system. It just provide you an alternative way to access to it.

by Éric Rogé on 2009-02-23, tagged doctrine  event  orm 
(5 comments)

Create Automatic Canonical Link Tags

<link rel="canonical" href="<?php echo url_for($sf_context->getRouting()->getCurrentInternalUri(true), true); ?>" />
 
by Erik Osterman on 2009-02-19, tagged canonical  link  tags 
(1 comment)

Noscript "off" in dev with "prod" controller and "on" in prod with the same settings.yml file

[Edit 06/02/09] Enabling the apache mod_rewrite is a little faster than using this snippet. ;)

Well if you are using Windows + Wamp for your dev, the "noscript" setting (settings.yml) does not work well when you are using the prod controller to test your application with you dev computer. (if mod_rewrite is not installed)

So i wanted to deactivate this settings in dev environment with the prod controller and having it on in prod with the prod controller without having 2 different settings.yml files.

So here here a small tip for this:

prod:
  .settings:
    no_script_name:         <?php echo ((isset($_SERVER['HTTP_HOST']) && strstr($_SERVER['HTTP_HOST'], 'dev') ? 'off' : 'on')) . "\n"; ?>
    logging_enabled:        on
 

This code assumes that your dev url contains "dev" like http://dev.blogsnippets.com.

by Loïc Vernet on 2009-01-19, tagged dev  noscript  prod  settings 
(2 comments)

Javascript loading optimalization

Hi!

I am using a lot of hand written jscript codes due to the required behavior not found in any javascript framework. I've created my Objects in distinct files, although some of them are quite small ( <1kb ). So to make it fast, I've written a filter class that actually parses the files found in the /js directories having *.js suffix. It does not only parse them together but removes whitespaces, comments, indentations so the sum filesize gets a bit compressed. The filter also includes the final file into the response so there is no need to include any *.js ( inside /js ) in a template.

<?php
 
    class JavascriptParser extends sfFilter
    {
      public function execute($filterChain)
      {
        $filterChain->execute();
 
 
        $fp = fopen( "js/compiled.js", "w" );
 
        JavascriptParser::getJSFiles( "js", $fp );
 
        fclose ( $fp );
 
        $response = $this->getContext()->getResponse();
        $content = $response->getContent();
        if (false !== ($pos = strpos($content, '</head>')))
        {
          $html = "<script type='text/javascript' src='/js/compiled.js'></script>";
 
          if ($html)
          {
            $response->setContent(substr($content, 0, $pos).$html.substr($content, $pos));
          }
        }
 
      }
 
      public function getJSFiles( $dir, &$fp )
      {
        $hDir = opendir( $dir );
        while( ( $filename = readdir( $hDir ) ) !== false )
        {
            if ( is_dir( $filename ) )
            {
            }
            else if ( is_dir( $dir."/".$filename ) )
                JavascriptParser::getJSFiles( $dir."/".$filename, $fp );                
            else if ( strpos( $filename, ".js" ) !== false && $filename != "compiled.js" )
            {
                $tmpFile = fopen( $dir."/".$filename, "r" );
                $data = fread( $tmpFile, filesize( $dir."/".$filename ) );
 
                $data = preg_replace( "'\/\*.*?\*\/'si", "", $data  );
                $data = preg_replace( "'//.*?\n'si", "", $data );
                $data = preg_replace( "'[ \t]+'", " ", $data );
 
                fwrite( $fp, " \n".$data );
                fclose( $tmpFile );
            }
        }
        closedir( $hDir );  
      }
    }
 
?>
 

The same should be done with *.css files, since they are even smaller and loading many small files takes much more time then loading one big.

Best Regards

by Kormany Gabor on 2008-04-13, tagged filter  javascript 
(1 comment)