Skip to content
May 3 12

Mirroring the current application in javascript

by Sjors Rijsdam

Ever wondered why that awesome bit of AJAX stuff you’ve build in your Symfony site just doesn’t seem to be working when the client is testing it but it does when you are testing it? Ever felt like an ass when it turned out you forgot to remove the ‘frontend_dev.php’ bit from your AJAX urls that you put in there so you could more easily debug the errors in the responses? I have, and recently I decided to end that. So I wrote a nifty little Javascript function that automagically extracts the application name from the document url and prepends it when needed to the URL that it gets as a parameter.

function sfUrl (url) {
  var docLoc         = document.location,
      docPath        = docLoc.pathname,
      docHost        = docLoc.hostname,
      docHostRegExp  = new RegExp('^http(s)?://' + docHost, 'i'),
      appNameRegExp  = /^\/([a-z0-9_-]+)\.php/i,
      retVal         = url, //return the incoming value by default
      docAppMatches  = docPath.match(appNameRegExp),
      docAppName     = docAppMatches !== null ? docAppMatches[0] : '',
      urlAppName, urlAppMatches,
      urlHasHTTP     = url.match(/^http(s)?:\/\//i) !== null,
      urlHostMatches = url.match(docHostRegExp),
      urlHasSameHost = urlHostMatches !== null,
      urlDocHost     = urlHasSameHost ? urlHostMatches[0] : '';

  // if the url starts with http only continue if it is the same hostname
  // otherwise only continue if it begins with a slash
  if (urlHasSameHost || (!urlHasHTTP && url.substring(0,1) === '/')) {
    url = url.replace(docHostRegExp, '');

    urlAppMatches = url.match(appNameRegExp);
    urlAppName = urlAppMatches !== null ? urlAppMatches[0] : '';

    // only prepend the app name from the document url if no app was present in the url itself
    if (urlAppName === '') {
     url = docAppName + url;
    }

    // if the original url has a full domain in front of it, place it back for good measure
    if (urlHasHTTP) {
      url = urlDocHost + url;
    }

    retVal = url;
  }

  return retVal;
}

Basically what it does is check wether the url in the parameter does not have a hostname that is different from where we are operating and doesn’t already have a application defined in it. When both those conditions are met and there is an application defined in the document url, it takes that and places that in the url and returns it.

Now as long as you use this function on every url, you never have to worry about mismatching applications.

Happy coding!

Mar 22 12

Caching forms with CSRF tokens

by Sander Coolen

Caching is always a good idea, but it can lead to unexpected results or cause complex, or at least hard to resolve, issues. Caching is not just a thing that you can turn on, but an extra layer of complexity that needs careful consideration and implementation. Especially the invalidation part. You could choose to sit back and let time stale your cache, but most of the time you need more control over the invalidation process.

Symfony 1.4 brings some good out of the box caching mechanisms of which the View Cache is an easy way to cache your templates or parts of them. The goal of caching is to store content – in case of the View Cache the response body – for a short period of time. Sometimes seconds, or even milliseconds for highly volatile data to up to a day or longer for more static content. You want to relief your application or web server from having to do resource intensive tasks for which it’s tolerable that the outcome doesn’t reflect real-time. Or you might want to cache the results of repetitive tasks to de-stress your soft and hardware. Why would you generate your news overview page every request, if only once a week or so a new article is published? Why would you refresh your twitter feed every request, if only once every few minutes you receive new tweets? The short answer is: you shouldn’t.

Caching is a great tool to speed up your website, but works less well when you have a lot of dynamic personalized pages. It works equally well (in theory), but a cache for every unique visitor is probably a bad idea. Your cache quickly becomes huge, which can lead to a whole array of problems in other areas: storage, scalability, balancing/synchronization, deployment and maintenance etc. Try to find a sweet spot between performance gain and saving bandwidth versus storage requirements and the impact on development.

In our Symfony projects we come across the following issue on a regular basis: perfectly cacheable pages become “uncacheable” because the page contains a form with a CSRF token. Such a token is unique for the a user’s session and therefor unsuitable for caching. Symfony recommends the simplest solution: disabling CSRF protection. As a practical experiment I tried to go for a different – less pragmatic – approach: cache the page including the form plus the exact line and character number of any tokens on said page. To accomplish this I extended the sfViewCacheManager class:

  1. Before setting the cached response replace CSRF tokens (set method)
  2. Store the position of the tokens with the response (so it gets cached as well), for example in a temporary slot
  3. Before returning a response from the cache inject fresh CSRF tokens (get method)

Below you’ll find the code that searches and stores the CSRF tokens:

protected function _replaceCsrfTokens (sfWebResponse $response)
  {
    $content = $response->getContent();
    /*
     * Find all _csrf_token input fields
     */
    if (0 < preg_match_all('/]*name="([^"]*_csrf_token\]?)"[^>]*\/>/msu', $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
      $csrfTokens = array();
      foreach ($matches as $i => $match) {
        /*
         * Grep value attribute that should hold the name of the form
         */
        preg_match('/[^>]*value="([^"]*)"/msu', $content, $match, PREG_OFFSET_CAPTURE, $match[0][1]);
        $formClassName = $match[1][0];
        if (!is_subclass_of($formClassName, 'BaseForm')) {
          /*
           * Ignore if not a BaseForm subclass
           */
          continue;
        }
        $csrfTokens[] = array('class' => $formClassName, 'offset' => $match[1][1]
        );
        $this->_replaceCsrfToken(end($csrfTokens), $content);
      }
      /*
       * Replace content in actual response.
       */
      $this->context->getResponse()->setContent($content);
      /*
       * Use a slot so we have access to this data in get()
       */
      $response->setSlot('_csrf_tokens', $csrfTokens);
    }
  }

Next; in our ViewManager’s get-method we can check for the _crsf_tokens slot and see if we have to inject new tokens. If not, just traverse up to the parent class. If so, insert freshly generated CSRF tokens before returning the serialized response.

I know it has a bit of a hocus-pocus-feel to it, but I have seen it work quite well in a production environment. Anywho, it was a fun and enlightening thing to try.

Mar 1 12

Doctrine – Dynamically disable timestampable behaviour

by Ronald

Sometimes you want to disable the Doctrine timestampable behaviour at runtime. I needed to do this today but did not know how to do it.

A quick search on the net and help of a colleague got me to the following code:

protected function _disableTimestampable()
{
  $listenerChain = $this->getListener();

  $i = 0;

  while ($listener = $listenerChain->get($i))
  {
    if ($listener instanceof Doctrine_Template_Listener_Timestampable)
    {
      $listener->setOption('disabled', true);
      break;
    }
    $i++;
  }
}

Just add it to your to your table class and call it if needed.

Feb 23 12

Setting up a multi-user, multi-project development environment

by Jean-Marie de Boer

In our company a number of developers work together on Symfony projects, so we need a sensible, coherent and predictable development environment. A solution would be to have each developer run a php-enabled webserver on their local machine, but this has some obvious drawbacks when it comes to predictability. By having everybody run their code on a central server we ensure that it works on a reference system, avoiding ‘well it runs on my machine’ type situations. It also means that developers don’t have to worry about maintaining webservers.

Here’s how I created a flexible environment that is low on maintenance.

First of all I set up a DNS server for our local network. It is authorative for the fake .intra top level domain. I might also have chosen .lan, .local or whatever, als long as it’s not a real TLD. The .intra zone has some normal entries for printers and workstations and such, and a wildcard entry that points to the development server. So, if I lookup a nonexistent hostname in that zone, it always resolves to the development box, i.e:

$ dig server1.intra a
server1.intra. 38400 IN A 10.0.0.1
$ dig server2.intra a
server2.intra. 38400 IN A 10.0.0.2

server2 is the development box, so:

$ dig something.intra a
something.intra. 38400 IN A 10.0.0.2
$ dig totally.random.intra a
totally.random.intra. 38400 IN A 10.0.0.2

Ok, so I want to serve some Symfony2 (or other) content now. The development box is a linux server (CentOS FTW ;) ), running the same software as the production servers. In our case that’s Apache/PHP5.3/MySQL and some assorted php modules. Pretty standard stuff. The trick is in configuring apache.

Let’s say we have two developers, John and Jane. They are working on two projects, coolproject and stuffyproject.

In their home directory, each developer has a directory called ‘checkouts’ (or another name of your choice). Inside that directory, they create a directory for each project. How they get the files in there is irrelevant for this article, but it will likely be a Git or Subversion checkout accessed via Samba or NFS. So we have:
/home/john/checkouts/coolproject
/home/john/checkouts/stuffyproject
/home/jane/checkouts/coolproject
/home/jane/checkouts/stuffyproject

Each of these directories contains a documentroot, called ‘web’ in the case of Symfony2.

Now, to have apache to serve content from these directories, rewrites to the rescue! As the last (so I don’t inadvertedly match to greedy) virtual host in my configuration, I create this:

<VirtualHost *:80>
  ServerName *.*.intra

  RewriteEngine on
  RewriteCond %{HTTP_HOST} ^([a-z0-9\-]+)+\.([a-z0-9\-]+)+\.intra$
  RewriteCond /home/%1/checkouts/%2/web -d
  RewriteRule ^(.+) %{HTTP_HOST}$1 [C]
  RewriteRule ^([a-z0-9\-]+)\.([a-z0-9\-].+)\.intra(.*) /home/$1/checkouts/$2/web$3 [L]

  <Directory "/home/*/checkouts/web">
    Options FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>

</VirtualHost>

Basically what this does is map the hostname to a specific user’s project documentroot while also retaining the rest of the URI.
For instance http://john.coolproject.intra/something results in apache opening /home/john/checkouts/coolproject/web/something
Likewise http://jane.stuffyproject.intra/foo/bar results in apache opening /home/jane/checkouts/stuffyproject/web/foo/bar

Since I use AllowOverride All you can stick the Symfony2 .htaccess file in the documentroot and all rewrites function normally, i.e. you frontend controller gets started and the URI is passed to it.

The neat thing about this is that anybody with a home directory can create a virtual host at any time and it will work instantly. If the directory /home/jack/checkouts/testproject/web get created, its will be visible on http://jack.testproject.intra immediately, no further configuration necessary.

Almost there, all that’s left to tackle now is the sharing of data.

In our setup, everybody has their own checkout, but we (usually) share the database. This is simple enough, we have a non-versioned config file with the database credentials in it. But normally, there is also uploaded or generated files associated with that data, so you’d want to share that too. It’s simple enough. Say you have an ‘uploads’ directory in ‘web’. We make sure it’s not versioned but ignored.

Then I create a shared directory somewhere, say for instance /www/coolproject/pages/uploads
After that each user just creates a symlink to it, like so:

ln -s /www/coolproject/pages/uploads /home/jane/checkouts/coolproject/pages/uploads

So any file that Jane uploads is visible in John’s checkout and vice versa. Database and files stay in sync for all users.

In reality our setup is a little bit more complicated than this, as we can also reach the checkouts via external (real) url’s, and we cater for more documentroots such as htdocs, and we may use other directory structures too. But the idea is the same.

You can also use this setup if you are on a *nix box working solo, once you have it set up you can create virtual hosts on the fly, you may never need to open httpd.conf again! ;)

