Symfony2 Logo

Symfony2 : More about the ExtraToolsBundle

Following some of my previous posts A translation message extractor command and Create your own constraint validator in symfony2 : A Doctrine unique validator, these two handful functionalities are available on Github in the ExtraToolBundle.

Please, fork me ;)

Also, both functionalities have been strongly improved since I have blogged about them.

The Doctrine UniqueValidator, is now much more configurable and have been updated to support the new namespace annotation injection. And, thanks to Matt Agar the trans:udpate command is now much more robust and supports export to xliff and php translation files. He did a very nice job and it was very nice to have another developer to challenge and contribute to my implementation.

You can find the full details of the new tools on the ExtraToolsBundle’s github.

Symfony2 Logo

Create your own constraint validator in symfony2 : A Doctrine unique validator

The new symfony2 form framework is definitely one of my favorites. It is quite simple, and very powerful and straightforward. Moreover it is very extensible.

One of the most common tasks you need to perform when validating a form is to check the unity of a field in your database. Unfortunately, there is no build-in validator coupled with doctrine. Here is my implementation.

Create the unique validator

The first thing to do when implementing a new validator is to create the constraint class:
[cc lang="php"]
namespace MyAppMyBundleValidator;

use SymfonyComponentValidatorConstraint;

class Unique extends Constraint
{
public $message = ‘This value is already used';
public $entity;
public $property;

public function validatedBy()
{
return ‘validator.unique';
}

public function requiredOptions()
{
return array(‘entity’, ‘property’);
}

public function targets()
{
return self::PROPERTY_CONSTRAINT;
}
}
[/cc]

As you can see, this definition is pretty simple. We define the error message and two more required options which are the Entity that doctrine with check and its property. The validatedBy() method returns the service’s name that is in charge to validate the constraint (we will declare it just after). Finally, the targets() function limits the use of the constraint only to class’ property.

Now that our constraint is set, let’s create the constraint’s validator:
[cc lang="php"]
namespace MyAppMyBundleValidator;

use DoctrineORMEntityManager;
use SymfonyComponentValidatorConstraint;
use SymfonyComponentValidatorConstraintValidator;

class UniqueValidator extends ConstraintValidator
{
private $entityManager;

public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}

public function isValid($value, Constraint $constraint) {
// try to get one entity that matches the constraint
$user = $this->entityManager->getRepository($constraint->entity)
->findOneBy(array($constraint->property => $value));
// if there is already an entity
if($user != null){
// the constraint does not pass
$this->setMessage($constraint->message);
return false;
}
// the constraint passes
return true;
}
}
[/cc]

The main function here is isValid(). It checks the validity of the constraint and consequently returns true/false and may add some error message. Here our job is to check is the value is already used in the database. The simplest way to do that is to try to get one matching entity. If Doctrine finds an entity the constraints does not pass, if there is no entity, the value can be used and the constraint passes.

One thing to know is that the Unique constraint instance is pass to the isValid() function, so that we can get the entity et property values.

Finally, our UniqueValidator has a dependency to the Doctrine manager. We will need to declare it in our services:

[cc lang="yaml"]
# MyApp/MyBundle/Resources/config/services.yml
parameters:
my.validator.unique.class: MyAppMyBundleValidatorUniqueValidator

services:
my.validator.unique:
class: %my.validator.unique.class%
arguments: [@doctrine.orm.entity_manager]
tags:
– { name: validator.constraint_validator, alias: validator.unique }
[/cc]

We defined here the way to instantiate our UniqueValidator with its dependencies (arguments line). The tag line is very important. The name specifies that it is load as a constraint validator, and the alias name is used to link the constraint validator to its constraint (remember the Unique::validatedBy() function).
One last thing is to load our services.yml file. We can do it in the dependency injection configuration :
[cc lang="php"]
load(‘services.yml’);
}

public function getAlias() {
return ‘my';
}
}
[/cc]

The load() function is launched at the configuration of the container. Here we simple retrieve the services.yml file and load it to the current configuration.

That’s it you know have a working constraint that you can use to check the unity of a field at form’s submission. Don’t forget to set the entity and property values.

Support annotation

Actually I prefer setting my constraints using annotations. To do so, we will need some more work.

Let’s say we want to use our constraint like that:
[cc lang="php"]
/**
* @orm:Entity
*/
class User{
/**
* @orm:Column(length=255, unique=”TRUE”)
* @myvalidation:Unique(entity=”MyBundle:User”, property=”username”)
*/
protected $username;

//…
}
[/cc]

