Wednesday, July 20, 2011

Doctrine object relational mapping integrate on Zend Framework

Integrate Doctrine 2.0.1 with Zend Framework 1.11.3

Note: Doctrine work on only PHP5.3 and above Version (not working PHP5.2)



Step 1 – Download libraries and set up the project

First of all, we need to download the libraries and set up our project.
Start with the Zend Framework. Go to it’s download page and download (at least) the minimal version. Right now, it is ZF 1.11.3. Unpack the download, go to a command line (whichever your system provides), change to the bin/ folder of the package and type the following lines:

1
2
chmod +x zf.sh
./zf.sh create project /path/to/App

This is what you do on a Unix shell. It is slightly different on Windows machines, there you would have to use the zf.bat file. However, you will have to figure out how to do that on your own, since I don’t work on Windows and am too lazy to google it right now.

Your project should be created now, meaning that the folder /path/to/App should contain several subfolder such as application/, library/ and so on. We create a subfolder with the name bin/ in this folder.

What has to be done next is to move all the libraries into our project. First of all, in the downloaded package of the Zend Framework, there should be a libraries/ folder containing another folder named Zend/. We move that Zend/ folder into the library/ folder of our project.

We then go on by downloading the Doctrine packages. I say packages, because we need the Doctrine ORM as well as the DBAL and the Doctrine Commons project. Luckily, all of them are included in one package, which can be downloaded on the Doctrine ORM’s download page. It is the latest Doctrine package. (As of now, it is version 2.0.1) Unpack it and simply copy the Doctrine/ folder into our project’s library/ folder.

The last step in setting up the project is to move or copy the contents of the bin/ folders of both downloaded packages into the bin/ folder that we just created in our project.

Step 2 – The configuration file

In this tutorial, I will show how to integrate Doctrine using MySQL as database engine. The config settings might be different for other engines or when you use different database interfaces. However, we open the application/configs/application.ini of our project and insert the following lines into the [production] block of the ini file.

1
2
3
4
5
6
doctrine.conn.host = '127.0.0.1'
doctrine.conn.user = 'root'
doctrine.conn.pass = 'siva123'
doctrine.conn.driv = 'pdo_mysql'
doctrine.conn.dbname = 'app'
doctrine.path.models = APPLICATION_PATH "/models"

At this point, you have to fill in your personal database connection settings. These might be completely different from the ones shown here. You should also adjust the database driver now, when you don’t want to use MySQL.

Step 3 – Bootstrapping

Now comes the fun part. We have to load all the Doctrine libraries and set them up correctly. To do that, we open the application/Bootstrap.php file of our project and fill it with the required functions. I will first post the whole file here and explain it below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
/**
* generate registry
* @return Zend_Registry
*/

protected function _initRegistry(){
$registry = Zend_Registry::getInstance();
return $registry;
}

/**
* Register namespace Default_
* @return Zend_Application_Module_Autoloader
*/

protected function _initAutoload()
{
$autoloader = new Zend_Application_Module_Autoloader(array(
'namespace' => 'Default_',
'basePath' => dirname(__FILE__),
));
return $autoloader;
}

/**
* Initialize Doctrine
* @return Doctrine_Manager
*/

public function _initDoctrine() {
// include and register Doctrine's class loader
require_once('Doctrine/Common/ClassLoader.php');
$classLoader = new \Doctrine\Common\ClassLoader(
'Doctrine',
APPLICATION_PATH . '/../library/'
);
$classLoader->register();

// create the Doctrine configuration
$config = new \Doctrine\ORM\Configuration();

// setting the cache ( to ArrayCache. Take a look at
// the Doctrine manual for different options ! )
$cache = new \Doctrine\Common\Cache\ArrayCache;
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);

// choosing the driver for our database schema
// we'll use annotations
$driver = $config->newDefaultAnnotationDriver(
APPLICATION_PATH . '/models'
);
$config->setMetadataDriverImpl($driver);

// set the proxy dir and set some options
$config->setProxyDir(APPLICATION_PATH . '/models/Proxies');
$config->setAutoGenerateProxyClasses(true);
$config->setProxyNamespace('App\Proxies');

// now create the entity manager and use the connection
// settings we defined in our application.ini
$connectionSettings = $this->getOption('doctrine');
$conn = array(
'driver' => $connectionSettings['conn']['driv'],
'user' => $connectionSettings['conn']['user'],
'password' => $connectionSettings['conn']['pass'],
'dbname' => $connectionSettings['conn']['dbname'],
'host' => $connectionSettings['conn']['host']
);
$entityManager = \Doctrine\ORM\EntityManager::create($conn, $config);

// push the entity manager into our registry for later use
$registry = Zend_Registry::getInstance();
$registry->entitymanager = $entityManager;

return $entityManager;
}

}

The first two methods don’t have anything to to with Doctrine. We just want to use a registry in our project (later, our EntityManager instance will be stored here!) and we want to register a namespace Default_ for use in our project. These methods are pure Zend Framework-ish, so I assume you know what they do. If not, consult the ZF manual.

Now for the _initDoctrine() method. The first thing we do in line 33 is to include the class loader of Doctrine. Unfortunately, Doctrine already uses the new namespacing of PHP 5 (or 6?). The Zend Framework doesn’t. So we need another class loader with the correct namespacing scheme. We set it up in the following five lines 34-38, telling it that we need the namespace Doctrine and where the include path of the library is.

We then create a Doctrine Configuration instance and configure Doctrine (lines 41-59). Depending on how you want to control the database scheme and stuff like that, you might want to adjust some of the settings here. The ArrayCache can be changed and set to something more performant and if you don’t want to use annotations in the Entities to define the database scheme, you should use a different MetadataDriver. In this tutorial, we use both the ArrayCache and the DefaultAnnotationDriver. When we instantiate the last one, we have to tell it where our classes with the annotations (I will talk about these more, later) are.

I never _really_ understood what the Proxy classes are. They are created at runtime by Doctrine and my guess is, they are to speed up the database interaction by sort of caching joined requests. Anyways, we need to define where these classes should be saved and what namespace that is. This is done in lines 57-59.

After that, we get our connection settings from the application.ini file and can finally create the EntityManager instance by telling it all our configurations and connection information. This EntityManager can be understood as the interface between the Database and our models (called Entities). We are saving it’s instance in the Zend Registry we created earlier.

And that’s it. We can now start creating Entities (models) and trying to do some database interaction!

Step 4 – The Entity

To test everything, we create a small dummy Entity. I will not go into detail on how to create Entites and define the database scheme of their tables. As we defined in the Doctrine config earlier, we want to use annotations (in the Docblock of our Entity) to define the database scheme. We create a file application/models/Test.php and fill it with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

/**
* @Entity
* @Table(name="jacksiva")
*/

class Default_Model_Test
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/

private $id;

/** @Column(type="string") */
private $name;

public function setName($string) {
$this->name = $string;
return true;
}
}

As you see, we want a models with the fields id and name, id being an integer and auto_incremented and name being a string. The table name is set to jacksiva. As you can see, an Entity is nothing more than a PHP class with some members and methods. Only the definitions for how Doctrine should build the table have to be given in the annotations. You could as well use YAML or XML files to define these.

The next step is to adjust the command line tool of Doctrine to fit into our project.

Step 5 – the command line tool

Simply copy the following code into bin/doctrine.php replacing all of the code already in the file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41


define('APPLICATION_ENV', 'development');

define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
get_include_path(),
)));

// Doctrine and Symfony Classes
require_once 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', APPLICATION_PATH . '/../library');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Symfony', APPLICATION_PATH . '/../library/Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Entities', APPLICATION_PATH . '/models');
$classLoader->setNamespaceSeparator('_');
$classLoader->register();

// Zend Components
require_once 'Zend/Application.php';

// Create application
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);

// bootstrap doctrine
$application->getBootstrap()->bootstrap('doctrine');
$em = $application->getBootstrap()->getResource('doctrine');

// generate the Doctrine HelperSet
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);

We do some bootstrapping of our project here to obtain the EntityManager instance. We then create a HelperSet with the EntityManager and run the cli tool.

To create our database table based on the Entity we created earlier, we open a command line, change into the bin/ folder of our project and run

1
2
chmod +x ./doctrine
./doctrine orm:schema-tool:create

Again, this is for the Unix shell and is different on Windows machines!
Fire up your favourite database administration tool and have a look! The table test123 should have been created properly.

Step 6 – Using the database

I will just post a small example of how to use everything. Replace the content of your application/controllers/IndexController.php with the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


class IndexController extends Zend_Controller_Action
{
public function init()
{
$registry = Zend_Registry::getInstance();
$this->_em = $registry->entitymanager;
}

public function indexAction()
{
$testEntity = new Default_Model_Test;
$testEntity->setName('Zaphod Beeblebrox');
$this->_em->persist($testEntity);
$this->_em->flush();
}

}

This is just some quick and dirty dummy code to check if everything works. We get our EntityManager out of the registry and save it as a member of the Controller. We then create a new model (Entity), set the name and by using the EntityManager‘s persist method, we save it. EntityManager->flush() actually invokes the database action and the row should occur in our table.

Test this by simply browse to your project in your favourite webbrowser. If everything worked fine, the table should now have one entry with the name Zaphod Beeblebrox.

1 comment:

  1. Hello,
    when I try to run the doctrine.php I am getting these errors:

    PHP Fatal error: Cannot redeclare class Symfony\Component\Console\Helper\HelperSet in /usr/share/pear/Doctrine/Symfony/Component/Console/Helper/HelperSet.php on line 22
    PHP Stack trace:
    PHP 1. {main}() /home/gayatri/zend_projects/Wall-Z/doctrine/doctrine.php:0
    PHP 2. Doctrine\Common\ClassLoader->loadClass() /usr/share/pear/Doctrine/Common/ClassLoader.php:0
    PHP 3. require() /usr/share/pear/Doctrine/Common/ClassLoader.php:148

    Fatal error: Cannot redeclare class Symfony\Component\Console\Helper\HelperSet in /usr/share/pear/Doctrine/Symfony/Component/Console/Helper/HelperSet.php on line 22

    Call Stack:
    0.0003 335688 1. {main}() /home/gayatri/zend_projects/Wall-Z/doctrine/doctrine.php:0
    0.0409 3748744 2. Doctrine\Common\ClassLoader->loadClass() /usr/share/pear/Doctrine/Common/ClassLoader.php:0
    0.0412 3764320 3. require('/usr/share/pear/Doctrine/Symfony/Component/Console/Helper/HelperSet.php') /usr/share/pear/Doctrine/Common/ClassLoader.php:148

    Can you help please?
    Thanks,
    Gayatri

    ReplyDelete