As part of one of our current Symfony projects in work, a colleague and I decided that rather than reinventing the wheel everytime we used a new admin-generated module in the application with regards to overriding partials and actions in pretty much the same way each time, it would be productive and easier in the long run to create an admin generator theme. The documentation on the Symfony site for this is sparse to say the least, and when I get some time, I’ll look at contributing back to the Symfony docs a condensed version of this blog post so that it’s a bit more straightforward for the next person. You have the ability to customise pretty much every part of the admin generator including batch actions, form actions, filters, table headers and so on, so it’s worth the work in the long run if you’re looking for quick admin-generated module deployments in line with your site’s template.
OK, so before I start, be warned this is nice and long (and hopefully explanatory)…! I’ll also say that clearing your cache upon making changes to partials is vital. I repeat, VITAL! :-)
The first step I carried out, which is mentioned on the Symfony site, was to copy all the files from the default theme into your project. In our case, we’re using Doctrine as the ORM, so the files were found in
lib/vendor/symfony/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin, and they were copied to
data/generator/sfDoctrineModule/ourTheme. This directory contains 3 subdirectories – we’ll need all 3. The names should be self-explanatory – “
parts” consists of all the parts needed to dynamically generate the code, “
skeleton” consists of skeleton files that form the basis of the final module (
generator.yml etc), and “
template” consists of the template files that use the files in “
That’s the basic step out of the way. If you want to check that everything is being correctly read here, simply alter an existing generator.yml file as follows:
Clear your cache, and reload your page. You shouldn’t see any change – this is because the files are exactly the same as the default theme. Now comes the fun part!
For the following paths, I’ll assume the prefix of
data/generator/sfDoctrineModule/ourTheme/ to avoid typing it out each time.
Adding configuration options
The first change I wanted to carry out was to add some extra configuration options via the
generator.yml file. One major gripe I have with the Symfony admin generator is that once you’ve added a new object, or edited one, the default destination is the edit page, rather than the list page. Personally I see returning to the list as expected behaviour, and every project I use the admin generator in, I end up overriding the
processForm() action and making this change. I wanted to be able to specify this destination via a configuration option on the ‘new’ and ‘edit’ contexts, with a ‘return_to’ option. Carrying out this change involves overriding some other classes in Symfony, most notably the
sfModelGeneratorConfiguration class. I copied this class from the Symfony core to
Once you’ve done this, alter
parts/configuration.php so that the generated class extends from
myModelGeneratorConfiguration instead of
Open up this class, and you’ll see a lot of abstract methods defined near the top. These methods are created during the generation process, and are located in the
parts/fieldsConfiguration.php file. You’ll see that it consists of what looks like PHP within PHP – this is a common theme throughout the development of an admin generator theme, and can get a tad confusing if you forget what context you’re in when you’re writing code! You can use the example methods to build your own. In my case, I added the following:
public function getNewReturnTo()
return '<?php echo $this->escapeString(isset($this->config['new']['return_to']) ? $this->config['new']['return_to'] : 'edit') ?>';
<?php unset($this->config['new']['return_to']) ?>
public function getEditReturnTo()
return '<?php echo $this->escapeString(isset($this->config['edit']['return_to']) ? $this->config['edit']['return_to'] : 'edit') ?>';
<php unset($this->config['edit']['return_to']) ?>
These methods both return a configuration value if it exists in the YAML file, or the defaults of ‘new’ and ‘edit’ respectively if no configuration value is specified.
In order to make this option available in the configuration, the
myModelGeneratorConfiguration->compile() method should be altered to allow this option:
'new' => array(
'fields' => array(),
'title' => $this->getNewTitle(),
'actions' => $this->getNewActions() ? $this->getNewActions() : $this->getFormActions(),
'return_to' => $this->getNewReturnTo()
and the same for the edit option. The final change is to actually action this change in the
processForm() method, conveniently found in
parts/processFormAction.php. This file (along with all the other
*Action.php files) are included in the generated
sfActions class specific to the module you’re creating. I removed the final redirect line which redirected back to the ‘edit’ URL, and replaced it with the following changes:
$thisContext = $form->getObject()->isNew() ? 'new' : 'edit';
$notice = $form->getObject()->isNew() ? 'The item was created successfully.' : 'The item was updated successfully.';
$this->redirect('@<?php echo $this->getUrlForAction('new') ?>');
$route = '<?php echo $this->getSingularName(); ?>';
$returnTo = strtolower($this->configuration->getValue($thisContext . '.return_to'));
if ($returnTo != 'list')
$route .= '_' . $returnTo;
$returnArray = array('sf_route' => $route);
if ($returnTo != 'list')
$returnArray['sf_subject'] = $<?php echo $this->getSingularName(); ?>;
That’s it! Add the configuration option into your generator.yml as follows:
Clear your cache, reload the page and test it out. After creating a new object, you should be returned to the list. This is restricted to eg
my_model_ACTION-type routes, but you can easily enhance the above code to redirect to any other route, check for an “@” symbol if needs be and so on.
I wanted to implement an Ajax edit form on a “view” page for my model, so that the user wasn’t taken away. The templates I was working from had this as a pop-over box, but instead of redirecting the user afterwards, I wanted the edit and update to be seamless. In terms of the form, the ajax form was similar (but slightly different) to the normal admin-generated edit form, so I wanted the ability to be able to configure the form separately from the edit page.
I experimented with configuring it as part of my ‘view’ context but ran into problems with nested array configurations, so I decided to abstract it out and create a new “context” with which I could configure items in the same way as a normal context (‘new’, ‘edit’, ‘list’ and so on). I termed this ‘ajaxedit’; NB I’m calling them ‘contexts’ here as I’m sure I read that’s what they were called somewhere! Correct me if not ;-)
The creation of this is very similar to the above changes for adding a configuration value. In
myModelGeneratorConfiguration.class.php, add the following in to the configuration array in
'ajaxedit' => array(
'title' => $this->getAjaxeditTitle(),
'actions' => $this->getAjaxeditActions() ? $this->getAjaxeditActions() : $this->getFormActions(),
'fields' => array()
I added this after the ‘edit’ one currently there. If you proceed down through this method, I also duplicated the ‘edit’ line in the first
foreach() loop, where the fields are configured using
sfModelGeneratorConfigurationField, and altered ‘edit’ to ‘ajaxedit’ where appropriate. Same in the ‘virtual fields’ configuration, the form actions, the field configuration (duplicated from ‘list field configuration’), and the
You should also add ‘ajaxedit’ to the credentials array near the end of the
compile() method, so that you can control access to the popup/context with credentials, and again into the
getConfig() method at the end of the class.
You then need to create the relevant abstract methods as before, so that the context can be correctly configured. Again, I used the ‘edit’ context as an example to base my changes and additions on.
The next step is to configure the templates and partials so that they recognise this. I used the default
_form.php partial so that I could re-use code where possible, but added a ‘context’ variable to the partial when calling it, eg. in my view page:
[?php include_partial('<?php echo $this->getModuleName() ?>/form', array('<?php echo $this->getSingularName() ?>' => $<?php echo $this->getSingularName() ?>, 'form' => $form, 'configuration' => $configuration, 'helper' => $helper, 'context' => 'ajaxedit')) ?]
In my form partial, I then altered the
getFormFields() call to the following:
[?php foreach ($configuration->getFormFields($form, $form->isNew() ? 'new' : 'edit') as $fieldset => $fields): ?]
so that I was using the correct set of fields for the form display. Don’t forget to clear your cache after making these changes.
You can handle the ajax post however you like; I used jQuery and a callback handler to adjust details and display new values etc where required. The handling of the form data was done in the same way as a normal Symfony action; details of this (and Ajax-specific stuff) are in the main Symfony documentation.
The one major thing to remember is that clearing your cache is VITAL to a speedy admin generator theme development. If you’re running in dev mode via your front controller, then Symfony will only regenerate partials that are directly related to your
generator.yml on the fly. It won’t recreate action templates if there is no need to from
generator.yml changes. This means that you will need to manually call “
./symfony cc” from the command line whenever you make changes to partials. I lost count of how many times I ran that task during my theme development :-)
I’d imagine there are nicer ways to do some of the above – I’m not particularly a fan of the overriding of classes and having them separate from the theme, although I know that Symfony’s autoloading can be pretty clever sometimes, so it would probably find the classes with a bit of tweaking, if I put them as part of the theme. It’s also a bit tricky to get your head around the whole PHP-creating-PHP idea, and remembering whereabouts you are, and what variables you have available to you. I lost a good few hours trying to use variables that were in the top-level PHP, in my generated partials, and wondering why they weren’t working.
However, the end result is definitely worth it and saves a lot of time in the long run if you’re looking to re-use templates and code across a number of admin-generated modules!