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.

One thought on “Doctrine 1.2: Add properties to a relationship

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s