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 http://svn.symfony-project.com/plugins/sfDoctrineGuardPlugin/trunk 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:
all:
.settings:
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
.
Profile: columns: sf_guard_user_id: integer(4) first_name: string(255) middle_name: string(255) last_name: string(255) email_address: string(255) relations: User: 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('p.id = ?', 1) ->fetchOne(); $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.
sfGuardUser: jwage: username: jwage password: changeme is_super_admin: true Profile: first_name: Jonathan middle_name: Hurley last_name: Wage email_address: jonwage@gmail.com
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'
Profile:
id: '1'
sf_guard_user_id: '1'
first_name: Jonathan
middle_name: Hurley
last_name: Wage
email_address: jonwage@gmail.com
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: jonwage@gmail.com
User:
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:
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() { parent::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.
generator: class: sfDoctrineGenerator param: config: form: class: sfGuardUserAdminForm display: "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
Now when you add and edit users you have the ability to enter the Profile information directly inside of the user form.
Great article Jonathan :-) thx
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.
@Jonathan 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 http://example.org/backend_dev.php/sf_guard_user
But: http://example.org/backend_dev.php/sf_guard_user/new
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 http://myserver/backend_dev.php/sfGuardUser 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 :)
thanks
Hey, Jonathan! thanks a lot! it`s great.
guys... can i make authorization by email instead of username?
super article! thanks!