Zend Framework dependency manager

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Zend Framework dependency manager

Post by Eran »

I wrote a utility class for the Zend Framework that manages dependencies on the framework in application code. What it basically does is allow you to use ZF dependent code anywhere and the class will auto resolve dependencies and download them from the ZF SVN repository. You can also using it on existing ZF projects to get the minimally needed ZF library components, as it will only fetch what is being used in the project.

It works by prepending an autoload function to the autoload stack, interceptions calls to missing ZF classes and fetch them from the repository. If those classes have additional dependencies managed internally in the framework (helpers, plugins), it will fetch those dependencies as well. I've managed to build a working ZF library for several active projects I use using this class.

I'm looking for feedback on the code and missing dependencies if you encounter them.

The class - Zdm (Zend Framework Dependency Manager)

Code: Select all

<?php
/**
 * Zend Framework dependency manager
 *
 * Manages dependencies of the Zend Framework in application code.
 *
 * @category   Zdm
 * @copyright  Lionite Ltd.
 * @author     Eran Galperin
 * @license    http://www.opensource.org/licenses/mit-license.php  MIT license
 */
class Zdm {
	protected static $_instance = null;
	protected $_config = array(
		'libraryPath' => '/library', // If not passed will be set relative to Zdm class
		'prependStack' => true, // Prepend autoloader to autoload stack (otherwise append)
		'zfVersion' => '1.10.8', // Zend Framework version
		'repository' => 'http://framework.zend.com/svn/framework/standard/tags/release-'
	);

	protected $_dependencies = array(
		'Zend/Layout.php' => array('Zend/Filter/Word','Zend/Filter/StringToLower.php'),
		'Zend/View.php' => 'Zend/View',
		'Zend/Controller/Action.php' => 'Zend/Controller/Action/Helper'
	);

	/**
	 * Initialize and register autoloader instance
	 * @param array $config
	 */
	public static function start($config = array()) {
		self::$_instance = new self($config);
	}

	/**
	 * Get autoloader instance
	 * @return Zdm
	 */
	public static function getInstance() {
		if(is_null(self::$_instance)) {
			self::$_instance = new self();
		}
		return self::$_instance;
	}

	/**
	 * Constructor
	 * @param array $config
	 */
	protected function  __construct($config = array()) {
		$this -> _config = array_merge($this -> _config,$config);
		if(!isset($config['libraryPath'])) {
			$this -> _config['libraryPath'] = dirname(__FILE__);
		}

		spl_autoload_register(get_class($this) . '::autoload',true,$this -> _config['prependStack']);
	}

	/**
	 * Autoload function
	 * @param string $class
	 */
	public static function autoload($class) {
		if(!class_exists($class) && stripos($class,'Zend') !== false) {
			$file = str_replace('_','/',$class) . '.php';
			$self = self::getInstance();
			$self -> load($file);
		}
	}

	/**
	 * Require file - fetch from repository if does not exist
	 * @param string $file
	 */
	public function load($file) {
		$local = $this -> _config['libraryPath'] . DIRECTORY_SEPARATOR . $file;
		if(!is_file($local)) {
			$this -> fetch($file);
		}
		require($local);
	}

	/**
	 * Fetch Zend Framework class from repository
	 * @param string $file ZF class file to fetch
	 */
	public function fetch($file) {
		$local = $this -> _config['libraryPath'] . DIRECTORY_SEPARATOR . $file;
		$target = $this -> _config['repository'] . $this -> _config['zfVersion'] . '/library/' . $file;
		$data = file_get_contents($target);
		if(!is_dir(dirname($local))) {
			mkdir(dirname($local),0755,true);
		}
		$offset = 0;
		while(($pos = strpos($data,"require_once",$offset)) !== false) {
			$data = substr($data,0,$pos) . "//" . substr($data,$pos);
			$offset = $pos + 10;
		}
		file_put_contents($local, $data);
		$this -> fetchDependencies($file);
	}

	/**
	 * Fetch a directory of Zend Framework classes from repository
	 * @param string $dir
	 */
	public function fetchDir($dir) {
		$localDir = $this -> _config['libraryPath'] . DIRECTORY_SEPARATOR . $dir;
		if(!is_dir($localDir)) {
			mkdir($localDir,0755,true);
		}
		$target = $this -> _config['repository'] . $this -> _config['zfVersion'] . '/library/' . $dir;

		//Local directory fetch
		if(is_dir($target)) {
			foreach(glob($target . DIRECTORY_SEPARATOR . '*.*') as $file) {
				$base = $dir . DIRECTORY_SEPARATOR . basename($file);
				if(is_dir($file)) {
					$this -> fetchDir($base);
				} else {
					$this -> fetch($base);
				}
			}
		//Zend Framework repo fetch
		} else {
			$result = file_get_contents($target);
			$tree = new DOMDocument();
			$tree -> loadHTML($result);
			$xpath = new DOMXPath($tree);
			$result = $xpath -> query('//ul/li/a');
			foreach($result as $node) {
				$file = $node -> getAttribute('href');
				if(strpos($file,'.php') === false) {
					if($file != '../') {
						$this -> fetchDir($dir .'/' . $file);
					}
				} else {
					$this ->fetch($dir . '/' . $file);
				}
			}
		}
	}

	/**
	 * Fetch dependencies for file
	 * @param string $file
	 */
	public function fetchDependencies($file) {
		if(array_key_exists($file, $this -> _dependencies)) {
			$deps = (array)$this -> _dependencies[$file];
			foreach($deps as $dep) {
				if(stripos($dep,'.php') === false) {
					$this -> fetchDir($dep);
				} else {
					$this -> fetch($dep);
				}
			}
		}
	}
}
Use instructions:
1. Include the class in your application (if it is a ZF application, it should be in the bootstrap)
2. Initialize the autoload by calling the static ::start() method
3. You can pass several optional parameters to the ::start() method -
* 'libraryPath' - the path that will store the ZF classes. Default is relative to the location of the Zdm class
* 'zfVersion' - version of the ZF to use
4. You would probably need to set the script time limit to 0 for the first run, as fetching the core of the library will take a while, depending on your connection.

Note: You need to remove 'require' and 'require_once' statements to ZF classes - those statements apply before the autoloader has a chance to catch the missing class and will result in a fatal error if the class is not present yet.

Example (in the beginning of the bootstrap):

Code: Select all

set_time_limit(0);
require_once('Zdm.php');
Zdm::start();
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Zend Framework dependency manager

Post by Eran »

I'm wondering if anyone gave it a shot, and if so did it work as expected?
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Re: Zend Framework dependency manager

Post by matthijs »

Looks interesting. Could be handy in case I want to use only a few parts of the ZF inside a website. I'll give it a try
Post Reply