Nowadays, ORM is one of the best paradigms to manage the persistence vs. maintaining SQL and handling persistence via code directly.
ORMs make transparent the database as well as how instances are loaded and stored on it.
In this section, we're going to show some practices to integrate an ORM within the model layer.
In this section we're going to cover 2 ORMs: Doctrine and Propel. However, the idea is enough generic to be extendible to whatever ORM.
Doctrine
Doctrine is a PHP ORM (object relational mapper) for PHP 5.2.3+ that sits on top of a powerful PHP DBAL (database abstraction layer). One of its key features is the ability to optionally write database queries in an OO (object oriented) SQL-dialect called DQL inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains a maximum of flexibility without requiring needless code duplication.
We are assuming that you have some knowledge about Doctrine and you have already defined/generated your model according to Doctrine documentation.
To learn how, take a look at Doctrine home
Including Doctrine
Doctrine has the following directory structure out of the box:
The only file that we're going to include is the Doctrine.php, placed within the doctrine lib directory. The Doctrine.php contains the main class Doctrine, which has an autoload method in charge of include the rest of files as soon as they are needed.
First of all, we need to place Doctrine inside our project. A good practice is to use a directory to place all the third-party libraries. We usually place our libraries within a directory thrdparty under the libs (/libs/thrdparty/) just to keep a placing convention, but it could be placed wherever
Second, we need to include the Doctrine.php file, so we can do that easily by adding an entry within the app/config/includepath.xml file:
So, the Doctrine.php file will be included just when needed, more like on demand, it is, just when the Doctrine class is used.
Now let's register the Doctrine autoload function that we have told about within our configuration file as following:
<?xml version = "1.0" standalone="yes"?>
<configuration>
<configuration-directives>
<autoload class="Doctrine" method="autoload"/>
</configuration-directives>
</configuration>
This will register the Doctrine autoload callback instead of ussing the spl_autoload_register function directly within our code.
Now we are able to consume doctrine API directly from our model.
Configuring Doctrine
We recommends to use Lion's dependency injection capabilities to configure Doctrine. It eases and centralizes the doctrine configuration. Let's see it with some examples:
The following code has been extracted from doctrine documentation, explaining how to use PDO to create a connection:
<?php
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';
try {
$dbh = new PDO($dsn, $user, $password);
$conn = Doctrine_Manager::connection($dbh);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
?>
Well, we can use dependency injection to setup a connection, let's see it in terms of context instance definitions:
Take the followin benefits of configuring Doctrine with Lion's dependency injection:
Maintain a configuration file declarativelly (vs. maintain a PHP source code programatically)
Let Lion to discover and resolve dependency between instances (vs. keep a concrete order to create instances programatically)
The connection instance is managed by the application context: it can be retrieved from whatever part within our code (vs. implementing singleton mechanism to make available our connection instance)
$users = $q->from('User u')->where('u.id IN (1,2,3)')->execute();
// Doctrine uses 'connection 2' for fetching here
$groups = $q->from('Group g')->where('g.id IN (1,2,3)')->execute();
?>
In this example, we are going to setup 2 connections, each one associated to a model class: One to handle User's persistence and the other one to handle Group's persistence. Each one connects to a different data source.
First thing to do will be to setup connection by ussing dependency injection, as shown in the following code:
<?xml version = "1.0" standalone="yes"?>
<configuration>
<context-instances>
<context-instance id = "doctrine_manager" class="Doctrine_Manager"/>
Next, we'll associate each connection to each model class.
The way to do that could be by ussing the same context.xml file, the only thing to take into account is the fact that we should need a manager::bindComponent method to get an array of pairs class, connection ids. Unfortunatelly we don't have this method within the original Doctrine API, so we could solve it by subclassing the Doctrine_Manager in order to add this method. i.e.:
<?php
class LionDoctrine_Manager extends Doctrine_Manager {
public function bindComponents(array $components) {
foreach($components as $component_class => $component_connection_id) {
Now that we have the bindComponents method allowing to set an array of pairs, we are able to associate connections declarativelly within our context.xml. i.e.:
<?xml version = "1.0" standalone="yes"?>
<configuration>
<context-instances>
<!-- We have changed the doctrine_manager class to point to our
new LionDoctrine_Manager class -->
<context-instance id = "doctrine_manager" class="LionDoctrine_Manager">
Now we have everything configure in order to use the Doctrine API:
<?php
$q = Doctrine_Query::create();
// Doctrine uses 'connection 1' for fetching here
$users = $q->from('User u')->where('u.id IN (1,2,3)')->execute();
// Doctrine uses 'connection 2' for fetching here
$groups = $q->from('Group g')->where('g.id IN (1,2,3)')->execute();
Again, the summary is to let the framework handle the configuration to just focus on code.
Propel
Propel is an Object-Relational Mapping (ORM) framework for PHP5. It allows you to access your database using a set of objects, providing a simple API for storing and retrieving data.
We are assuming that you have some knowledge about Propel and you have already defined/generated your model according to Propel documentation.
To learn how, take a look at Propel home
Including Propel
Propel has the following directory structure out of the box:
The only file that we're going to include is the Propel.php, placed within the propel runtime/classes/propel directory. The Propel.php contains the main class Propel, which has an autoload method in charge of include the rest of files as soon as they are needed.
First of all, we need to place Propel inside our project. A good practice is to use a directory to place all the third-party libraries. We usually place our libraries within a directory thrdparty under the libs (/libs/thrdparty/) just to keep a placing convention, but it could be placed wherever
Second, we need to include the Propel.php file, so we can do that easily by adding an entry within the app/config/includepath.xml file: