2007-07-19 12:40:28 +02:00
< ? php
2008-02-25 03:10:37 +01:00
/**
* @ package sapphire
* @ subpackage core
*/
2007-07-19 12:40:28 +02:00
/**
* Base object that all others should inherit from .
* This object provides a number of helper methods that patch over PHP ' s deficiencies .
2008-02-25 03:10:37 +01:00
* @ package sapphire
* @ subpackage core
2007-07-19 12:40:28 +02:00
*/
class Object {
2007-08-16 08:37:49 +02:00
/**
* This DataObjects extensions , eg Versioned .
* @ var array
*/
protected $extension_instances = array ();
/**
* Extensions to be used on this object . An array of extension names
* and parameters eg :
*
* static $extensions = array (
* " Hierarchy " ,
* " Versioned('Stage', 'Live') " ,
* );
*
* @ var array
*/
public static $extensions = null ;
2007-07-19 12:40:28 +02:00
protected static $extraStatics = array ();
protected static $classConstructed = array ();
protected static $extraMethods = array ();
protected static $builtInMethods = array ();
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Use the class in the value instead of the class in the key
*/
private static $custom_classes = array ();
private static $strong_classes = array ();
2007-08-16 08:37:49 +02:00
2007-07-19 12:40:28 +02:00
/**
2007-09-15 23:34:40 +02:00
* This function allows you to overload class creation methods , so certain classes are
* always created correctly over your system .
*
2007-07-19 12:40:28 +02:00
* @ param oldClass = the old classname you want to replace with .
* @ param customClass = the new Classname you wish to replace the old class with .
* @ param strong - If you want to force a replacement of a class then we use a different array
* e . g for use in singleton classes .
*/
public static function useCustomClass ( $oldClass , $customClass , $strong = false ) {
if ( $strong ){
self :: $strong_classes [ $oldClass ] = $customClass ;
} else {
self :: $custom_classes [ $oldClass ] = $customClass ;
}
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
public static function getCustomClass ( $oldClass ) {
if ( array_key_exists ( $oldClass , self :: $custom_classes ) )
return self :: $custom_classes [ $oldClass ];
else {
return $oldClass ;
}
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Create allows us to override the standard classes of sapphire with our own custom classes .
2007-09-15 23:34:40 +02:00
* create will load strong classes firstly for singleton level and database interaction , otherwise will
* use the fallback custom classes .
* To set a strong custom class to overide an object at for say singleton use , use the syntax
2007-07-19 12:40:28 +02:00
* Object :: useCustomClass ( 'Datetime' , 'SSDatetime' , true );
* @ param className - The classname you want to create
* @ param args - Up to 9 arguments you wish to pass on to the new class
*/
public static function create ( $className , $arg0 = null , $arg1 = null , $arg2 = null , $arg3 = null , $arg4 = null , $arg5 = null , $arg6 = null , $arg7 = null , $arg8 = null ) {
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
$useStrongClassName = isset ( self :: $strong_classes [ $className ]);
$useClassName = isset ( self :: $custom_classes [ $className ]);
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
if ( $useStrongClassName ){
$classToCreate = self :: $strong_classes [ $className ];
} elseif ( $useClassName ){
2007-09-15 23:34:40 +02:00
$classToCreate = self :: $custom_classes [ $className ];
2007-07-19 12:40:28 +02:00
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
$hasStrong = isset ( self :: $strong_classes [ $className ]) && class_exists ( self :: $strong_classes [ $className ]);
$hasNormal = isset ( self :: $custom_classes [ $className ]) && class_exists ( self :: $custom_classes [ $className ]);
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
if ( ! isset ( $classToCreate ) || ( ! $hasStrong && ! $hasNormal )){
$classToCreate = $className ;
}
2007-09-15 23:34:40 +02:00
return new $classToCreate ( $arg0 , $arg1 , $arg2 , $arg3 , $arg4 , $arg5 , $arg6 , $arg7 , $arg8 );
2007-07-19 12:40:28 +02:00
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Strong_create is a function to enforce a certain class replacement
* e . g Php5 . 2 ' s latest introduction of a namespace conflict means we have to replace
* all instances of Datetime with SSdatetime .
* this allows us to seperate those , and sapphires classes
* @ param className - The class you wish to create .
* @ param args - pass up to 8 arguments to the created class .
*/
public static function strong_create ( $className , $arg0 = null , $arg1 = null , $arg2 = null , $arg3 = null , $arg4 = null , $arg5 = null , $arg6 = null , $arg7 = null , $arg8 = null ) {
$useStrongClassName = isset ( self :: $strong_classes [ $className ]);
if ( $useStrongClassName ){
$classToCreate = self :: $strong_classes [ $className ];
}
if ( ! isset ( $classToCreate ) || ! class_exists ( self :: $strong_classes [ $className ])){
$classToCreate = $className ;
}
2007-09-15 23:34:40 +02:00
return new $classToCreate ( $arg0 , $arg1 , $arg2 , $arg3 , $arg4 , $arg5 , $arg6 , $arg7 , $arg8 );
2007-07-19 12:40:28 +02:00
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
function __construct () {
$this -> class = get_class ( $this );
2007-08-16 08:37:49 +02:00
// Set up the extensions
if ( $extensions = $this -> stat ( 'extensions' )) {
foreach ( $extensions as $extension ) {
$instance = eval ( " return new $extension ; " );
$instance -> setOwner ( $this );
$this -> extension_instances [ $instance -> class ] = $instance ;
}
}
2007-07-19 12:40:28 +02:00
if ( ! isset ( Object :: $classConstructed [ $this -> class ])) {
$this -> defineMethods ();
Object :: $classConstructed [ $this -> class ] = true ;
}
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Returns true if this object " exists " , i . e . , has a sensible value .
* Overload this in subclasses .
* For example , an empty DataObject record could return false .
*/
public function exists () {
return true ;
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Returns true if the given method exists .
*/
public function hasMethod ( $methodName ) {
if ( ! isset ( $this -> class )) $this -> class = get_class ( $this );
if ( ! isset ( Object :: $builtInMethods [ '_set' ][ $this -> class ])) $this -> buildMethodList ();
if ( isset ( Object :: $builtInMethods [ $this -> class ][ $methodName ])) return true ;
if ( isset ( Object :: $extraMethods [ $this -> class ][ $methodName ])) return true ;
return false ;
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Calls a method .
* Extra methods can be hooked to a class using
*/
public function __call ( $methodName , $args ) {
if ( isset ( Object :: $extraMethods [ $this -> class ][ $methodName ])) {
$config = Object :: $extraMethods [ $this -> class ][ $methodName ];
if ( isset ( $config [ 'parameterName' ])) {
if ( isset ( $config [ 'arrayIndex' ])) $obj = $this -> { $config [ 'parameterName' ]}[ $config [ 'arrayIndex' ]];
else $obj = $this -> { $config [ 'parameterName' ]};
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
if ( $obj ) {
return call_user_func_array ( array ( & $obj , $methodName ), $args );
} else {
if ( $this -> destroyed ) user_error ( " Attempted to call $methodName on destroyed ' $this->class ' object " , E_USER_ERROR );
else user_error ( " ' $this->class ' object doesn't have a parameter $config[parameterName] ( $config[arrayIndex] ) to pass control to. Perhaps this object has been mistakenly destroyed? " , E_USER_WARNING );
}
} else if ( isset ( $config [ 'wrap' ])) {
array_unshift ( $args , $methodName );
return call_user_func_array ( array ( & $this , $config [ 'wrap' ]), $args );
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
} else if ( isset ( $config [ 'function' ])) {
$function = $config [ 'function' ];
return $function ( $this , $args );
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
} else if ( $config [ 'function_str' ]) {
$function = Object :: $extraMethods [ $this -> class ][ $methodName ][ 'function' ] = create_function ( '$obj, $args' , $config [ 'function_str' ]);
return $function ( $this , $args );
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
} else {
user_error ( " Object::__call() Method ' $methodName ' in class ' $this->class ' an invalid format: " . var_export ( Object :: $extraMethods [ $this -> class ][ $methodName ], true ), E_USER_ERROR );
}
} else {
user_error ( " Object::__call() Method ' $methodName ' not found in class ' $this->class ' " , E_USER_ERROR );
}
}
/**
* Add the all methods from a given parameter to this object .
* This is used for extensions .
* @ param parameterName The name of the parameter . This parameter must be instanciated with an item of the correct class .
* @ param arrayIndex If parameterName is an array , this can be an index . If null , we ' ll assume the value is all that is needed .
*/
protected function addMethodsFrom ( $parameterName , $arrayIndex = null ) {
$obj = isset ( $arrayIndex ) ? $this -> { $parameterName }[ $arrayIndex ] : $this -> $parameterName ;
if ( ! $obj ) user_error ( " Object::addMethodsFrom: $parameterName / $arrayIndex " , E_USER_ERROR );
2007-10-02 06:52:31 +02:00
// Hack to fix Fatal error: Call to undefined method stdClass::allMethodNames()
if ( method_exists ( $obj , 'allMethodNames' )) {
$methodNames = $obj -> allMethodNames ( true );
foreach ( $methodNames as $methodName ) {
Object :: $extraMethods [ $this -> class ][ $methodName ] = array ( " parameterName " => $parameterName , " arrayIndex " => $arrayIndex );
}
2007-07-19 12:40:28 +02:00
}
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Add a 'wrapper method' .
* For example , Thumbnail ( $arg , $arg ) can be defined to call generateImage ( " Thumbnail " , $arg , $arg )
*/
protected function addWrapperMethod ( $methodName , $wrapperMethod ) {
Object :: $extraMethods [ $this -> class ][ $methodName ] = array ( " wrap " => $wrapperMethod );
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Create a new method
* @ param methodName The name of the method
* @ param methodCode The PHP code of the method , in a string . Arguments will be contained
* in an array called $args . The object will be $obj , not $this . You won ' t be able to access
* any protected methods ; the method is actually contained in an external function .
*/
protected function createMethod ( $methodName , $methodCode ) {
Object :: $extraMethods [ $this -> class ][ $methodName ] = array ( " function_str " => $methodCode );
}
/**
* Return the names of all the methods on this object .
* param includeCustom If set to true , then return custom methods too .
*/
function allMethodNames ( $includeCustom = false ) {
if ( ! $this -> class ) $this -> class = get_class ( $this );
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
if ( ! isset ( Object :: $builtInMethods [ '_set' ][ $this -> class ])) $this -> buildMethodList ();
if ( $includeCustom && isset ( Object :: $extraMethods [ $this -> class ])) {
return array_merge ( Object :: $builtInMethods [ $this -> class ], array_keys ( Object :: $extraMethods [ $this -> class ]));
} else {
return Object :: $builtInMethods [ $this -> class ];
}
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
function buildMethodList () {
if ( ! $this -> class ) $this -> class = get_class ( $this );
$reflection = new ReflectionClass ( $this -> class );
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
$methods = $reflection -> getMethods ();
foreach ( $methods as $method ) {
$name = $method -> getName ();
$methodNames [ $name ] = $name ;
}
Object :: $builtInMethods [ $this -> class ] = $methodNames ;
Object :: $builtInMethods [ '_set' ][ $this -> class ] = true ;
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* This constructor will be called the first time an object of this class is created .
* You can overload it with methods for setting up the class - for example , extra methods .
*/
protected function defineMethods () {
2007-08-16 08:37:49 +02:00
if ( $this -> extension_instances ) foreach ( $this -> extension_instances as $i => $instance ) {
$this -> addMethodsFrom ( 'extension_instances' , $i );
}
2007-07-19 12:40:28 +02:00
if ( isset ( $_REQUEST [ 'debugmethods' ]) && isset ( Object :: $builtInMethods [ $this -> class ])) {
2007-11-08 00:46:00 +01:00
Debug :: require_developer_login ();
2007-07-19 12:40:28 +02:00
echo " <h2>Methods defined for $this->class </h2> " ;
foreach ( Object :: $builtInMethods [ $this -> class ] as $name => $info ) {
echo " <li> $name " ;
}
}
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* This method lets us extend a built - in class by adding static variables to it
*/
static function addStaticVars ( $class , $statics ) {
if ( empty ( Object :: $extraStatics [ $class ])) {
Object :: $extraStatics [ $class ] = ( array ) $statics ;
} else {
2007-07-26 23:57:01 +02:00
$ar1 = ( array ) Object :: $extraStatics [ $class ]; // First Array To Merge
$ar2 = ( array ) $statics ; // Second Array To Merge
Object :: $extraStatics [ $class ] = array_merge_recursive ( $ar1 , $ar2 );
2007-07-19 12:40:28 +02:00
}
}
function parentClass () {
return get_parent_class ( $this );
}
function is_a ( $class ) {
return is_a ( $this , $class );
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Set an uninherited static variable
*/
function set_uninherited ( $name , $val ) {
return Object :: $uninherited_statics [ $this -> class ][ $name ] = $val ;
}
/**
* Get an uninherited static variable
*/
function uninherited ( $name , $builtIn = false ) {
// Copy a built-in value into our own array cache. PHP's static variable support is shit.
if ( $builtIn ) {
$val = $this -> stat ( $name );
2007-09-16 02:19:54 +02:00
$val2 = null ;
try {
2007-12-13 23:33:25 +01:00
// The reflection doesn't work properly in 5.1.2
if ( phpversion () == '5.1.2' ) {
$val2 = eval ( 'return ' . get_parent_class ( $this ) . " :: \$ $name ; " );
} else {
$reflection = new ReflectionClass ( get_parent_class ( $this ));
$property = $reflection -> getProperty ( $name );
$val2 = $property -> getValue ();
}
2007-09-16 02:19:54 +02:00
} catch ( Exception $exc ) {
// do nothing.. the property doesn't exists!
}
2007-07-19 12:40:28 +02:00
return ( $val != $val2 ) ? $val : null ;
}
return isset ( Object :: $uninherited_statics [ $this -> class ][ $name ]) ? Object :: $uninherited_statics [ $this -> class ][ $name ] : null ;
}
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
protected static $statics = array ();
protected static $static_cached = array ();
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
/**
* Get a static variable
*/
function stat ( $name ) {
if ( ! $this -> class ) $this -> class = get_class ( $this );
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
if ( ! isset ( Object :: $static_cached [ $this -> class ][ $name ])) {
$classes = ClassInfo :: ancestry ( $this -> class );
foreach ( $classes as $class ) {
if ( isset ( Object :: $extraStatics [ $class ][ $name ])) {
$extra = Object :: $extraStatics [ $class ][ $name ];
if ( ! is_array ( $extra )) return $extra ;
break ;
}
}
$stat = eval ( " return { $this -> class } :: \$ $name ; " );
Object :: $statics [ $this -> class ][ $name ] = isset ( $extra ) ? array_merge ( $extra , ( array ) $stat ) : $stat ;
Object :: $static_cached [ $this -> class ][ $name ] = true ;
}
return Object :: $statics [ $this -> class ][ $name ];
}
/**
* Set a static variable
*/
function set_stat ( $name , $val ) {
Object :: $statics [ $this -> class ][ $name ] = $val ;
Object :: $static_cached [ $this -> class ][ $name ] = true ;
}
public $class ;
private static $uninherited_statics = array ();
2007-09-15 23:34:40 +02:00
2007-07-19 12:40:28 +02:00
public function __toString () {
return $this -> class ;
}
2007-08-16 08:37:49 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// EXTENSION METHODS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Run the given function on all of this object ' s extensions
*
* @ param string $funcName The name of the function .
* @ param mixed $arg An Argument to be passed to each of the extension functions .
*/
public function extend ( $funcName , & $arg ) {
if ( $this -> extension_instances ) {
foreach ( $this -> extension_instances as $extension ) {
if ( $extension -> hasMethod ( $funcName )) {
$extension -> $funcName ( $arg );
}
}
}
}
/**
* Get an extension on this DataObject
*
* @ param string $name Classname of the Extension ( e . g . 'Versioned' )
*
* @ return DataObjectDecorator The instance of the extension
*/
2008-02-25 03:10:37 +01:00
public function extInstance ( $name ) {
2007-08-16 08:37:49 +02:00
return $this -> extension_instances [ $name ];
}
/**
* Returns true if the given extension class is attached to this object
*
* @ param string $requiredExtension Classname of the extension
*
* @ return boolean True if the given extension class is attached to this object
*/
public function hasExtension ( $requiredExtension ) {
return isset ( $this -> extension_instances [ $requiredExtension ]) ? true : false ;
}
/**
* Add an extension to the given object .
* This can be used to add extensions to built - in objects , such as role decorators on Member
*/
public static function add_extension ( $className , $extensionName ) {
Object :: addStaticVars ( $className , array (
'extensions' => array (
$extensionName ,
),
));
}
2008-02-25 03:10:37 +01:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CACHE METHODS (added by simon_w (simon -at- simon -dot- geek -dot- nz))
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Loads a current cache from the filesystem , if it can .
*
* @ param string $cachename The name of the cache to load
* @ param int $expire The lifetime of the cache in seconds
* @ return mixed The data from the cache , or false if the cache wasn ' t loaded
*/
protected function loadCache ( $cachename , $expire = 3600 ) {
$cache_dir = TEMP_FOLDER ;
$cache_path = $cache_dir . " / " . $this -> sanitiseCachename ( $cachename );
if (( ! isset ( $_GET [ 'flush' ]) || $_GET [ 'flush' ] != 1 ) && ( @ file_exists ( $cache_path ) && (( @ filemtime ( $cache_path ) + $expire ) > ( time ())))) {
return @ unserialize ( file_get_contents ( $cache_path ));
}
return false ;
}
/**
* Saves a cache to the file system
*
* @ param string $cachename The name of the cache to save
* @ param mixed $data The data to cache
*/
protected function saveCache ( $cachename , $data ) {
$cache_dir = TEMP_FOLDER ;
$cache_path = $cache_dir . " / " . $this -> sanitiseCachename ( $cachename );
$fp = @ fopen ( $cache_path , " w+ " );
if ( ! $fp ) {
return ; // Throw an error?
}
@ fwrite ( $fp , @ serialize ( $data ));
@ fclose ( $fp );
}
/**
* Makes a cache name safe to use in a file system
*
* @ param string $cachename The cache name to sanitise
* @ return string the sanitised cache name
*/
protected function sanitiseCachename ( $cachename ) {
// Replace illegal characters with underscores
$cachename = str_replace ( array ( '~' , '.' , '/' , '!' , ' ' , " \n " , " \r " , " \t " , '\\' , ':' , '"' , '\'' , ';' ), '_' , $cachename );
return $cachename ;
}
/**
* Caches the return value of a method .
*
* @ param callback $callback The method to cache
* @ param int $expire The lifetime of the cache
* @ param string | int $id An id for the cache
* @ return mixed The cached return of the method
*/
public function cacheToFile ( $callback , $expire = 3600 , $id = false ) {
if ( ! $this -> class ) {
$this -> class = get_class ( $this );
}
if ( ! method_exists ( $this -> class , $callback )) {
user_error ( " Class { $this -> class } doesn't have the method $callback . " , E_USER_ERROR );
}
$cachename = $this -> class . " _ " . $callback ;
if ( $id ) {
$cachename .= " _ " . ( string ) $id ;
}
if (( $data = $this -> loadCache ( $cachename , $expire )) !== false ) {
return $data ;
}
// No cache to use
$data = $this -> $callback ();
if ( $data === false ) {
// Some problem with function. Didn't give anything to cache. So don't cache it.
return false ;
}
$this -> saveCache ( $cachename , $data );
return $data ;
}
/**
* Caches the return value of a method . Passes args to the method as well .
*
* @ param callback $callback The method to cache
* @ param array $args The arguments to pass to the method
* @ param int $expire The lifetime of the cache
* @ param string | int $id An id for the cache
* @ return mixed The cached return of the method
*/
// I know this is almost exactly the same as cacheToFile, but call_user_func_array() is slow.
// Which is why there's two separate functions
public function cacheToFileWithArgs ( $callback , $args = array (), $expire = 3600 , $id = false ) {
if ( ! $this -> class ) {
$this -> class = get_class ( $this );
}
if ( ! method_exists ( $this -> class , $callback )) {
user_error ( " Class { $this -> class } doesn't have the method $callback . " , E_USER_ERROR );
}
$cachename = $this -> class . " _ " . $callback ;
if ( $id ) {
$cachename .= " _ " . ( string ) $id ;
}
if (( $data = $this -> loadCache ( $cachename , $expire )) !== false ) {
return $data ;
}
// No cache to use
$data = call_user_func_array ( array ( $this , $callback ), $args );
if ( $data === false ) {
// Some problem with function. Didn't give anything to cache. So don't cache it.
return false ;
}
$this -> saveCache ( $cachename , $data );
return $data ;
}
2007-07-19 12:40:28 +02:00
}
/**
* PHP 5.2 has a namespace conflict with our datetime class ,
* for legacy support , we use this overload method .
* // ENFORCE STRONG_CREATE
*/
Object :: useCustomClass ( 'Datetime' , 'SSDatetime' , true );