Feb 9 12

How to pass variables to your layout

by Ronald

Symfony offers the possibility to add template code to your layout. Normally this is enough to achieve what you want. But sometimes it would very handy if you could pass variables from an action to the layout. Symfony does not offer you this out of the box but there is an easy way to do it.

The key to this is the sfWebResponse object. Symfony programmers use it all the time to pass variables to the layout. An example is the page title.

  public function executeIndex(sfWebRequest $request)
  {
    $this->getResponse()->setTitle('Page title passed to layout');
    ...
   }

I would like to pass a variable from my action.class.php with the following code:

  public function executeIndex(sfWebRequest $request)
  {
    $this->getResponse()->setExampleVar($foo);
    ...
  }

After that I want to use it in my layout.php like this:

  ...
  <?php $foo = $sf_response->getExampleVar(); ?>
  <?php // do something usefull with $foo ?>
  ...

As the sfWebResponse object does not offer me the setExampleVar() and getExampleVar() methods I will have to extend it.

/lib/response/appWebResponse.class.php

class appWebResponse extends sfWebResponse
{
  private $_foo;
  public function setExampleVar($foo)
  {
    $this->_foo = $foo;
  }

  public function getExampleVar()
  {
    return $this->_foo;
  }
}

As a last step I need to tell Symfony I like to use appWebResponse and not the default sfWebResponse. This can be done in the /app/appname/config/factory.yml config file.

I change

all:
  ...
  response:
    class: sfWebResponse

to

all:
  ...
  response:
    class: appWebResponse

After clearing my cache I should be done.

I know it is a bit of a dirty trick. The variable I added is not really a response variable like a response code or a meta title. On the other hand it saves me from adding slots to a lot of my templates, making them much cleaner and more readable.

Oct 22 11

Symfony Day 11 Cologne

by Sander Coolen

After a tiresome and informative 9 hours at Symfony Day in Cologne I had the plan of writing this post in the ICE back to Amsterdam. Unfortunately the letter S key of the laptop I brought didn’t work. It’s hard writing about Symfony without using the letter S. You can talk about anything, but not Symfony and similar stuff.

It was the third – and last in its current form, as was announced afterwards – Symfony Day in Cologne. Next year it will be succeeded by a Symfony Live event which will be held in a bigger venue and in a bigger city. Berlin!
The current location is the Komed MediaPark; a convention centre at walking distance of the Main Train Station.

After a slow and little bit chaotic start the conference was kicked-off by Igor Wiedler. He talked about Silex. A micro-framework build on top of the Symfony2 components for which he is one of the lead developers. Silex is a lean and mean framework that enables you to map dynamic routes to controllers in a single step. Its lightweight stuff (a single Phar include) to build lightweight apps. Looks quite suitable for prototyping and your personal once-off mini projects.

Next up was Marc Weistroff who had a good talk about clean pragmatic code and some best practices and programming principles. He spoke about Separation of Concerns, a recurring theme throughout the day, probably the fundamental principle behind the Symfony Components architecture.

The third session was a duo talk by Stefan Koopmanschap and Christian Schaefer. Pretentious blatter versus modest and insightful about sums it up. I promise I will actually write a shitload of constructive criticism on Joind.In actually.

After the lunch… no wait… let’s talk about during the lunch:
Eat all the things!

Thomas Rabaix was next to drag us through our lunch dips. Didn’t work. I fought off sleep and almost dozed off a couple of times. The topic was Sonata AdminBunble and could have been an interesting hour. Unfortunately he tried to crank too much in his 60 minutes in which he was more or less summing up functionalities in monotone English with a heavy French accent. Thomas started of by saying: “I didn’t like the Symfony 1 Admin generator…”. Next time please tell us why you didn’t like it and how you did things better in Sonata. The concept and thought processes behind your approach are much more interesting than their outcome.

Following Rabaix was Hugo Hamon talking about the Symfony2 Console component. A good talk in which he gradually build a command-line hangman game to show us the power and extensibility of the new CLI tool. He compared a command (or task) to a controller: input/output versus request/response and promoted refactoring your command line the way you would refactor your controller code. First: just write everything in the controller or command to make it work. Second: move the business logic to where it belongs. Often resulting in just a couple of lines of code in you controller or command. Separation of concerns.

Last speaker before Fabien’s keynote was Richard Miller with a great talk on the evolution of, or maybe to, Dependency Injection. He had a (more or less) real world example of some crappy, highly coupled, piece of code, which he kept on refactoring by removing dependencies (decoupling) until eventually you were left with needing something to inject the dependencies back into your application… wait… what?!

After a failed intermezzo in which they were planning to officially launch SensioLabs Germany with cake and what not, the stage was given to Fabien Potencier. He had a bit of a philosophical lecture on why Symfony2 is more than a classic MVC framework: Symfony2 is a full-stack framework, but Symfony2 is Symfony2 Components too. SymfonyTWO! Wow, it all makes sensio now!

Aug 19 11

Create subclasses of Doctrine_Query for each model class

by Matthias Noback

To make your model code better readable and reusable, it helps to create  custom Query classes which extend Doctrine_Query. For example, when you have defined a NewsItem in your schema, upon running ./symfony doctrine:build-model a NewsItem.class.php and NewsitemTable.class.php will be generated for you. Now, also create a query class for creating NewsItem queries:

class NewsItemQuery extends Doctrine_Query
{
}

Then modify your NewsItemTable class so it looks like this:

class NewsItemTable extends Doctrine_Table
{
  public function construct()
  {
    $this->setAttribute(Doctrine::ATTR_QUERY_CLASS, 'NewsItemQuery');
  }
}

Notice the fact that it says “construct()”, not “__construct()”. It is better not to override the parent __construct() method, but to use this pseudo-construct method, which the parent class calls at the end of the real __construct() method.

The code above means: whenever a new query is created by calling Doctrine_Core::getTable('NewsItem')->createQuery() the NewsItemQuery class should be used, instead of the default Doctrine_Query class.

