分类: 系统运维
2007-02-05 11:31:32
如何集成Propel到Zend Framework?
It is very easy to integrate other tools and components into the . I have already shown, how to integrate Smarty as a template engine and the ez Components to expand the selection of useful components to build your own framework based on the Zend Framework.
Since the Zend Framework is currently (Preview Version 0.1.3) lacking an ORM-Layer (Object Relational Mapping), I want to show you how to integrate . Propel allows you to access your database using a set of objects, providing a simple API for storing and querying data. So Propel can easily take over the model part in a MVC system. For detailled information on Propel, its dependencies on and and how to install it please refer to the and the .
First you need to set up the directory structure for our libraries. Besides the Zend Framework we have added the ez Components, Smarty and Propel to our library. Propel depends on Phing and Creole so we need to add them as well.
/_library
/creole
/ezComponents
/phing
/propel
/generator
/runtime
/Smarty
/ZendFramework
/Mycompany
/Zend
Zend.php
Below the /_library/ZendFramework
we have the basic directory
Zend
and a custom directory Mycompany
for our custom classes
which extend the Zend Framework classes. Propel basically exists of two parts: generator
and runtime engine. Both libraries are located below the /_library/propel
directory.
Our application also needs some structure. We need to place our controllers, views and models, as well as our website root, some build files and our test files.
/project
/application
/config
/controller
/model
/view
/build
/www
/view
/website
/files
/css
/js
/img
/tests
Our /project/application
directory has four subdirectories for the
configuration, controller, model and view files. In /project/application/model
we will place the files generated by Propel later on. The /project/build
directory will hold the files Propel needs for building. The
/project/www/website
will be the document root of the webserver where we
place CSS and Javascript files, our images and the
bootstrap file.
To finish our setup we need to add the following paths to our include_path.
/_library/creole/classes
/_library/ezComponents
/_library/phing/classes
/_library/propel/runtime/classes
/_library/propel/generator/classes
/_library/Smarty
/_library/ZendFramework
/project/application/config
For the Propel build process you need a couple of files which should be located in
the /project/build
directory. Since this article is not meant to be an
introduction to Propel please refer to the .
The most important file is the schema.xml
which defines the structure
of your database. Here is an example for a very simple CMS with just three tables
cms_article
, cms_category
and user_main
:
Next, we need the runtime-conf.xml
file which holds the access data to
your database and some logging information. Here is a basic example:
propel-mycms
7
mysql
mysql
localhost
mycms
root
very secret
Finally, we need a build.properties
which holds all properties for the
Propel build process. Here is an example with some comments inside.
# set the paths to the Propel installation, your project home and your
# build files
propel.home = full/path/to/_library/propel/generator
project.home = full/path/to/project
project.build = ${project.home}/build
# set some basic properties for the project and the database connection
propel.project = mycms
propel.database = mysql
propel.targetPackage =
propel.database.url = mysql://root:@localhost/mycms
propel.mysql.tableType = InnoDB
# set the directories for the schema.xml and the runtime-conf.xml files
# and the path to the template files that Propel uses
propel.schema.dir = ${project.build}
propel.conf.dir = ${project.build}
propel.templatePath = ${propel.home}/templates
# set the directories for the generated output, i.e. the data object classes, a
# PHP file with the configuration data and the SQL files
propel.output.dir = ${project.home}
propel.php.dir = ${propel.output.dir}/application/model
propel.phpconf.dir = ${propel.output.dir}/application/config
propel.sql.dir = ${project.build}/sql
# set the name for the configuration file
propel.runtime.phpconf.file = propel-config.php
To build the data object classes you need change to the directory where you have installed Propel and run the Propel build.
# Linux/Unix
$> cd /usr/local/propel/generator
$> phing -Dproject.dir=full/path/to/project/build -Dproject=mycms
# Windows
C:\> cd C:\path\to\propel\generator
C:\path\to\propel\generator> phing -Dproject.dir=c:\path\to\project\build -Dproject=mycms
When you start the Propel build, Phing will look for the build.properties
file in your /project/build
directory. The schema.xml
will be
read and the data object classes be created and saved in the directory you defined in
the propel.php.dir
property. Also the SQL files for creating the database
and the configuration file will be created and saved in the defined directories.
If you look into your /project/application/model
directory you will
notice that for each defined table in schema.xml
two classes were created
in. These classes are stubs and all your changes to the data object classes should be
placed here.
In the subdirectory /project/application/model/om
you will
find the base classes which will be recreated in each Propel build. So, if you amend your
schema.xml
file and restart the Propel build, these files will be
overwritten while the files in /project/application/model
will be kept
unchanged.
Now, finally we want to use the Propel built data object classes as our model classes in the Zend Framework. Since we have already set up the include_path we can directly include them in our Controller classes.
public function indexAction()
{
$temp_file = 'list.htm';
$view = Zend::registry('view');
if (false === $view->isCached($temp_file))
{
require_once 'propel/Propel.php';
Propel::init('propel-config.php');
include_once 'propel/util/Criteria.php';
include_once 'CmsArticlePeer.php';
$c = new Criteria();
$c->setLimit(10);
$count = CmsArticlePeer::doCount($c);
$list = CmsArticlePeer::doSelect($c);
$articles = array();
foreach($list as $article)
{
$row = array();
$row['title' ] = $article->getArtTitle();
$row['text' ] = $article->getArtText();
$row['user' ] = $article->getUserMain()->getUserName();
$row['category'] = $article->getCmsCategory()->getCatName();
$articles[] = $row;
}
$articles = $view->escape($articles);
$view->assign('articles', $articles);
}
$view->output($temp_file);
}
Please note: This example uses the Smarty integration which was described in the former articles.
The usage of Propel data object classes is quite simple. In this example we select a
list of articles, loop through it and assign the data to the template. If you want to
retrieve an object by a given primary key, just use a
$article = CmsArticlePeer::retrieveByPK(1)
function call.
Since I like to keep my action methods as simple and short as possible, the
including stuff for Propel is annoying me a bit. To solve this, we can amend the
__autoload()
function.
I have formerly set up my __autoload()
function to load all Zend
classes and ez Components whenever they are used. To add autoloading for Propel we can
amend it like this:
function __autoload($class)
{
/**
* autoload Propel classes
*/
if ('Propel' == $class)
{
require_once 'propel/Propel.php';
Propel::init('propel-config.php');
}
elseif ('Criteria' == $class)
{
include_once 'propel/util/Criteria.php';
}
elseif ('Cms' == substr($class, 0, 3) or 'User' == substr($class, 0, 4))
{
include_once($class . '.php');
}
/**
* autoload ezComponents classes
*/
elseif ('ezcBase' == $class)
{
require_once 'Base/src/base.php';
}
elseif ('ezc' == substr($class, 0, 3))
{
ezcBase::autoload($class);
}
/**
* autoload Zend Framework classes
*/
else
{
Zend::loadClass($class);
}
}
The first if-statement loads and initializes the Propel class, whenever it is requested. The second if-statement makes sure that the Criteria class is loaded, whenever you need it. The third if-statement makes sure that your individual data object classes are autoloaded. Since we use a prefix of either "Cms" or "User" it can be identified by these prefixes. But what happens, if we have more than two table prefixes? We would need to add all of them to this if-statement.
Since this is very suboptimal we need a way to tell Propel to automatically add a custom prefix to all the data object classes which are generated.
We are looking for a build property to add a prefix to all generated classes, but
unfortunately there is none currently (version 1.2.0RC1). What we can find is a
propel.basePrefix
property to add a prefix to all the base classes. But how
can we set up a new propel.stubPrefix
property to be used by Propel?
Have a closer look at the properties propel.builder.peer.class
,
propel.builder.object.class
, propel.builder.objectstub.class
and propel.peerstub.peer.class
which are set to default classes in the
default.properties
file in the /_library/propel/generator
path.
All we need to do is to create our own builder classes and extend the four default
classes PHP5ComplexObjectBuilder.php
, PHP5ComplexPeerBuilder.php
,
PHP5ExtensionObjectBuilder.php
and PHP5ExtensionPeerBuilder.php
.
We name our classes MyComplexObjectBuilder.php
and so on and place them in a
subdirectory /project/build/om
. In all these four classe we only need to
write getClassname()
method.
// getClassname() method in MyExtensionObjectBuilder.php
public function getClassname()
{
return $this->getBuildProperty('stubPrefix') . $this->getTable()->getPhpName();
}
// getClassname() method in MyExtensionPeerBuilder.php
public function getClassname()
{
return $this->getBuildProperty('stubPrefix') . $this->getTable()->getPhpName() . 'Peer';
}
// getClassname() method in MyComplexObjectBuilder.php
public function getClassname()
{
return $this->getBuildProperty('basePrefix') . $this->getTable()->getPhpName();
}
// getClassname() method in MyComplexPeerBuilder.php
public function getClassname()
{
return $this->getBuildProperty('basePrefix') . $this->getTable()->getPhpName() . 'Peer';
}
To make sure that our new classes are used we need to amend our build.properties
file. So we add the following properties to this file.
# set our new stubPrefix property
propel.stubPrefix = Dao
# set the builder classes
propel.builder.peer.class = path.to.project.build.om.MyComplexPeerBuilder
propel.builder.object.class = path.to.project.build.om.MyComplexObjectBuilder
propel.builder.objectstub.class = path.to.project.build.om.MyExtensionObjectBuilder
propel.builder.peerstub.class = path.to.project.build.om.MyExtensionPeerBuilder
Before we restart the Propel build we should delete all the classes from the
/project/application/model
directory. After the Propel build we will find
our data object classes be prefixed with Dao
.
The last step is to amend the __autoload()
function.
[...]
elseif ('Dao' == substr($class, 0, 4))
{
include_once($class . '.php');
}
[...]
To integrate Propel to the Zend Framework is a bit more complicated than to integrate Smarty or the ez Components. Nevertheless, it is feasible. If you don't mind to bloat your action methods with the include-stuff for Propel you can forget about amending your __autoload() function and the Propel build process. If you are like me and want to keep your controllers as simple as possible you should do the extra work.
Any comments regarding the Propel integration into the Zend Framework are welcome. If you have some detail questions regarding Propel please refer to the or the .