» symfony 1.0 tutorial : Extending the admin generator
By COil on Thursday 3 April 2008 22:59 :: symfony :: Permalink :: Tags :: #admin #generator #plugin #symfony #tutorial View comments
Tweet this[En] Tutorial content:
- Creating a plugin with a custom admin generator theme
- Extending the sfActions class
- Extending the sfPropelAdminGenerator class
Here is little contribution for the symfony code sprint, a symfony tutorial about the admin generator.
Do you know about it ? I think it's one of my favourite symfony tool, i have developed several applications with symfony and for 75% of them i have made a custom admin generator theme. And as said in the symfony documentation, the admin generator is very very very powerful.
In this tutorial we will see a concrete example on how to extend it to provide a useful new feature. Witch feature ? Well it was one of the things a customer asked me, "I want to be able to delete several rows at a time with check boxes" ... Ahhhhh indeed, it's not a native feature of the admin generator. So.... let's do it...
::: Pre-requisite :::
Well i will not explain here how to install symfony as there are lots of tutorials about this on the symfony website and in the wiki. So i will assume you have a clean symfony installation so you can type the symfony
command in cli. I used the 1.0.13 version for this tutorial.
::: Creating and setting a new project :::
:: Creating the new project ::
First we will create the new project that we will call tutorial
, create a new folder tutorial and launch the init-project task into it.
symfony init-project tutorial
Now let's create the backend application:
symfony init-app backend
As it is always better to have a submain for our application add the following in you apache httpd.conf file and change it with your own path
## COil symfony tutorial n°1 : Extending the admin generator ##
<VirtualHost *:80>
ServerName dev.tutorial.com
DocumentRoot "C:\wamp\www\tutorial\web"
DirectoryIndex index.php
Alias /sf "c:\wamp\bin\php\php5.2.5\PEAR\data\symfony\web\sf"
<Directory "C:\wamp\www\tutorial\web">
AllowOverride All
</Directory>
</VirtualHost>
Add the sub-domain in you hosts file:
127.0.0.1 dev.tutorial.com
Ok, at this point we should have our "Project created!" page. So Let's try to browse http://dev.tutorial.com/backend_dev.php
:: The database ::
Create a local database, let's call it tutorial again. (same as the project name)
Modify the database settings in the config/database.yml and config/propel.ini file, your database.yml file should look like this:
all:
propel:
class: sfPropelDatabase
param:
dsn: mysql://root@localhost/tutorial
Now we need a schema, let's take the one of Askeet
- Download the http://svn.askeet.com/trunk/config/schema.xml file and copy it to your /config folder
- Download the http://svn.askeet.com/trunk/data/fixtures/test_data.yml file and copy it to your /datas/fixtures folder
Ok now let's try to build the database:
symfony.bat propel-build-all-load backend
Oops, it seems there are some errors.
As we are quite lazy just delete the password columns and the QuestionTag section of the /datas/fixtures/test_data.yml file.
Let's try again to run the last command. If your are successful you should now have some tables and data in your tutorial database. If not, you probably missed something.
At this point we have a valid application with a database, some tables and fixtures. Settings are ok, so can go to the next step.
...
: Creation of the new admin generator theme :
When creating an admin generator them, in fact you will create a new plugin. Let's create the following folder:
/plugins/sfTutorialAdminThemePlugin/data/generator/sfPropelAdmin
Then copy into it, the following folder: (replace your_symfony_lib by the correct path)
/your_symfony_data_dir/symfony/generator/sfPropelAdmin/default
Then rename default to TutorialAdminTheme At this point your plugin folder should look like this:
OK. Now we have our plugin structure, let's initialize an admin generated module. For example on the Question table. Run the following command: (don't forget to check the generator documentation while reading this tutorial
symfony propel-init-admin backend questions Question
Now we should have our basic admin generated page with the default theme.
- Browse to http://dev.tutorial.com/backend_dev.php/questions and check if it is ok. The module use the default theme. Let's see if we can switch to our new theme.
- Edit the tutorial/apps/backend/modules/questions/config/generator.yml file and change the theme key value from default to TutorialAdminTheme
- Refresh the admin http://dev.tutorial.com/backend_dev.php/questions and check if it OK, if not your folder structure may be wrong and try to clear the cache