It won’t work right away. This is because the myvalidation is a shortcut to a namespace (just like validation or orm). Because we did not declare this shortcut, symfony cannot find the Unique class and simply ignores it.

To declare the myvalidation shortcut you can do it directly in the app/config/config.yml. I chose to do it in the bundle because I don’t like to play so much with the app configuration. One of the benefits of bundle is that they decoupled the application, so we don’t want our general configuration to become dependent to our bundle.

To declare the shortcut in our bundle we can do it in the dependency injection configuration:
[cc lang="php"]
load(‘services.yml’);

// get the existing registered namespaces for validator annotations
$namespaces = $container->getParameter(‘validator.annotations.namespaces’);
// add our namespace under the alias myvalidation
$namespaces['myvalidation'] = ‘MyApp\MyBundle\Validator\';
// save it
$container->setParameter(‘validator.annotations.namespaces’, $namespaces);
}

public function getAlias() {
return ‘my';
}
}
[/cc]

What we need to do is to retrieve the namespace aliases of the validators and then add our alias. Don’t forget the trailing backslashes.

And voila! Our Unique constraint works also with annotations.

I hope that this tutorial will help you to build new powerful constraints and share them to the community.

symfony

Symfony 1.4 vs. sfGuardUserPlugin : understand credentials and permissions

I am currently dealing with a project that has strong requirements on how the user authorizations are given amount the users.
If you are familiar with the symphony 1.4 jobeet tutorial you should have seen first authorizations and then a quick introduction to the sfGuardUserPlugin. Let’s get around that first:
Credentials
They are a nice way to define borders to you application. You can place them on every action very easily by editing the security.yml file of the zone where you are setting your rights.
[yml]
all:
is_secure: false
new:
is_secure: true
credentials: manager
create:
is_secure: true
credentials: manager
[/yml]
Here I simply said that my module is accessible for everyone, except the new and create actions where the user needs to be logged and evermore he need to have the manager credential.
The action workflow checks the needed credentials just before loading the action, and if the user has it, it executes the actions, if not, a 403 page is given. The function of the user that is used is called hasCredential.
The first thing you have to know is that credentials are given to the sf_user, even if he is not logged. This means that thay are session dependent and cannot be persists amount sessions. Given credentials are not mean to be stored in your database.
Permissions
They came with the sfGuarduserPlugin. The plugin gives you the sfGuardSecurityUser, which extends the sf_user and which can be persisted. You can also give to him permissions or groups. Groups can have permissions in such a way that a user automatically gets the permissions of his group.
Moreover, the User, Groups and Permissions can be stored in your database.
[php]
// add a group
$sfGuardSecurityUser->addGroupByName(‘manager’);
// add permission
$sfGuardSecurityUser->addPermissionByName(‘manager’);
// get permissions (direct permissions and also group permissions)
$sfGuardSecurityUser->getAllPermissions();
[/php]
The matter now is that the controller workflow does not use the permissions in order to compare them to the needed credentials for an action.
How to check credentials using the permissions
If we want to combine the nice yaml credential description and the powerful permission system, we need to override the hasCredential method.
The cool thing here is that the instance of the user used in this case is the myUser class defined in your application folder. This way our can override the method in a non-obtrusive manner:
[php]
// /apps/frontend/lib/myUser.class.php
class myUser extends sfGuardSecurityUser {
public function hasCredential($credential, $useAnd = true) {
// combine the credential and the permission check
return (parent::hasCredential($credential, $useAnd) || parent::hasPermission($credential));
}
}
[/php]
Upgrade the 403 page
What I like to do sometimes is to tell the user what credentials he misses. In order to do that, we will need to change the secure function that redirects to the 403 page.
Go to the sfGuardPlugin and look for the sfGuardAuthActions.class.php file :
[php]
// /plugins/sfDoctrineGuardPlugin/modules/sfGuardAuth/actions/actions.class.php
class sfGuardAuthActions extends BasesfGuardAuthActions
{
public function executeSecure($request) {
parent::executeSecure($request);
// retrieve the needed credentials
$this->credentials = $this->getContext()->getActionStack()->getFirstEntry()->getActionInstance()->getCredential();
// make sure the credentials are an array (in case of unique non-array credential)
if(!is_array($this->credentials))
$this->credentials = array($this->credentials);
}
}
[/php]
You noticed here that I use the first controller called in the action stack. You have to do that in order to retrieve credentials of the first action that tried to reach the user. If not, you will only retrieve the credentials of the current controller which does not have any.
You now have the credentials that you can display in your template.
[php]
// /plugins/sfDoctrineGuardPlugin/modules/sfGuardAuth/tempates/secureSuccess.php
You have to meet the following requirements to have access to this action:
<ul>
<?php foreach($credentials as $credential): ?>
<li>
<?php switch ($credential){
case ‘manager':
echo ‘You need to be a manager';
break;
}?>
</li>
<?php endforeach;?>
</ul>
[/php]
And voila, you have now a nice and powerful user authorization management on your symphony website.

symfony

How to use filters on custom fields with symfony 1.4

I recently continued to use my particular relationship that embeds some properties. This time I wanted to add some filters to my student request using the pre-generated filter used in the backend.

Here again, is our example:

N to N relationship with properties

N to N relationship with properties

And the pre-coded filter is here:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
{
}
}
[/php]

Here the filter already includes fields for every field on Student objects (id and name). It is a regular form, so you can override the predefined widgets and validator or even unset some of them. Let’s unset the id:

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure(){
unset($this['id']);
}
}
[/php]

Now we can use our filter like this in our controller:
[php]
// app/frontend/module/student/actions/actions.class.php
class studentActions extends sfActions {
public function executeIndex(sfWebRequest $request) {
//create a default query
$query = Doctrine::getTable(‘Student’)->getQuery();

//obtain the filter values that could have been submited
$filterValues = $request->getParameter(‘student_filters’);
//create the StudentFormFilter using the values, and giving it the query
$this->formFilter = new StudentFormFilter($filterValues);
$this->formFilter->setQuery($query);
//if the form have been submited
if($filterValues){
//bind the values
$this->formFilter->bind($filterValues);
//if valid, specialise the request using the form
if($this->formFilter->isValid())
$query = $this->formFilter->getQuery();
}

//finally get the students
$this->students = $query->execute();
}
}
[/php]

This code could seem a little weird but it is finally all you need to know in order to make a basic use of the auto generated filters. Thus, your template can look like this:

[php]
<?php echo form_tag(url_for(‘student_index’)) ; ?>
<table>
<?php echo $formFilter ; ?>
</table>
<input type="submit"/>
<?php echo ‘</form>’ ?>
// …
<ul>
<?php foreach($students as $student): ?>
<li><?php echo $student->getName(); ?></li>
<?php endforeach; ?>
</ul>
[/php]

Ok, everything is working quite well. But, let’s getting our filter trickier. I want to add a filter that permits me to select students that have a specific enrolment date.

First of all, we need to add a widget and a validator:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
unset($this['id']);

$this->setWidget(‘EnrolmentDate’, new sfWidgetFormFilterDate( array(
‘from_date’ => new sfWidgetFormDate(),
‘to_date’ => new sfWidgetFormDate(),
‘with_empty’ => false)));
$this->setValidator(‘EnrolmentDate’, new sfValidatorDateRange(array(
‘required’ => false,
‘from_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 00:00:00′)
),
‘to_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 23:59:59′)))));
}
}
[/php]

Here, our form will display properly, but the getQuery method used in the will simply ignore our new EnrolmentDate field.

Now the things are getting really uncommon. Symfony is not really built for this kind of behavior, and we will need to override some behavior in our StudentFormFilter to get thing to work. In order to do that I read the sfFormFilterDOctrine.class.php file and more specifically the doBuildQuery function where the request is built. Some of the things I’ll do are a bit ugly but it is the price to pay if we want not to spread our modifications into the core framework, thus I managed to keep everything in the StudentFormFilter.class.php file. This way our ugliness remains contained into a single file and does not impact the framework. So let’s go for it!

First of all we need to add the EnrolmentDate as a new field of the form, to do that we need to override the getFields function:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function getFields() {
return array_merge(parrent::getFields(), array(‘EnrolmentDate’=>’EnrolmentDate’));
}
}
[/php]

The very important thing here is to know that I added a field EnrolmentDate of type EnrolmentDate. It will be crucial later.

Also, the doBuildQuery method will get the Student table and check for the presence of the field EnrolmentDate. The workaround here is to make a stub table and override the getTable function in order to return this stub table.

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function getTable() {
return new StudentStubTable();
}
}

// my stub table
class StudentStubTable extends StudentTable{
public function hasField($field) {
if($field == ‘EnrolmentDate’)
return true;
else
return parent::hasField($field);
}
public function getFieldName($field){
if($field == ‘EnrolmentDate’)
return ‘EnrolmentDate';
else
return parent::getFieldName($field);
}
}
[/php]

As you can see, two methods need to be overridden in my StudentStubTable class. Those two functions are used by the doBuildQuery function. Here i make the field EnrolmentDate exist and create his fieldName in the table is EnrolmentDate.

From here, all we need to know is that the doBuildQuery function will look for an addEnrolmentDateQuery function in our StudentFormFilter class. This function is called based on the type of the fields that I defined earlier in the getFields method. This call is made by the concatenation of ‘add’ + type of the field + ‘Query’.

So, let’s code the function and make it do our join with the Enrolment table.

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function addEnrolmentDateQuery(Doctrine_Query $query, $field, $values) {
//add our join part
$query->leftJoin(sprintf(‘%s.Enrolment enrolment’,$query->getRootAlias()));

//set the where part depending on the value
if (isset($values['is_empty']) && $values['is_empty'])
{
$query->addWhere(‘enrolment.EnrolmentDate IS NULL’);
}
else
{
if (null !== $values['from'] && null !== $values['to'])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values['from']);
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values['to']);
}
else if (null !== $values['from'])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values['from']);
}
else if (null !== $values['to'])
{
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values['to']);
}
}
}
}
[/php]

In this method, we retrieve the query as a parameter and we just have to build our method based on the values we receive. Everything here is about building a proper DQL query.

Now we have a nice filter that can filter on our property embedded into a relationship.

Here is the final code:

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
unset($this['id']);

$this->setWidget(‘EnrolmentDate’, new sfWidgetFormFilterDate( array(
‘from_date’ => new sfWidgetFormDate(),
‘to_date’ => new sfWidgetFormDate(),
‘with_empty’ => false)));
$this->setValidator(‘EnrolmentDate’, new sfValidatorDateRange(array(
‘required’ => false,
‘from_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 00:00:00′)
),
‘to_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 23:59:59′)))));
}

public function addEnrolmentDateQuery(Doctrine_Query $query, $field, $values) {
//add our join part
$query->leftJoin(sprintf(‘%s.Enrolment enrolment’,$query->getRootAlias()));

//set the where part depending on the value
if (isset($values['is_empty']) && $values['is_empty'])
{
$query->addWhere(‘enrolment.EnrolmentDate IS NULL’);
}
else
{
if (null !== $values['from'] && null !== $values['to'])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values['from']);
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values['to']);
}
else if (null !== $values['from'])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values['from']);
}
else if (null !== $values['to'])
{
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values['to']);
}
}
}

public function getFields() {
return array_merge(parrent::getFields(), array(‘EnrolmentDate’=>’EnrolmentDate’));
}

public function getTable() {
return new StudentStubTable();
}
}

// my stub table
class StudentStubTable extends StudentTable{
public function hasField($field) {
if($field == ‘EnrolmentDate’)
return true;
else
return parent::hasField($field);
}
public function getFieldName($field){
if($field == ‘EnrolmentDate’)
return ‘EnrolmentDate';
else
return parent::getFieldName($field);
}
}
[/php]

If you have any remarks about my implementation (I am sure you have) don’t hesitate to tell me about, I think this code can still be improved.

symfony

Add properties to a relationship – Dealing with forms in symfony 1.4

Lately, I showed how you can a simply add properties to a relationship:

Doctrine 1.2: Add properties to a relationship

I continued to use this solution until I recently faced some issue for it. In fact, I discovered what I thought was a weird behavior until I completely understood the full impact of the solution.

Let’s take back our example:

N to N relationship with properties

N to N relationship with properties

I am using it in the symfony Framework, and I want to make a student form that includes also the enrolment properties. To do that, I can just use the embedded relation:

[php]
class StudentForm extends BaseStudentForm {
public function configure() {
$this->embedRelation("Enrolments");
}
}
[/php]

This way, symfony automatically takes care about including the collection of enrolments into my form. And process it after the submission.

You can simply include it in your template:

[php]
$form = new StudentForm();
echo $form;
[/php]

This is working pretty well!

The matter is that I want to customize my form, and thus I template it:

[php]
<form action="./submit" method="post">
<table>
<tbody>
<tr>
<th>Name</th>
<td><?php echo $form['name']->render() ?></td>
</tr>
<?php /* Include the collection of enrolments */ ?>
<?php foreach($form['Enrolments'] as $enrolment): ?>
<tr>
<?php echo $enrolment['enrolementDate']->render() ?>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<input type="submit" value="Save" />
</td>
</tr>
</tfoot>
</table>
<?php /* Don’t forget hidden fields */ ?>
<?php echo $form->renderHiddenFields() ?>
</form>
[/php]

Now, I have a fully customizable form… but it is not working when submit. Worse, it erases your enrolments!!! If you look into the doctrine log, I can see that you have a DELETE statement that should not be here. And then the update is done on your enrolments, even if they have been deleted.

At first, it is very strange. The reason why, is because when you embedded the enrolment relationship into your form, you also have indirectly embedded the course relationship. This is because the enrolment carries the course relationship. When symfony processes the form, it sees that you have enrolments, but you don’t have the courses that they should be bound to. So, it must be that your removed the course, and thus, the enrolments have no reason to exist anymore.

In order to work around this issue, you simply have to add the course into your form. I just did it with hidden inputs:

[php]
<?php foreach($form->getObject()->getCourses() as $course): ?>
<input type="hidden"
name="student[courses_list][]"
value="<?php echo $course->getid() ?>">
<?php endforeach; ?>
[/php]

Now, when symfony processes the form, it finds the courses ids and keeps the relationship intact.

You now have a good working form!

symfony

Adding actions to an auto generated admin in symfony 1.4

I know this functionality is nicely described in the symfony jobeet tutorial, but recalling this particular point emphasis the fact that symfony is definitely a good out of the box tool to easily generate powerful admin that should normally be very painful to code by hand.

In my example I will generate an admin for a system that deals with TV shows. The particular admin screen that I will customize is the TV Show list screen, in order to add it the “add season” action.
The “add season” action will simply add a season to my TV Show by retrieving the last season and increment the number and the year of the season.

Here is the UML for the TV Shows and the seasons:

UML of the tv show example

UML of the tv show example

We will first add the buttons to the admin interface, in order to do that I’ll just go to the generator.yml file and add the following lines:

[yml]
# /apps/backend/module/tvshow/config/generator.yml

generator:
class: sfDoctrineGenerator
param:
model_class: Tvshow
theme: admin
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: tvshow
with_doctrine_route: true
actions_base_class: sfActions

config:
actions: ~
fields: ~
# customization of the list screen
list:
# header of the <table/>
display: [=Name]
# actions that will be in the combo box
batch_actions:
addSeason: ~
# actions that will be in the <table/>
object_actions:
addSeason: ~
_edit: ~
_delete: ~
filter: ~
form: ~
edit: ~
new: ~
[/yml]

You can note that the auto generated actions start with “_”, such as “_edit” and “_delete”. Moreover, the new “addSeason” action is automatically inserted in the screen and the routes and form submission are already working. You should obtain this:

Customized admin

Customized admin

Now we just need to code the actions that correspond to the route. Everything happens in the actions.calss.php dedicated to the tvshow generated module:

[php]
<?php

// /apps/backend/module/tvshow/actions/actions.class.php

require_once dirname(__FILE__).’/../lib/serieGeneratorConfiguration.class.php';
require_once dirname(__FILE__).’/../lib/serieGeneratorHelper.class.php';

class tvshowActions extends autoTvshowActions
{
/*
* Action executed when excecuting "addSeason" via the combo box
*/
public function executeBatchAddSeason(sfWebRequest $request)
{
// retrieving the ids correspondig to the checked boxes
$ids = $request->getParameter(‘ids’);

// query to retrieve the items via Doctrine
$q = Doctrine_Query::create()
->from(‘Tvshow t’)
->whereIn(‘t.id’, $ids);

// performing the addSeason on each TV show
foreach ($q->execute() as $tvshow)
$tvshow->addSeason();

// always inform the user
$this->getUser()->setFlash(‘notice’, ‘The selected TV Shows have been extended successfully.’);

// redirect to the auto generated module
$this->redirect(‘tvshow’);
}

/*
* Action executed when excecuting "addSeason" via the list directly
*/
public function executeListAddSeason(sfWebRequest $request)
{
// retrieving the tv show
$tvshow = $this->getRoute()->getObject();

// perform the addSeason
$tvshow->addSeason();

// always inform the user
$this->getUser()->setFlash(‘notice’, ‘The selected tv show have been extended successfully.’);

// redirect to the auto generated module
$this->redirect(‘tvshow’);
}
}
[/php]

