Archive for the 'framework' Category

Is symfony 1.1 too verbose?

Among the remarks I have about symfony 1.1, the most recurring one is the shift of philosophy between the 1.0 and 1.1 syntax. If symfony 1.0 syntax was made to write code fast, I believe it is not the case anymore with symfony 1.1, which is designed primarily for extensibility.

The result is that a symfony 1.1 application looks a lot more like a Java program. I tend to agree that Object-Orientation is a good thing because it forces you to organize your code in a modular way. But when object-orientation makes you need to keep a symfony book aside at all times and multiply the number of LOC by two, I think it's a dead end.

Disclaimer: I'm not a developer by training. I don't have much experience in web development. I started learning PHP about three years ago, and I don't think that code purity is something you should stick to. I believe in code smell, though, but that's another matter. My main interest is usability in general.

Example #1 - Tasks

Let's see how the new symfony philosophy applies to the new command line utility framework: the symfony tasks. This is a very powerful addition to symfony 1.1, made to allow an easy parsing of CLI arguments and options, and to write CLI scripts in an extensible way.

Here is a typical task initialization method, taken from sfLogRotateTask (for details about the syntax, please refer to the symfony 1.1 documentation):

protected function configure()
{
  $this->addArguments(array(
    new sfCommandArgument('application', sfCommandArgument::REQUIRED, 'The application name'),
    new sfCommandArgument('env', sfCommandArgument::REQUIRED, 'The environment name'),
  ));

  $this->addOptions(array(
    new sfCommandOption('history', null, sfCommandOption::PARAMETER_REQUIRED, 'The maximum number of old log files to keep', 10),
    new sfCommandOption('period', null, sfCommandOption::PARAMETER_REQUIRED, 'The period in days', 7),
  ));
 
  ...
}


To be able to write a task by yourself, you need to know what an sfCommandArgument is, what an sfCommandOption is, and you need to know their constants as well. That means that the number of things to learn is quite important. And why do we need to instantiate a new object to set arguments, since the only thing that addArguments() accepts is an array of sfCommandArgument objects? Because, sometime, you will take advantage of the fact that you can pass a subclass of sfCommandArgument to extend the task framework.

Developed according to the symfony 1.0 style, this would probably give:

protected function configure()
{
  $this->addArgument('application', true, 'The application name');
  $this->addArgument('env', true, 'The environment name');

  $this->addOption('history', null, true, 'The maximum number of old log files to keep', 10);
  $this->addOption('period', null, true, 'The period in days', 7);
 
  ...
}


Do you see the difference? The latter is maybe not as easy to extend, but for 90% of the cases, faster to write. Besides, the addArgument() method does not prevent you from using the long version with addArguments(), so you get both extensibility and brevity.

Note: By reading the above code, you don't know what the null and true parameters stand for. It is a matter of taste, and longer to write, but I'd rather have my method calls look like the following when the method takes many arguments. That way, every method call in your application is a useful reminder of the API.

protected function configure()
{
  $this->addArgument('application', $required = true, $help = 'The application name');
  $this->addArgument('env', $required = true, $help = 'The environment name');

  $this->addOption('history', $shortcut = null, $required = true, $help = 'The maximum number of old log files to keep', $default = 10);
  $this->addOption('period', $shortcut = null, $required = true, $help = 'The period in days', $default = 7);
 
  ...
}


Fabien added an addArgument() method to sfTask not so long ago, but it just seemed useless to him to duplicate the method signature. I think he did that just to make me stop yelling. You, developer, should learn The Right Way of Doing Things, and the right way is the longer way. If you can't understand or learn the longer way, bummer, you can't use symfony.

Example #2 - Events

The event system is another great addition to symfony 1.1. It extends the principle of mixin to allow more runtime class modifications. Once again, the symfony book is up to date on this part, so you should read the related chapter if you need to understand the syntax.

So here is how to allow a class to be extensible by way of a generic __call() in symfony 1.1:

