Archive for the 'symfony' Category

Application Lego: Build a Wiki with Symfony in 20 Minutes

This tutorial shows how fast you can develop with symfony. It showcases symfony's admin generator capabilities, and makes great use of a couple of symfony plugins.

Installation

Start with an empty symfony sandbox. Then use the symfony command line to install two plugins from the symfony plugins repository. Open a terminal, go to the sf_sandbox repository and type:

> php symfony plugin-install http://plugins.symfony-project.com/sfPropelVersionableBehaviorPlugin
> php symfony plugin-install http://plugins.symfony-project.com/sfAdvancedAdminGeneratorPlugin

The first plugin requires one change in the default symfony configuration, since it is a behavior. A behavior is a model modification bringing additional capabilities to the model classes for which it is enabled. To activate behaviors in your symfony project, you need to change the last line of the config/propel.ini to:

propel.builder.addBehaviors = true

Model initialization

A wiki is basically a tool to manage articles and to keep every modification of these articles. This means that you need at least an article table to store the information of the articles. So define the following structure in the config/schema.yml file:

propel:
  article:
    id:         ~
    title:      varchar(255)
    body:       longvarchar
    version:    integer
    updated_at: ~

From this description, symfony can both initialize a database and an object model to map the table to an object-oriented API. You just need to type in the command line:

> php symfony propel-build-all

The Article model should be versionable, so that every modification to it creates a new version. Thanks to the plugin you just installed, this is as easy as adding this line at the end of the lib/model/Article.php model file:

sfPropelBehavior::add('Article', array('versionable'));

One last thing: clear the cache. Don't forget to do this every time you add a class

> php symfony clear-cache

Check that nothing is broken by browsing to the application's default page:

http://localhost/sf_sandbox/web/frontend_dev.php

Article edition interface

Use the command line to create a wiki module based on the Article model with the admin generator:

> php symfony propel-init-admin frontend wiki Article

Play with the apps/frontend/modules/wiki/config/generator.yml file until you get something satisfactory, or paste the following configuration:

 generator:
   class:              sfAdvancedAdminGenerator
   param:
     model_class:      Article
     theme:            default

     list:
       title:        List of Articles
       sort:         title
       click_action: show
       display:      [=title, updated_at]
       filters:      [title, updated_at]
       object_actions:
         _show:      { name: View Article }
         _edit:      { name: Edit Article }
     show:
       actions:
         _list:      { name: Back to the list }
         _edit:      { name: Edit Article }
     edit:
       fields:
         updated_at: { type: plain }
         version:    { type: plain }
         body:       { params: size=80x15 }
       actions:
         _save:      { name: Save modifications }
         _show:      { name: View Article }
         _list:      { name: Back to the list }
         _delete:    { name: Delete Article }

The results should be visible at the following URL. Go on, play with the interface, enter a few articles, try to edit one a few times - you will see that the version number automatically increases in the show view. That's one of the benefits offered by the sfPropelVersionableBehaviorPlugin.

http://localhost/sf_sandbox/web/frontend_dev.php/wiki

Note that the above file uses an extension for the symfony Admin Generator called sfAdvancedAdminGenerator - that's the purpose of the second plugin you installed. It offers a "show" view and separates the "create" view from the "edit" one. Apart from that, the syntax is a simple illustration of the Admin Generator capabilities - refer to the 'Admin Generator' Chapter in the symfony book for more information.

History of modifications on an article

A wiki keeps every revision of an article, and offers a list of past revisions. So you will add a history action to the wiki module. Edit the apps/frontend/modules/wiki/actions/actions.class.php class and add the following method:

public function executeHistory()
{
  $this->article = $this->getArticleOrCreate();
}


This method just reuses an existing method created by the Admin Generator. This getArticleOrCreate returns an Article object based on the id request parameter. If you are curious about how this method works, you will find the generated actions class in cache/frontend/dev/modules/autoWiki/actions/actions.class.php.

Now, to the history template. Create a apps/frontend/modules/wiki/templates/historySuccess.php file with the following content:

<?php use_stylesheet('/sf/sf_admin/css/main') ?>