Here we just miss the addSeason function:

[php]
<?php
// /lib/model/doctine/Tvshow.class.php

class Tvshow extends BaseTvshow {

/*
* Add a season the the TV show
*/
public function addSeason(){
// retrieve the last season via Doctrine
$last = Doctrine::getTable(‘Season’)
->createQuery()
->where(‘tvshowId=?’, $this->getId())
->orderBy(‘number DESC’)
->fetchOne();

// create the new one
$season = new Season();
$season->setTvshowId($this->getId());
$season->setYear($last->getYear() + 1);
$season->setNumber($last->getNumber() + 1);
$season->setNumberOfEpisodes($last->getNumberOfEpisodes());

// save it
$season->save();
}
}
[/php]

And everything is set. We now have a perfectly working new action included in our auto generated admin. This showing how symfony is highly customizable despite great functionalities in auto generated code.

Doctrine Logo

Doctrine 1.2: Add properties to a relationship

I recentry had the need to had properties to a relationship using Doctrine. In fact this use case is not taken care of by the ORM (and ORMs in general) and there is no really good solution. Nevertheless, I found a not so bad way to implement such a pattern.

Here is what I’ll do:

As you can see, a very common school example.

We can already describe our objects and the N to N relationship.

[yml]
# Shema.yml
Student:
columns:
id:
type: integer
primary: true
autoincrement: true
name:
type: string
relations:
Courses:
class: Course
local: student_id
foreign: course_id
foreignAlias: Students
refClass: Enrolment

Course:
columns:
id:
type: integer
primary: true
autoincrement: true
name:
type: string

Enrolment:
columns:
student_id:
type: integer
primary: true
course_id:
type: integer
primary: true
[/yml]

In can now load the Doctrine task Doctrine_Core::generateModelsFromYaml to generate my model.

A good way to see I the generation went well is to open the generated base files and read the class comments. Let’s have a look:

[php]
/**
* BaseCourse
*
* @property integer $id
* @property string $name
* @property Doctrine_Collection $Students
*/
abstract class BaseCourse extends Doctrine_Record
{
// …
}

/**
* BaseEnrolment
*
* @property integer $student_id
* @property integer $course_id
*/
abstract class BaseEnrolment extends Doctrine_Record
{
// …
}

/**
* BaseStudent
*
* @property integer $id
* @property string $name
* @property Doctrine_Collection $Courses
*/
abstract class BaseStudent extends Doctrine_Record
{
// …
}
[/php]

You can see that all my classes have been load with the right properties. The interestig part here is to see that the student and Course classes is agnostic from the Enrolment class. Thus the couse already has a collection of students and the student a collection of courses.

So we have here a perfectly working N to N relationship. Doctrine takes care it self of the join operation when accessing the database.

Now, let’s had our properties on the enrolment relationship.

The idea here is to provide Doctrine a way to acces the enrolment class. The most simplier way to do that is to add the following relationships to the enrolment definition:

[yml]
Enrolment:
columns:
student_id:
type: integer
primary: true
course_id:
type: integer
primary: true
enrolementDate:
type: date
notnull: true
relations:
Course:
local: course_id
foreign: id
foreignAlias: Enrolments
Student:
local: student_id
foreign: id
foreignAlias: Enrolments
[/yml]

You can notice that I added the One to N relationships and the enrolmentDate property.

After a class generation we obtain :

[php]
/**
* BaseCourse
*
* @property integer $id
* @property string $name
* @property Doctrine_Collection $Students
* @property Doctrine_Collection $Enrolments
*/
abstract class BaseCourse extends Doctrine_Record
{
// …
}

/**
* BaseEnrolment
*
* @property integer $student_id
* @property integer $course_id
* @property date $enrolmentDate
* @property Course $Course
* @property Student $Student
*/
abstract class BaseEnrolment extends Doctrine_Record
{
// …
}

/**
* BaseStudent
*
* @property integer $id
* @property string $name
* @property Doctrine_Collection $Courses
* @property Doctrine_Collection $Enrolments
*/
abstract class BaseStudent extends Doctrine_Record
{
// …
}
[/php]