Now, enhance the NewsItemQuery class, for example in the following way:

class NewsItemQuery extends Doctrine_Query
{
  /**
   * Join the translation
   *
   * @param string $culture The culture for which to join the translation
   * @return NewsItemQuery
   */
  public function joinTranslation($culture = null)
  {
    if (null === $culture)
    {
      $culture = sfDoctrineRecord::getDefaultCulture();
    }

    return $this->leftJoin($this->getRootAlias().'.Translation t WITH t.lang = ?', $culture);
  }
}

Now you can fetch all news items and join their translation by executing this command:

$news_items = Doctrine_Core::getTable('NewsItem')
  ->createQuery('n')->joinTranslation('nl')->execute();

By using a special Query class for each of your models, your code gets more readable, but will also contain less duplicate code, because you don’t have to copy the ->leftJoin(...) line all over your table class anymore.

Aug 19 11

Processing cachegrind files on Windows

by Matthias Noback

Recently I wanted to get some impression of the performance of a certain web page generated by some PHP code written by myself, on top of the Symfony framework and the Apostrophe Now CMS. This was the picture I had in mind:

KCachegrind call graphThis way I could find out if too much time was spent in certain parts of the code.

First I enabled Xdebug (which was already installed on my server) from within the .htaccess in the web directory of my project. I added these lines:

php_value xdebug.profiler_enable_trigger 1
php_value xdebug.profiler_output_dir /project_dir/data/profiler

I created the given profiler output directory and gave it write permissions for the user that runs the webserver.

Then I typed the URL of the page I wanted to profile inside the browser and added ?XDEBUG_PROFILE to the URL. After pressing Enter, I found a cachegrind.out.xxxx file was generated in the profiler output directory I had provided.

Now, which tool to use (on my Windows computer) to process this file and get a visual representation of my code’s performance?

After some Googling, I found KCachegrind for Windows (called QCachegrind, compiled by Lailin Chen). This seemed to be the best application available. And after downloading the ZIP-file and it’s contents to the Program Files directory, I executed kcachegrind.exe and opened the generated cachegrind file (I had to type in the exact name of the file, since it didn’t match the file filter in the File open dialog of KCachegrind).

When the file was processed, I selected the {main} function and clicked on Call Graph. Who would have guessed: it generated exactly the picture I had in mind!

Jul 8 11

Symfony forms convenience method: addPostValidator

by Matthias Noback

In your Symfony form, only one post validator can be set, by using $this->getValidatorSchema()->setPostvalidator(). But sometimes you extend a form and a post validator was already set in the parent class, and you don’t want to replace it with your own post validator, but instead, add your own validator to the validatorschema. I wrote a convenience method for this task. Add this method to your form class:

public function addPostValidator(sfValidatorBase $post_validator)
{
  if ($existing_post_validator = $this->getValidatorSchema()->getPostValidator())
  {
    $this->getValidatorSchema()->setPostValidator(new sfValidatorAnd(array($existing_post_validator, $post_validator)));
  }
  else
  {
    $this->getValidatorSchema()->setPostValidator($post_validator);
  }
}

Use this method for example in your form’s configure or setup method:

$post_validator = new sfValidatorCallback(array('callback' => array($this, 'postValidator'));

$this->addPostValidator($post_validator);
Jun 9 11

sfDoctrineActAsTaggablePlugin displays JavaScript array methods as tags

by Matthias Noback

I recently stumbled upon some strange behaviour in a Symfony 1.4 Apostrophe project. The built-in media browser uses sfDoctrineActAsTaggablePlugin. Whenever I added some new media item or edited an existing one, a list of strange tags were added to the list of tags, which appeared to be JavaScript array methods (see the screenshot).

Apparently, serveral of the “for-in” statement in the plugin’s JS file loop over array methods as if they were elements of the array. The problem is fixed by making use of the jQuery .each() method to loop over the array elements. You can download the fixed JS file here: pkTagahead.js.

Until the fix is added to the sfDoctrineActAsTaggablePlugin trunk, you may fix the problem in the following way:

  1. Remove the symlink /web/sfDoctrineActAsTaggablePlugin
  2. Create a directory /web/sfDoctrineActAsTaggablePlugin
  3. Create a directory /web/sfDoctrineActAsTaggablePlugin/js
  4. Copy the fixed pkTagahead.js (download link above) to this directory