<div id="sf_admin_container">
  <h1><?php echo sprintf('History of "%s" modifications', $article->getTitle()) ?></h1>
  <div id="sf_admin_content">

    <?php foreach ($article->getAllResourceVersions('desc') as $resourceVersion): ?>
    <div class="form-row">
      <?php echo sprintf("'%s', Version %d, updated on %s (%s)\n",
        link_to($resourceVersion->getTitle(), 'wiki/show?id='.$article->getId().'&version='.$resourceVersion->getNumber()),
        $resourceVersion->getNumber(),
        $resourceVersion->getCreatedAt(),
        $resourceVersion->getComment()
      ) ?>
    </div>
    <?php endforeach; ?>

    <ul class="sf_admin_actions">
      <li><?php echo button_to('Show Article', 'wiki/show?id='.$article->getId(), 'class=sf_admin_action_show') ?></li>
      <li><?php echo button_to('Edit Article', 'wiki/edit?id='.$article->getId(), 'class=sf_admin_action_edit') ?></li>
    </ul>
  </div>
</div>


The getAllResourceVersions() method is a model extension provided by the sfPropelVersionableBehaviorPlugin, which returns an array of ResourceVersion objects, each giving details about a particular revision.

To check the resulting HTML for the new action and template, just browse to:

http://localhost/sf_sandbox/web/frontend_dev.php/wiki/history/id/1

You should see the history of modifications of the article of id 1. Each revisions provides a link to the show view. For this link to display the correct version, you need to override one method of the actions class. Basically, if a version parameter is present in the request, this method forces the article to this version.

protected function getArticleOrCreate($id = 'id')
{
  $article = parent::getArticleOrCreate($id);
  if($this->getRequest()->hasParameter('version'))
  {
    $article->toVersion($this->getRequest()->getParameter('version'));
  }

  return $article;
}


That's about all. Oh, yes, you can plug the history page into your other generator's pages by modifying the generator.yml. For both the show and the edit view, add the following entry under the actions: key:

_history:   { name: History }

Conclusion

In about 50 lines and 20 minutes, you have a working wiki, with search, sorting and pagination capabilities. You can review (and reuse) any older revision of an article. Of course, this is not enough to publish your new app as a shiny open-source project - you should at least add user authentication through sfGuardPlugin and article formatting through markdown. Maybe you will need to write an additional 20 lines of code for that.

But that illustrates how to build up an application with symfony. The framework and the plugins provide the features, you just assemble them and glue them together according to your needs. Just like you used to assemble Legos to create a fire station. Since there are very few lines of code, the application is easy to review, maintain, and run.

sfPropelFinder is like jQuery for Propel

Are you tired of writing long Criteria definitions for simple queries? Do you sometimes wish that symfony had an ActiveRecord (even if it is not really possible in PHP5 as of now)? Do you feel that the world of Javascript has changed since jQuery came up? Do you like the way sfFinder and sfTestBrowser work?

Then take a look at this:

// With Peer and Criteria
$c = new Criteria()
$c->add(ArticlePeer::TITLE, '%world', Criteria::LIKE);
$c->add(ArticlePeer::IS_PUBLISHED, true);
$c->addAscendingOrderByColumn(ArticlePeer::CREATED_AT);
$articles = ArticlePeer::doSelect($c);

// with sfPropelFinder
$articles = sfPropelFinder::from('Article')->
  whereTitle('like', '%world')->
  whereIsPublished(true)->
  orderByCreatedAt()->
  find();


This example shows the philosophy of a new symfony plugin I just commited. Check it out if you're interested.

A small symfony for a fast response

Sometimes, the price of a request when dealing with a symfony application can be overwhelming. But instead of getting back to spaghetti PHP, maybe you can get a handful of symfony features for a share of its initialization time.

The features without the cost

I met this case when designing a feature-rich Content Management System that made a heavy use of the cache - and of the Super Cache. Basically, symfony was able to compute very complex pages and serve them as static page, that means very fast.

The Super Cache was a very efficient performance enhancer, for a small cost. Imagine that you set the lifetime of the super cache to 10 seconds; when a server is under a heavy load, a given page is only calculated once every 10 seconds, even if requested 500 times in between. With these figures, activating the Super Cache roughly multiplies your site's responsiveness by 500.

Design with speed in mind

But "with great power comes great responsibility", or so they say. To be able to use page caching with layout, and therefore the Super Cache plugin, the application had to be designed very carefully.

The most forbidden thing when you want to use page cache with layout are parts of the page that depend on the session - think about a header saying "Hello, John Doe" when you are connected, even if the rest o the page is completely session-independent. Unfortunately, that's always what the client wants on every page. So I had to find a solution to make the page cacheable without losing the basic user customization.

Ajax to the rescue

As explained in a previous post, you can always defer the customization of the page to the next request, by serving a session-independent page that calls an Ajax action in the background to retrieve the data necessary to change the user name in the header.

