Call the expert: Customizing sfDoctrineGuardPlugin

Getting Started

If you are just now getting started with Doctrine, check this previous blog post for how to get started with a new symfony + Doctrine project.

Installing sfDoctrineGuardPlugin

The sfDoctrineGuardPlugin has not been packaged for symfony 1.2 yet so you will need to install via svn like the following:

$ cd /path/to/symfony1.2project
$ svn co plugins/sfDoctrineGuardPlugin

Now that the plugin is in your plugins folder you need to tell symfony to enable it. Edit config/ProjectConfiguration.class.php and enable the sfDoctrineGuardPlugin.

public function setup()
  $this->enableAllPluginsExcept(array('sfPropelPlugin', 'sfCompat10Plugin'));

Be sure to clear your cache after doing the previous steps.

$ ./symfony cc

Enable Modules

sfDoctrineGuardPlugin comes with three modules by default, so in order to use these we will need to enable them in our apps/backend/config/settings.yml. Open it up and modify the enabled_modules setting like the following:

    enabled_modules:        [default, sfGuardUser, sfGuardGroup, sfGuardPermission]

Schema and Data Fixtures

First we need to add some new models to our schema that have relationships defined to the sfGuardUser model included in the sfDoctrineGuardPlugin. So, create a config/doctrine/schema.yml and place the below YAML inside.

In this example we're going to add a Profile model and relate it to sfGuardUser to include some additional information. By default the sfGuardUser schema only includes the information required for the authentication process, nothing more and nothing less. So, in order to capture additional information it is common practice to create a Profile model and relate it to sfGuardUser.

    sf_guard_user_id: integer(4)
    first_name: string(255)
    middle_name: string(255)
    last_name: string(255)
    email_address: string(255)
      class: sfGuardUser
      foreignType: one

Now you will see we defined a one-to-one relationship between Profile and sfGuardUser. By adding that schema, the following is now possible.

$user = new sfGuardUser();
$user->Profile->first_name = 'Jonathan';

And you can also access the relationship from the opposite end as Doctrine automatically makes relationships bi-directional.

$profile = Doctrine_Query::create()
  ->from('Profile p')
  ->innerJoin('p.User u')
  ->where(' = ?', 1)
$user = $profile->User;

Now that we have our schema defined, we need to define some simple data fixtures to test against. Edit data/fixtures/data.yml and place the following YAML inside.

    username:       jwage
    password:       changeme
    is_super_admin: true
      first_name: Jonathan
      middle_name: Hurley
      last_name: Wage
      email_address: [email protected]

Now that we have our schema and data fixtures defined, we need to build everything. You can do so by running the following command:

$ ./symfony doctrine:build-all-reload

Well that was pretty easy! Now lets just do a little inspecting and make sure that all is well and the data is loaded properly. Run the following DQL query to inspect the data.

$ ./symfony doctrine:dql "FROM sfGuardUser u, u.Profile p"
>> doctrine  executing dql query
DQL: FROM sfGuardUser u, u.Profile p
found 1 results
  id: '1'
  username: jwage
  algorithm: sha1
  salt: 9509acc86d1201e5d8314c2421339896
  password: a9119b7ca39bbb842fed640ed93c121990a37dbf
  is_active: true
  is_super_admin: true
  last_login: null
  created_at: '2008-11-12 13:40:42'
  updated_at: '2008-11-12 13:40:42'
    id: '1'
    sf_guard_user_id: '1'
    first_name: Jonathan
    middle_name: Hurley
    last_name: Wage
    email_address: [email protected]

You can even do the opposite to get the Profile objects with the User record joined.

$ ./symfony doctrine:dql "FROM Profile p, p.User u"
>> doctrine  executing dql query
DQL: FROM Profile p, p.User u
found 1 results
  id: '1'
  sf_guard_user_id: '1'
  first_name: Jonathan
  middle_name: Hurley
  last_name: Wage
  email_address: [email protected]
    id: '1'
    username: jwage
    algorithm: sha1
    salt: 9509acc86d1201e5d8314c2421339896
    password: a9119b7ca39bbb842fed640ed93c121990a37dbf
    is_active: true
    is_super_admin: true
    last_login: null
    created_at: '2008-11-12 13:40:42'
    updated_at: '2008-11-12 13:40:42'

Now we are ready to take a look at the enabled modules and check out the functionality they provide. Open http://yourhost/backend_dev.php/sf_guard_user and you should see the sfGuardUser module in action like the following:

sfDoctrineGuardPlugin Users

The Customizing

Now we finally get to the fun part. We added a Profile model and related it to sfGuardUser but how do we make that editable when editing sfGuardUser records via the admin generator? This is pretty simple. We need to tweak a few pieces of code to make this possible.

First, lets update our sfGuardUserAdminForm to embed the ProfileForm. To do this we need to copy a file from the plugin to our project so we can customize the form.

$ cp plugins/sfDoctrineGuardPlugin/lib/form/doctrine/sfGuardUserAdminForm.class.php lib/form/doctrine/

Now open lib/form/doctrine/sfGuardUserAdminForm.class.php and override the configure() method to customize it and embed the ProfileForm.

public function configure()
  $profileForm = new ProfileForm($this->object->Profile);
  unset($profileForm['id'], $profileForm['sf_guard_user_id']);
  $this->embedForm('Profile', $profileForm);

The last thing we need to make this all work is to customize the generator.yml that comes with the plugin. To do this we need to override some of the plugin.

$ mkdir apps/backend/modules/sfGuardUser
$ mkdir apps/backend/modules/sfGuardUser/config
$ touch apps/backend/modules/sfGuardUser/config/generator.yml

Now we need to open apps/backend/modules/config/generator.yml in our editor and tweak it a bit to include the embedded form we named Profile in the previous step.

  class: sfDoctrineGenerator
        class: sfGuardUserAdminForm
          "NONE":                   [username, password, password_again, Profile]
          "Permissions and groups": [is_active, is_super_admin, groups_list, permissions_list]

Notice all we added was the Profile to the list of things to display in the form.

Final Product

sfDoctrineGuardPlugin Edit User

Now when you add and edit users you have the ability to enter the Profile information directly inside of the user form.

Help the Symfony project!

As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.

Call the expert: Customizing sfDoctrineGuardPlugin

Tweet this


Great article Jonathan :-)
Why not just merge the forms?
Just great, I can't wait for the stable sf1.2 version for start working.

thx Jonathan for all your articles. they convinced me to change to Doctrine.
Yes, this is very nice.

After reading this 2 things need to clarify for me : Can someone explain which fields need to be unset when embedding forms ? All PK and FKs ?

Also, can someone explain the difference between merging and embedding forms ?

good work jwage
@Leandro Merging the forms won't save the embedded objects properly.

@Rob0x The id and foreign keys needed unsetting because if you don't then the form will submit null values and update the object with null values, causing the object to save with null values for the foreign key, and that would mean the objects wouldn't be related.

Since we populate the ProfileForm object with the Profile object that is from the $user->Profile object then when we save the user, it automatically knows to satisfy the foreign keys between the 2 objects. Basically if you embed a form in to another that is a relationship in Doctrine, just make sure that the objects are linked together, and Doctrine will take care of making sure all foreign keys are properly set.
Nice article, but stranegely enough, it adds two records of Profile each time I enter a new sfGuardUser.

The only possible explanation lies in the saveEmbeddedForms method, which can be sometimes great but saves everything twice in this case.

Is there a workaround?
when I run doctrine:build-all-reload,I got
"While exporting model class 'Profile' to SQL: Couldn't find class sfGuardUser"

I found there is only "PluginsfGuardUser" in the plugin lib.
Great tutorial. It would be great if someone publishes some tutorial explaining how to do the usual propel queries but using doctrine.
I got the same problem as William.
Any idea how this can be solved?
Won't copying files from the plugin into the main app source tree cause problems in the future when upgrading sfDoctrineGuardPlugin?
@william This doesn't do it for me. Make sure you unset the id and sf_guard_user_id.

@Ice_j7 I will post an article soon that just demonstrates how to query for data using Doctrine on some sample schema.
I followed your tutorial step by step; I have the latest branch release and I still got the same issue: profile table is populated with 2 records for each sfGuardUser record.
It seems that the duplicated record is inserted when embedded form's object is saved ($form->getObject()->save($con)), the other place being in updateObject method: $this->object->fromArray($values).

An workaround for this situation might be creating the following method in sfGuardUserAdminForm:
public function saveEmbeddedForms($con = null, $forms = array())
return parent::saveEmbeddedForms($con, $forms);

altough I don't know how good this is..
I finally found the real problem and this is fixed in svn now.
This example don't work for me. I always get the message:

500 Internal Server Error
The route "sf_guard_user_collection" does not exist.

This Exception raises, if i call


Shows the form to insert a new User. There is realy something wrong with the routing, but i don't know what it could be.

Any hints?
...found the problem. Yes, I tried 20 minutes to signup on trac to make a report - doesn't work. Absolutely impossible. So this is my last try to report the fix.

In sfDoctrineGuardPlugin/lib/sfGuardRouting.class.php there are 3 typos: "with_wilcard_routes" has to be "with_wildcard_routes".

After fixing this everything works as expected.
I have just upgraded my project to RC1 and have followed the tutorial (thank you!). Both dql queries work but accessing
I get this error
"The route "sf_guard_user_collection" does not exist."
I have double checked and there are no "wilcard" typos and I have cleared the cache.
this is a fantastic tutorial :)

Hey, Jonathan! thanks a lot! it`s great.

guys... can i make authorization by email instead of username?
super article! thanks!

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.