Now to check if we are really using our new theme, we will just modify it a little:
Open the
sfTutorialAdminThemePlugin/data/generator/sfPropelAdmin/TutorialAdminTheme/template/templates/_list_header.php
and add some text:
<h1>TutorialAdminTheme</h1>
Refresh the page, you should see the title we just added in the _list_header.php file.
OK, so at this point we have our plugin witch contains a new admin generator theme called TutorialAdminTheme we can go trough the next step.
: Extending the new theme :
For now we've just added some text in one of the template used. Now we will see how the modify the generator to add the feature we want.
There are several steps for this, we will:
- Extend the default sfActions class
- Extend the default sfPropelAdminGenerator class
- Add new properties to the generator.yml file
- Modify the generated action class
- Modify the generated templates
:: Extending the action class ::
So let's go. 1st step we will create a new action class in witch we will put several new functions that will be used by our new theme.
In the lib folder of the plugin (create this one) create a new file, let's call it tuActions.class.php, thus this class will just extend the standart symfony sfActions.class.php. I used to take 2 letters of the project name in order to prefix extended class of the project, in this case tu for tutorial (or whatever you want...) Your file should look like this:
[php]
<?php
/**
* Modified actions class for out new admin theme.
*
* @package tutorial1
* @author COil
* @since 3 apr 08
*/
class tuActions extends sfActions
{
}
?>
OK, now we have our class, we have to tell the admin generator to use this one instead of the standard sfActions class. Open the:
plugins\sfTutorialAdminThemePlugin\data\generator\sfPropelAdmin\TutorialAdminTheme\template\actions\actions.class.php
file (what a path !) As you can see, this file is not a standard php file, because it's php that generate php... But once what have understood the way it works it's not so hard to modify it. So the <php tags are standard php tags that will be used by the admin generator and the [php tags will be rendered as <?php tags in the generated code.
Line 13 we will then replace sfActions class by our new one. So you just have to modify the 2 first letter:
[php] class <?php echo $this->getGeneratedModuleName() ?>Actions extends tuActions
Ok, let's check out admin page if is still OK. Clear you sf cache and then refresh.
There shouldn't be any change as our new actions class is still empty, but let's verify that our modification was taken in account. To do this, just go to the cache directory:
tutorial/cache/backend/dev/modules/autoQuestions/actions/actions.class.php
If all is OK the file should start like this:
[php]
<?php
// auto-generated by sfPropelAdmin
// date: 2008/04/03 14:12:38
?>
<?php
/**
* autoQuestions actions.
*
* @package ##PROJECT_NAME##
* @subpackage autoQuestions
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @version SVN: $Id: actions.class.php 7997 2008-03-20 12:29:34Z noel $
*/
class autoQuestionsActions extends tuActions
{
public function executeIndex()
{
return $this->forward('questions', 'list');
}
?>
Indeed the autoQuestionsActions class extends out new tuActions class. Perfect. We will come back to our new actions class later.
:: Modification of the generator.yml file ::
First of all, we will build a standart generator file for our Question table, let's use this basic one:
generator:
class: sfPropelAdminGenerator
param:
model_class: Question
theme: TutorialAdminTheme
fields:
id: { name: ID }
user_id: { name: User }
title: { name: Title }
interested_users: { name: Interested user }
reports: { name: Reports }
created_at: { name: Created at , params: date_format='dd/MM/yyyy' }
updated_at: { name: Updated at, params: date_format='dd/MM/yyyy' }
list:
layout: tabular
title: Questions
filters: [id, user_id, title, interested_users, reports, created_at, updated_at]
display: [id, user_id, title, interested_users, reports, created_at, updated_at]
max_per_page: 10
object_actions:
_edit: ~
_delete: ~
actions:
_create: ~
sort: [id, asc]
Refresh the page, it should work.
Now we will add settings for our new actions on objects, let's add just after the sort key (and at the same level), the following line:
object_global_actions:
delete: { name: Deleted the selection }
Here we define a new section object_global_actions that will contain the actions we will be able to do on a list of selected object, like the delete function.
Ok, now we have our new properties in our generator.yml file, let's add our check boxes now, that's the funny part of the tutorial.
:: Modification of the admin generator templates ::
First, we need to know witch file we have to modify, we are on the list screen, thus we will have to modify 2 files of our template plugin folder: _list.php and _list_td_actions.php
Open _list.php, first we need to retrieve the new properties that we have defined, add at the top of the file:
<?php $object_global_actions = $this->getParameterValue('list.object_global_actions'); ?>
<?php if ($object_global_actions): ?>
[?php echo form_tag('<?php echo $this->getModuleName(); ?>/global_action', array('name' => 'global_action_form')); ?]
[?php echo input_hidden_tag('object_name', '<?php echo $this->getSingularName() ?>'); ?]
<?php endif; ?>
As you can see, our new parameter section is called list.object_global_actions and we can retrieve it with the getParameterValue function. (this is a function of the sfPropelAdminGenerator class) Then we declare a new form that will include our check boxes. It will post data to the global_action function, it's a new function that we will have to add in our tuActions class. We also need the name of the object, that's why we need this hidden tag after the form_tag.
In this file, it is tested if there are actions defined for the rows, but now we have 2 types of actions, the standard ones, delete, edit... and our new global ones global_delete... So we will have now to check both. Modify the following line (17):
[php]
<?php if ($this->getParameterValue('list.object_actions')): ?>
by
[php]
<?php if ($this->getParameterValue('list.object_actions') || $object_global_actions): ?>
So as you can see we are checking if we have "object" actions OR "global" actions. We must do the same for the others lines where we have the following test:
if ($this->getParameterValue('list.object_actions')):
It should be another one in this _list file so modify this other test in the same way.
Now we must add our submit buttons and close the form tag, let's add the following code just before the </tr></foot> tags.
<th>
<?php if ($object_global_actions): ?>
[?php $confirm_lib = __('Are you sure ?') ; ?]
<?php foreach ($object_global_actions as $object_global_action => $params): ?>
[?php echo submit_tag('<?php echo $params['name']; ?>', array('name' => '<?php echo 'global_action['. $object_global_action. ']'; ?>', 'confirm' => $confirm_lib)); ?]<br/>
<?php endforeach; ?>
</form>
<?php endif; ?>
</th>
So here we are iterating through our global actions (we just have one for now), and we build the corresponding submit button with an alert message and we close our form. (no really at the good place but that's not important for now). OK, so we have made some modifications, let's see if our page has changed. Let's refresh it. (Don't forget to clear the cache each time you will modify the admin generator templates or action)
Oops, we can see the delete button Delete the selection
, but it seems that the main table is broken. Indeed, it's because we have modified the numbers of columns displayed if the table.
In the following line (28), add "one column":
[php]
<?php
<th colspan="<?php echo $this->getParameterValue('list.object_actions') || $object_global_actions ? count($this->getColumns('list.display')) + 1: count($this->getColumns('list.display')); ?>">
Thus replace it by:
[php]
<th colspan="<?php echo $this->getParameterValue('list.object_actions') || $object_global_actions ? count($this->getColumns('list.display')) : count($this->getColumns('list.display')) -1; ?>">
Clear the cache and refresh, OK, it looks better now. But our submit button is useless as we don't have any check box.
: Adding the check boxes :
Now we have to modify our 2nd templates, the _list_td_actions.php, open it, at the top of the file add the same line we have in the _list.php template, also add the same test on the following if statement as we already done. Then just after the foreach statement add the following code:
<?php if ($object_global_actions): ?>
<li>[?php echo checkbox_tag('<?php echo $this->getSingularName() ?>[id]['. <?php echo $this->getPrimaryKeyValue(); ?>.']', 1, false); ?]</li>
<?php endif; ?>
As you can see, we will use the singular
name of the object to build the name of the check box tag (in this case question) and we will use its primary key to send the data as an array to the action function, so we can easily retrieve the objects we have selected.
Clear the cache and refresh.
OOps, it seems that i have forgotten something, indeed here i am using a new function of the sfGenerator class getPrimaryKeyValue. Once again let's see how to do this.
So put the following code in the lib folder of our plugin and let's call it tuPropelAdminGenerator.class.php
<?php
/**
* Class that extends the default sfPropelAdminGenerator class.
*
* @subpackage tutorial1
* @author COil
* @since 03 apr 08
*/
class tuPropelAdminGenerator extends sfPropelAdminGenerator
{
/**
* Returns the value of the primary key for a row, if there are many
* keys they are returned separated by an underscore.
*
* @author COil
* @since 03 apr 08
*/
function getPrimaryKeyValue($prefix = '')
{
$params = array();
foreach ($this->getPrimaryKey() as $pk)
{
$phpName = $pk->getPhpName();
$fieldName = sfInflector::underscore($phpName);
$params[] = $this->getColumnGetter($pk, true, $prefix);
}
return (count($params) > 1) ? implode('_', $params) : $params[0];
}
}
?>
So this class extends the default sfPropelAdminGenerator class in order to provide a new function. This function will allow us to retrieve the value of a primary key and will also able to manage cases where there are more than one key for this object. OK, we now have our class, but once again we must tell our admin module to use it. Just check the 2nd line of our generator.yml file, and change sfPropelAdminGenerator to tuPropelAdminGenerator ! Easy ! 
Clear the cache and refresh. yeah ! This time is seems to work ! Good job.
At this point, you list should look like this:
Now we have finished to modify our form, we must implement the function that will catch the submitted data (global_action).
Modifying the tuActions.class.php
So first we need to catch witch button was clicked, add the following method to our tuActions.class.php :
<?php
/**
* Catch the button that was clicked for a global action.
*
* @author COil
* @since 3 apr 08
*/
public function executeGlobal_action()
{
$action_key = $this->getRequestParameter('global_action');
$this->forward404Unless($action_key);
$this->forward($this->getModuleName(), 'global_'. key($action_key));
}
?>
So we get the key of the global_key array that was submitted. Then we forward to the action defined by the submit button, so here we take the convention that all global actions are prefixed by global_, so in this case the method we will have to implement is global_delete.
Now we just have to implement this function, here is the code (try to di it by yourself !!
), once again just add it to the tuActions class:
<?php
/**
* Generic delete function for the global action delete.
*
* @author COil
* @since 03 apr 08
*/
public function executeGlobal_delete()
{
$object_name = $this->getRequestParameter('object_name');
$human_object_name = sfInflector::humanize($object_name);
$peer_class = $human_object_name. 'Peer';
$ids = $this->getRequestParameter($object_name. '[id]');
$this->forward404Unless($object_name && $human_object_name && $peer_class &&
class_exists($peer_class)
);
try
{
if ($ids)
{
$c = new Criteria();
$c->add(constant($peer_class. '::ID'), $keys = array_keys($ids), Criteria::IN);
call_user_func_array(array($peer_class, 'doDelete'), array($c));
$this->setFlash('notice', 'The following '. $human_object_name. ' were deleted : '. implode(', ', $keys));
}
else
{
$this->getRequest()->setError('delete', 'Please choose at least one '. $human_object_name. '.');
}
}
catch (Exception $e)
{
$this->getRequest()->setError('delete', 'Could not delete the selected '. $human_object_name. '. Make sure they do not have any associated items.');
}
$this->forward($this->getModuleName(), 'list');
}
?>
We can see that we need here the name of the object in order to know the peer class that we will have to use. We must construct the Criteria dynamically and then we can call the doDelete function. OK, that should be good now ! Clear the cache and test ! It seems OK.
We don't see the notice so you have to modify the templates in order to see them.
Edit the _list_header.php template and then include in it the _edit_messages.php in it. That's it ! 
(be careful there is a little trap in this last exercise...)
Now, it's your turn to work !!
So here are some exercises if you want to continue...
Exercice 1: Modify the templates/action to avoid the error when no message is selected
Exercice 2: Implement a duplicate object
function in the same way as the delete function
Exercice 3: Add select all / unselect all / invert selection links (ok that's javacript this time)
Exercice 4: Add a test to force the user to select a row berore clicking on a button (js again)
I hope you enjoyed this tutorial, feel free to add comments, remarks or to report errors. See you. COil 







» Comments
Coil-
VERY NICE tutorial here. I just read through it (without trying it out yet), and you've covered from the very basic to the more advanced features. I'm looking forward to expanding the admin generator extensively in my own projects. This is an excellent added feature to the Symfony documentation.
wow great !

why using windows?
Il a l'air bien sympa ce tutoriel, je ne l'ai pas encore lu mais je vais regarder ça de près ce midi ou ce soir
Great stuff. Have you thought about integrating these mods with the advanced admin generator plugin?
Bravo Coil, je l'ai pas encore testé mais il est simple à suivre à la lecture, j'ai pas été perdu !
A quand une édition multiple ?
Coil,
Did you know that symfony 1.1 offers the "batch deletion" feature, with checkbox for every line in the list view?
@François: I didn't know it !
But it's just an example of the possibilities that offers the admin generator. So the feature implemented is not really important. I will have a look on how it is implemented in 1.1

@Arsenik : I don't know if it could be done easly... but it could be done for sure.
@Mike : Indeed, but i am not using this plugin, fell free to integrate the code into it if you want.
nice tutorial. I followed it for my own website, and it works just fine. Nice to hear that Sf 1.1 has this features integrated
Excellent tutorial!!! it's just what i need. I was looking for some advanced tutorial to extends symfony admin generator and it's great to begin!
thanks!!
joan teixidó
Hi ,
Nice tutotial, I tried it but it is not working. There is some trap in the code. Could nay body help me to sort it out?
Hi,
excellent tutorial i tried it and it work but can you give me other solution like selectAll/unselectAll please if some one help me.
@Praven: What's the problem ? Yes there are some traps indeed
bonjour Mr Coil vous avez réalisé un grand travail sur le générateur, je pense que vous pouvez m'aider, avez vous une idée sur comment ajouter une nouvelle action (active /notactive) cad dans mon liste d'affichage il y a des items qui sont à l'état actif et d'autres non donc pour mettre (active->inactive,inactive->active)
cordialement
@aboudi: Et bien c'est très simple c'est exactement comme la méthode delete, sauf qu'au lieu d'appeler cette méthode, vous allez faire une méthode:
executeGlobal_setactive() (ou executeGlobal_switchStatus()) et itérer sur les identifiants que va nous soumettre le formulaire. On prendra le même modèle que executeGlobal_delete()
Vous allez donc remplacer le bloc suivant
if ($ids) { $c = new Criteria(); $c->add(constant($peer_class. '::ID'), $keys = array_keys($ids), Criteria::IN); call_user_func_array(array($peer_class, 'doDelete'), array($c)); $this->setFlash('notice', 'The following '. $human_object_name. ' were deleted : '. implode(', ', $keys)); }par:
if ($ids) { // Boucler sur les ids foreach ($ids as $id) { // recuperation de l'objet $object = PeerClass::retrieveByPk($id); // Appeler la methode setActive ou setInactive... $object->setActive(); // ou ->switchStatus() $object->save(); } }Il vous reste à associer la bonne action sur le bouton (voir tuto). Voilà , je vous laisse compléter le code.
Bravo Coil, je l'ai pas encore testé mais c'est simple à suivre
je vous remercie bq pour vos efforts et vos conseils constructifs.
Hey,
This was excellent tutorial, i followed all of it and it works great, and give a good explanation of how to extend the admin generator.
there are few type/mistakes in the snippets you showed which probably caused Mr aboudi to unsuccessful implementation.
1. in the generator.yml add sort: [id, asc] instead of [id, asc]
2. in the changing of _list.php author wrote Modify the following line (17) and meant line 11 and the second change in the code is 17
cheers,
Zvika
sorry
1. in the generator.yml add sort: [id, asc] instead of id, asc
Bonjour,
Très bon tuto! malheureusement je suis sur symfony 1.1. Y-a-t-il un moyen de faire la même chose avec sf 1.1?
@Zvika Van-Straten: fixed.
@chok: Oui, pour l'instant l'admin generator de la 1.1 est identique à celui de la 1.0 donc ça devrait marcher sans trop de soucis même si je n'ai pas testé la chose. Ce ne sera plus le cas pour la 1.2 puisque l'admin generator va être entièrement ré-écrit.
Muy buen tutorial !!!
Muchas Gracias COil
a greate tutorial.
Can you show me how to change the directory of the generated admin generator? Now it has been generate to the "cache" directory.
Thanks
Humm, i'am not sure to understand your question. Yes the "cache" folder is always used by default. Why do you want to change it ?
bonjour Coil ravi de vous contacter j'ai une petite question concernant le validators est ce c'est possible de créer un validateur pour valider un formulaire qui n'appartien pas à edit_form par exemple un fichier qui s'appelle testSuccess.php (j'utilise symfony version 1.1)
@aabbassi: It's not related to the tutorial, please use the sf mailing list
see you.
Bonjour COil,

Je suis entrain de tester ton tuto sous sf1.2 mais des que j'essaye d'etendre le generator.yml
en ajoutant au meme nibeau que les propriétés de liste:
...
list:
...
new_propertie: a_value
...
J'ai une erreur de parsing...Your generator configuration contains some errors for the "list" contex
in SF_SYMFONY_LIB_DIR/generator/sfModelGenerator.class.php line 416 ...
J'ai essayé de définir un nouveau context ... (au meme niveau que list et edit, new )
...
list:
...
new_propertie: a_value
...
=> tjs une erreur de parsing
J'ai alors mis l'es nouvelles propriétées au niveau de config
...
config:
...
list:
...
new_propertie: a_value
...
Là je n'ai pas d'erreur de parsing du fichier mais je ne peux plus récupérer les valeur par l'intermediaire de l'objet $this->config['new_context']['title']
BTW je suis en proel
As tu la moindre iddée de comment faire ?
Une piste ... n'importe quoi ?
Merci d'avance pcq je suis vraiment bloqué...
C'est un tutoriel pour symfony 1.0 non pour symfony 1.2, ça n'a pas été testé pour cette version... l'admin-generator ayant été ré-écrit je suppose que ça a pas mal de conséquences... Bon courage.
Pas de soucis
Pour ceux qui seraient interessé, J'ai posté sur le forum de Symfony: forum.symfony-project.org...
COil, merci bcp pour tous ces tutos très bien réalisés ...
Si j'y arrive je te ferai signe ...
A+
Buen Tutorial!!
Podrias explicarme como debo modficar el _list_header.php?
Aun no me sale el borrado, cuando le doy deleted the selection solo me sale el mesaje de confirmacion y se desmarca el checkbox, q me estaria faltando??
Perdon.. Manejo muy poco el ingles, pero si entiendo al leerlo..
Gracias..
Hola samy,
Bueno, hay lo qué tengo por este "partial":
<h1>TutorialAdminTheme</h1> [?php include_partial('edit_messages'); ?]Hasta luego. COil