Last active
November 10, 2015 22:37
-
-
Save patricksavalle/ce1289f76a1615d37d89 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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