Skip to content

Instantly share code, notes, and snippets.

@patricksavalle
Last active November 10, 2015 22:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save patricksavalle/ce1289f76a1615d37d89 to your computer and use it in GitHub Desktop.
Save patricksavalle/ce1289f76a1615d37d89 to your computer and use it in GitHub Desktop.
<?php
if ( !defined( 'BASEPATH' ) )
exit( 'No direct script access allowed' );
/**
*
* Project Name : MOBBR-kernel
*
* @package Util
* @author Patrick Savalle
* @since Version 1.0b
* @filesource /libraries/transformation/transformation_functions.php
*
* This class creates a new function that consist of a chain of existing functions.
* These chains can be used to perform all the chained functions on a value/field at once.
* For complex input validations and conversions. Also used by our validator engine.
*
* Usage:
* Transformation x = new TransformationChain();
* x->sin( )->cos( )->abs()->round( 2 );
* y = x->apply( 10.0 );
* x = x->apply( -3.0 );
*
* WARNING: heayy use of lambda's and closures.
* See -> http://culttt.com/2013/03/25/what-are-php-lambdas-and-closures/
*/
class TransformationException extends Exception
{
public function __construct( $message = null, $code = 0, $previous = null )
{
parent::__construct( $message, $code, $previous );
}
}
class NoException extends Exception
{
// empty, special case exception used for special code flow
}
// ---------------------------------------------------------------------------
class TransformationChain
{
// @formatter:off
protected $chain = array( /* $closure, ... */ );
protected $stack = array( /* $value, ... */ );
// @formatter:on
/**
* constructor
*
* @access public
* @param array (of closures)
*/
public function __construct( array $chain = null )
{
// construct chain from: array( 'name'=> ..., 'args' => array( ... ) )
if ( !empty( $chain ) )
{
foreach ( $chain as $element )
{
if ( is_array( $element ) )
{
list( $name, $args ) = $element;
if ( is_scalar( $args ) )
{
if ( !isset( $args ) )
{
$args = array( );
}
else
{
$args = array( $args );
}
}
}
else
{
$name = $element;
$args = array( );
}
$this->__call( $name, $args );
}
}
}
/**
* Apply all operators/functions to a value
*
* @access public
* @param any
* @param string
* @return string
*/
public function apply( $value, $error_message = '' )
{
if ( $value !== NULL )
{
try
{
foreach ( $this->chain as $closure )
{
$value = call_user_func( $closure, $value );
}
}
catch( NoException $e)
{
// nothing, this exception indicates we should terminate without error
}
catch( Exception $e )
{
throw new Exception( $error_message );
}
}
return $value;
}
/**
* Add a operator/function to the transformation chain.
* Method is chainable / returns self
*
* @access protected
* @param callable
* @return self
*/
portected function add_function( $closure )
{
if ( !is_callable( $closure ) )
throw new Exception( 'Not a callable function or closure' );
$this->chain[ ] = $closure;
return $this;
}
/**
* Add operator chain to this chain
*
* @access public
* @param array (of closures)
* @return self
*/
public function chain( array $transformationchain )
{
assert( "$transformationchain instanceof $this /* argument must be class transformationchain */" );
$rule = function( $value ) use ( $transformationchain )
{
return $transformationchain->apply( $value );
};
return $this->add_function( $rule );
}
/**
* Adding new operators using new PHP syntax
*
* @access public
* @param callable
* @param array
* @return self
*/
public function __call( $function, array $args )
{
if ( !is_callable( $function ) )
throw new Exception( "No such method:" . $function );
// wrap function and its arguments in a closure
$rule = function( $value ) use ( $function, $args )
{
$args = empty( $args ) ? array( $value ) : array_merge( array( $value ), $args );
$ret = call_user_func_array( $function, $args );
if ( !isset( $ret ) || $ret === FALSE )
{
// most probably a failure of some check, or out-of-range, translate to exception
// e.g. $chain->is_integer()->apply( '10.0' );
throw new TransformationException( );
}
else if ( $ret === TRUE )
{
// most probably success of some check, must return the original value so chain can continue
// e.g. $chain->is_integer()->apply( '10' );
return $value;
}
else
{
// return the transformed value
return $ret;
}
};
// store closure in the chain
return $this->add_function( $rule );
}
/**
* Set the last pushed value as current value
*
* @access public
*/
public function pop( )
{
// communicating with class-variables from closures is tricky, this by-reference does the job
$stack = &$this->stack;
return $this->add_function( function( $val ) use ( &$stack )
{
assert( $val===$val ); // just to remove compiler warning
return array_pop( $stack );
} );
}
/**
* Save the current value on a stack
*
* @access public
*/
public function push( )
{
// communicating with class-variables from closures is tricky, this by-reference does the job
$stack = &$this->stack;
return $this->add_function( function( $val ) use ( &$stack )
{
array_push( $stack, $val );
return $val;
} );
}
}
// ?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment