mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API CHANGE: Replaced eval based creation of extension and field objects with Object::create_from_string().
API CHANGE: Introduced new function Object::create_from_string() to instantiate an object from a string like 'Int(50)' (from r101093) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@111585 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
deb88d1143
commit
cc355a4f18
@ -285,7 +285,7 @@ class RSSFeed_Entry extends ViewableData {
|
|||||||
*/
|
*/
|
||||||
function rssField($fieldName, $defaultClass = 'Varchar') {
|
function rssField($fieldName, $defaultClass = 'Varchar') {
|
||||||
if($fieldName) {
|
if($fieldName) {
|
||||||
if($this->failover->castingHelperPair($fieldName)) {
|
if($this->failover->castingHelper($fieldName)) {
|
||||||
$value = $this->failover->$fieldName;
|
$value = $this->failover->$fieldName;
|
||||||
$obj = $this->failover->obj($fieldName);
|
$obj = $this->failover->obj($fieldName);
|
||||||
$obj->setValue($value);
|
$obj->setValue($value);
|
||||||
|
101
core/Object.php
101
core/Object.php
@ -96,6 +96,100 @@ abstract class Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static $_cache_inst_args = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an object from a string representation. It treats it as a PHP constructor without the
|
||||||
|
* 'new' keyword. It also manages to construct the object without the use of eval().
|
||||||
|
*
|
||||||
|
* Construction itself is done with Object::create(), so that Object::useCustomClass() calls
|
||||||
|
* are respected.
|
||||||
|
*
|
||||||
|
* `Object::create_from_string("Versioned('Stage','Live')")` will return the result of
|
||||||
|
* `Object::create('Versioned', 'Stage', 'Live);`
|
||||||
|
*
|
||||||
|
* It is designed for simple, clonable objects. The first time this method is called for a given
|
||||||
|
* string it is cached, and clones of that object are returned.
|
||||||
|
*
|
||||||
|
* If you pass the $firstArg argument, this will be prepended to the constructor arguments. It's
|
||||||
|
* impossible to pass null as the firstArg argument.
|
||||||
|
*
|
||||||
|
* `Object::create_from_string("Varchar(50)", "MyField")` will return the result of
|
||||||
|
* `Object::create('Vachar', 'MyField', '50');`
|
||||||
|
*
|
||||||
|
* Arguments are always strings, although this is a quirk of the current implementation rather
|
||||||
|
* than something that can be relied upon.
|
||||||
|
*/
|
||||||
|
static function create_from_string($classSpec, $firstArg = null) {
|
||||||
|
if(!isset(self::$_cache_inst_args[$classSpec.$firstArg])) {
|
||||||
|
// an $extension value can contain parameters as a string,
|
||||||
|
// e.g. "Versioned('Stage','Live')"
|
||||||
|
if(strpos($classSpec,'(') === false) {
|
||||||
|
if($firstArg === null) self::$_cache_inst_args[$classSpec.$firstArg] = Object::create($classSpec);
|
||||||
|
else self::$_cache_inst_args[$classSpec.$firstArg] = Object::create($classSpec, $firstArg);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
list($class, $args) = self::parse_class_spec($classSpec);
|
||||||
|
|
||||||
|
if($firstArg !== null) array_unshift($args, $firstArg);
|
||||||
|
array_unshift($args, $class);
|
||||||
|
|
||||||
|
self::$_cache_inst_args[$classSpec.$firstArg] = call_user_func_array('Object::create', $args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone self::$_cache_inst_args[$classSpec.$firstArg];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a class-spec, such as "Versioned('Stage','Live')", as passed to create_from_string().
|
||||||
|
* Returns a 2-elemnent array, with classname and arguments
|
||||||
|
*/
|
||||||
|
static function parse_class_spec($classSpec) {
|
||||||
|
$tokens = token_get_all("<?php $classSpec");
|
||||||
|
$class = null;
|
||||||
|
$args = array();
|
||||||
|
$passedBracket = false;
|
||||||
|
foreach($tokens as $token) {
|
||||||
|
$tName = is_array($token) ? $token[0] : $token;
|
||||||
|
// Get the class naem
|
||||||
|
if($class == null && is_array($token) && $token[0] == T_STRING) {
|
||||||
|
$class = $token[1];
|
||||||
|
// Get arguments
|
||||||
|
} else if(is_array($token)) {
|
||||||
|
switch($token[0]) {
|
||||||
|
case T_CONSTANT_ENCAPSED_STRING:
|
||||||
|
$argString = $token[1];
|
||||||
|
switch($argString[0]) {
|
||||||
|
case '"': $argString = stripcslashes(substr($argString,1,-1)); break;
|
||||||
|
case "'": $argString = str_replace(array("\\\\", "\\'"),array("\\", "'"), substr($argString,1,-1)); break;
|
||||||
|
default: throw new Exception("Bad T_CONSTANT_ENCAPSED_STRING arg $argString");
|
||||||
|
}
|
||||||
|
$args[] = $argString;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_DNUMBER:
|
||||||
|
$args[] = (double)$token[1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_LNUMBER:
|
||||||
|
$args[] = (int)$token[1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_STRING:
|
||||||
|
switch($token[1]) {
|
||||||
|
case 'true': $args[] = true; break;
|
||||||
|
case 'false': $args[] = false; break;
|
||||||
|
default: throw new Exception("Bad T_STRING arg '{$token[1]}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($class, $args);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to {@link Object::create()}, except that classes are only overloaded if you set the $strong parameter to
|
* Similar to {@link Object::create()}, except that classes are only overloaded if you set the $strong parameter to
|
||||||
* TRUE when using {@link Object::useCustomClass()}
|
* TRUE when using {@link Object::useCustomClass()}
|
||||||
@ -535,11 +629,7 @@ abstract class Object {
|
|||||||
|
|
||||||
if($extensions = self::uninherited_static($class, 'extensions')) {
|
if($extensions = self::uninherited_static($class, 'extensions')) {
|
||||||
foreach($extensions as $extension) {
|
foreach($extensions as $extension) {
|
||||||
// an $extension value can contain parameters as a string,
|
$instance = self::create_from_string($extension);
|
||||||
// e.g. "Versioned('Stage','Live')"
|
|
||||||
if(strpos($extension,'(') === false) $instance = new $extension();
|
|
||||||
else $instance = eval("return new $extension;");
|
|
||||||
|
|
||||||
$instance->setOwner(null, $class);
|
$instance->setOwner(null, $class);
|
||||||
$this->extension_instances[$instance->class] = $instance;
|
$this->extension_instances[$instance->class] = $instance;
|
||||||
}
|
}
|
||||||
@ -552,7 +642,6 @@ abstract class Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attemps to locate and call a method dynamically added to a class at runtime if a default cannot be located
|
* Attemps to locate and call a method dynamically added to a class at runtime if a default cannot be located
|
||||||
*
|
*
|
||||||
|
@ -74,11 +74,7 @@ class ViewableData extends Object implements IteratorAggregate {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function castingObjectCreator($fieldSchema) {
|
public static function castingObjectCreator($fieldSchema) {
|
||||||
if(strpos($fieldSchema, '(') === false) {
|
user_error("Deprecated in a breaking way, use Object::create_from_string()", E_USER_WARNING);
|
||||||
return "return Object::create('{$fieldSchema}', \$fieldName);";
|
|
||||||
} else {
|
|
||||||
return 'return Object::create(' . preg_replace('/^([^(]+)\(/', '\'$1\', $fieldName, ', $fieldSchema) . ';';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,21 +85,7 @@ class ViewableData extends Object implements IteratorAggregate {
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function castingObjectCreatorPair($fieldSchema) {
|
public static function castingObjectCreatorPair($fieldSchema) {
|
||||||
if(strpos($fieldSchema, '(') === false) {
|
user_error("Deprecated in a breaking way, use Object::create_from_string()", E_USER_WARNING);
|
||||||
return array (
|
|
||||||
'className' => $fieldSchema,
|
|
||||||
'castingHelper' => self::castingObjectCreator($fieldSchema)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(preg_match('/^([^(]+)\(/', $fieldSchema, $parts)) {
|
|
||||||
return array (
|
|
||||||
'className' => $parts[1],
|
|
||||||
'castingHelper' => self::castingObjectCreator($fieldSchema)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidArgumentException("ViewableData::castingObjectCreatorPair(): bad field schema '$fieldSchema'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIELD GETTERS & SETTERS -----------------------------------------------------------------------------------------
|
// FIELD GETTERS & SETTERS -----------------------------------------------------------------------------------------
|
||||||
@ -251,15 +233,8 @@ class ViewableData extends Object implements IteratorAggregate {
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function castingHelperPair($field) {
|
public function castingHelperPair($field) {
|
||||||
if(!isset(self::$casting_cache[$this->class])) {
|
user_error("castingHelperPair() Deprecated, use castingHelper() instead", E_USER_NOTICE);
|
||||||
if($this->failover) {
|
return $this->castingHelper($field);
|
||||||
$this->failover->buildCastingCache(self::$casting_cache[$this->class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->buildCastingCache(self::$casting_cache[$this->class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isset(self::$casting_cache[$this->class][$field])) return self::$casting_cache[$this->class][$field];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -270,7 +245,12 @@ class ViewableData extends Object implements IteratorAggregate {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function castingHelper($field) {
|
public function castingHelper($field) {
|
||||||
if($pair = $this->castingHelperPair($field)) return $pair['castingHelper'];
|
if($this instanceof DataObject && ($fieldSpec = $this->db($field))) {
|
||||||
|
return $fieldSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
$specs = Object::combined_static(get_class($this), 'casting');
|
||||||
|
if(isset($specs[$field])) return $specs[$field];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -280,7 +260,12 @@ class ViewableData extends Object implements IteratorAggregate {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function castingClass($field) {
|
public function castingClass($field) {
|
||||||
if($pair = $this->castingHelperPair($field)) return $pair['className'];
|
$spec = $this->castingHelper($field);
|
||||||
|
if(!$spec) return null;
|
||||||
|
|
||||||
|
$bPos = strpos($spec,'(');
|
||||||
|
if($bPos === false) return $spec;
|
||||||
|
else return substr($spec, 0, $bPos-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -386,10 +371,10 @@ class ViewableData extends Object implements IteratorAggregate {
|
|||||||
|
|
||||||
if(!is_object($value) && ($this->castingClass($fieldName) || $forceReturnedObject)) {
|
if(!is_object($value) && ($this->castingClass($fieldName) || $forceReturnedObject)) {
|
||||||
if(!$castConstructor = $this->castingHelper($fieldName)) {
|
if(!$castConstructor = $this->castingHelper($fieldName)) {
|
||||||
$castConstructor = self::castingObjectCreator($this->stat('default_cast'));
|
$castConstructor = $this->stat('default_cast');
|
||||||
}
|
}
|
||||||
|
|
||||||
$valueObject = eval($castConstructor);
|
$valueObject = Object::create_from_string($castConstructor, $fieldName);
|
||||||
$valueObject->setValue($value, ($this->hasMethod('getAllFields') ? $this->getAllFields() : null));
|
$valueObject->setValue($value, ($this->hasMethod('getAllFields') ? $this->getAllFields() : null));
|
||||||
|
|
||||||
$value = $valueObject;
|
$value = $valueObject;
|
||||||
|
@ -2007,10 +2007,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
|
|
||||||
// Otherwise, we need to determine if this is a complex field
|
// Otherwise, we need to determine if this is a complex field
|
||||||
if(self::is_composite_field($this->class, $field)) {
|
if(self::is_composite_field($this->class, $field)) {
|
||||||
$helperPair = $this->castingHelperPair($field);
|
$helper = $this->castingHelper($field);
|
||||||
$constructor = $helperPair['castingHelper'];
|
$fieldObj = Object::create_from_string($helper, $field);
|
||||||
$fieldName = $field;
|
|
||||||
$fieldObj = eval($constructor);
|
|
||||||
|
|
||||||
// write value only if either the field value exists,
|
// write value only if either the field value exists,
|
||||||
// or a valid record has been loaded from the database
|
// or a valid record has been loaded from the database
|
||||||
@ -2156,7 +2154,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
}
|
}
|
||||||
$castingHelper = $this->castingHelper($fieldName);
|
$castingHelper = $this->castingHelper($fieldName);
|
||||||
if($castingHelper) {
|
if($castingHelper) {
|
||||||
$fieldObj = eval($castingHelper);
|
$fieldObj = Object::create_from_string($castingHelper, $fieldName);
|
||||||
$fieldObj->setValue($val);
|
$fieldObj->setValue($val);
|
||||||
$fieldObj->saveInto($this);
|
$fieldObj->saveInto($this);
|
||||||
} else {
|
} else {
|
||||||
@ -2409,9 +2407,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
return new PrimaryKey($fieldName, $this);
|
return new PrimaryKey($fieldName, $this);
|
||||||
|
|
||||||
// General casting information for items in $db or $casting
|
// General casting information for items in $db or $casting
|
||||||
} else if($helperPair = $this->castingHelperPair($fieldName)) {
|
} else if($helper = $this->castingHelper($fieldName)) {
|
||||||
$constructor = $helperPair['castingHelper'];
|
$obj = Object::create_from_string($helper, $fieldName);
|
||||||
$obj = eval($constructor);
|
|
||||||
$obj->setValue($this->$fieldName, $this->record, false);
|
$obj->setValue($this->$fieldName, $this->record, false);
|
||||||
return $obj;
|
return $obj;
|
||||||
|
|
||||||
@ -2445,8 +2442,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
$component = singleton($rel);
|
$component = singleton($rel);
|
||||||
} elseif ($rel = $component->many_many($relation)) {
|
} elseif ($rel = $component->many_many($relation)) {
|
||||||
$component = singleton($rel[1]);
|
$component = singleton($rel[1]);
|
||||||
} elseif($info = $this->castingHelperPair($relation)) {
|
} elseif($className = $this->castingClass($relation)) {
|
||||||
$component = singleton($info['className']);
|
$component = $className;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,7 +313,7 @@ abstract class SS_Database {
|
|||||||
$fieldSpec=substr($fieldSpec, 0, $pos);
|
$fieldSpec=substr($fieldSpec, 0, $pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fieldObj = eval(ViewableData::castingObjectCreator($fieldSpec));
|
$fieldObj = Object::create_from_string($fieldSpec, $fieldName);
|
||||||
$fieldObj->arrayValue=$arrayValue;
|
$fieldObj->arrayValue=$arrayValue;
|
||||||
|
|
||||||
$fieldObj->setTable($table);
|
$fieldObj->setTable($table);
|
||||||
|
@ -336,6 +336,20 @@ class ObjectTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testParseClassSpec() {
|
||||||
|
$this->assertEquals(
|
||||||
|
array('Versioned',array('Stage', 'Live')),
|
||||||
|
Object::parse_class_spec("Versioned('Stage','Live')")
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
array('Versioned',array('Stage,Live', 'Stage')),
|
||||||
|
Object::parse_class_spec("Versioned('Stage,Live','Stage')")
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
array('Versioned',array('Stage\'Stage,Live\'Live', 'Live')),
|
||||||
|
Object::parse_class_spec("Versioned('Stage\'Stage,Live\'Live','Live')")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**#@+
|
/**#@+
|
||||||
|
Loading…
Reference in New Issue
Block a user