Reasons to use ES6 for Angular 1.5 applications

First of all the current version of JavaScript ( ES5 ) is quite old, it was released on December 3, 2009 and was used for 7 years and of course she did his job. Like everything also JavaScript should evolve so a new version was release in June 2015 with many cool features.

If you are the greatest fan of Internet Explorer you will have some problems with ES6 because is not supported right now ( 13 June 2016 ) and you need to use some tooling to convert your code from ES6 to ES5.

Actual support of ES6:

Screen Shot 2016-07-13 at 18.26.43

 

ES6 Features:

 

Variables and constants

Now you can finally use immutable data in your code with const and defined block scoped variables with let


const START_FROM = 1; //immutable constant

for(let i=START_FROM;i<10;i++)
{
...
}

//  here i is undefined

 

Arrow functions

This is a major feature added to ES6 which can be confusing at first, but has benefits in larger code bases.


// Example 1

[1,2,3,4,5].filter((n) => n < 2); // [3, 4, 5]

// Example 2

class Car {
  start() {
    // "this" is the scope of Car
    this.async( () => this._start() ); 
  }
}

 

Modules

Some projects, like Webpack, let you take advantage of modularization now. But, ES6 delivers it as part of native JavaScript in the browser for the first time.

The addition of modularization is a huge reason to start learning ES6 now, as it’ll change the way you work for good if you’re not already using modules.


// file main.js
import { strtolower } from './utils';
let lower = strtolower('ABC');

// file utils.js
export function strtolower(str) {
  return (str+'').toLowerCase();
}
export function uppercase(str) {
...
}

 

Spread

It’s very simple, but the spread operator is a hugely convenient syntax that’s being added to expand an array wherever it’s called

//  with ES5
function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);

// with ES6
function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);

 

For-of

Iterators are finally getting improvements, so you’re able to iterate through variables with less messing around. For-in has existed for a long time, but for-of is being added in ES6:


var list = [3, 5, 7];

list.foo = 'bar';
 
for (var key in list) {
  console.log(key); // 0, 1, 2, foo
}
 
for (var value of list) {
  console.log(value); // 3, 5, 7
}

 

Classes

ES6 classes encourage the prototype-based object oriented pattern and they bring support for inheritance, constructors, static methods and more. Classes make your code more concise and self-documenting, which is reason enough to use them.


class Animal {
  constructor(race) {
    this.race = race;
  }
}

class Cat extends Animal {
  constructor(race) {
    super(race);
  }
  say() {
    console.log(this.race + ' says "Meow!"');
  }
}

class Dog extends Animal {
  constructor(race) {
    super(race);
  }
  say() {
    console.log(this.race + ' says "Bark!"');
  }
}

var cat = new Cat('cheshire')
  , dog = new Dog('bulldog')
;

cat.say();
dog.say();

console.log(cat instanceof Cat);
console.log(cat instanceof Dog);
console.log(cat instanceof Animal);
console.log(dog instanceof Animal);

 

Why to use it in Angular 1.5

 

Easy migration to NG2

Doing your actual code with ES6 will let you move to NG2 very nice and easy.

Components:


// Angular 2

@Component({
  selector: 'xbutton',
  template: '<button>{{label}}</button>'
});
class XButton {
  constructor() {
    this.label = "Hi";
  }
}

// To match NG2 you can do like this on current version

angular.module('...')
       .component('xbutton',{
         template:'<button>{{$ctrl.label}}</button>',
         controller: XButton
       });
// Bussiness logic is the same
class XButton {
  constructor() {
    this.label = "Hi";
  }
}

Services:


// Angular 2

@Injectable()
class Jobs {
  constructor(private http:Http){}
  getJobs() {
    return new Promise<any[]>( (done, reject) => {
      http.get('/jobs').subscribe(done, reject);
    });
  }
}

// To match NG2 you can do like this on current version

angular.module('...')
       .service('Jobs', Jobs);

class Jobs {
  constructor( $http ) {
    this.http = $http;
  }

  getJobs() {
    return new Promise( (done, reject) => {
      http.get('/jobs').then((response)=>done(response.data), reject);
    });
  }
}

 

Cons

  • You need to use a transpiler
  • Code in production will not mirror exactly your source code

Overall there are only benefits of using ES6 for your Angular 1.5. Anyway the ES6 is there and sooner or later it will be the way how we develop front-end apps.

What are your pros and cons about ES6?

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();
    }
}

Using nodejs to read counter strike server info

NodeJs is a very powerfull tool for creating server side applications. Nodejs is a event-driven I/O JavaScript environment based on V8, wich is the engine for the chrome browser and is very fast, . This article shows how to read counter strike server info trought udp protocol wich the server runs. On default the server runs on the port 27015, and to read the info you need to send some pakets for the what you need: the server details, the players list or the server rules.

var Packet = {
	ping : [0xFF,0xFF,0xFF,0xFF,0x69],
	challenge : [0xFF,0xFF,0xFF,0xFF,0x56,0x00,0x00,0x00,0x00],
	details : [0xFF,0xFF,0xFF,0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00],
	players : [0xFF,0xFF,0xFF,0xFF,0x55],
	rules : [0xFF,0xFF,0xFF,0xFF,0x56]
};

For every information wich server can serve you need to send a packet from above and you will receive the binary data. To read more about the counter strike server queries click here.

Here is the code to take a look and next i will explain much more about it.


var util = require('util');
var DGram = require('dgram');
var EventEmitter = require('events').EventEmitter;

var Packet = {
	ping : [0xFF,0xFF,0xFF,0xFF,0x69],
	challenge : [0xFF,0xFF,0xFF,0xFF,0x56,0x00,0x00,0x00,0x00],
	details : [0xFF,0xFF,0xFF,0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00],
	players : [0xFF,0xFF,0xFF,0xFF,0x55],
	rules : [0xFF,0xFF,0xFF,0xFF,0x56]
};

var XBuffer = function(data)
{
	this.data = data;
	this.position = 0;
}

XBuffer.prototype.readString = function()
{
	var string = '';

	while(this.data[this.position] != 0x00)
	{
		string += String.fromCharCode(this.data[this.position]);
		this.position++;

		if(this.position > this.data.length)
			break;
	}

	//skip  0x00
	this.position++;

	return string;
}
XBuffer.prototype.skip = function(n)
{
	n = n || 1;
	this.position += n;
}
XBuffer.prototype.available = function()
{
	return Math.max(this.data.length - this.position, 0);
}
XBuffer.prototype.read = function(n)
{
	var b = new Buffer(n);
	n = n || 1;
	for(var i=0; i < n; i++)
	{
		b[i] = this.data[this.position+n];
		this.position++;
	}

	return new XBuffer(b);
}
XBuffer.prototype.readByte = function()
{
	var value = this.data[this.position];
	this.position++;
	return String.fromCharCode(value);
}
XBuffer.prototype.lookAhead = function(n)
{
	var tempPos = this.position;
	var buff = this.read(n);
	this.position=tempPos;
	return buff;
}

XBuffer.prototype.readUInt8 = function()
{
	var value = this.data.readUInt8(this.position);
	this.position++;
	return value;
}
XBuffer.prototype.readInt8 = function()
{
	var value = this.data.readInt8(this.position);
	this.position++;
	return value;
}
XBuffer.prototype.readInt16LE = function()
{
	var value = this.data.readInt16LE(this.position);
	this.position+=2;
	return value;
}
XBuffer.prototype.readInt16BE = function()
{
	var value = this.data.readInt16BE(this.position);
	this.position+=2;
	return value;
}
XBuffer.prototype.readInt32LE = function()
{
	var value = this.data.readInt32LE(this.position);
	this.position+=4;
	return value;
}
XBuffer.prototype.readFloatLE = function()
{
	var value = this.data.readFloatLE(this.position);
	this.position+=4;
	return value;
}
XBuffer.prototype.toString = function()
{
	return "[XBuffer " + this.data + "]";
}

function parseRules( data )
{
	var d = {};
	var b = new XBuffer(data);

	b.skip(5);

	var count = b.readInt16LE();

	if (count == 65535)
	{
		b.skip();
		count = b.readInt16LE();
	}

	d.num_rules = count;

	while(b.available())
	{
		d[b.readString()]=b.readString();
	}

	return d;
}

function parseDetails( data )
{
	b = new XBuffer(data);
	var d = {};

	b.skip(4);

	d.type =  b.readInt8();

	if (d.type == 0x6D)
		d.address =  b.readString();
	else
		d.protocol= b.readInt8();

	d.hostname =  b.readString();
	d.map =  b.readString();
	d.game_dir =  b.readString();
	d.game_desc =  b.readString();

	if (d.type != 0x6D) d.steamappid = b.readInt16LE();

	d.num_players= b.readInt8();
	d.max_players= b.readInt8();

	if (d.type == 0x6D) d.protocol = b.readInt8();
	else               d.num_bots = b.readInt8();

	d.dedicated = b.readByte();
	d.os		= b.readByte();
	d.password		= b.readInt8();
	d.secure		= b.readInt8();
	d.version		= b.readInt8();

	return d;
}

function parsePlayers( buffer )
{
	var b = new XBuffer( buffer );

	var players=[];

	b.skip(5);

	var playersn = b.readUInt8();

	for(var i=0; i < playersn; i++)
	{
		players.push({
			id : b.readUInt8(),
			name : b.readString(),
			score : b.readInt32LE(),
			time : b.readFloatLE()
		});
	}

	return players;
}

var Protocol = function(ip, port)
{
	this.ip = ip;
	this.port = port;
	this.client = DGram.createSocket("udp4");

	if(false === (this instanceof Protocol)) {
        return new Protocol(ip, port);
    }

	EventEmitter.call(this);
}

util.inherits(Protocol, EventEmitter);

Protocol.prototype.request = function()
{
	var self = this;

	var data = {};

	self.dgramRequest(new Buffer(Packet['ping']), function(challData,rinfo){
		if(challData[4] == 0x6A)
		{
			//pong received

			//request details
			self.dgramRequest(new Buffer(Packet['details']), function( detailsData, rinfo1){

				//parse server details
				try {
					data.details = parseDetails( detailsData );
				}catch(e){
					self.emit("exception", e);
				}

				//request the server details
				self.dgramRequest( new Buffer(Packet['players']), function(playersData, rinfo2){

					//parse players data and ad it to the data object
					try {
						data.players = parsePlayers( playersData );
					}catch(e) {
						self.emit("exception", e);
					}

					self.dgramRequest( new Buffer(Packet['rules']), function(rulesData, rinfo3){

						//parse server rules
						try {
						data.rules = parseRules( rulesData );
						} catch(e) {
							self.emit("exception", e);
						}

						//dispatch event
						self.emit("success", data);
					});

				});
			});

		}
		else
		{
			//no pong received, server is down
			self.emit("offline");
		}
	});

}

function preprocess( data )
{
	//used if you need to filter data from the server
	return data;
}

Protocol.prototype.dgramRequest = function( buffer, callback )
{
	var self = this;

	var offlineTimeout;

	var _internalCallback = function(data, rinfo)
	{
		data = preprocess( data );

		clearTimeout(offlineTimeout);

		if(callback)
		setTimeout(callback,0, data, rinfo);

		self.client.removeListener("message", _internalCallback);
	}

	offlineTimeout = setTimeout(function(){
		self.emit("offline");
	}, 2000);

	this.client.on("message", _internalCallback);

	this.client.send(buffer, 0, buffer.length, parseInt(this.port), this.ip, function(err, bytes) {
	  if(err){	console.log("Fail to broadcase packet to " + self.ip); }
	});

}

exports.Protocol = Protocol;

As you see there is required the dgram package wich is used for udp communications. UDP uses a simple transmission model without implicit handshaking dialogues for providing reliability, ordering, or data integrity. Thus, UDP provides an unreliable service and datagrams may arrive out of order, appear duplicated, or go missing without notice.

In this code are two objects the XBuffer and Protocol, XBuffer simply increase the reading pointer when reading data, and the Protocol send all pakets, filter data and dispatched as event.

Example of requesting info from the server

self.dgramRequest( new Buffer(Packet['players']), function(playersData, rinfo2) {
//your code here
} );

The function gramRequest is used to make syncron request to the server, send the packet and wait for response. If the response not apear for 2 senconds will trigger the offline event

The requests are sent one after another, parsed for a human readable format and pushed to the data object, when all complete is dispatched the success event ( self.emit(“success”, data); )

For every kind of received info i used functions to parse the binary data, for example, the player data is parsed with parsePlayers function. See the code comments for see how its work.

