Doctrine 2
So, I recently started a new project and decided this was a good opportunity to try out Doctrine 2. Let me tell that it is very different from Doctrine 1.2! There are a lot of changes including the use of entities, the entity manager, annotations, proxy objects, repositories, the query builder. The list goes on. I stumbled quite a bit at first trying to wrap my head around the new way of doing things. In coming posts I will try to elucidate some of these little details that tripped me up.
Ultimate Zend Framework and Doctrine Integration
I’ve been through a lot to get this all working and feel I MUST share! This guide should spell out exactly what you need to get these two great technologies working in harmony. I always forget little quirks each time I start a new project so this will be a reminder to me as well. I’m using Zend Framework 1.11.0 with Doctrine 1.2.3 and MySQL.
Start by creating your Zend application using the zf command line/terminal tool. Next create a folder called doctrine within your application folder at the same level as the configs, controllers, and other folders. Within the doctrine folder create three folders: data, migrations, and schema. Within the data folder create two folders: fixtures and sql. At the same level as your application folder, create a folder named scripts.
There are four key files to this setup – the application’s configuration file, the application’s bootstrap file, the Doctrine cli script, and the yaml model file.
Application Configuration
The following is my baseline application.ini. Yours may vary slightly on a few items like appnamespace and Doctrine connection string.
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 includePaths.library = APPLICATION_PATH "/../library" bootstrap.path = APPLICATION_PATH "/Bootstrap.php" bootstrap.class = "Bootstrap" appnamespace = "" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.frontController.params.displayExceptions = 0 autoloaderNamespaces[] = "Doctrine_" doctrine.connection_string = "mysql://root@localhost/mydb" doctrine.cache = false [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.params.displayExceptions = 1
Let’s break this down one piece at a time referencing the changes you’ll notice from the default file.
appnamespace = ""
I’ve set my appnamespace to be blank since I don’t want my model classes to be named like Application_Model_User. I just want it to be Model_User. We are going to be using PEAR style model naming where folder levels will dictate an underscore in our class names. So, our model class names will be Model_User, because they live inside the Zend Framework models folder. Our base class names are structured Model_Base_User since they reside in models/Base.
autoloaderNamespaces[] = "Doctrine_"
I’ve set up an additional namespace for Zend Framework to autoload: Doctrine_. We’ll see later that we will tell Doctrine we are going to be using PEAR naming so when it encounters a class it needs and it begins with the Doctrine_ prefix, it will know where to find it based on our PEAR naming scheme.
doctrine.connection_string = "mysql://root@localhost/mydb" doctrine.cache = false
We set our connection string which is how Doctrine will know to connect to our database. I’ve got query caching turned off for now.
Application Bootstrap
Next comes our application’s Bootstrap.php file. Add a Doctrine function for initialization:
protected function _initDoctrine()
{
$config = $this->getOption('doctrine');
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine_Core::ATTR_MODEL_CLASS_PREFIX, 'Model_');
$manager->setAttribute(Doctrine_Core::ATTR_MODEL_LOADING, Doctrine_Core::MODEL_LOADING_PEAR);
$manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL);
$manager->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true);
$manager->setAttribute(Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS, true);
$manager->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
$manager->setAttribute(Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES, true);
if (isset($config['cache']) && $config['cache'] == true) {
$cacheDriver = new Doctrine_Cache_Apc();
$manager->setAttribute(Doctrine_Core::ATTR_QUERY_CACHE, $cacheDriver);
}
$connection = $manager->openConnection($config['connection_string'], 'doctrine');
$connection->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true);
$connection->setCharset('utf8');
return $connection;
}
Let’s break it down again.
$config = $this->getOption('doctrine');
Next, we grab all the doctrine settings from our application.ini file and store them in the $config array.
$manager = Doctrine_Manager::getInstance(); $manager->setAttribute(Doctrine_Core::ATTR_MODEL_CLASS_PREFIX, 'Model_'); $manager->setAttribute(Doctrine_Core::ATTR_MODEL_LOADING, Doctrine_Core::MODEL_LOADING_PEAR); $manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL); $manager->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true); $manager->setAttribute(Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS, true); $manager->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true); $manager->setAttribute(Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES, true);
Now, we create our Doctrine Manager instance and pass it some configuration settings. We tell Doctrine that our model class names will be prefixed with Model_. We are using PEAR naming, We want Doctrine to validate our queries against our schema. We want to allow DQL callbacks. Doctrine will automatically free query objects when it detects we aren’t using them anymore. We can override default getter/setter functions in our models. We want to use table classes and Doctrine will load them for us.
if (isset($config['cache']) && $config['cache'] == true) {
$cacheDriver = new Doctrine_Cache_Apc();
$manager->setAttribute(Doctrine_Core::ATTR_QUERY_CACHE, $cacheDriver);
}
Check if we want to use the APC cache driver (recommended) for query caching (not result caching). I’ve got that turned off for now.
$connection = $manager->openConnection($config['connection_string'], 'doctrine');
$connection->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true);
$connection->setCharset('utf8');
Create a connection to the database using our connection string from the config array, then set a couple of options on the connection to allow enums and use UTF-8 encoding.
return $connection;
Returning the connection makes it available to other application resources (_init functions), although I’ve never had to use it.
Doctrine cli
Within the scripts folder you created at the beginning, create a blank php file named doctrine.php. This will be our Doctrine cli script that will help us to automatically generate our Doctrine model files. Add the following code to the file:
// Define path to application directory
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
// Define application environment
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
get_include_path()
)));
/** Zend_Application */
require_once 'Zend/Application.php';
// Create application, bootstrap, and run
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->getAutoloader()->pushAutoloader(array('Doctrine_Core', 'autoload'));
$application->getBootstrap()->bootstrap('doctrine');
$config = $application->getOption('doctrine');
$options = array(
'data_fixtures_path' => APPLICATION_PATH . "/doctrine/data/fixtures",
'sql_path' => APPLICATION_PATH . "/doctrine/data/sql",
'migrations_path' => APPLICATION_PATH . "/doctrine/migrations",
'yaml_schema_path' => APPLICATION_PATH . "/doctrine/schema",
'models_path' => APPLICATION_PATH . "/models",
'generate_models_options' => array(
'pearStyle' => true,
'generateTableClasses' => true,
'generateBaseClasses' => true,
'baseClassPrefix' => 'Base_',
'baseClassesDirectory' => null,
'classPrefixFiles' => false,
'classPrefix' => 'Model_'
)
);
$config = array_merge($config, $options);
$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']);
This should look almost exactly like your application’s index.php file located in the public folder with a few variations.
$application->getAutoloader()->pushAutoloader(array('Doctrine_Core', 'autoload'));
We add the Doctrine autoload class to the Zend Framework autoloader. As far as I know, the only purpose this serves is to be able to autoload the Symfony yaml parsing libraries which are included with Doctrine (which don’t follow PEAR style naming). We need these libraries to parse our schema yaml file (and fixtures).
$application->getBootstrap()->bootstrap('doctrine');
Instead of bootstrapping and running the application, we are going to get the application’s bootstrap and run the _initDoctrine() function we created. This will handle our database connection for us.
$config = $application->getOption('doctrine');
Next, we get the Doctrine options from the application.ini.
$options = array( 'data_fixtures_path' => APPLICATION_PATH . "/doctrine/data/fixtures", 'sql_path' => APPLICATION_PATH . "/doctrine/data/sql", 'migrations_path' => APPLICATION_PATH . "/doctrine/migrations", 'yaml_schema_path' => APPLICATION_PATH . "/doctrine/schema", 'models_path' => APPLICATION_PATH . "/models", 'generate_models_options' => array( 'pearStyle' => true, 'generateTableClasses' => true, 'generateBaseClasses' => true, 'baseClassPrefix' => 'Base_', 'baseClassesDirectory' => null, 'classPrefixFiles' => false, 'classPrefix' => 'Model_' ) );
Now we set the paths to all those Doctrine folders we created at the beginning. These settings will tell the Doctrine cli where to find and store our files. Finally, we set some additional options which tell the Doctrine cli how we want our models structured – using PEAR style, with table classes and base classes, with a base class prefix of Base_, with no additional base class directory besides Base, with no prefix on our class file names besides Model_, having a class name prefix of Model_ for our main model and table classes.
$config = array_merge($config, $options);
Now we merge the Doctrine options from our appllication.ini with those that we have set here.
$cli = new Doctrine_Cli($config);
Pass the Doctrine options to a new instance of the Doctrine cli class.
$cli->run($_SERVER['argv']);
Finally, we run the cli.
Yaml Schema
The last file we need to create is our yaml schema file. In the doctrine/schema folder you created at the beginning, create a file named schema.yml. We’ve already told the cli to look in this folder earlier for our schema files. This is the file that Doctrine will use to generate our models and our database for us. We’ll just set up a very basic User model at this point. There are a lot of configuration options you can use with Doctrine and your schema files not covered here. Put the following in the schema.yml file you just created:
User:
columns:
username: string(20)
password: string(20)
Remember that yaml files are very sensitive about indentation. Those are two space indentations between the three sections.
Model Creation
We’ve finally come to the fun part – creating our models! Open up a command prompt or terminal and navigate to the scripts folder of your application. You can also create a shell script or batch file to run the cli if you don’t like calling it directly from php like I’m doing here. Run the following command to make sure that you can access the Doctrine cli correctly:
php doctrine.php
If all is well, you should see a bunch of options appear. We’re now ready to create our actual models. Run the following command:
php doctrine.php generate-models-yaml
This tells the Doctrine cli to run the task that will generate our models for us based on the yaml file we passed it. You should now see some new files in your application’s models directory.
If starting from scratch, you’ll also want to have the cli create your database schema for you. The easiest way to do this is to run the following command. You’ll probably have to at least create your database first:
php doctrine.php build-all-reload
This will drop the database (hence why you have to create it first), re-create it, build the models, and create your tables in your database. Pretty sweet!
You should now be able to use your models in your application. Try it out by editing your index controller’s index action.
$user = new Model_User(); $user['me']; $user['password'] = 'secret'; $user->save(); $this->view->user = $user->toArray();
That will create a new user and save it to your database. Check your database and the new user should be there.
Then in your view file echo out your user:
echo 'My name is ' . $this->user['username'] . ' and my password is ' . $this->user['password'];
Fire up your browser and take a look.
Hopefully this will get you going. Let me know if you see any mistakes or have any optimizations.
After the fact…
I’m nearing “completion” of my first major project using Zend Framework and Doctrine. Let me say that I’ve really enjoyed the experience. I’m still trying to convince our marketing/content guy that it really is easier having everything separated the way it is. I’d really like to start creating some tutorials here to help people through the problems I’ve encountered along the way. More to come.
Zend Framework Modules and Layouts
I’m really getting into Zend now! I recently converted my test app into a module-based structure. Along with that came the desire to have a different layout for each module. Looks like I’m not the only one who has ever wanted to be able to do this. You’d think it would be something trivial, but that’s not the case. Here are the steps I took to get it working in a pretty clean way.
- I ended up removing all references to layout from the config file since I can’t figure out how to get access to them in the way that I needed.
- I created a front controller plug-in which initializes the layout based on the request’s module. It sets the layout and the layout path. See code below.
- I added an _init method to the default bootstrap file that loads the plug-in I created.
I saw somewhere that there is also a Zend_Layout_Controller_Plugin_Layout class that you can extend instead of the usual Zend_Controller_Plugin_Abstract. I wasn’t totally sure of what benefits the former had over the latter, so I stuck with the norm. There was also varying information as to which function to override in the plugin (preDispatch, dispatchLoopStartup, etc). I went with preDispatch and it works.
Here is my plug-in code:
class App_Controller_Plugin_Layout extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
Zend_Layout::startMvc(array(
'layout' => $request->getModuleName(),
'layoutPath' => APPLICATION_PATH . '/modules/' . $request->getModuleName() . '/layouts/scripts'
));
}
}
Here is the _init method that goes in your main bootstrap file:
protected function _initLayout()
{
$frontController = Zend_Controller_Front::getInstance();
$frontController->registerPlugin(new App_Controller_Plugin_Layout());
}
I’m not sure if there are any downsides to this method of getting different layouts based on the module. Make sure that your layout file has the same basename as your module. So, for instance, if you have the default module and an admin module then you’d end up with an admin layout located at application/modules/admin/layouts/scripts/admin.phtml.
Zend Framework Authentication and ACL options
I’m slowly working my way into Zend. Right now I’m trying to figure out the best way to use auth and ACL. From my research, it looks like you have three main options when it comes to implementing this:
- Extend Zend_Controller_Action controller
- Create a controller plug-in
- Create an action helper
The first option, extending Zend_Controller_Action to use as a base controller for all of your controllers is highly frowned upon. I can’t recall the exact reasoning right now. The second and third options are more what I’m interested in and would like to know which is better (if one is better) and why. I’ve got code to do the controller plug-in and partial code for the action helper.
Found this again: http://devzone.zend.com/article/3350-Action-Helpers-in-Zend-Framework
ZFDebug bar with Doctrine
It took me most of the afternoon, but I finally got the ZFDebug bar with Doctrine support working. This one is crazy stupid! You have to have the following in your application.ini file or the debug bar won’t show up:
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
There is NO mention of this in the docs, whatsoever. I finally downloaded a working application and compared my config one setting at a time until I found this one.
Zend Framework 1.9.6 and Doctrine 1.2.1 table creation from YAML
UPDATE
See my latest post on the new best way to get this working.
So, I’ve spent the last while trying, trying, trying (!) to get Zend integrated with Doctrine. I’m almost there! I ran into a couple of problems and found some solutions that I thought I’d share since they were so difficult to come across.
I was having problems getting the Doctrine CLI to work when trying to create models and build the DB from a YAML file. First issue I ran into was the following error when running the task “build-all-reload”:
build-all-reload - Are you sure you wish to drop your databases? (y/n) y build-all-reload - Successfully dropped database for connection named 'doctrine' Fatal error: Class 'sfYaml' not found in /home/jeremy/aptana_workspace/blog/library/Doctrine/Parser/Yml.php on line 80
Doctrine couldn’t find the sfYaml classes (borrowed from Symfony, I’m using Zend). Solution I found (didn’t come up with it myself) was this:
$loader = Zend_Loader_Autoloader::getInstance();
$loader->pushAutoloader(array('Doctrine', 'autoload'), 'Doctrine');
$loader->registerNamespace('sfYaml')->pushAutoloader(array('Doctrine', 'autoload'), 'sfYaml');
That last line is the one that does the trick. Register a new namespace for the sfYaml files (they all begin with “sfYaml”) and then attach that namespace to the Doctrine autoloader.
The next problem I encountered was that the Doctrine CLI task of “build-all-reload” said it was creating the tables in the database, but it wasn’t. No errors, nothing. The database itself was getting dropped and re-created and the model classes were generated without any problem. I finally found a post on the Doctrine Google discussion board that indicated that the following code, when run by the CLI, will stop the tables from getting created.
$manager->setAttribute(
Doctrine::ATTR_MODEL_LOADING,
Doctrine::MODEL_LOADING_CONSERVATIVE
);
Comment that line out of your _initDoctrine method or your Application Resource plug-in and you should be able to create tables. Make sure to uncomment when you’re done.