But symfony does not really fit for the second request. The cost of the initialization of a symfony request is not to neglect, and counts for 90% of the response time in very small requests - such as getting a username from the database based on a key.

Bitter swift symfony

That's when the idea of a "small symfony" comes. Wouldn't it be great if you could get access to the model layer, the configuration, the autoloading, the user object, the helpers, and keep a MVC separation, without initializing the whole framework?

That would indeed give a boost to any application designed according to the principle exposed in the quoted article. But wait a minute, we already know of "lightweight actions". They are called "components" in symfony. The only problem is that they cannot be called from the outside.

Inside out

Can't they? I'm not so sure. Imagine a script lying under your web root folder, a "lightweight front controller", with the following code:

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'frontend');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

$module = $_GET['module'];
unset($_GET['module']);
$action = $_GET['action'];
unset($_GET['action']);
sfLoader::loadHelpers('Partial');
include_component($module, $action, $_GET);


It looks very much like a regular symfony front controller script, except that it lacks the final sfController::dispatch() line. And indeed, it does not initialize the filter chain, handle validation nor output escaping. It just initializes the smallest part of symfony required to execute a "lightweight action"

I saved this script under web/component.php. Now, my Ajax calls can be made to the following URI:

http://mysite/component.php?module=foo&action=bar&key1=value1

The server will then return the result of the execution of an

include_component('foo', 'bar', array(
'key1' =&gt; 'value1'
));


Does it work?

Now I can execute a component from the client side. The component architecture offers native View/Controller separation, and the configuration initialization brings autoloading, database access, and more. It does work perfectly, but is it fast? Speed tests show that not launching the filter chain saves about 40% to 50% of the cost of a symfony initalization. This means that you can multiply the number of requests that your server can handle by two - for very simple requests.

Be aware that this trick can only be used in some very particular cases, and only for very light requests. It may jeopardize security, and will often prove to be very limited. But for a page split between a session-independent part and a small session-dependent action, it does the trick.

Before we leave

The great thing about this article is that the trick it exposes is not its best part. For the original need of including session-dependent data into a generated CMS page, Ajax is not the only solution. JavaScript alone can do all the job on the client side, and so your server will never need to embed user data in the page. Ok, it will need to do it once, after which the client keeps the data in a cookie, and a JavaScript executed at page load inserts this data into the page - on the client side. And now every CMS page can be cacheable with its layout.

Symfony, My Family Tells You Merci!

I just received more than 400€ of donations from the symfony community for the birth of my daughter. My family and I are still amazed about the generosity of the gesture, and both my wife and daughter join me to express our big thanks.

With the help of the money gathered by the community, we'll soon be able to get our daughter out of jail!

Nina In Jail

Database Session Handling and Garbage Collector

Database Session Handling and Garbage Collector

Note for self: When using a distributed session storage with symfony (sfMySQLSessionStorage, sfMemcacheSessionStorage, sfPDOSessionStorage, etc.), don't forget to check that session.gc_probability must not be set to 0 in the php.ini, and that the line is not commented, which seems to be the case on some PHP distributions.

; Define the probability that the 'garbage collection' process is started
; on every session initialization.
; The probability is calculated by using gc_probability/gc_divisor,
; e.g. 1/100 means there is a 1% chance that the GC process starts
; on each request.

session.gc_probability = 1
session.gc_divisor     = 100

If set to 0, the session garbage collector will never run, and sessions will pile up forever in your storage facility - eventually inflating the storage space way too much for decent performance.

Live User Testing with sfSpyPlugin

Doing user testing is more or less like watching users interact with an application. sfSpyPlugin allows you do to it online and for free, instead of paying a lot for doing it for real.

User testing is usually a long and expensive process that few organizations can afford. You must recruit users, write scenarios, animate the tests, record browsing sessions, and visualize the tapes. What if you could just choose some of the users currently using the application online, start recording their actions, and playback their way through the application later? That's what the new sfSpyPlugin offers.

See it in action: One browser window is "spying" another browser window - but that could very well be two different computers. Click on the following link to see a 2 minutes screencast of the plugin in action.

http://screencast.com/t/tnhx8kBl5P1

The plugin displays a list of the online users on any symfony application. You can choose to watch online or record for later the actions of any user. The replay interface offers VCR-like controls to pause, restart, accelerate or slow down the playback. You can also organize your recordings by giving them a name. The interface should be pretty self-explanatory, and yet there is a complete installation and usage guide available at the symfony project wiki.

And if you are not of the point-and-click type, you can always trigger the recording from code, to watch how a certain user uses the application, or how a certain module of the application is used.

