Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • Aggregate
  • Aggregate_Relationship
  • AssetAdminQuotaExtension
  • AttachedFilesExtension
  • BookingWidget
  • ClassInfo
  • ControllerRedirectExtension
  • CSSContentParser
  • DisableJSValidation
  • Extension
  • HtmlEditorQuotaExtension
  • ManifestBuilder
  • MobileExtension
  • Object
  • PaymentMethodAutoHide
  • ProductSearchFormExtension
  • SS_Cache
  • TokenisedRegularExpression
  • ValidationResult
  • WebylonSiteSearchExtension
  • YamlFixture

Functions

  • __autoload
  • _t
  • array_fill_keys
  • getClassFile
  • getSysTempDir
  • getTempFolder
  • increase_memory_limit_to
  • increase_time_limit_to
  • project
  • singleton
  • stripslashes_recursively
  • translate_memstring
   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. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0