function parsePlayers( buffer )
{
        //create the xbuffer object with received buffer
	var b = new XBuffer( buffer );

	var players=[];
        //skip 5 unused bytes
	b.skip(5);
        //read a 2 bytes int wich is the number of the players
	var playersn = b.readUInt8();
        //make a loop to players length
	for(var i=0; i < playersn; i++)
	{
		players.push({
			id : b.readUInt8(), // read 2 bytes of int with the id of the player
			name : b.readString(), //read n bytes ending with 0x00 wich is the player name
			score : b.readInt32LE(), //read 4 bytes int with user score
			time : b.readFloatLE() //read 4 bytes float with user playing time
		});
	}

	return players;
}

Mini example to use this code

var Protocol = require("./communication.js").Protocol;

var protocol = new Protocol(server.ip, server.port);

protocol.on("success", function(data){
 //use data
});
protocol.request();

I hope this article helps you, and if you have some questions leave me a message.

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");
}
//....

jQuery apear plugin

Simple jquery plugin used to trigger a function when a specific element apears on viewport, this callback is trigged one time.
This plugin is very useful when you what to preload an image when this apears on the viewport.


$('img[osrc]').apear(function(){
			$(this).load(function(){
				$(this).fadeIn();
			});
			$(this).attr('src',$(this).attr('osrc')).attr('osrc',null);
			console.log('apear item');
		});

In this example the image is printed with osrc attribute wich keeps the path to the image, when the apear plugin callback is trigged the osrc attribute is replaced with src attribute and add new event to loaded function to fade in the image.

Other way to use this is when you have multiple boxes ( widgets ) on the page and its not necessary to apply a javascript code to it before to apear in the viewport.


$('.widget-x').apear(initWidgetX);

Download plugin or try the Demo

Working with JSON RPC from javascript

JSON-RPC is lightweight remote procedure call protocol similar to XML-RPC. Its very simple and its used by alots of web applications at least json wich is very used in modern applications.

To communicate with server in a professional way, json-rpc is a good solution all you need to do is to create server side classes and pass it to an json rpc server class like Zend_Json_Server, read the documentation about how to create the server side.

For the client side you need to create a javascript class witch sends the request to the server and parse the response.

The json request for json-rpc is very simple has three properties in the json object: the method, params and request id.

{method : "Account.login", params : ["user","pass"], id : "194" }

And response contain the result of call, the error if it is, and the request id number.

{"result": "done", "error": null, "id": "194"}

The id of the request and response is used to  match the request and reponse of the current call. For example if you send a request and also add a listener for it, when the response comes call the listener with the specific id. This id is increasing at every request.

I made a simple json-rpc implementation for javscript that handle a json-rpc protocol. Sintax of using this rpc implementation is very simple, its provide a fluent call mode with two functions “call” and “done”.

Rpc.call("Account.login", "username", "password").done( onLoginResponse );

This Rpc class depends by a public json encoder/decoder class, you can find this class here. For this work you must to change the url from ajax call and changed to your url, and thats all.

var Rpc = {
		id   : 0,
		cid  : 0,
		callbacks:[],
		call : function()
		{
			var args = [];
			
			for(var i=0;i<arguments.length;i++)
				args.push(arguments[i]);
			
			var method;
			var tosend;
			this.id++;
			this.cid = this.id;
			
			if(args.length)
			{
				method = args.shift();

				var tosend = {method : method, params : args, id : this.cid };

				jQuery.ajax({
					async: true,
					contentType: 'application/json',
					type: 'POST',
					processData: false,
					dataType: 'json',
					url: '/api/js',
					cache: false,
					data: JSON.stringify(tosend),
					error: function(req,stat,err){
						Boxy.alert(err);
					},
					success: function(data){
						for(i=0;i<public.Rpc.callbacks.length;i++)
						{
							if(public.Rpc.callbacks[i])
							if(public.Rpc.callbacks[i].id == data.id)
							{
								var callback = public.Rpc.callbacks[i].callback;
								
								if(callback)
								{
									callback.apply(null, [data.result]);
									public.Rpc.callbacks.splice(i,1);
								}
							}
						}
					}
				});
			}
			
			return this;
		},
		
		done   : function(callback)
		{
			if(typeof(callback) == 'function')
			this.callbacks.push( { id : this.cid, callback : callback } );
		}
};

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.

Invisible detector made to be simple and user friendly

Today invisible-detector.net is live and is more user friendly and fast then last version. Yesterday i refused to go to club and I decided to restore design to insible-detector.net. I have made ​​it much easier to use and much faster, the results are now directly displayed in the input where you write the id. History is quite different  then others websites, this is with suggestions autocomplete. Enter and tell your opinion! Thanks!

invisible-detector.net