Version: current

       1 <?php
       2 /**
       3  * A base class for all sapphire objects to inherit from.
       4  *
       5  * This class provides a number of pattern implementations, as well as methods and fixes to add extra psuedo-static
       6  * and method functionality to PHP.
       7  *
       8  * See {@link Extension} on how to implement a custom multiple
       9  * inheritance for object instances based on PHP5 method call overloading.
      10  *
      11  * @todo Create instance-specific removeExtension() which removes an extension from $extension_instances,
      12  * but not from static $extensions, and clears everything added through defineMethods(), mainly $extra_methods.
      13  *
      14  * @package sapphire
      15  * @subpackage core
      16  */
      17 abstract class Object {
      18
      19     /**
      20      * An array of extension names and parameters to be applied to this object upon construction.
      21      *
      22      * Example:
      23      * <code>
      24      * public static $extensions = array (
      25      *   'Hierachy',
      26      *   "Version('Stage', 'Live')"
      27      * );
      28      * </code>
      29      *
      30      * Use {@link Object::add_extension()} to add extensions without access to the class code,
      31      * e.g. to extend core classes.
      32      *
      33      * Extensions are instanciated together with the object and stored in {@link $extension_instances}.
      34      *
      35      * @var array $extensions
      36      */
      37     public static $extensions = null;
      38
      39     /**#@+
      40      * @var array
      41      */
      42
      43     private static
      44         $statics = array(),
      45         $cached_statics = array(),
      46         $uninherited_statics = array(),
      47         $cached_uninherited_statics = array(),
      48         $extra_statics = array(),
      49         $replaced_statics = array(),
      50         $_cache_statics_prepared = array();
      51
      52     private static
      53         $classes_constructed = array(),
      54         $extra_methods       = array(),
      55         $built_in_methods    = array();
      56
      57     private static
      58         $custom_classes = array(),
      59         $strong_classes = array();
      60
      61     /**#@-*/
      62
      63     /**
      64      * @var string the class name
      65      */
      66     public $class;
      67
      68     /**
      69      * @var array all current extension instances.
      70      */
      71     protected $extension_instances = array();
      72
      73     /**
      74      * An implementation of the factory method, allows you to create an instance of a class
      75      *
      76      * This method first for strong class overloads (singletons & DB interaction), then custom class overloads. If an
      77      * overload is found, an instance of this is returned rather than the original class. To overload a class, use
      78      * {@link Object::useCustomClass()}
      79      *
      80      * @param string $class the class name
      81      * @param mixed $arguments,... arguments to pass to the constructor
      82      * @return Object
      83      */
      84     public static function create() {
      85         $args  = func_get_args();
      86         $class = self::getCustomClass(array_shift($args));
      87
      88         if(version_compare(PHP_VERSION, '5.1.3', '>=')) {
      89             $reflector = new ReflectionClass($class);
      90             return $reflector->newInstanceArgs($args);
      91         } else {
      92             // we're using a PHP install that doesn't support ReflectionClass->newInstanceArgs()
      93
      94             $args = $args + array_fill(0, 9, null);
      95             return new $class($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]);
      96         }
      97     }
      98
      99     private static $_cache_inst_args = array();
     100
     101     /**
     102      * Create an object from a string representation.  It treats it as a PHP constructor without the
     103      * 'new' keyword.  It also manages to construct the object without the use of eval().
     104      *
     105      * Construction itself is done with Object::create(), so that Object::useCustomClass() calls
     106      * are respected.
     107      *
     108      * `Object::create_from_string("Versioned('Stage','Live')")` will return the result of
     109      * `Object::create('Versioned', 'Stage', 'Live);`
     110      *
     111      * It is designed for simple, clonable objects.  The first time this method is called for a given
     112      * string it is cached, and clones of that object are returned.
     113      *
     114      * If you pass the $firstArg argument, this will be prepended to the constructor arguments. It's
     115      * impossible to pass null as the firstArg argument.
     116      *
     117      * `Object::create_from_string("Varchar(50)", "MyField")` will return the result of
     118      * `Object::create('Vachar', 'MyField', '50');`
     119      *
     120      * Arguments are always strings, although this is a quirk of the current implementation rather
     121      * than something that can be relied upon.
     122      */
     123     static function create_from_string($classSpec, $firstArg = null) {
     124         if(!isset(self::$_cache_inst_args[$classSpec.$firstArg])) {
     125             // an $extension value can contain parameters as a string,
     126             // e.g. "Versioned('Stage','Live')"
     127             if(strpos($classSpec,'(') === false) {
     128                 if($firstArg === null) self::$_cache_inst_args[$classSpec.$firstArg] = Object::create($classSpec);
     129                 else self::$_cache_inst_args[$classSpec.$firstArg] = Object::create($classSpec, $firstArg);
     130
     131             } else {
     132                 list($class, $args) = self::parse_class_spec($classSpec);
     133
     134                 if($firstArg !== null) array_unshift($args, $firstArg);
     135                 array_unshift($args, $class);
     136
     137                 self::$_cache_inst_args[$classSpec.$firstArg] = call_user_func_array(array('Object','create'), $args);
     138             }
     139         }
     140
     141         return clone self::$_cache_inst_args[$classSpec.$firstArg];
     142     }
     143
     144     /**
     145      * Parses a class-spec, such as "Versioned('Stage','Live')", as passed to create_from_string().
     146      * Returns a 2-elemnent array, with classname and arguments
     147      */
     148     static function parse_class_spec($classSpec) {
     149         $tokens = token_get_all("<?php $classSpec");
     150         $class = null;
     151         $args = array();
     152         $passedBracket = false;
     153
     154         // Keep track of the current bucket that we're putting data into
     155         $bucket = &$args;
     156         $bucketStack = array();
     157
     158         foreach($tokens as $token) {
     159             $tName = is_array($token) ? $token[0] : $token;
     160             // Get the class naem
     161             if($class == null && is_array($token) && $token[0] == T_STRING) {
     162                 $class = $token[1];
     163             // Get arguments
     164             } else if(is_array($token)) {
     165                 switch($token[0]) {
     166                 case T_CONSTANT_ENCAPSED_STRING:
     167                     $argString = $token[1];
     168                     switch($argString[0]) {
     169                         case '"': $argString = stripcslashes(substr($argString,1,-1)); break;
     170                         case "'": $argString = str_replace(array("\\\\", "\\'"),array("\\", "'"), substr($argString,1,-1)); break;
     171                         default: throw new Exception("Bad T_CONSTANT_ENCAPSED_STRING arg $argString");
     172                     }
     173                     $bucket[] = $argString;
     174                     break;
     175
     176                 case T_DNUMBER:
     177                     $bucket[] = (double)$token[1];
     178                     break;
     179
     180                 case T_LNUMBER:
     181                     $bucket[] = (int)$token[1];
     182                     break;
     183
     184                 case T_STRING:
     185                     switch($token[1]) {
     186                         case 'true': $args[] = true; break;
     187                         case 'false': $args[] = false; break;
     188                         default: throw new Exception("Bad T_STRING arg '{$token[1]}'");
     189                     }
     190
     191                 case T_ARRAY:
     192                     // Add an empty array to the bucket
     193                     $bucket[] = array();
     194                     $bucketStack[] = &$bucket;
     195                     $bucket = &$bucket[sizeof($bucket)-1];
     196
     197                 }
     198
     199             } else {
     200                 if($tName == ')') {
     201                     // Pop-by-reference
     202                     $bucket = &$bucketStack[sizeof($bucketStack)-1];
     203                     array_pop($bucketStack);
     204                 }
     205             }
     206         }
     207
     208         return array($class, $args);
     209     }
     210
     211     /**
     212      * Similar to {@link Object::create()}, except that classes are only overloaded if you set the $strong parameter to
     213      * TRUE when using {@link Object::useCustomClass()}
     214      *
     215      * @param string $class the class name
     216      * @param mixed $arguments,... arguments to pass to the constructor
     217      * @return Object
     218      */
     219     public static function strong_create() {
     220         $args  = func_get_args();
     221         $class = array_shift($args);
     222
     223         if(isset(self::$strong_classes[$class]) && ClassInfo::exists(self::$strong_classes[$class])) {
     224             $class = self::$strong_classes[$class];
     225         }
     226
     227         if(version_compare(PHP_VERSION, '5.1.3', '>=')) {
     228             $reflector = new ReflectionClass($class);
     229             return $reflector->newInstanceArgs($args);
     230         } else {
     231             $args = $args + array_fill(0, 9, null);
     232             return new $class($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]);
     233         }
     234     }
     235
     236     /**
     237      * This class allows you to overload classes with other classes when they are constructed using the factory method
     238      * {@link Object::create()}
     239      *
     240      * @param string $oldClass the class to replace
     241      * @param string $newClass the class to replace it with
     242      * @param bool $strong allows you to enforce a certain class replacement under all circumstances. This is used in
     243      *        singletons and DB interaction classes
     244      */
     245     public static function useCustomClass($oldClass, $newClass, $strong = false) {
     246         if($strong) {
     247             self::$strong_classes[$oldClass] = $newClass;
     248         } else {
     249             self::$custom_classes[$oldClass] = $newClass;
     250         }
     251     }
     252
     253     /**
     254      * If a class has been overloaded, get the class name it has been overloaded with - otherwise return the class name
     255      *
     256      * @param string $class the class to check
     257      * @return string the class that would be created if you called {@link Object::create()} with the class
     258      */
     259     public static function getCustomClass($class) {
     260         if(isset(self::$strong_classes[$class]) && ClassInfo::exists(self::$strong_classes[$class])) {
     261             return self::$strong_classes[$class];
     262         } elseif(isset(self::$custom_classes[$class]) && ClassInfo::exists(self::$custom_classes[$class])) {
     263             return self::$custom_classes[$class];
     264         }
     265
     266         return $class;
     267     }
     268
     269     /**
     270      * Get a static variable, taking into account SS's inbuild static caches and pseudo-statics
     271      *
     272      * This method first checks for any extra values added by {@link Object::add_static_var()}, and attemps to traverse
     273      * up the extra static var chain until it reaches the top, or it reaches a replacement static.
     274      *
     275      * If any extra values are discovered, they are then merged with the default PHP static values, or in some cases
     276      * completely replace the default PHP static when you set $replace = true, and do not define extra data on any child
     277      * classes
     278      *
     279      * Note that from SilverStripe 2.3.2, Object::get_static() can only be used to get public
     280      * static variables, not protected ones.
     281      *
     282      * @param string $class
     283      * @param string $name the property name
     284      * @param bool $uncached if set to TRUE, force a regeneration of the static cache
     285      * @return mixed
     286      */
     287     public static function get_static($class, $name, $uncached = false) {
     288         if(!isset(self::$_cache_statics_prepared[$class])) {
     289             Object::prepare_statics($class);
     290         }
     291
     292         if(!isset(self::$cached_statics[$class][$name]) || $uncached) {
     293             //if($class == 'DataObjectDecoratorTest_MyObject') Debug::message("$class - $name");
     294             $extra     = $builtIn = $break = $replacedAt = false;
     295             $ancestry  = array_reverse(ClassInfo::ancestry($class));
     296
     297             // traverse up the class tree and build extra static and stop information
     298             foreach($ancestry as $ancestor) {
     299                 if(isset(self::$extra_statics[$ancestor][$name])) {
     300                     $toMerge = self::$extra_statics[$ancestor][$name];
     301
     302                     if(is_array($toMerge) && is_array($extra)) {
     303                         $extra = array_merge($toMerge, $extra);
     304                     } elseif(!$extra) {
     305                         $extra = $toMerge;
     306                     } else {
     307                         $break = true;
     308                     }
     309
     310                     if(isset(self::$replaced_statics[$ancestor][$name])) $replacedAt = $break = $ancestor;
     311
     312                     if($break) break;
     313                 }
     314             }
     315
     316             // check whether to merge in the default value
     317             if($replacedAt && ($replacedAt == $class || !is_array($extra))) {
     318                 $value = $extra;
     319             } elseif($replacedAt) {
     320                 // determine whether to merge in lower-class variables
     321                 $ancestorRef     = new ReflectionClass(reset($ancestry));
     322                 $ancestorProps   = $ancestorRef->getStaticProperties();
     323                 $ancestorInbuilt = array_key_exists($name, $ancestorProps) ? $ancestorProps[$name] : null;
     324
     325                 $replacedRef     = new ReflectionClass($replacedAt);
     326                 $replacedProps   = $replacedRef->getStaticProperties();
     327                 $replacedInbuilt = array_key_exists($name, $replacedProps) ? $replacedProps[$name] : null;
     328
     329                 if($ancestorInbuilt != $replacedInbuilt) {
     330                     $value = is_array($ancestorInbuilt) ? array_merge($ancestorInbuilt, (array) $extra) : $extra;
     331                 } else {
     332                     $value = $extra;
     333                 }
     334             } else {
     335                 // get a built-in value
     336                 $reflector = new ReflectionClass($class);
     337                 $props     = $reflector->getStaticProperties();
     338                 $inbuilt   = array_key_exists($name, $props) ? $props[$name] : null;
     339                 $value     = isset($extra) && is_array($extra) ? array_merge($extra, (array) $inbuilt) : $inbuilt;
     340             }
     341
     342             self::$cached_statics[$class][$name] = true;
     343             self::$statics[$class][$name]        = $value;
     344         }
     345
     346         return self::$statics[$class][$name];
     347     }
     348
     349     /**
     350      * Set a static variable
     351      *
     352      * @param string $class
     353      * @param string $name the property name to set
     354      * @param mixed $value
     355      */
     356     public static function set_static($class, $name, $value) {
     357         if(!isset(self::$_cache_statics_prepared[$class])) {
     358             Object::prepare_statics($class);
     359         }
     360
     361         self::$statics[$class][$name] = $value;
     362         self::$uninherited_statics[$class][$name] = $value;
     363         self::$cached_statics[$class][$name] = true;
     364         self::$cached_uninherited_statics[$class][$name] = true;
     365     }
     366
     367     /**
     368      * Get an uninherited static variable - a variable that is explicity set in this class, and not in the parent class.
     369      *
     370      * Note that from SilverStripe 2.3.2, Object::uninherited_static() can only be used to get public
     371      * static variables, not protected ones.
     372      *
     373      * @todo Recursively filter out parent statics, currently only inspects the parent class
     374      *
     375      * @param string $class
     376      * @param string $name
     377      * @return mixed
     378      */
     379     public static function uninherited_static($class, $name, $uncached = false) {
     380         if(!isset(self::$_cache_statics_prepared[$class])) {
     381             Object::prepare_statics($class);
     382         }
     383
     384         if(!isset(self::$cached_uninherited_statics[$class][$name]) || $uncached) {
     385             $classRef = new ReflectionClass($class);
     386             $classProp = $classRef->getStaticPropertyValue($name, null);
     387
     388             $parentClass = get_parent_class($class);
     389             if($parentClass) {
     390                 $parentRef = new ReflectionClass($parentClass);
     391                 $parentProp = $parentRef->getStaticPropertyValue($name, null);
     392                 if($parentProp == $classProp) $classProp = null;
     393             }
     394
     395             // Add data from extra_statics if it has been applied to this specific class (it
     396             // wouldn't make sense to have them inherit in this method).  This is kept separate
     397             // from the equivalent get_static code because it's so much simpler
     398             if(isset(self::$extra_statics[$class][$name])) {
     399                 $toMerge = self::$extra_statics[$class][$name];
     400
     401                 if(is_array($toMerge) && is_array($classProp)) {
     402                     $classProp = array_merge($toMerge, $classProp);
     403                 } elseif(!$classProp) {
     404                     $classProp = $toMerge;
     405                 }
     406             }
     407
     408             self::$cached_uninherited_statics[$class][$name] = true;
     409             self::$uninherited_statics[$class][$name] = $classProp;
     410         }
     411
     412         return self::$uninherited_statics[$class][$name];
     413     }
     414
     415     /**
     416      * Traverse down a class ancestry and attempt to merge all the uninherited static values for a particular static
     417      * into a single variable
     418      *
     419      * @param string $class
     420      * @param string $name the static name
     421      * @param string $ceiling an optional parent class name to begin merging statics down from, rather than traversing
     422      *        the entire hierarchy
     423      * @return mixed
     424      */
     425     public static function combined_static($class, $name, $ceiling = false) {
     426         $ancestry = ClassInfo::ancestry($class);
     427         $values   = null;
     428
     429         if($ceiling) while(current($ancestry) != $ceiling && $ancestry) {
     430             array_shift($ancestry);
     431         }
     432
     433         if($ancestry) foreach($ancestry as $ancestor) {
     434             $merge = self::uninherited_static($ancestor, $name);
     435
     436             if(is_array($values) && is_array($merge)) {
     437                 $values = array_merge($values, $merge);
     438             } elseif($merge) {
     439                 $values = $merge;
     440             }
     441         }
     442
     443         return $values;
     444     }
     445
     446     /**
     447      * Merge in a set of additional static variables
     448      *
     449      * @param string $class
     450      * @param array $properties in a [property name] => [value] format
     451      * @param bool $replace replace existing static vars
     452      */
     453     public static function addStaticVars($class, $properties, $replace = false) {
     454         foreach($properties as $prop => $value) self::add_static_var($class, $prop, $value, $replace);
     455     }
     456
     457     /**
     458      * Add a static variable without replacing it completely if possible, but merging in with both existing PHP statics
     459      * and existing psuedo-statics. Uses PHP's array_merge_recursive() with if the $replace argument is FALSE.
     460      *
     461      * Documentation from http://php.net/array_merge_recursive:
     462      * If the input arrays have the same string keys, then the values for these keys are merged together
     463      * into an array, and this is done recursively, so that if one of the values is an array itself,
     464      * the function will merge it with a corresponding entry in another array too.
     465      * If, however, the arrays have the same numeric key, the later value will not overwrite the original value,
     466      * but will be appended.
     467      *
     468      * @param string $class
     469      * @param string $name the static name
     470      * @param mixed $value
     471      * @param bool $replace completely replace existing static values
     472      */
     473     public static function add_static_var($class, $name, $value, $replace = false) {
     474         if(is_array($value) && isset(self::$extra_statics[$class][$name]) && !$replace) {
     475             self::$extra_statics[$class][$name] = array_merge_recursive(self::$extra_statics[$class][$name], $value);
     476         } else {
     477             self::$extra_statics[$class][$name] = $value;
     478         }
     479
     480         if ($replace) {
     481             self::set_static($class, $name, $value);
     482             self::$replaced_statics[$class][$name] = true;
     483
     484         // Clear caches
     485         } else {
     486             self::$cached_statics[$class][$name] = null;
     487             self::$cached_uninherited_statics[$class][$name] = null;
     488         }
     489     }
     490
     491     /**
     492      * Return TRUE if a class has a specified extension
     493      *
     494      * @param string $class
     495      * @param string $requiredExtension the class name of the extension to check for.
     496      */
     497     public static function has_extension($class, $requiredExtension) {
     498         $requiredExtension = strtolower($requiredExtension);
     499         if($extensions = self::combined_static($class, 'extensions')) foreach($extensions as $extension) {
     500             $left = strtolower(Extension::get_classname_without_arguments($extension));
     501             $right = strtolower(Extension::get_classname_without_arguments($requiredExtension));
     502             if($left == $right) return true;
     503         }
     504
     505         return false;
     506     }
     507
     508     /**
     509      * Add an extension to a specific class.
     510      * As an alternative, extensions can be added to a specific class
     511      * directly in the {@link Object::$extensions} array.
     512      * See {@link SiteTree::$extensions} for examples.
     513      * Keep in mind that the extension will only be applied to new
     514      * instances, not existing ones (including all instances created through {@link singleton()}).
     515      *
     516      * @param string $class Class that should be decorated - has to be a subclass of {@link Object}
     517      * @param string $extension Subclass of {@link Extension} with optional parameters
     518      *  as a string, e.g. "Versioned" or "Translatable('Param')"
     519      */
     520     public static function add_extension($class, $extension) {
     521         if(!preg_match('/^([^(]*)/', $extension, $matches)) {
     522             return false;
     523         }
     524         $extensionClass = $matches[1];
     525         if(!class_exists($extensionClass)) {
     526             user_error(sprintf('Object::add_extension() - Can\'t find extension class for "%s"', $extensionClass), E_USER_ERROR);
     527         }
     528
     529         if(!ClassInfo::is_subclass_of($extensionClass, 'Extension')) {
     530             user_error(sprintf('Object::add_extension() - Extension "%s" is not a subclass of Extension', $extensionClass), E_USER_ERROR);
     531         }
     532
     533         // unset some caches
     534         self::$cached_statics[$class]['extensions'] = null;
     535         $subclasses = ClassInfo::subclassesFor($class);
     536         $subclasses[] = $class;
     537         if($subclasses) foreach($subclasses as $subclass) {
     538             unset(self::$classes_constructed[$subclass]);
     539             unset(self::$extra_methods[$subclass]);
     540         }
     541
     542         // merge with existing static vars
     543         $extensions = self::uninherited_static($class, 'extensions');
     544
     545         // We use unshift rather than push so that module extensions are added before built-in ones.
     546         // in particular, this ensures that the Versioned rewriting is done last.
     547         if($extensions) array_unshift($extensions, $extension);
     548         else $extensions = array($extension);
     549
     550         self::set_static($class, 'extensions', $extensions);
     551
     552         // load statics now for DataObject classes
     553         if(ClassInfo::is_subclass_of($class, 'DataObject')) {
     554             DataObjectDecorator::load_extra_statics($class, $extension);
     555         }
     556     }
     557
     558     /**
     559      * Prepare static variables before processing a {@link get_static} or {@link set_static}
     560      * call.
     561      */
     562     private static function prepare_statics($class) {
     563         // _cache_statics_prepared setting must come first to prevent infinite loops when we call
     564         // get_static below
     565         self::$_cache_statics_prepared[$class] = true;
     566
     567         // load statics now for DataObject classes
     568         if(is_subclass_of($class, 'DataObject')) {
     569             $extensions = Object::uninherited_static($class, 'extensions');
     570             if($extensions) foreach($extensions as $extension) {
     571                 DataObjectDecorator::load_extra_statics($class, $extension);
     572             }
     573         }
     574     }
     575
     576
     577     /**
     578      * Remove an extension from a class.
     579      * Keep in mind that this won't revert any datamodel additions
     580      * of the extension at runtime, unless its used before the
     581      * schema building kicks in (in your _config.php).
     582      * Doesn't remove the extension from any {@link Object}
     583      * instances which are already created, but will have an
     584      * effect on new extensions.
     585      * Clears any previously created singletons through {@link singleton()}
     586      * to avoid side-effects from stale extension information.
     587      *
     588      * @todo Add support for removing extensions with parameters
     589      *
     590      * @param string $class
     591      * @param string $extension Classname of an {@link Extension} subclass, without parameters
     592      */
     593     public static function remove_extension($class, $extension) {
     594         if(self::has_extension($class, $extension)) {
     595             self::set_static(
     596                 $class,
     597                 'extensions',
     598                 array_diff(self::uninherited_static($class, 'extensions'), array($extension))
     599             );
     600         }
     601
     602         // unset singletons to avoid side-effects
     603         global $_SINGLETONS;
     604         $_SINGLETONS = array();
     605
     606         // unset some caches
     607         self::$cached_statics[$class]['extensions'] = null;
     608         $subclasses = ClassInfo::subclassesFor($class);
     609         $subclasses[] = $class;
     610         if($subclasses) foreach($subclasses as $subclass) {
     611             unset(self::$classes_constructed[$subclass]);
     612             unset(self::$extra_methods[$subclass]);
     613         }
     614
     615     }
     616
     617     /**
     618      * @param string $class
     619      * @param bool $includeArgumentString Include the argument string in the return array,
     620      *  FALSE would return array("Versioned"), TRUE returns array("Versioned('Stage','Live')").
     621      * @return array Numeric array of either {@link DataObjectDecorator} classnames,
     622      *  or eval'ed classname strings with constructor arguments.
     623      */
     624     function get_extensions($class, $includeArgumentString = false) {
     625         $extensions = self::get_static($class, 'extensions');
     626         if($includeArgumentString) {
     627             return $extensions;
     628         } else {
     629             $extensionClassnames = array();
     630             if($extensions) foreach($extensions as $extension) {
     631                 $extensionClassnames[] = Extension::get_classname_without_arguments($extension);
     632             }
     633             return $extensionClassnames;
     634         }
     635     }
     636
     637     // -----------------------------------------------------------------------------------------------------------------
     638
     639     public function __construct() {
     640         $this->class = get_class($this);
     641
     642         // Don't bother checking some classes that should never be extended
     643         static $notExtendable = array('Object', 'ViewableData', 'RequestHandler');
     644
     645         if($extensionClasses = ClassInfo::ancestry($this->class)) foreach($extensionClasses as $class) {
     646             if(in_array($class, $notExtendable)) continue;
     647
     648             if($extensions = self::uninherited_static($class, 'extensions')) {
     649                 foreach($extensions as $extension) {
     650                     $instance = self::create_from_string($extension);
     651                     $instance->setOwner(null, $class);
     652                     $this->extension_instances[$instance->class] = $instance;
     653                 }
     654             }
     655         }
     656
     657         if(!isset(self::$classes_constructed[$this->class])) {
     658             $this->defineMethods();
     659             self::$classes_constructed[$this->class] = true;
     660         }
     661     }
     662
     663     function __wakeup() {
     664         if(!isset(self::$classes_constructed[$this->class])) {
     665             $this->defineMethods();
     666             self::$classes_constructed[$this->class] = true;
     667         }
     668     }
     669
     670     /**
     671      * Attemps to locate and call a method dynamically added to a class at runtime if a default cannot be located
     672      *
     673      * You can add extra methods to a class using {@link Extensions}, {@link Object::createMethod()} or
     674      * {@link Object::addWrapperMethod()}
     675      *
     676      * @param string $method
     677      * @param array $arguments
     678      * @return mixed
     679      */
     680     public function __call($method, $arguments) {
     681         // If the method cache was cleared by an an Object::add_extension() / Object::remove_extension()
     682         // call, then we should rebuild it.
     683         if(empty(self::$cached_statics[get_class($this)])) {
     684             $this->defineMethods();
     685         }
     686
     687         $method = strtolower($method);
     688
     689         if(isset(self::$extra_methods[$this->class][$method])) {
     690             $config = self::$extra_methods[$this->class][$method];
     691
     692             switch(true) {
     693                 case isset($config['property']) :
     694                     $obj = $config['index'] !== null ?
     695                         $this->{$config['property']}[$config['index']] :
     696                         $this->{$config['property']};
     697
     698                     if($obj) {
     699                         if(!empty($config['callSetOwnerFirst'])) $obj->setOwner($this);
     700                         $retVal = call_user_func_array(array($obj, $method), $arguments);
     701                         if(!empty($config['callSetOwnerFirst'])) $obj->clearOwner();
     702                         return $retVal;
     703                     }
     704
     705                     if($this->destroyed) {
     706                         throw new Exception (
     707                             "Object->__call(): attempt to call $method on a destroyed $this->class object"
     708                         );
     709                     } else {
     710                         throw new Exception (
     711                             "Object->__call(): $this->class cannot pass control to $config[property]($config[index])." .
     712                             ' Perhaps this object was mistakenly destroyed?'
     713                         );
     714                     }
     715
     716                 case isset($config['wrap']) :
     717                     array_unshift($arguments, $config['method']);
     718                     return call_user_func_array(array($this, $config['wrap']), $arguments);
     719
     720                 case isset($config['function']) :
     721                     return $config['function']($this, $arguments);
     722
     723                 default :
     724                     throw new Exception (
     725                         "Object->__call(): extra method $method is invalid on $this->class:" . var_export($config, true)
     726                     );
     727             }
     728         } else {
     729             // Please do not change the exception code number below.
     730
     731             throw new Exception("Object->__call(): the method '$method' does not exist on '$this->class'", 2175);
     732         }
     733     }
     734
     735     // -----------------------------------------------------------------------------------------------------------------
     736
     737     /**
     738      * Return TRUE if a method exists on this object
     739      *
     740      * This should be used rather than PHP's inbuild method_exists() as it takes into account methods added via
     741      * extensions
     742      *
     743      * @param string $method
     744      * @return bool
     745      */
     746     public function hasMethod($method) {
     747         return method_exists($this, $method) || isset(self::$extra_methods[$this->class][strtolower($method)]);
     748     }
     749
     750     /**
     751      * Return the names of all the methods available on this object
     752      *
     753      * @param bool $custom include methods added dynamically at runtime
     754      * @return array
     755      */
     756     public function allMethodNames($custom = false) {
     757         if(!isset(self::$built_in_methods[$this->class])) {
     758             self::$built_in_methods[$this->class] = array_map('strtolower', get_class_methods($this));
     759         }
     760
     761         if($custom && isset(self::$extra_methods[$this->class])) {
     762             return array_merge(self::$built_in_methods[$this->class], array_keys(self::$extra_methods[$this->class]));
     763         } else {
     764             return self::$built_in_methods[$this->class];
     765         }
     766     }
     767
     768     /**
     769      * Adds any methods from {@link Extension} instances attached to this object.
     770      * All these methods can then be called directly on the instance (transparently
     771      * mapped through {@link __call()}), or called explicitly through {@link extend()}.
     772      *
     773      * @uses addMethodsFrom()
     774      */
     775     protected function defineMethods() {
     776         if($this->extension_instances) foreach(array_keys($this->extension_instances) as $key) {
     777             $this->addMethodsFrom('extension_instances', $key);
     778         }
     779
     780         if(isset($_REQUEST['debugmethods']) && isset(self::$built_in_methods[$this->class])) {
     781             Debug::require_developer_login();
     782
     783             echo '<h2>Methods defined on ' . $this->class . '</h2><ul>';
     784             foreach(self::$built_in_methods[$this->class] as $method) {
     785                 echo "<li>$method</li>";
     786             }
     787             echo '</ul>';
     788         }
     789     }
     790
     791     /**
     792      * Add all the methods from an object property (which is an {@link Extension}) to this object.
     793      *
     794      * @param string $property the property name
     795      * @param string|int $index an index to use if the property is an array
     796      */
     797     protected function addMethodsFrom($property, $index = null) {
     798         $extension = ($index !== null) ? $this->{$property}[$index] : $this->$property;
     799
     800         if(!$extension) {
     801             throw new InvalidArgumentException (
     802                 "Object->addMethodsFrom(): could not add methods from {$this->class}->{$property}[$index]"
     803             );
     804         }
     805
     806         if(method_exists($extension, 'allMethodNames')) {
     807             $methods = $extension->allMethodNames(true);
     808
     809         } else {
     810             if(!isset(self::$built_in_methods[$extension->class])) {
     811                 self::$built_in_methods[$extension->class] = array_map('strtolower', get_class_methods($extension));
     812             }
     813             $methods = self::$built_in_methods[$extension->class];
     814         }
     815
     816         if($methods) {
     817             $methodInfo = array(
     818                 'property' => $property,
     819                 'index'    => $index,
     820                 'callSetOwnerFirst' => $extension instanceof Extension,
     821             );
     822
     823             $newMethods = array_fill_keys($methods, $methodInfo);
     824
     825             if(isset(self::$extra_methods[$this->class])) {
     826                 self::$extra_methods[$this->class] =
     827                     array_merge(self::$extra_methods[$this->class], $newMethods);
     828             } else {
     829                 self::$extra_methods[$this->class] = $newMethods;
     830             }
     831         }
     832     }
     833
     834     /**
     835      * Add a wrapper method - a method which points to another method with a different name. For example, Thumbnail(x)
     836      * can be wrapped to generateThumbnail(x)
     837      *
     838      * @param string $method the method name to wrap
     839      * @param string $wrap the method name to wrap to
     840      */
     841     protected function addWrapperMethod($method, $wrap) {
     842         self::$extra_methods[$this->class][strtolower($method)] = array (
     843             'wrap'   => $wrap,
     844             'method' => $method
     845         );
     846     }
     847
     848     /**
     849      * Add an extra method using raw PHP code passed as a string
     850      *
     851      * @param string $method the method name
     852      * @param string $code the PHP code - arguments will be in an array called $args, while you can access this object
     853      *        by using $obj. Note that you cannot call protected methods, as the method is actually an external function
     854      */
     855     protected function createMethod($method, $code) {
     856         self::$extra_methods[$this->class][strtolower($method)] = array (
     857             'function' => create_function('$obj, $args', $code)
     858         );
     859     }
     860
     861     // -----------------------------------------------------------------------------------------------------------------
     862
     863     /**
     864      * @see Object::get_static()
     865      */
     866     public function stat($name, $uncached = false) {
     867         return self::get_static(($this->class ? $this->class : get_class($this)), $name, $uncached);
     868     }
     869
     870     /**
     871      * @see Object::set_static()
     872      */
     873     public function set_stat($name, $value) {
     874         self::set_static(($this->class ? $this->class : get_class($this)), $name, $value);
     875     }
     876
     877     /**
     878      * @see Object::uninherited_static()
     879      */
     880     public function uninherited($name) {
     881         return self::uninherited_static(($this->class ? $this->class : get_class($this)), $name);
     882     }
     883
     884     /**
     885      * @deprecated
     886      */
     887     public function set_uninherited() {
     888         user_error (
     889             'Object->set_uninherited() is deprecated, please use a custom static on your object', E_USER_WARNING
     890         );
     891     }
     892
     893     // -----------------------------------------------------------------------------------------------------------------
     894
     895     /**
     896      * Return true if this object "exists" i.e. has a sensible value
     897      *
     898      * This method should be overriden in subclasses to provide more context about the classes state. For example, a
     899      * {@link DataObject} class could return false when it is deleted from the database
     900      *
     901      * @return bool
     902      */
     903     public function exists() {
     904         return true;
     905     }
     906
     907     /**
     908      * @return string this classes parent class
     909      */
     910     public function parentClass() {
     911         return get_parent_class($this);
     912     }
     913
     914     /**
     915      * Check if this class is an instance of a specific class, or has that class as one of its parents
     916      *
     917      * @param string $class
     918      * @return bool
     919      */
     920     public function is_a($class) {
     921         return $this instanceof $class;
     922     }
     923
     924     /**
     925      * @return string the class name
     926      */
     927     public function __toString() {
     928         return $this->class;
     929     }
     930
     931     // -----------------------------------------------------------------------------------------------------------------
     932
     933     /**
     934      * Calls a method if available on both this object and all applied {@link Extensions}, and then attempts to merge
     935      * all results into an array
     936      *
     937      * @param string $method the method name to call
     938      * @param mixed $argument a single argument to pass
     939      * @return mixed
     940      * @todo integrate inheritance rules
     941      */
     942     public function invokeWithExtensions($method, $argument = null) {
     943         $result = method_exists($this, $method) ? array($this->$method($argument)) : array();
     944         $extras = $this->extend($method, $argument);
     945
     946         return $extras ? array_merge($result, $extras) : $result;
     947     }
     948
     949     /**
     950      * Run the given function on all of this object's extensions. Note that this method originally returned void, so if
     951      * you wanted to return results, you're hosed
     952      *
     953      * Currently returns an array, with an index resulting every time the function is called. Only adds returns if
     954      * they're not NULL, to avoid bogus results from methods just defined on the parent decorator. This is important for
     955      * permission-checks through extend, as they use min() to determine if any of the returns is FALSE. As min() doesn't
     956      * do type checking, an included NULL return would fail the permission checks.
     957      *
     958      * The extension methods are defined during {@link __construct()} in {@link defineMethods()}.
     959      *
     960      * @param string $method the name of the method to call on each extension
     961      * @param mixed $a1,... up to 7 arguments to be passed to the method
     962      * @return array
     963      */
     964     public function extend($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
     965         $values = array();
     966
     967         if($this->extension_instances) foreach($this->extension_instances as $instance) {
     968             if(method_exists($instance, $method)) {
     969                 $instance->setOwner($this);
     970                 $value = $instance->$method($a1, $a2, $a3, $a4, $a5, $a6, $a7);
     971                 if($value !== null) $values[] = $value;
     972                 $instance->clearOwner();
     973             }
     974         }
     975
     976         return $values;
     977     }
     978
     979     /**
     980      * Get an extension instance attached to this object by name.
     981      *
     982      * @uses hasExtension()
     983      *
     984      * @param string $extension
     985      * @return Extension
     986      */
     987     public function getExtensionInstance($extension) {
     988         if($this->hasExtension($extension)) return $this->extension_instances[$extension];
     989     }
     990
     991     /**
     992      * Returns TRUE if this object instance has a specific extension applied
     993      * in {@link $extension_instances}. Extension instances are initialized
     994      * at constructor time, meaning if you use {@link add_extension()}
     995      * afterwards, the added extension will just be added to new instances
     996      * of the decorated class. Use the static method {@link has_extension()}
     997      * to check if a class (not an instance) has a specific extension.
     998      * Caution: Don't use singleton(<class>)->hasExtension() as it will
     999      * give you inconsistent results based on when the singleton was first
    1000      * accessed.
    1001      *
    1002      * @param string $extension Classname of an {@link Extension} subclass without parameters
    1003      * @return bool
    1004      */
    1005     public function hasExtension($extension) {
    1006         return isset($this->extension_instances[$extension]);
    1007     }
    1008
    1009     /**
    1010      * Get all extension instances for this specific object instance.
    1011      * See {@link get_extensions()} to get all applied extension classes
    1012      * for this class (not the instance).
    1013      *
    1014      * @return array Map of {@link DataObjectDecorator} instances, keyed by classname.
    1015      */
    1016     public function getExtensionInstances() {
    1017         return $this->extension_instances;
    1018     }
    1019
    1020     // -----------------------------------------------------------------------------------------------------------------
    1021
    1022     /**
    1023      * Cache the results of an instance method in this object to a file, or if it is already cache return the cached
    1024      * results
    1025      *
    1026      * @param string $method the method name to cache
    1027      * @param int $lifetime the cache lifetime in seconds
    1028      * @param string $ID custom cache ID to use
    1029      * @param array $arguments an optional array of arguments
    1030      * @return mixed the cached data
    1031      */
    1032     public function cacheToFile($method, $lifetime = 3600, $ID = false, $arguments = array()) {
    1033         if(!$this->hasMethod($method)) {
    1034             throw new InvalidArgumentException("Object->cacheToFile(): the method $method does not exist to cache");
    1035         }
    1036
    1037         $cacheName = $this->class . '_' . $method;
    1038
    1039         if(!is_array($arguments)) $arguments = array($arguments);
    1040
    1041         if($ID) $cacheName .= '_' . $ID;
    1042         if(count($arguments)) $cacheName .= '_' . implode('_', $arguments);
    1043
    1044         if($data = $this->loadCache($cacheName, $lifetime)) {
    1045             return $data;
    1046         }
    1047
    1048         $data = call_user_func_array(array($this, $method), $arguments);
    1049         $this->saveCache($cacheName, $data);
    1050
    1051         return $data;
    1052     }
    1053
    1054     /**
    1055      * Clears the cache for the given cacheToFile call
    1056      */
    1057     public function clearCache($method, $ID = false, $arguments = array()) {
    1058         $cacheName = $this->class . '_' . $method;
    1059         if(!is_array($arguments)) $arguments = array($arguments);
    1060         if($ID) $cacheName .= '_' . $ID;
    1061         if(count($arguments)) $cacheName .= '_' . implode('_', $arguments);
    1062
    1063         $file = TEMP_FOLDER . '/' . $this->sanitiseCachename($cacheName);
    1064         if(file_exists($file)) unlink($file);
    1065     }
    1066
    1067     /**
    1068      * @deprecated
    1069      */
    1070     public function cacheToFileWithArgs($callback, $arguments = array(), $lifetime = 3600, $ID = false) {
    1071         user_error (
    1072             'Object->cacheToFileWithArgs() is deprecated, please use Object->cacheToFile() with the $arguments param',
    1073             E_USER_NOTICE
    1074         );
    1075
    1076         return $this->cacheToFile($callback, $lifetime, $ID, $arguments);
    1077     }
    1078
    1079     /**
    1080      * Loads a cache from the filesystem if a valid on is present and within the specified lifetime
    1081      *
    1082      * @param string $cache the cache name
    1083      * @param int $lifetime the lifetime (in seconds) of the cache before it is invalid
    1084      * @return mixed
    1085      */
    1086     protected function loadCache($cache, $lifetime = 3600) {
    1087         $path = TEMP_FOLDER . '/' . $this->sanitiseCachename($cache);
    1088
    1089         if(!isset($_REQUEST['flush']) && file_exists($path) && (filemtime($path) + $lifetime) > time()) {
    1090             return unserialize(file_get_contents($path));
    1091         }
    1092
    1093         return false;
    1094     }
    1095
    1096     /**
    1097      * Save a piece of cached data to the file system
    1098      *
    1099      * @param string $cache the cache name
    1100      * @param mixed $data data to save (must be serializable)
    1101      */
    1102     protected function saveCache($cache, $data) {
    1103         file_put_contents(TEMP_FOLDER . '/' . $this->sanitiseCachename($cache), serialize($data));
    1104     }
    1105
    1106     /**
    1107      * Strip a file name of special characters so it is suitable for use as a cache file name
    1108      *
    1109      * @param string $name
    1110      * @return string the name with all special cahracters replaced with underscores
    1111      */
    1112     protected function sanitiseCachename($name) {
    1113         return str_replace(array('~', '.', '/', '!', ' ', "\n", "\r", "\t", '\\', ':', '"', '\'', ';'), '_', $name);
    1114     }
    1115
    1116     /**
    1117      * @deprecated 2.4 Use getExtensionInstance
    1118      */
    1119     public function extInstance($extension) {
    1120         user_error('Object::extInstance() is deprecated. Please use Object::getExtensionInstance() instead.', E_USER_NOTICE);
    1121         return $this->getExtensionInstance($extension);
    1122     }
    1123
    1124 }
    1125

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -