Custom properties in symfony’s admin generator configuration

September 9, 2009

For one of my projects I needed to add a custom property to the “generator.yml” file. Something like this:

generator:
# ...
  config:
    actions: ~
    fields: ~
    list:
      my_property: value
    filter: ~
    edit: ~
    new: ~

Unfortunately the site threw an InvalidArgumentException:

Your generator configuration contains some errors for the “list” context. The following configuration cannot be parsed: array( ‘my_property’ => ‘value’,).

After a bit searching I found the solution in this form post in the official symfony forum. Here I would like to give a more detailed description:

Create a custom generator theme

The customization of the generator configuration is only useful if you need to modify the code generation somehow (and that means creating a custom generator theme). For example I changed the database query for listing the objects, but the query should only be changed if a certain flag (in the ‘generator.yml’) is set.

To create a new theme, we do the following:

  1. Create the folder ‘data/generator/sfPropelModule/[your_theme_name]‘ or ‘data/generator/sfDoctrineModule/[your_theme_name]‘ in case you use Doctrine.
  2. Copy all the files in ‘$sf_symfony_lib_dir/plugins/sfPropelPlugin/data/generator/sfPropelModule/admin‘ (resp. ‘$sf_symfony_lib_dir/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin‘ to the newly created folder.

Our custom theme can now be activated for a module by changing the theme property in the ‘generator.yml’:

# apps/[application]/modules/[module]/config/generator.yml
generator:
  class: sfPropelGenerator
  param:
    model_class:           ModelClass
    theme:                 your_theme_name
    # ...

Edit your custom theme

We add the following line to the end of the class in “data/generator/sfPropelModule/[theme_name]/parts/configuration.php“:

// data/generator/sfPropelModule/[theme_name]/parts/configuration.php
[?php
class Base<?php echo ucfirst($this->getModuleName()) ?>GeneratorConfiguration extends sfModelGeneratorConfiguration
{
    //...
    // This is the new line
    <?php include dirname(__FILE__).'/customConfiguration.php' ?>
}

The file 'customConfiguration.php' will contain our extensions to the generator configuration. This way it is easier to keep an overview of which code is written by us.

Before we go further,  a little explanation of the function names in the '*Configuration.php' files:

As we already can conclude from the exception, every entry directly under the 'config' key of the generator.yml is referred to as a context. So the default contexts are: actions, fields, list, filter, edit and new.

Every function is named like

get[Context][Property]

If we want to create a new property under the list context called my_property, we have to create this function:

public function getListMyProperty() {
}

How to process the custom property

The complete function will look like this:

// data/generator/sfPropelModule/[theme_name]/parts/customConfiguration.php

public function getListMyProperty() {
    return <?php echo isset($this->config['list']['my_property']) ? (integer) $this->config['list']['my_property'] : 10 ?>;
<?php unset($this->config['list']['my_property']) ?>
}

Please notice the non-existence of the <?php tag at the beginning of the file. That is because we deal with templates of PHP files here. That means this file is parsed once with PHP to create another PHP file. The instructions between <?php ... ?> are instructions for the parser.

Lets have a look at the function in detail:

The whole generator configuration is accessible via the array $this->config and is structured as $this->config[context][property]. So what happens here is that the return value of the function getListMyProperty is specified (remember: it is a PHP template). If my_property exists then its value is taken (and transformed to an integer in this example because my_property is supposed to hold an integer) otherwise the default value 10 is used.

Afterwards the array key my_property is deleted (unset).
Important: This statement must be included, otherwise you will get the mentioned exception nonetheless! Read below if you are interested why.

Keep in mind to always follow this procedure: Check for existence of the property and provide a default value if it does not. Otherwise the generated code may end up in a mess.

The generated code

So what does the generated code look like? Lets have a look at the file 'cache/[application_name]/dev/modules/[module_name]/lib/Base[Module_name]GeneratorConfiguration.class.php':

<?php

class BaseModule_nameGeneratorConfiguration extends sfModelGeneratorConfiguration
{
    // ...

    // If my_property was defined in 'generator.yml':
    public function getListMyProperty() {
        return 20;
    }

    // ...
}

That is the generated code if my_property was defined with the value '20' in 'generator.yml'. Otherwise the function would return the default value:

    public function getListMyProperty() {
        return 10;
    }

How to access the custom property

In the other theme files, at least in the  '*Actions.php' files,  you have now access to the property via $this->configuration->getListMyProperty().

How to return values correctly

In this example the function was supposed to return an integer. But of course any valid value can be returned. Just keep in mind that we deal with templates of PHP files here.

  • To return a string, enclose the commands for the parser in quotation marks, like:
    return '<?php echo isset(...) ? ... : ... ?>';
  • It is wise to escape the string when it should be printed on the page (like the title property). You can use the method escapeString() for that:
    return '<?php echo $this->escapeString(isset(...) ? ... : ... ) ?>';
  • Integer and boolean values should be casted:
    (integer) resp. (boolean)
  • If your property contains an array (either associative like the object_actions property or numerical like the display property), you should use the method asPhp():
    return <?php echo $this->asPhp(isset(...) ? ... : ... ) ?>;

How to add a custom context

Adding a custom context is easy. Actually it is the same like just adding a new property only that the 'generator.yml' looks a bit different:

generator:
-- ...
  config:
    actions: ~
    fields: ~
    list: ~
    filter: ~
    edit: ~
    new: ~
    my_context:
      my_property: value

And the function we create looks like this (assuming that my_property holds an integer again):

// data/generator/sfPropelModule/[theme_name]/parts/customConfiguration.php

public function getMycontextMyProperty() {
    return <?php echo isset($this->config['my_context']['my_property']) ? (integer) $this->config['my_context']['my_property'] : 10 ?>;
<?php unset($this->config['my_context']['my_property']) ?>
}

We just have to change the list context to our custom context.

Why to unset the property in the configuration array?

To answer this question we have to investigate where and why the InvalidArgumentException is thrown. Fortunately symfony delivers us the stack trace when something goes wrong and there we see that the exception is thrown in 'SF_ROOT_DIR/lib/vendor/symfony-1.2.8/lib/generator/sfModelGenerator.class.php' line 416 (in symfony version 1.2.8).

The interesting part of the enclosing function is the following:

protected function loadConfiguration()
{

    // ...

    // validate configuration

    foreach ($this->config as $context => $value)
    {
       if (!$value)
       {
           continue;
       }
       throw new InvalidArgumentException(sprintf('Your generator configuration contains some errors for the "%s" context. The following configuration cannot be parsed: %s.', $context, $this->asPhp($value)));
    }
    return new $class();

}

As you can see the $this->config is examined in a loop. Each property of each context is tested for existence. It becomes clearer now, why every property has to be unset:

If a property is not unset it means that it is not processed and therefore not understood by the parser. if(!$value) returns false then and the exception is thrown. Thats the whole magic. With this in mind I have to revert something that I have said before: We don't have to name our function like get[Context][Property]. We have just to make sure the the property is unset. Nevertheless to keep the code consistent and self-explanatory I strongly recommend to name it the same way as the other functions.

About these ads

3 Responses to “Custom properties in symfony’s admin generator configuration”

  1. Anonymous Says:

    Interesting post. I thought to let you know that you website isn’t getting displayed properly on blazer browser on my mobile phone.

    I wish that more and more number of web site owners would deliberate upon the fact that there is an ever growing number of users browsing webpages on the mobile.
    Best Wishes

  2. liquidingenuity Says:

    I can’t thank you enough for this post. I love Symfony but often times it’s hard to find the help I need online. This post gave me exactly the info I needed, and in a very clear and concise way. Thank you!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: