PHP Dependency Injection

Simple but powerfull dependency injector based on phpdoc. It maps classes and instances into a injector instance. To inject classes and instances into a object instance just call the $injector->injectInto($instance);. This is a very simple aproach but when is used in a application si very powerfull.

class A {}
class B {}
class C { public $name; }

//THE BOOTSTRAP PART
$injector = Injector::getInstance();
$injector->mapClass("A");
$injector->mapSingleton("B");

$c = new C();
$c->name = "C Class";

$injector->mapInstance($c);

class YourClass
{
	/**
	 * @var B
	 */
	public $b_class;

	/**
	 * @var C
	 */
	public $c_class;

	public function init()
	{
	//use here $this->c_class and b_class
	}
}

$yc = new YourClass();

$injector->injectInto($yc);

$yc->init();

The injector takes all public properties and check the @var phpdoc param, if params match a class maped in the injector the value is passed to the property. Dependincy injection reduces significantly the the code you write, you dont need to create or pass instances for all classes just put it in the injector map set the php doc and thats all.

Click on the show source to view the injector class. If you have some questions just leave me a comment.

<?php

/**
 * Injector map item
 *
 * @autor		Albulescu Cosmin <albulescu.cosmin@gmail.com>
 * @version		1.0
 */
class Injector_Item
{

	/**
	 * The classname
	 * @var string
	 */
	public $class;

	/**
	 * The class is singleton
	 * @var boolean
	 */
	public $singleton;

	/**
	 * Holds instance name
	 * @var mixed
	 */
	public $instance;

	/**
	 * Array with params of constructing class
	 * @var array
	 */
	public $params;

	/**
	 * New injector map item
	 * @param string $class
	 * @param boolean $singleton
	 * @param object $instance
	 * @param array $params
	 */
	public function __construct($class, $singleton = false, $instance = null, $params = array())
	{
		$this->class = $class;
		$this->singleton = $singleton;
		$this->instance = $instance;
		$this->params = $params;
	}
}

/**
 * Injector manager
 *
 * @autor		Albulescu Cosmin <albulescu.cosmin@gmail.ro>
 * @version		1.0
 */
class Injector
{
	private static $_instance = null;

    /**
     * @return Injector
     */
    public static function getInstance()
    {
        if (null === self::$_instance)
        {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    private $map = array();

    /**
     * Map singleton class
     * @param string $class
     * @throws Exception
     */
    public function mapSingleton($class)
    {
    	if(!is_string($class))
    	{
    		throw new Exception("Class name must be a string");
    	}

    	$this->map[$class] = new Injector_Item($class, true);
    }

    /**
     * Map a class
     * @param string $class
     * @throws Exception
     */
    public function mapClass($class, $params = array())
    {
    	if(!is_string($class))
    	{
    		throw new Exception("Class name must be a string");
    	}

    	$this->map[$class] = new Injector_Item($class, false, null, $params);
    }

    /**
     * Map a object instance
     * @param object $instance
     * @throws Exception
     */
    public function mapInstance($instance)
    {
    	if(!is_object($instance))
    	{
    		throw new Exception("Instance must be an object " .gettype($instance). " given");
    	}

    	$item = new Injector_Item(get_class($instance), true, $instance);

    	$this->map[get_class($instance)] = $item;
    }

    /**
     * Get mapped instance of an object
     * @param string $class
     * @return mixed
     */
    public function getMappedInstance($class)
    {
    	if($this->hasMap($class))
    	{
    		$item = $this->map[ $class ];

    		if($item->singleton)
    		{
    			if(!$item->instance)
    			{
    				$item->instance = new $class;
    			}

    			return $item->instance;
    		}
    		else
    		{
	    		$r = new ReflectionClass( $item->class );

				if($r->hasMethod("__construct"))
				{
					$instance = $r->newInstance( $item->params );
				}
				else
				{
					$instance = $r->newInstance();
				}

				return $instance;
    		}
    	}

    	return null;
    }

    /**
     * Check if class mapped
     * @param string $class
     * @return boolean
     */
    public function hasMap($class)
    {
    	return array_key_exists($class, $this->map);
    }

    /**
     * Create new instance of a class with injected properties
     * @param string $class
     * @param array $params
     * @return mixed|NULL
     */
    public function newInstance($class, $params = array())
    {
    	if(class_exists($class,true))
    	{
    		$r = new ReflectionClass($class);

			if($r->hasMethod("__construct"))
			{
				$instance = $r->newInstance($params);
			}
			else
			{
				$instance = $r->newInstance();
			}

			$this->injectInto($instance);

			return $instance;
    	}

    	return null;
    }

    /**
     * Inject dependinces into a object instances
     * @param object $instance
     */
    public function injectInto($instance)
    {
    	$r = new ReflectionClass($instance);
    	$properties = $r->getProperties(ReflectionProperty::IS_PUBLIC);

    	foreach($properties as $property)
    	{
    		$doc = $property->getDocComment();

    		$doc =  trim(preg_replace('/\r|\r\n *\* */', '', $doc));

    		$lines = explode("\n", $doc);

    		//remove start and and of comment block
    		array_shift($lines);
    		array_pop($lines);

    		$propertyName = $property->getName();

    		foreach($lines as $line)
    		{
    			preg_match_all('/@([a-z]+)\s+(.*?)\s*(?=$|@[a-z]+\s)/s', $line, $matches);

    			if(count($matches) >=3 && count($matches[1]) && count($matches [2]))
    			{
	    			$info = array_combine($matches[1], $matches[2]);

	    			if(isset($info['var'])) {
	    				if($this->hasMap($info['var'])) {
	    					$instance->$propertyName = $this->getMappedInstance($info['var']);
	    				}

	    				break;//stop iterating, var found and injected if has a reference
	    			}
    			}
    		}
    	}
    }

    /**
     * Clear the map
     */
    public function clearMap()
    {
    	$this->map = array();
    }
}

Yahoo messenger in php

One of my articles was Yahoo Messenger Client with Action Script 3 now i will show you how to write a yahoo messenger application in php. The script flow its exactly like code from action script because its connect to the same yahoo messenger protocol. For binary working i used a zend package ( Zend_Io ).  The implementation of  yahoo messenger in php its very simple, use only minimal 6 base classes.

  1. ChallengeResponseV16 – Used to make the authentification
  2. Network – Used to communicate trought the socket
  3. Packet – Keeps human readable packet info
  4. PacketBody – Keeps human readable packet body info
  5. Protocol – Builds packets, make login and other yahoo messenger protocol implementation
  6. Service – Keeps constants with services provided by protocol

Try the yahoo messenger in php or download the source code

Web application versioning when using Zend Framework

This is a simple way to versioning a zend framework application using just two files, a ini file that keeps the current version and a proxy/model file wich load the current version from the ini file and test the user passed version for a new feature. The ini file is splited in three states: production, testing and development.


[production]

; 1.1.1 -
; 1.2.0 - From this we add video section
; 1.0.0 - First release of the site

version = "1.0.0";

[testing : production]

version = "1.0.0";

[development : production]

version = "1.0.0";

The good think about this is that you can set a version for each stage of application. While application is in development mode, just set a constant for enviroement and current version will be that from development block. The class wich handle this operations is a very simple singleton class with “version” method.


define('APPLICATION_ENV', 'development');

class Proxy_Version
{
	private static $_instance = null;

	private $versions;

    /**
     * Singleton instance
     * @abstract
     * @return Proxy_Version
     */
    public static function getInstance()
    {
        if (null === self::$_instance)
        {
            self::$_instance = new self();
        }

        return self::$_instance;
	}

	private function __construct()
	{
		$this->versions = new Zend_Config_Ini('version.ini', APPLICATION_ENV);
	}

    public static function version( $version = null )
    {
        self::getInstance();

    	$currentVersion = self::$versions->version;

    	$userVersion = false;//if you have stored in db the version for each user

    	if( $userVersion !== false )
    	{
    		$currentVersion = $userVersion;
    	}

    	if($version)
    	{
    		return version_compare($currentVersion, $version, ">=");
    	}

    	return $currentVersion;
    }
}

Also if you look closer in this class you can set a user version, for example when you want to let a limited numbers of users to test the next version of the application. To be more easy to use you can apply a shortcut in a view helper and access it with $this->version().


//context of version 1.0

if($this->version("1.2"))
{
//content of version 1.2
}

//context of version 1.0

echo $this->version();

Or you can set a shortcut in a abstract zend controller action and load views depentds of version method.


//controller class
//....
public function indexAction()
{
  if($this->version("1.2"))
    $this->render("index_1.2.phtml");
}
//....

Improve Zend Framework application speed by separate static content and using Zend_Cache and Zend_View for static files

Zend Framework Speed UP

Introduction

Why doing this, the reason is simple. We dont need to process all static files in our framework, because this increase loading time of your application. The ideea is to create a separate domain for your application eg. http://s.domain.com wich will render and cache all requested files. I personally used this method and my application wich is based on Zend Framework is considerably faster. The Zend_View is used to merge multiple css or js files and pass parameters. Also compressing and obfuscation classes are used to decrease file sizes.

Create .htaccess

For beggining a .htaccess file is created for redirecting all request to render file. The files that is redirected to the render file is just js and css files, the rest of them like pictures, zip and other binary files are loaded from normal path. Also if apache deflate  module is available we add filters for css,xml and javascript files to compress files.

AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript

RewriteEngine on RewriteRule !\.(swf|xml|jpg|png|PNG|JPG|gif|otf|ttf|htc|ico)$ render.php
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ render.php [NC,L]

Render file

This is the file called render.php wich process all requests sepcified by .htaccess file. For example if request is /css/common.css  this file is loaded into Zend_View and rendered. If the request has some parameters like /css/common.css?foo=1&bar=1 the request is parsed with php function parse_str and passed to the Zend_View.  Before the file is sent to output the content is compressed with specific class, for css i personally used  the CSSCompressor by Stephen Clay and for javascript files JSMinPlus by Tino Zijdel. After the file is compressed the content is cached by Zend_Cache with the md5 of request like id. And finaly send the cache headers for browsers and print it to ouput.

<!?php 
error_reporting(E_ALL); 
ini_set('display_errors','1'); 
set_include_path(implode(PATH_SEPARATOR, array(     dirname(__FILE__).'/library',     get_include_path(), ))); 

require_once "Zend/Uri.php"; 
require_once "Zend/Cache.php"; 
require_once "Zend/View.php"; 
require_once 'functions.php'; 

define('CACHE_DAYS', 30); 
define('USE_CACHE', false); 
define('USE_COMPRESSOR', false); 

$request_file = $_SERVER['REQUEST_URI']; 

if($request_file == "/" || empty($request_file)) 
{  notFound(); } 

$cache_id = md5($request_file); 
$uri = Zend_Uri::factory("http://s.domain.com".$request_file); 
parse_str($uri->getQuery(), $uri_params);

$file_parts = explode('.', basename($uri->getPath()));

$extension = $file_parts[ count($file_parts) - 1 ];

$file = basename($uri->getPath());

$file_path = str_replace('\\','/',dirname(__FILE__) . $uri->getPath());

$frontendOptions = array(
   'lifetime' => 3600 * 24 * CACHE_DAYS, // cache lifetime of 2 hours
   'automatic_serialization' => true
);

$backendOptions = array(
	'cache_dir' => dirname(__FILE__).'/cache/' // Directory where to put the cache files
);

// getting a Zend_Cache_Core object
$cache = Zend_Cache::factory('Core',
							 'File',
							 $frontendOptions,
							 $backendOptions);

if(file_exists($file_path) && is_file($file_path))
{
	switch ( $extension )
	{
	    case "css":
	        header("Content-Type: text/css");

			if(USE_COMPRESSOR)
	        require_once "CSSCompressor.php";

			sendHeaders($file_path);

			if( ($result = $cache->load($cache_id)) === false )
			{
				$result = renderView($file_path, $uri_params);

				if(USE_COMPRESSOR)
				$result = CSSCompressor::process($result);

				$result = appendCacheInfo($result);

				if(USE_CACHE)
				$cache->save($result, $cache_id);
			}

			echo $result;

	        break;

	    case "js":
	        header("Content-Type: text/javascript");

			if(USE_COMPRESSOR)
	        require_once "JSMin.php";

			sendHeaders($file_path);

			if( ($result = $cache->load($cache_id)) === false )
			{
				 $result = renderView($file_path, $uri_params);

				 if(USE_COMPRESSOR)
				 $result = JSMin::minify($result);
				 $result = appendCacheInfo($result);

				 if(USE_CACHE)
				 $cache->save($result, $cache_id);
			}
	        echo $result;
	        break;
	    default:
	    	notFound();
	    	break;
	}
}
else
{
	notFound();
}

This steps considerably increase the time of loading of your application. The compressing calsses make files smaller with 20%. Also the big advantage for this is you can use the css and javascript files like php. For example if you want to merge multiple common files into one file, simply use render function from Zend_View

@CHARSET "UTF-8";
<?php echo $this--->render("reset.css"); ?>
<?php echo $this--->render("grid.css"); ?>
<?php echo $this--->render("layout.css"); ?>

This method is very usefully because reduce the number of files loaded trought the network.

To make sure the files are not requested every time and the client browser do its job, we send the caching headers using the below sendHeaders function.

function sendHeaders($file)
{
	$cache_days = CACHE_DAYS;

	if(file_exists($file))
	{
		$filetime = filemtime($file);

		$last_modified = date('D, d M Y H:i:s', $filetime) . ' GMT';
		$expires       = date('D, d M Y H:i:s', strtotime('+30 days',$filetime)) . ' GMT';
		$hasid         = '"' . md5($last_modified) . '"';

		header("Cache-Control:max-age=".($cache_days * 24 * 60 * 60));
		header("Pragma:private");
		header("Last-Modified:".$last_modified);
		header("Expires:".$expires);
		header("Vary: Accept-Encoding");
		header("Etag:".$hasid);

		$PageWasUpdated = !(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) and
			strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified);

		$DoIDsMatch = (isset($_SERVER['HTTP_IF_NONE_MATCH']) and
			@ereg($hasid, $_SERVER['HTTP_IF_NONE_MATCH']));

		// Does one of the two ways apply?
		if (!$PageWasUpdated or $DoIDsMatch)
		{
			header('HTTP/1.1 304 Not Modified');
			header('Connection: close');
			exit(1);
		}
	}

}

To work this function you must have some modules activated in your apache server. The modules are headers_module, expires_module.

Static files rendered like views

With your css and javascript files you cand do alots of things. In fact in your .css and .js files you cand write php code, for eg:

If you request: /js/account.js?logged=1 from the view file you can do somethink like this:

function onSomeButtonClick()
{
var logged = <?php echo ($this--->logged=="1") ? 'true' : 'false'; ?>;
if(logged) {
//do something
} else {
//do something
}
}

Conclusion

For my application this method is very fast and with alots of advantages and functionalities. The filesize is smaler using the compressing classes, also the apache is compress it with defalate module. With this positive points also have some issues, if you what to use the params sent to the view every time you must to disable cache.