This way, you can watch and monitor how users interact with your application, or part of it. The only think you will miss is the voice of the user commenting what he/she is doing. I created this plugin so that you have no excuse to let live applications having a poor usability without doing anything to improve them...

sfForms: The Missing Component

Symfony 1.1 introduces a new Form subframework, combining Widgets and Validators to generate poweful form objects. Thatsquality.com explains how to use the new objects in a series of tutorial articles.

Symfony has always been about providing powerful and easy-to-use components to implement the best practices of web development and avoid long and repeating code. But there is a problem with the new subframework: it is incredibly powerful, but also incredibly not easy to use.

The Base Example

I won't explain the whole thing here, but this is the main part: to define both the components and validation rules of an sfForm object, you must use the setWidgetSchema() and setValidatorSchema() methods of an sfForm extending object, in a configure() method.

Here is an example for a contact form:

class ContactForm extends sfForm
{
  public function configure()
  {
    $this->setWidgetSchema(new sfWidgetFormSchema(array(
      'email'   => new sfWidgetFormInput(),
      'name'    => new sfWidgetFormInput(),
      'body'    => new sfWidgetFormTextarea(),
      'captcha' => new sfWidgetFormReCaptcha(array('public_key' => sfConfig::get('app_recaptcha_public_key'))),
    )));

    $this->setValidatorSchema(new sfValidatorSchema(array(
      'email'   => new sfValidatorString(),
      'name'    => new sfValidatorAll(array(new sfValidatorString(array('required' => false), new sfValidatorEmail())), # not sure about the syntax there
      'body'    => new sfValidatorString(),
      'captcha' => new sfValidatorReCaptcha(array('private_key' => sfConfig::get('app_recaptcha_private_key'))),
    )));

    $this->widgetSchema->setNameFormat('contact[%s]');
  }
}

I won't discuss the power of the system, it's incredible and allows you for almost everything in no time.

Enters YAML

In no time? But what if the sfForm object had a configureFromArray() method accepting an associative array - or, in other terms, a YAML configuration? The same as above could be achieved with this YAML code:

contact:                          # This key is not necessary. Just an entry point for the hash
  name_format: contact[%s]
  fields:
    email:
      widget:   { class: sfWidgetFormInput }
      format:                     # 'format' is just so much faster to write than 'validator'
        - { class: sfValidatorEmail }
        - { class: sfValidatorString } # This is how I combine two validators with an AND
      required: true
    name:
      widget:   { type: input }   # Easy to guess class from type, which is more familiar and faster to write
      format:   { type: string }  # If only one validator, no need for an array
      required: false
    body:
      widget:   { type: textarea }
      format:   { type: string }
      required: true
    captcha:
      widget:   { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% } # All keys except type and class are passed to the constructor
      format:   { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% }
      required: true

That's longer than the PHP code, will you say. For now.

Convention over configuration

No need to be so verbose, symfony can have conventions for the 80% most common cases and do part of the job for you. So the configuration could reduce to:

contact:
  name_format: contact[%s]
  fields:
    email:
      widget:   input      # if only type, no need to use hash
      format:   [email, string]
    # required: true       # required is true by default
    name:
    # widget:   input      # widget is of type input by default
    # format:   string     # format is of type string by default
      required: false
    body:
      widget:   textarea
    # format:   string     # if widget is defined, format has sensible defaults (ex: input/textarea => string, date => date, etc )
    # required: true
    captcha:
      widget:   { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% }
      format:   { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% }
    # required: true

Cleaning up

Once all the implied settings removed, the configuration could be shorter:

contact:
  name_format: contact[%s]
  fields:
    email:
      format:   [email, string]
    name:
      required: false
    body:
      widget:   textarea
    captcha:
      widget:   { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY%, autoformat: true }
      # if autoformat is true in the widget, then format takes the same params as widget (minus, of course, autoformat)

Or, if we use the inline YAML syntax:

contact:
  name_format: contact[%s]
  fields:
    email:     { format: [email, string] }   # Wait a minute. Couldn't symfony guess this one for me ?
    name:      { required: false }
    body:      { widget: textarea }          # And what about this one ?
    captcha:
      widget:  { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY%, autoformat: true } # And this one ?

One step further

If symfony were really smart, all you should write to define the widgets and validation rules of a contact form would be:

contact:
  name_format: contact[%s]
  fields:
    email:    ~
    name:     { required: false }
    body:     ~
    captcha:  { private_key: %APP_RECAPTCHA_PRIVATE_KEY% }

THAT, my friend, is both powerful and very easy to use.

More use cases

The principle of YAML configuration works well on simple forms. But I believe it can be extended to most of the forms, leaving only 10% to 20% of the forms to be defined with setWidgetSchema() and setValidatorSchema(). Here are more examples:

my_form:
  culture: fr
  focus:  message                # Yep, that's for JavaScript focus()
  fields:
    id:       ~   # let me guess: { widget: hidden, format: integer } ?
    name:     ~
    email:    ~

