Jul 19, 2008 8:17:07 AM

Tags: Zend Framework

I've been finding very it very tricky to get around to blogging recently. Firstly, I've been busy both with Zoopy and with freelance work. Secondly, the Zend Framework API has been improving at such a rate that it's becoming less and less necessary to invent interesting code to achieve my goals, and therefore more and more difficult to find topics to share with you.

So I thought I'd start a series of mini posts dedicated to what are essentially Zend Framework use cases. These are not going to be in any particular order, but I hope as always that you'll be able to make use them.

I'll start things off with a technique to use module-specific models.

The problem

If you're like me, you like to use different classes to represent the same model in different modules. The rationale is quite simple: by using different classes you're encapsulating module-specific business logic within that module. For instance, a user model in an admin module may require functions related to access control, whereas the model's counterpart in a website module might need functionality to send messages to other users.

But if you're like me you've also encountered the difficulty of using classes with the same name with Zend_Loader's registerAutoload(). The problem is that registerAutoload() uses PHP's include path, in which path ordering is significant. This means that you can't, in your bootstrap, just set the include path to include the model directories for each module.

How, then, do you set the following up?

modules/
	website/
		models/
			User.php
	admin/
		models/
			User.php

The solution

The solution is actually amazingly simple. The way to achieve this is to modify the include path in a subclass of Zend_Controller_Action:

<?php

// bootstrap

set_include_path(
	get_include_path() . PATH_SEPARATOR .
	'../library'
);

require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

// subclass of Zend_Controller_Action -- this is shared between
// controllers in the application

class App_Controller_Action extends Zend_Controller_Action
{
	public function init()
	{
		$module = $this->getRequest()->getModuleName();
		set_include_path(
			get_include_path() . PATH_SEPARATOR .
			'../modules/' . $module . '/models'
		);
	}
}

You can now safely call...

<?php

$user = new User();

...and ZF's autoload functionality will instantiate the correct class based on the module that you're in.

Recent Posts

Discussion

Subscribe to an RSS feed of these comments

alan

Jul 19, 2008 10:18:46 PM

Hi again

Remember me? We had dispute about dynamic routes in ZF couple weeks ago :)

Ad topic: I think it is bad idea. Somewhere in the future U'll need both models (donno, maybe in partial or something) and this overlaping of classname will be errornous.
And what with code assist? I can quarantee You that it will misslead you thousands of times :)

In better coding language namespaces would be solution but at this moment use Zendish prefixes and don't produce hacks like that.
I'm really hope this proof of concept will help You out.

Cheers, Alan

P.S. In Agavi I use - which is great BTW :] - There is distinguish between global and module models eg. UserModel and Admin_UsersModel. U have to know which object You are using.

aan

Jul 19, 2008 10:26:20 PM

Little, tiny "proof of concept" of course :)

Neil Garb

Jul 20, 2008 10:28:48 AM

@alan Thanks for your comments. You could name your module-specific models mutually exlusively, but you still have to include module model paths in your include path. This requires knowledge of your modules in your bootstrap, and I don't particularly like that idea.

You're 100% correct about code hinting -- have gotten into trouble here before :) -- but there are ways to mitigate that risk, e.g. using phpdoc tags.

LBO

Jul 20, 2008 10:41:26 AM

"This requires knowledge of your modules in your bootstrap, and I don't particularly like that idea."

Thats why you should use factory, something like Controller->loadModel($name, $module = null);

read this: http://blog.mikeseth.com/index.php?/archives/4-ActiveRecord-sucks,-but-Kore-Nordmann-is-wrong.html

Neil Garb

Jul 20, 2008 10:49:24 AM

@alan Mike Seth (I assume that's the author's name) makes a good point. I suppose the equivalent lesson for ZF is that models shouldn't subclass Zend_Db_Table_Abstract. But I'm not seeing the value that the factory pattern is adding (especially not to his argument).

alan

Jul 20, 2008 11:10:26 AM

"But I'm not seeing the value that the factory pattern is adding (especially not to his argument)."

Well, thanks that, u are completly free of directory layout. U can rewrite loadModel if it changes. Or maybe write Zendish plugin system for that :)

"@alan Mike Seth (I assume that's the author's name) makes a good point. I suppose the equivalent lesson for ZF is that models shouldn't subclass Zend_Db_Table_Abstract."

yep, that is right - in my projects (based on Agavi) I use Propel as my ORM, but never directly in controller - always IN Model objects (but not proxy!).
So, at first I generate propel classess, build the site and when its necessary I rewrite some model methods to use even native drivers (eg mysql_*) which is huge performance kick :)

alan

Jul 20, 2008 11:17:04 AM

One more thing.

In factory You can always prepare your object. In Agavi, U can force loadModel() to return Singleton of model (by AgaviModel implements AgaviSingletonModel). I often use that to get some data in routing (like dynamic routes we were talking before) - after this sigleton model has some data already in it (without quering the database, web service etc).

Neil Garb

Jul 20, 2008 11:19:49 AM

@alan sure: if ever there was an argument for not extending Zend_Db_Table for models that is it.

AmirBehzad Eslami

Jul 23, 2008 1:27:15 AM

Take a look at the following discussion in ZF mailing list:
http://www.nabble.com/Shared-Models-in-Different-Modules-td14117646.html

AmirBehzad Eslami

Jul 23, 2008 1:29:54 AM

By the way, I've switched to use phpDoctrine instead of Zend_DB; and it really made my life easier.

Neil Garb

Jul 23, 2008 7:06:07 AM

@Amir: thanks for pointing that thread out. It's a little difficult to follow in Nabble, but I think it does cover the issues alan and I have been discussing.

Re: phpDoctrine: I've had no issues with Zend_Db and I don't think that the choice of DAL code affects this particular discussion, but I'd love to hear why phpDoctrine makes your life easier (I know some other people that are using it).

Taco

Aug 6, 2008 6:21:23 PM

[ed. to display code correctly]

I personally feel more for an controller action helper to load the models. I'm using the following action helper for loading my config files, but it can be easily extended to load anything:


<?php

/**
* Action Helper for loading forms
*
* @uses Zend_Controller_Action_Helper_Abstract
*/
class Xendl_Controller_Action_Helper_ConfigLoader extends Zend_Controller_Action_Helper_Abstract
{
/**
* @var Zend_Loader_PluginLoader
*/
public $pluginLoader;

/**
* Initialize plugin loader
*
* @return void
*/
public function __construct()
{
$this->pluginLoader = new Zend_Loader_PluginLoader();
}

/**
* Load a configuration file.
*
* @param string $name
* @return Zend_Config_Xml
*/
public function loadConfig($name)
{
$module = $this->getRequest()->getModuleName();
$controller = $this->getRequest()->getControllerName();
$front = $this->getFrontController();
$default = $front->getDispatcher()
->getDefaultModule();

if (empty($module)) {
$module = $default;
}

$moduleDirectory = $front->getControllerDirectory($module);
$configDirectory = dirname($moduleDirectory) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . $controller . DIRECTORY_SEPARATOR;

return new Zend_Config_Xml($configDirectory . $name . '.xml');
}

/**
* Call helper as broker method
*
* @param string $name
* @return Zend_Config_Xml
*/
public function direct($name)
{
return $this->loadConfig($name);
}
}

Your comment