public function __call($method, $arguments)
{
  $event = $this->dispatcher->notifyUntil(new sfEvent($this, 'foo.method_not_found', array(
    'method'    => $method,
    'arguments' => $arguments
  )));
  if (!$event->isProcessed())
  {
    throw new sfException(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
  }
 
  return $event->getReturnValue();
}


And here is how you register a new bar() method with this system:

$dispatcher = sfContext::getInstance()->getEventDispatcher();
$dispatcher->connect('foo.method_not_found', array('myClass', 'listenToFooMethodNotFound'));

class myClass
{
  public function listenToFooMethodNotFound(sfEvent $event)
  {
    $parameters = $event->getParameters();
    switch($method = $parameters['method'])
    {
      case 'bar':
        $this->bar($event, $parameters['arguments'])
        return true;
      default:
        return false;
    }
  }
 
  protected static function bar($event, $arguments = array())
  {
    // ...
   
    $event->setReturnValue($result);
  }
}


As I wrote the documentation for this feature, I couldn't refrain from thinking this could be a lot easier. With symfony 1.0 philosophy, I guess you would only need to write:

public function __call($method, $arguments)
{
  return $this->dispatcher->handle($this, 'my.class.event.name', $method, $arguments);
}


And you would probably register a new method this way:

$dispatcher = sfContext::getInstance()->getEventDispatcher();
$dispatcher->addMethod('foo.method_not_found', array('myClass', 'bar'));

class myClass
{
  public function bar($object, $arguments = array())
  {
    // ...
   
    return $result;
  }
}


You can guess how the handle() and addMethod() methods of sfEventDispatcher could do all the dirty job for you. That would save you a copy/paste from the first version of the code, would be less error prone, and after all, if the program has less lines, then it's cheaper to maintain.

But, according to Fabien, that would be hardcoding the way you throw an exception in __call(), and this addition would only be useful in a very minority of cases. While the fact that you pass to notify() an sfEvent instance instead of a simple list of parameters is, once again, based on the idea that you will sometime find it convenient to subclass sfEvent. Which I think will never happen before symfony 1.2 at least.

Example #3 - Forms

Don't get me started on forms. I already wrote a post about sfForms to say that it missed an important part, and that code usability had been left aside. I still think so. Fabien doesn't.

Oh, by the way, do you know that the First Project Tutorial in symfony 1.1 showcases the new Form framework? If that doesn't discourage newcomers, I think nothing will. I wish good luck to the one who will try to make this tutorial newbie-friendly.

Conclusion

Symfony 1.1 adds a ton of wonderful features to symfony 1.0. From a technical point of view, this is a more complete, more coherent, more beautiful piece of code than symfony 1.0 was. It kicks ass and I believe that it's more powerful than any other PHP framework out there.

On the other hand, I think that symfony 1.1 is more verbose, and therefore more error-prone, than symfony 1.0. It forces you to constantly look at the book for syntax description. The book cannot describe everything - it is just a guide on how to use the framework. It should remain as small as possible to keep readable. The book cannot go into the details of sfCommandArgument::REQUIRED or sfWidgetFormSchema. That means the book will not be enough to use symfony 1.1: You will need to know the full API doc as well, even for the basic uses. Fabien knows the API by heart, so he doesn't need any documentation. But you, my friend, you will sweat a lot to learn all the changes.

The heart of the problem is that, to use symfony 1.1, you don't just need to learn the usage guidelines, you also need to understand how it works inside.

I disagree with the philosophy shift symfony is taking. I think it should stick with the "KISS" motto, and it could do so without sacrificing extensibility. If the price to pay is a little more code in the symfony base, then symfony should pay this price - not the symfony users.

I'm tired of writing a book that looks more and more like an API documentation. I don't feel like explaining the core symfony classes to developers who just wants to use the framework to build an application. I think that The Definitve Guide To Symfony doesn't really make any sense anymore. You want to know how to use the framework? Well, read the API doc and guess. After all, that's what all the other open-source projects do.

Maybe I'm the only one thinking that. But, as I said at the beginning, I'm no developer.

Twitter Said To Drop Rails: So What?

Twitter is said to rewrite parts of their application with something else than RoR to solve their performance/availability issues. Is this good news? Is this a reason for Code Ignitor fans and Django preachers to just celebrate? Should the symfony community see this as a wonderful opportunity?

Not at all. There will be two major consequences, both bad for all frameworks out there. Firstly, IT managers will move away from open-source web application frameworks, because from their point of view, if the most well-known open-source web application framework can't handle the traffic, no other can. Secondly, the frameworks communities look dumber than ever, all fighting with zealot blindness and non-professional arguments. I don't see any enterprise decision maker pick a tool if this tools' support relies on a bunch of guys who just think Their Way Is The Best Way No Doubt About That.

Developers around the world really need to realize that there must not be only one solution. This is called a monopoly. Having many frameworks out there leaves more room for innovation, doesn't concentrate the future of web developement in too few hands. Besides, with millions of web sites out there, the cake is big enough for everybody to get a large piece. If there was only one way to build web apps - say symfony, for instance - then developers would be trapped. And for the cases where symfony would not be the best tool (I can see plenty), there would not be any alternative.

Doubt if the fuel of faith. There are good ideas to grab in every project. You shouldn't rely on the will of a single framework architect for your whole business model. And eventually, success is contagious. So please show your happyness when other frameworks get positive reviews, not the opposite.

Propel is not hard anymore

The two most common criticisms I hear about about Propel are the difficulty to learn the Criteria object, and the amount of code required to do a custom Join. That, and the fact that Propel is slow--but Propel 1.3 will just leave every other ORM behind, so speed is not a good reason to ignore Propel, especially now.

The abilities of Propel are just amazing, but the API is far too Java-inspired for our hectic programming speed. Rapid Application Developers need simple APIs that allow them to go fast in the majority of the cases. That's the philosophy of the sfPropelFinderPlugin, that I already introduced in a past post. This symfony plugin (which doesn't depend on symfony - you can use it with Propel alone, provided you load the Propel classes by hand) recently got better.

No more Criterions

sfPropelFinder encapsulates Criteria calls, so you never need to manipulate the complex Propel syntax. Instead, the plugin proposes a syntax reminiscent of SQL to retrieve Propel objects:

$articles = sfPropelFinder::from('Article')->
  where('Title', 'foo')->
  _and('PublishedAt', '<', time())->
  _or('Title', 'like', 'bar%')->
  find();


If you already tried to do AND and OR queries with Criterion objects, you know how the above can be long and complicated to write. Here, the Propel finder does all the dirty job for you. Another relief is the use of the CamelCase version of the properties names, which is consistent with the Propel object getters. You no longer need to type infamous strings looking like ArticlePeer::PUBLISHED_AT; PublishedAt will do the trick.

Easy joins

Another recent improvement of the finder plugin is its ability to deal with related objects in joins and hydrating. The idea is that you define relationships in the schema, with foreign keys and references, but Propel keeps asking you to repeat them each time you want to join two objects in a Criteria addJoin(). Since you don't like to repeat yourself, sfPropelFinder will quietly check the Propel TableMap to explicit a join if you write:

$postsToRead = sfPropelFinder::from('Post')->
  join('Author')->
  where('Author_Name', '<>', 'Joe')->
  find();


Wait, there is more. The finder can understand complex joins between several tables. Imagine that an Author has a Status. If you want to get all Posts for the Authors with a given Status, just chain join() calls. You get the speed of ActiveRecord-like syntax with the robustness of Propel code behind.

$postsToRead = sfPropelFinder::from('Post')->
  join('Author')->join('Status')->
  where('Status_Name', 'polite')->
  find();


Reduce queries, but don't increase code in return

Lastly, if you rely on Propel's generated doSelectJoinXXX() methods to reduce query count, you are probably often frustrated that the method you need is never among the generated ones. And if you ever tried writing such a method yourself, dealing with composite resultsets and complex hydrating, you probably upgraded your database for more raw power instead of pulling your hair off.

Fortunately, sfPropelFinder offers a convenient with() method allowing you to list the objects you want to be hydrated together with the main object. The following code issues only one database query:

$latestPosts = sfPropelFinder::from('Post')->
  with('Author', 'Status', 'Category')->
  orderBy('PublishedAt', 'desc')->
  find(10);
foreach($postsToRead as $post)
{
  echo $post->getTitle(),
       $post->getCategory()->getName(),              // Post's Category is already hydrated: no query
       $post->getAuthor()->getName(),                // Post's Author is already hydrated: no query
       $post->getAuthor()->getStatus()->getName();   // Author's Status is already hydrated: no query
}


Conclusion

I hope you find this syntax easier to use than Propel's native methods. The sfPropelFinder plugin gets better and better every day thanks to the contributions of several developers, and is already a good alternative to Peer classes and Criteria calls. This should give you time to deal with the more important stuff.

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?

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.