    date_of_birth:
      label:  Date of birth       # I know it belongs to the widget, but it's just more natural like that
      widget: date
      format: { type: date, min: 1900/01/01, max: 2005/31/12 }

    message:
      widget: textarea
      format: string or number    # Why not use the wonderful text validators here ?

    want_to_be_spammed:
      widget:  { type: checkbox, default: true, tabindex: 3, class: foo } # How do you define default values with the widgets?

    subject:
      widget: { type: select, choices: [administrative, legal, commercial], default: administratif }

    recipient:
      widget: { type: selectMany, populator: [this, getPossibleRecipients] } # Populator expects a callable to populate choices

    title:
      type:   input
      format: not in Article::title                # That's a text validator for the sfPropelUniqueValidator

What do you think? Should symfony implement this system by default?

How to learn symfony?

Last week, I gave another symfony workshop with Fabien Potencier and Marc Hugon in Paris. It was a great pleasure and a great experience, especially given the quality of the audience (this last sentence should bring me some kind comments).

We had thirty attendees, and that's a lot of people to train! Even if it was already the fourth time that I gave this particular training program, I still learned a lot from people's reactions. Given the feedback we got, it looks like we managed to pass some parts of our knowledge and experience. But the amount of things to learn is just too much, so I think we should cut on some feature descriptions. The problem is, that every person comes with some special expectations about i18n, Ajax, security, form validation, etc. If we try to satisfy them all, three days is clearly not enough.

Symfony workshops try to focus on the practice rather than the theory. In order to avoid endless talks about features that people generally forget the minute the slide disappears, we make the attendees work. Everyone is supposed to bring a laptop with a LAMP stack already installed (and that never happens). The workshop starts by transforming a classical PHP application into a symfony one. I think this first exercise is the most satisfactory, because in about four hours people know all the basics about symfony applications.

Practice also makes the "why" of things clearer. Why do partial names start with an underscore? Why are fixture files in YAML? Why does the plugin system uses PEAR? Why sfGuard is a plugin and not a core feature? The answers to these questions come naturally after a few hours of coding symfony applications.

But there are many concepts behind symfony that need explanation, and practice cannot suffice. The problem is that once you start explaining ORM, MVC, DRY, and KISS, you need to perform really well to keep having people's attention. I think that's the hardest part of giving a training: not losing the audience when you have to explain something that requires at least ten minutes of talking. Especially when your voice vanishes from too much talking...

Anyway, this session helped us realize how to further improve the training material, and how to get people more ready for what they will learn. No doubt that the next sessions will be even better!

That being said, what do you think is the best way to learn symfony?

Rails getting inspiration from symfony

Rails 2.0 improvements on fixtures look a lot like the standard symfony 1.0 fixtures features - apart from the many-to-many syntax. Take a look at this screencast in Railscast to see how revolutionary fixtures are in the latest Rails release.

I like looking at Rails, Django, Zend Framework and other good open-source projects to take inspiration. It doesn't prevent me from thinking on my own, but it also prevents me from reinventing the wheel. It is nice to see that the Rails developers do the same and don't keep on thinking that they invented the Best Thing Around.

Motilee: Symfony Powered Forum Engine Released Open Source

A new open-source project is born. It is called Motilee, and it is a forum engine written in PHP5 and symfony. Go take a look at the demo site and at the source code to see what it's like.

Motilee Forum

Motilee is a symfony application built entirely from plugins. Take a sandbox, pour in the sfSimpleForumPlugin, the sfGuardPlugin, the sfLucenePlugin, the sfFeed2Plugin and a few others, add some documentation, and mix the whole for a few days. There you go, a complete application comes out of the server. Download it and install it as you wish. Motilee is free to use and released under the MIT license.

Motilee focuses on user experience and tries to propose a new approach to online discussions. Don't focus too much on the graphical design, it will soon be reworked.

A stable release is yet to come, and the most important enhancements are already listed in the trac. Development is going pretty fast, and if you want to participate in the project, there is room for more developers. Drop me an email and I'll open you an account in the trac.

« Previous PageNext Page »