I now have the additionnal relationships and I can access my relationship class which has a new property: enrolementDate.

But, such a schem force us to change some way of working with the model.

Working with the model

The first thing that we need to keep in mind, is that if we add a student to a course or a course to a student by using the Course::Students and the Student::Courses collections, Doctrine will autogenerate the Enrolement object and save it. Thus we won’t have access to the enrolementDate field.

So instead of using the common way of adding element to a collection we should instantiate a Enrolement object, associate it with the course and student, and then save it. During this process we so have access to the enrolementDate.

[php]
$student = new Student();
$student->name = "Michel";
$student->save();

$course = new Course();
$course->name = "Programming 101";
$course->save();

//create the relationship
$enrolment = new Enrolment();
$enrolment->enrolmentDate = date(‘c’);
$enrolment->Course = $course;
$enrolment->Student = $student;
$enrolment->save();
[/php]

After that, I have a correct set of data in my database. I can also do some access:

[php]
//retrieve the courses
$courses = $student->Courses;

//retrieve the enrolments
$enrolments = $student->Enrolments;

//retrieve the course passing through and enrolment
$course = $student->Enrolments[0]->Course;
[/php]

The only matter is that I cannot access an enrolment given a student and a course.

Here is what I did to pass this issue. I simply added a function to my Student class that will generate the

[php]
class Student extends BaseStudent
{
public function getEnrolmentForCourse($course){
return Doctrine::getTable(‘Enrolment’)
->findOneByCourseIdAndStudentId(
$course->id,
$this->id
);
}
}
[/php]

As you can see, I simply use a magic function of doctrine. Even if the findOneByCourseIdAndStudentId function does not exist, Doctrine uses its name to generate the appropriate SQL query; I just need to fill the blanks with the ids of both objects.

Now I have a complete access to my enrolment object:
[php]
// find Michel
$student = Doctrine::getTable(‘Student’)->findOneByName(‘Michel’);
// take his first course
$course = $student->Courses[0];

// retrieve the enrolment data from it
$enrolment = $student->getEnrolmentForCourse($course);
[/php]

We saw here how to implement a full access to a relationship containing properties. It involves some schema description and a simple additional method in the classes’ code, but it imposes a specific way to create N to N relations. Thus it is not perfect; I hope this solution will help you working better with Doctrine.

About me…

As you know, I am a student in ECE Paris. It is an engineer school with a 5 year program. I am going through the last year and I took a specialization in software development and security. My courses also contain some management, software design, user experience, marketing and other useful skills.

I also enjoyed traveling during my studies. I went for a full semester in Canada where I discovered new techniques and way to approach management and software development issues. Also I am currently packing for a seminar in California which will last for a month.

About my skills, I developed a lot for web applications with two main platforms. I have a personal big project on a community web site which I am developing with some friends of mine. It is based on the symphony framework with the Doctrine ORM. I was already familiar with web development in php but this framework made me go on a whole new scale. I took advantage of this project to put into practice Extreme Programming which was a really good experience, not easy at all but definitely interesting.

Also, I am currently finishing an internship that I voluntary spend on a complete different technology: .NET. At the beginning I wasn’t used to the Microsoft platform but I learned and I finally took part in the development as regular member of the team. We are developing a fully configurable web application designed for HR services that should fit the need of many customers. It is a very complex project based on several key technologies of the .NET framework such as asp.mvc, entity framework and unity. The architecture that we are developing also challenges major issues of nowadays developments. Again another agile methodology, we are practicing scrum.

In my personal life I also enjoy playing the guitar, movies, and reading. Well, I just don’t like getting bored.

Welcome to my new blog !

Hi, welcome to my new blog!

Until recently I kept developing my own website based on the symphony framework but I realized that I didn’t have enough time to make it as good as I wanted. Also, with my current internship over the .NET framework and my personal big project on symphony I was really starting to become schizophrenic.

I was considering starting a real pro-blogging activity thus I decided to switch to a real tool: WordPress. Less time loosing reinventing the wheel, more time to share my experience. Some of you might notice that my old work is still available at perso.michelsalib.com.

With this blog, I intend to share on various subjects over several technologies. Symfony, of course, Doctrine, jQuery, but also Microsoft technologies such as asp.mvc, entity, and I hope Silverlight and WM7 development…

Yea, I am schizophrenic!