Customizing a plugin schema

In the evolution of symfony, plugins are an important step. But before we can think of applications made entirely with plugins - the 'Lego' dream - the framework must offer a way to customize plugin schemas.

Why plugin schemas must be configurable

More and more symfony plugins offer high-level features, even entire modules, based on a bundled schema. The sfGuardPlugin, for instance, defines several tables for user management. The sfSimpleForumPlugin defines tables for forums, topics and posts, in order to provide a complete forum packaged in one plugin.

The problem is that unlike all the other parts of a plugin, a plugin schema can't be customized. This is a very strong limitation, and it makes plugins impossible to use in many cases.

For instance, the sfGuardUser table doesn't have an email column. As its schema can't be customized, this means that if you want to use sfGuard and handle user emails (that should be about 99% of the cases), then you have to define a new table for user details with a 1:1 relationship to the sfGuardUser table. Bummer.

Worse, if you want to use a behavior, like for instance the sfPropelParanoidBehavior, on a plugin model, you simply can't. The said behavior needs an extra deleted_at column to be added to the table, and you can't add it to a plugin schema - or else it will be deleted the next time you update the plugin.

Using PHP in YAML files

But nothing is impossible with symfony. By using two powerful features of the symfony YAML parser, it is possible to make a schema more configurable.

First, you probably know that symfony sees YAML files as PHP, and therefore executes them before interpreting the YAML inside. This means that a YAML schema can include PHP code, and that gives a certain level of customization to schemas. For instance, look at how the sfSimpleBloginPlugin schema offers a way to change the connection and tables names:

// in plugins/sfSimpleBlogPlugin/config/schemaConfig.php
<?php

// Default values
$config = array(
  'connection'    => 'propel',
  'user_table'    => 'sf_guard_user',
  'user_id'       => 'id',
  'post_table'    => 'sf_blog_post',
  ...
);

// Check custom project values in my_project/config/sfBlogPlugin.yml
if(is_readable($config_file = sfConfig::get('sf_config_dir').'/sfBlogPlugin.yml'))
{
  $user_config = sfYaml::load($config_file);
  if(isset($user_config['schema']))
  {
    $config = array_merge($config, $user_config['schema']);
  }
}

// in plugins/sfSimpleBlogPlugin/config/schema.yml
<?php include('schemaConfig.php') ?>
<?php echo $config['connection'] ?>:
  _attributes:       { package: plugins.sfSimpleBlogPlugin.lib.model }
  <?php echo $config['post_table'] ?>:
    _attributes:     { phpName: sfSimpleBlogPost }
    id:              ~
    author_id:       { type: integer, foreignTable: <?php echo $config['user_table'] ?>, foreignReference: <?php echo $config['user_id'] ?>, onDelete: cascade  }
    title:           varchar(255)
    stripped_title:  { type: varchar(255), index: unique }
    extract:         longvarchar
    content:         longvarchar
    is_published:    { type: boolean, default: false }
    allow_comments:  { type: boolean, default: true }
    created_at:      ~
    ...


To customize table names, you just need to create a config/sfBlogPlugin.yml overriding the values of the $config array, like for instance:

// in config/sfBlogPlugin.yml
schema:
  connection: foo
  user_table: my_user
  post_table: bar_post


In a previous post, I explained how a custom config handler can make this task even easier.

This is more or less a hack, and it allows only the customization of predefined elements of the schema. Besides, PHP code makes the YAML schema less readable.

A YAML file is an array

A second property of YAML files in symfony is that, if they return a PHP array, then the YAML parser won't be invoked at all. Imagine the following YAML file:

foo: [bar1, bar2, bar3]
<?php return array('foo' => array('bar1', 'bar2'))?>


Since the YAML file returns an array, the YAML code is ignored. Everything happens as if the file had been:

foo: [bar1, bar2]


Combining this property and the power of sfToolkit::arrayDeepMerge(), it is possible to allow more configuration to plugin schemas. Look at how it is done in the sfSimpleForumPlugin:

// in plugins/sfSimpleForumPlugin/config/schemaConfig.php
<?php

function start_schema()
{
  ob_start();
}

