array_untab : how to pop a level of data from a PHP array

June 1st, 2007

Today, just a little function beacause I am tired, it’s late, etc…
This function, array_untab, I created when I worked for a major european energy company pop a level of data from an array in thi way :

$array[a][b]

->

$a[b]

When collision occurs, the last element added take a new key.

function array_untab( $array )
{
	if( is_array( $array ) && is_array( array_shift( $array ) ) )
	{
		$new_array = array();
		foreach( $array as $array_slice )
			array_merge( $new_array,  array_pop( $array_slice ) );
		return $new_array;
	}
	else
		return $array;
}

Automatized cache handler in PHP

May 30th, 2007

Here I propose to you a way to handle cache in your PHP applications with a relatively portable piece of code.
This cache handler uses the PEAR class Cache_Lite (it’s light, as its name lets us know ^^, and fast), 2 personnal functions for arrays handling (array_join and array_md5) and the PHP function ob_start that handle output buffer in PHP scripts.
The reason why I don’t use register_shutdown_function is that its behaviour is not constant trough the different releases of PHP, and it’s impossible (or nearly) to output data from a shutdown registered function, included errors or whatever.

What this “component” do is to search in the cache if a valid cached version of the page requested exists, and output the content, or instantiate a cache object which will cache the content after it has been generated, and just before it is outputted. This is the role of the handler function in :

ob_start( "handler_function" );

One recurrent question in this problem is “How can I identify what the user requested, and link it to what is in the cache ?”. In order to resolve this question, I chose to consider that all what identify a page of a website is contained in the POST and GET data. In fact, some data in POST are sended to modify content in the website and is not an identifier in any way. Sometimes is it the case for GET too, but this is a very bad habit I discourage : take the habit to send data to the application via the POST method and to request specific data via the GET method. Linked to this subject, I suggest you to learn (if necessary) about the REST architecture, that takes advantages of HTTP methods and uses them for what they were designed to.

Well, lets return to our cache component. In order to identify uniquely each POST/GET combinaison, we have to arrange them in a anonical form. This is done in 2 steps : merge the POST and GET arrays, and sort the keys in natural order.
Lets watch to the following functions, that do this work :

function &array_join()
{
$args = func_get_args();
$keys = array_keys( $args );
foreach( $keys as $key )
{
if( !is_array( $args[$key] ) )
{
unset( $args[$key] );
}
}
$a = array();
$keys = array_keys( $args );
foreach( $keys as $key )
{
$_keys = array_keys( $args[$key] );
foreach( $_keys as $k )
{
if( is_array( $a[$k] ) )
{
$a[$k] = array_join( $a[$k], $args[$key][$k] );
}
else
{
$a[$k] = $args[$key][$k];
}
}
}

return $a;
}
function ksort_recursive( &$array )
{
if( !is_array( $array ) )
{
return false;
}

$result = true;
$keys = array_keys( $array );
foreach( $keys as $key )
{
if( is_array( $array[$kay] ) )
{
$result &= ksort_recursive( $array[$key] );
}
}
ksort( $array );
return $result;
}

And the last one which gives us a MD5 hash a this result array :

function array_md5( &$array )
{
if( !is_array( $array ) )
{
return false;
}

ksort_recursive( $array );
return md5( serialize( $array ) );
}

I created a class to encapsulate the cache handling logic, here it comes :

// Lifetime a cached content
define( 'CACHE_TIME', 60 * 60 * 24 * 5 ); // 5 days

// Root directory of cache repository
// Please give PATH_ROOT a value before use ;-)
define( 'CACHE_PATH', PATH_ROOT . DIRECTORY_SEPARATOR . 'cache/' );

// Global switch to turn cache on/off
define( 'MYAPP_USE_CACHE', true );

class Cache
{
var $_token;

function Cache()
{
ob_start( array( &$this, 'end' ) );
/**
* Hack for servers which reset the working directory after
* setting a custom buffer handler
*
* @link http://php.net/ob_start
*/
chdir( dirname( $_SERVER['SCRIPT_FILENAME'] ) );
}

function handle()
{
global $_pearCache, $cache;
if( MYAPP_USE_CACHE )
{
// PEAR Cache_Lite class
require_once 'Cache/Lite.php';
$options = array(
'cacheDir' => CACHE_PATH,
'lifeTime' => CACHE_TIME,
'pearErrorMode' => CACHE_LITE_ERROR_DIE
);
$_pearCache = new Cache_Lite( $options );

require_once 'array_join.func.php';
require_once 'array_md5.func.php';
$token = array_md5( array_join( $_POST, $_GET ) );
if( $content = $_pearCache->get( $token ) )
{
die( $content );
}

$cache = new Cache();
$cache->_token = $token;
}
}

function end()
{
global $_pearCache;

$content = ob_get_contents();
$_pearCache->save( $content, $this->_token );
return $content;
}
}

And that’s all !
Oh yes, this is what you have to put at the beginning of your script in order to have the cache working :

Cache::handle();

Too hard…

I know this solution can be ameliorated in many ways, but it helps me very nicely in all my applications since… ok, juste 2 days. :-D

A Fair(y) Use Tale

May 29th, 2007

“- Dad ? What are copyrights ?
- Once upon a time…”

Et le lendemain…

May 28th, 2007

…avec son beau blog il fît le malin !

    echo "PHP c'est bien, mangez en...";