Archive for June, 2011

PHP classes autoload (namespaces) in Zend Framework

Thursday, June 9th, 2011

On previous posts I created a function to dynamically load namespace classes.
Now that I’m using Zend Framework I was wondering how can I use the same namespaces in combination with Zend’s. Following the logic I only needed to register my autoload function so that everything starts working as I wish.
Having read through the documentation I was starting to think it would be very difficult but turned out to be quite simple so I’m posting my learning experience here hoping it would be useful for others.

Background

I have a bunch of namespaces being used for several applications. I have them stored in a specific folder. I use Mac OS.
Let’s suppose I have the namespaces folder located under /var so the namespaces folder path is: /var/namespaces/
Also I have a folder with several non namespaced classes on the following path: /var/classes/

I can’t change paths because several other non ZF applications use that, and I won’t be copying and pasting them into the ZF library path cause if code needs to change I have them all in just one single place.

My ZF application is stored in the following path: /Users/spiro79/web/sites/ZFapp/

ZF must work and be able to load the classes from the different namespaces.

Making it work

I will be using the same logic used in previous posts regarding the autload. Believe it or not is pretty simple.
All changes are done within the bootstrap so open it.
First of all we need to create the autoload function. I wrote this code as a Bootstrap public static function.

    /**
     * Autoload function for PHP namespaces
     * @param string $class
     */
    public static function getMoreNamespaces($class){
        $class = strtolower($class);
        if(strpos($class,'\\')!==false){
            $class = preg_replace('/\\\/',DIRECTORY_SEPARATOR,$class);
        }
        $possibilities = explode(PATH_SEPARATOR, get_include_path());
        $found = false;
        foreach($possibilities as $path){
            if(file_exists($path . DIRECTORY_SEPARATOR . $class . ".php")){
                $found = true;
                require_once $path . DIRECTORY_SEPARATOR . $class . ".php";
                break;
            }
        }
    }

What this code does is that it searches for the namespace folder within all paths set on the include path.
The autoloader will call this function to try to load the namespaces classes.
Now let’s set the namespaces and classes folders in the application.ini file, just add these lines and make them match your own path.

nmspcspth = "/var/namespaces"
clssspth = "/var/classes"

The third and last step is to add my autoload function to the default autoloader. I did this by typing the following code also within the Bootstrap.

    /**
     * Init the autoloader for PHP namespaces
     */
    protected function _initMoreNamespace(){
        $namespacesPath = $this->getOption('nmspcspth');
        $classesPath = $this->getOption('clssspth');
        define('PHP_CLASSES_PATH',$classesPath);
        define('PHP_NAMESPACES_PATH',$namespacesPath);
        set_include_path(get_include_path() . PATH_SEPARATOR . PHP_CLASSES_PATH . PATH_SEPARATOR . PHP_NAMESPACES_PATH);
        $autoloader = Zend_Loader_Autoloader::getInstance();
        $autoloader->unshiftAutoloader(array('Bootstrap','getMoreNamespaces'));
    }

Done! Now you can use natural namespace syntax like:

Use Utils\Config as MyConfig;
$configObj = new MyConfig;
$otherNs = new Utils\Tool();

My problem has been solved and now I can proceed with development 😀

Multiple databases support on ZF

Wednesday, June 8th, 2011

Zend Framework is nice but learning it is very confusing and time demanding. Official doc isn’t that clear, lacks of good examples and sometimes is outdated. Whenever I want something to be done I read the official documentation and then google it so I can get a good picture of what everyone did.

One thing that I recently needed was to enable multiple schema support for a specific project. One schema stores client info, the other stores products. One very interesting thing is that the products schema depends on the account so clients for region 1 should use a schema called reg1, while clients from region 2 a schema called reg2.

So the products schema kind of switches dynamically according to users setup.

The first thing to do is setup the config file. This is the easy part, I added some lines that looked like this:

resources.multidb.maindb.adapter = oracle
resources.multidb.maindb.persistent = true
resources.multidb.maindb.host =
resources.multidb.maindb.username = usr
resources.multidb.maindb.password = usr123
resources.multidb.maindb.dbname = "(DESCRIPTION= (ADDRESS= (PROTOCOL=TCP)(HOST=oracle.host.com)(PORT=1521)) (ADDRESS= (PROTOCOL=TCP)(HOST=oracle2.host.com)(PORT=1521)) (LOAD_BALANCE=yes) (CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=srvcnm)))"

resources.multidb.seconddb.adapter = oracle
resources.multidb.seconddb.persistent = true
resources.multidb.seconddb.host =
resources.multidb.seconddb.username = usr2
resources.multidb.seconddb.password = usr2123
resources.multidb.seconddb.dbname = "(DESCRIPTION= (ADDRESS= (PROTOCOL=TCP)(HOST=soracle.host.com)(PORT=1521)) (ADDRESS= (PROTOCOL=TCP)(HOST=soracle2.host.com)(PORT=1521)) (LOAD_BALANCE=yes) (CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=srvcnm2)))"

resources.multidb.thirddb.adapter = oracle
resources.multidb.thirddb.persistent = true
resources.multidb.thirddb.host =
resources.multidb.thirddb.username = usr3
resources.multidb.thirddb.password = usr3123
resources.multidb.thirddb.dbname = "(DESCRIPTION= (ADDRESS= (PROTOCOL=TCP)(HOST=toracle.host.com)(PORT=1521)) (ADDRESS= (PROTOCOL=TCP)(HOST=toracle2.host.com)(PORT=1521)) (LOAD_BALANCE=yes) (CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=srvcnm3)))"

Notice I’m storing everything in a multidb array with three different elements: maindb, seconddb and thirddb. Let’s consider the maindb to be the users schema, for products I’ll be switching between seconddb and thirddb.

First of all the main connection needs to be set so we can login, update, delete and o more stuff with a user account. Alo this connection will be set as the default adapter. I did this in the bootstrap. Here is the code:

/**
* Init the main database connection.
*/
protected function _initDatabase(){
    $applicationConfig = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', APPLICATION_ENV);
    $config = $applicationConfig->toArray();
    $dbIni = $config['resources']['multidb']['maindb'];
    $db = Zend_Db::factory($dbIni['adapter'], $dbIni);
    Model_DbTable_Db::setDefaultAdapter($db);
}

So now we have the main connection up and working as a default adapter. This works the same as a basic database configuration. Now to the interesting part… set up the other database connection according to the user region. In my case the user’s account has a field/property that stores the correct products schema name so one of the things I’ll be doing is reading that property to know which schema I need to connect to for the products details. I won’t explain the account part because it’s quite simple.

We use a baseController from which we inherit all other controllers so we can easily concentrate most of the common methods in one single place. Within that baseController I added the following method:

/**
* Depending on the account we set up the adapter for the products tables
* @param Account A valid account object
*/
private function setProductsSchema(Account $account){
    if(!Zend_Registry::isRegistered('erpdb')){
        $applicationConfig = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', APPLICATION_ENV);
        $config = $applicationConfig->toArray();
        $dbIni = $config['resources']['multidb'][strtolower($account->getSchemaName())]; // In this case $account->getSchemaName() may return 'seconddb' or 'thirddb' 😀
        $erpdb = Zend_Db::factory($dbIni['adapter'], $dbIni);
        Zend_Registry::set('erpdb',$erpdb);
    }
}

Now the application is able to dynamically choose the proper schema for products according to the account. It’s important to add it to the registry with a name, in this case is erpdb. This is necessary in order to let the DbTable objects know which schema they’ll use. Be sure to call this method before making a query to the secondary schema.

Now let’s set up the DbTable objects so they know which connection they’ll be using. Right now all DbTable objects are using the default adapter set in the bootstrap, for those objects that refer to products we need to make them aware that they’ll be using the secondary connection. doing this is pretty straightforward.

Go to your DbTable object and add the following method:

/**
* Set the default adapter for this object
*/
public function init(){
    $this->_setAdapter('erpdb');
}

That’s it! Notice that we set the adapter name the same as the db registry added before.

And that’s basically all we need to do to make the connection dynamic 😀

Hope it helps.