function end_schema()
{
  $tables = array(
    'connection'       => 'propel',
    'user_table'       => 'sf_guard_user',
    'user_id'          => 'id',
    'forum_table'      => 'sf_simple_forum_forum',
    'topic_table'      => 'sf_simple_forum_topic',
    'post_table'       => 'sf_simple_forum_post',
    ...
  );

  $custom_fields = array();

  // Check custom project values in my_project/config/sfBlogPlugin.yml
  if(is_readable($config_file = sfConfig::get('sf_config_dir').'/sfSimpleForumPlugin-schema-custom.yml'))
  {
    $user_config = sfYaml::load($config_file);
    if(isset($user_config['tables']))
    {
      $tables = array_merge($tables, $user_config['tables']);
    }

    if(isset($user_config['custom_fields']))
    {
      $custom_fields = $user_config['custom_fields'];
    }

  }

  $yaml_schema = ob_get_clean();
  foreach ($tables as $key => $value)
  {
    $yaml_schema = str_replace('%'.$key.'%', $value, $yaml_schema);
  }

  $schema = sfYaml::load($yaml_schema);
  $schema[$tables['connection']] = sfToolkit::arrayDeepMerge($schema[$tables['connection']], $custom_fields);

  return $schema;
}

// in plugins/sfSimpleForumPlugin/config/schema.yml
<?php include_once('schemaConfig.php') ?>
<?php start_schema() ?>
%connection%:
  ...
  %topic_table%:
    _attributes:   { phpName: sfSimpleForumTopic, package: plugins.sfSimpleForumPlugin.lib.model }   
    id:
    title:         varchar(255)
    is_sticked:    { type: boolean, default: false }
    is_locked:     { type: boolean, default: false }
    forum_id:      { type: integer, foreignTable: %forum_table%, foreignReference: id, onDelete: cascade }
    created_at:
    updated_at:
    # performance enhancers
    latest_post_id: { type: integer, foreignTable: %post_table%, foreignReference: id, onDelete: setnull }
    user_id:       { type: integer, foreignTable: %user_table%, foreignReference: %user_id%, onDelete: setnull }
    stripped_title: varchar(255)
    nb_posts:      { type: bigint, default: 0 }
    nb_views:      { type: bigint, default: 0 }
  ...
<?php return end_schema() ?>


Not only is the schema more readable (the embedded PHP statements were replaced by tokens), it is now possible to add new fields to the schema:

// in config/sfSimpleForumPlugin-schema-custom.yml
tables:
  topic_table: my_topic_table
custom_fields:
  my_topic_table:
    deleted_at: timestamp


At last, it makes it possible to extend existing plugin schemas... That is, if the plugin planned for it.

What's next?

This feature is so crucial for plugins adoption that it should, in fact, be built-in the symfony plugin system instead of being implemented in each plugin. Users should be able to add a YAML file in the project's config/ directory for each of the schemas provided by the installed plugins, following a naming convention similar to the one I exposed in the sfSimpleForumPlugin.

Of course, this will never work for plugins that use the XML syntax for schemas. Unless we tell the propel tasks to transform every XML schema to YAML, to allow for customization, before transforming them back to XML, which is the only format Propel understands. But to me, the ability to be customized should clearly promote the YAML schema syntax as the only standard for symfony plugins. Future versions of symfony should drop support for the legacy XML schema syntax.

There is another step that could be taken towards standardization... The schema customization exposed in the sfSimpleForumPlugin example uses two different techniques to change table names and add fields. But if the YAML schema syntax was a little different, all this could be done simply through one YAML file. I'm thinking about a new syntax that would mix the current symfony schema syntax and the Doctrine schema syntax. Imagine that the topic table above is defined as follows:

connection: propel
objects:
  sfSimpleForumTopic:
    tableName: sf_simple_forum_topic
    package: plugins.sfSimpleForumPlugin.lib.model
    columns:
      id:
      title:         varchar(255)
      is_sticked:    { type: boolean, default: false }
      is_locked:     { type: boolean, default: false }
      forum_id:      { type: integer, foreignClass: sfSimpleForumForum, foreignReference: id, onDelete: cascade }
      created_at:
      updated_at:
      # performance enhancers
      latest_post_id: { type: integer, foreignClass: sfSimpleForumPost, foreignReference: id, onDelete: setnull }
      user_id:       { type: integer, foreignClass: sfGuardUser, foreignReference: id, onDelete: setnull }
      stripped_title: varchar(255)
      nb_posts:      { type: bigint, default: 0 }
      nb_views:      { type: bigint, default: 0 }
  ...


If this is automatically merged with user-defined schemas, it would be very easy to change table names or add custom fields, for instance with this file:

connection: my_connection
objects:
  sfSimpleForumTopic:
    tableName: my_topic_table
    columns:
      deleted_at: timestamp


Conclusion

The current YAML syntax for schemas (which I designed myself) was a first step, but it needs to evolve. To be able to fully replace XML schemas, and to allow compatibility between Propel and Doctrine, it is probably necessary to change it - but preserve backward compatibility, of course.

Developing an extension of the sfPropelDatabaseSchema class to implement both the new syntax and the ability to customize schemas is possible, but before getting myself into it, I wonder: Would anybody else benefit from this?

Define “Framework”

Many people wonder what a framework is. Many people work with people who work with a framework, and yet these people just pretend to know what this is about.

The problem is, not only the concept of framework is not immediately understandable, but also that most of the explanations of the term “Framework” suck.

How they define it

Take the most famous framework, Ruby on Rails. They don’t bother to define what a framework is. People coming to Rails either already know what this is all about, or think that this is something important because Rails is famous.

The Django framework proposes this definition:

A high-level Web framework is software that eases the pain of building dynamic Web sites. It abstracts common problems of Web development and provides shortcuts for frequent programming tasks.

They complement this definition with a page-long list of the Django features. That’s the same approach as Rails: Understand what our framework does, you will understand what a framework is in general. Kind of biaised.

Wikipedia tries to be more specific and says:

A software framework is a reusable design for a software system (or subsystem). This is expressed as a set of abstract classes and the way their instances collaborate for a specific type of software.

That’s too many words. 9 out of 10 IT managers jumped to the .Net platform before reading the second sentence (and therefore misunderstood the true nature of a framework). Besides, it doesn’t explain what a framework does for you.

Let’s see how symfony defines frameworks:

A framework streamlines application development by automating many of the patterns employed for a given purpose. A framework also adds structure to the code, prompting the developer to write better, more readable, and more maintainable code. Ultimately, a framework makes programming easier, since it packages complex operations into simple statements.

Although this sentence was written by me, it is not much better than the former ones.

Framework newbies are still wondering whether a framework is good, if they can use it to go shopping, if it enhances the sexual activity, and if it is something convenient to talk about during lunch.

Enter geeks

One of the reasons why the concept of framework is not that easy to make clear is that it’s a geek matter. Frameworks are used by programmers who develop enough applications to become unsatisfied with a language alone.

These people are interested in stuff like design patterns, test-driven development, object-relational mapping, and other dirty words. They just don't speak the same language as you do. And try to get an astrophysicist talk clearly about Strings Theory. Very few people know enough about a subject to be able to explain it, and unfortunately these people often don’t know enough people who don’t know anything about this subject, so they are not used to making it accessible.

Let’s try

Now let’s try it ourselves. The idea is to define the term “Framework” in a single sentence, and to check that anybody with minimum knowledge of web applications can understand it.

“If god had a framework, he would build the world in 3 days.”

All right, marketing boy, this may make your girlfriend laugh, but it’s not going to help us a lot.

“Frameworks are a supplementary layer on top of a programming language that make development faster and more productive”.

So this is something interesting for programmers, but what if I am a manager with three alternatives for a new website, built with either the ezPublish CMS, or the symfony framework, or the BEA Weblogic application server?

if($framework)
{
  Just::do($it);
}
else
{
  $it = strstr(fibonacci(75 * _SERVER['foo'])), strbin2hex('1101'));
  sort($it);
  if($it instanceof Crap)
  {
    thrown new CrapException('bummer!');
  }
  else
  {
    try_again();
  }
}


Hey spaghetti code man, I guess you learnt PHP development by hacking Wordpress and Gallery, didn’t you? Thanks, but you're not helping.

I can’t do this. Can you?

I guess I failed. Defining the word “Framework” in a single and simple sentence is just too hard for me tonight.

My only hope is that, as often in the web 2.0, the true value of this post will come from its comments, rather than from its body. Please help me: How would you define “Framework”?