Running Zend Framework modules from a Phar file

Using Zend Framework as an MVC application is probably the most common usage examples for Zend Framework. When you create such an MVC application, you will probably have heard about modules: reusable components of your application. Ideally these modules are drop-in and require little or no configuration before you can use them. This way you don’t have to recreate the same old “news module” for every client or customer. In my case, I usually just copy and paste the module from one base project into a new project. That’s easy. But it would be cooler to package your module as a Phar file, and run that file instead.

Bootstrapping Phar modules

Unfortunately, there is no support in Zend Framework 1.x for running a module from a Phar file. But we can build that support ourselves! With a little knowledge about bootstrapping, we can integrate Phar support for modules.

During the bootstrapping process, so called resources are used to prepare the application for running. A lot of resources are readily available, like a database resource for your database connection, cachemanager for configuring caches, … And there is a “Modules” resource. This one takes care of bootstrapping each module. The purpose of bootstrapping a module, is mainly to get the module namespace known in your application and to tell the FrontController where it can find a module.

This built-in modules resource loops over each directory it finds inside the specified modules folder, and then finds and executes the Bootstrap.php file inside each module. Since it iterates over directories, any Phar file we put there is just ignored. We will have to make sure that Phar files also get bootstrapped. We can do this by providing our own modules resource. The built-in one is called Zend_Application_Resource_Modules, and resides in Zend/Application/Resource/Modules.php. We don’t have to start hacking that file to add Phar support. We can create our own Modules.php resource, which extends the Zend Framework one. By providing our own implementation of an existing resource, we can add extra functionality on top of the existing code, without having to hack the existing code.

Here’s my implementation:

// file: library/MyLib/Application/Resource/Modules.php
class MyLib_Application_Resource_Modules extends Zend_Application_Resource_Modules
{
    /**
     * Initialize modules
     *
     * @return array
     */
    public function init()
    {
		// call parent functionality:
		parent::init();
		
		// find out module directory:
        $bootstrap = $this->getBootstrap();
        $bootstrap->bootstrap('FrontController');
        $front = $bootstrap->getResource('FrontController');
		$modulesDirectory = $front->getModuleDirectory() . '/modules'; // TODO hard coded...
		
        $default = $front->getDefaultModule();
        $curBootstrapClass = get_class($bootstrap);

		// find all PHAR modules, and bootstrap those:	
		$iterator = new DirectoryIterator($modulesDirectory);
		
		foreach ($iterator as $file) {
			if (!$file->isDot() && strpos($file->getFilename(), '.phar') > 0) {
				$module = str_replace('.phar', '', $file->getFilename());
				
				// bootstrap the module:
				$this->_bootstrapPharModule($file, $bootstrap, $default, $curBootstrapClass);
				
				// add to the modules in the FC
				$front->addControllerDirectory('phar://' . 
						$file->getPath() . 
						DIRECTORY_SEPARATOR . 
						$file->getFilename() . 
						DIRECTORY_SEPARATOR . 
						'controllers', 
				$module);
			}
		}
		
		return $this->_bootstraps;
    }
	
	/**
	 * Bootstraps a single PHAR module
	 *
	 * @param DirectoryIterator $file
	 * @param Zend_Application_Bootstrap $bootstrap
	 * @param string $default
	 * @param string $curBootstrapClass
	 * @return void
	 * @throws Zend_Application_Resource_Exception When bootstrap class was not found
	 */
	protected function _bootstrapPharModule (DirectoryIterator $file, $bootstrap, $default, $curBootstrapClass)
	{
		$fullPharPath = $file->getPath() . DIRECTORY_SEPARATOR . $file->getFilename();
		include($fullPharPath);
		$module = str_replace('.phar', '', $file->getFilename());
		
		$bootstrapClass = $this->_formatModuleName($module) . '_Bootstrap';
		if (!class_exists($bootstrapClass, false)) {
			$bootstrapPath  = 'phar://' . $fullPharPath . '/Bootstrap.php';
			if (file_exists($bootstrapPath)) {
				$eMsgTpl = 'Bootstrap file found for module "%s" but bootstrap class "%s" not found';
				include_once $bootstrapPath;
				if (($default != $module)
					&& !class_exists($bootstrapClass, false)
				) {
					throw new Zend_Application_Resource_Exception(sprintf(
						$eMsgTpl, $module, $bootstrapClass
					));
				} elseif ($default == $module) {
					if (!class_exists($bootstrapClass, false)) {
						$bootstrapClass = 'Bootstrap';
						if (!class_exists($bootstrapClass, false)) {
							throw new Zend_Application_Resource_Exception(sprintf(
								$eMsgTpl, $module, $bootstrapClass
							));
						}
					}
				}
			} else {
				// nothing to bootstrap, so let's move on
				return;
			}
		}
		
		if ($bootstrapClass == $curBootstrapClass) {
			// If the found bootstrap class matches the one calling this
			// resource, don't re-execute.
			return;
		}

		$moduleBootstrap = new $bootstrapClass($bootstrap);
		$moduleBootstrap->bootstrap();
		$this->_bootstraps[$module] = $moduleBootstrap;
	}
}

What happens is not very difficult: we find each Phar module, find the Bootstrap.php file in it, execute it and let the FrontController know we have bootstrapped a new module. The actual reading from a Phar file is handled transparently by the Phar module, and doesn’t need any special treatment in Zend Framework. Credits for this code should actually go to the Zend Framework: I looked into the original file, and almost literally copied what I could reuse.

Conclusion

Adding Phar support isn’t very hard. Too bad it isn’t built-in the framework, but luckily for us, the framework is flexible enough so we can add it. Once the module is bootstrapped, all classes can be used in your application. Once the FrontController knows where to locate your module, it’s accessible through the standard /module/controller/action request scheme. If you want to know more about creating your own Phar file, I recommend reading this excellent article from Cal Evans. It gave me exactly the information I needed for building my own Phar files.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>