mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
FIX Respect explicit casting before casting arrays (#11271)
This commit is contained in:
parent
e7d05aa524
commit
b53cda8de0
@ -521,13 +521,13 @@ class Form extends ViewableData implements HasRequestHandler
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function castingHelper($field)
|
||||
public function castingHelper($field, bool $useFallback = true)
|
||||
{
|
||||
// Override casting for field message
|
||||
if (strcasecmp($field ?? '', 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
|
||||
return $helper;
|
||||
}
|
||||
return parent::castingHelper($field);
|
||||
return parent::castingHelper($field, $useFallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -790,13 +790,13 @@ class FormField extends RequestHandler
|
||||
return $form->getSecurityToken()->isEnabled();
|
||||
}
|
||||
|
||||
public function castingHelper($field)
|
||||
public function castingHelper($field, bool $useFallback = true)
|
||||
{
|
||||
// Override casting for field message
|
||||
if (strcasecmp($field ?? '', 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
|
||||
return $helper;
|
||||
}
|
||||
return parent::castingHelper($field);
|
||||
return parent::castingHelper($field, $useFallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,7 +56,7 @@ class ReadonlyField extends FormField
|
||||
return 'readonly';
|
||||
}
|
||||
|
||||
public function castingHelper($field)
|
||||
public function castingHelper($field, bool $useFallback = true)
|
||||
{
|
||||
// Get dynamic cast for 'Value' field
|
||||
if (strcasecmp($field ?? '', 'Value') === 0) {
|
||||
@ -64,7 +64,7 @@ class ReadonlyField extends FormField
|
||||
}
|
||||
|
||||
// Fall back to default casting
|
||||
return parent::castingHelper($field);
|
||||
return parent::castingHelper($field, $useFallback);
|
||||
}
|
||||
|
||||
public function getSchemaStateDefaults()
|
||||
|
@ -3015,7 +3015,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function castingHelper($field)
|
||||
public function castingHelper($field, bool $useFallback = true)
|
||||
{
|
||||
$fieldSpec = static::getSchema()->fieldSpec(static::class, $field);
|
||||
if ($fieldSpec) {
|
||||
@ -3033,7 +3033,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
return parent::castingHelper($field);
|
||||
return parent::castingHelper($field, $useFallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -319,14 +319,14 @@ abstract class DBComposite extends DBField
|
||||
return $fieldObject;
|
||||
}
|
||||
|
||||
public function castingHelper($field)
|
||||
public function castingHelper($field, bool $useFallback = true)
|
||||
{
|
||||
$fields = $this->compositeDatabaseFields();
|
||||
if (isset($fields[$field])) {
|
||||
return $fields[$field];
|
||||
}
|
||||
|
||||
return parent::castingHelper($field);
|
||||
return parent::castingHelper($field, $useFallback);
|
||||
}
|
||||
|
||||
public function getIndexSpecs()
|
||||
|
@ -385,10 +385,11 @@ class ViewableData implements IteratorAggregate
|
||||
* for a field on this object. This helper will be a subclass of DBField.
|
||||
*
|
||||
* @param string $field
|
||||
* @return string Casting helper As a constructor pattern, and may include arguments.
|
||||
* @param bool $useFallback If true, fall back on the default casting helper if there isn't an explicit one.
|
||||
* @return string|null Casting helper As a constructor pattern, and may include arguments.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function castingHelper($field)
|
||||
public function castingHelper($field, bool $useFallback = true)
|
||||
{
|
||||
// Get casting if it has been configured.
|
||||
// DB fields and PHP methods are all case insensitive so we normalise casing before checking.
|
||||
@ -399,20 +400,41 @@ class ViewableData implements IteratorAggregate
|
||||
}
|
||||
|
||||
// If no specific cast is declared, fall back to failover.
|
||||
// Note that if there is a failover, the default_cast will always
|
||||
// be drawn from this object instead of the top level object.
|
||||
$failover = $this->getFailover();
|
||||
if ($failover) {
|
||||
$cast = $failover->castingHelper($field);
|
||||
$cast = $failover->castingHelper($field, $useFallback);
|
||||
if ($cast) {
|
||||
return $cast;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default_cast
|
||||
if ($useFallback) {
|
||||
return $this->defaultCastingHelper($field);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default "casting helper" for use when no explicit casting helper is defined.
|
||||
* This helper will be a subclass of DBField. See castingHelper()
|
||||
*/
|
||||
protected function defaultCastingHelper(string $field): string
|
||||
{
|
||||
// If there is a failover, the default_cast will always
|
||||
// be drawn from this object instead of the top level object.
|
||||
$failover = $this->getFailover();
|
||||
if ($failover) {
|
||||
$cast = $failover->defaultCastingHelper($field);
|
||||
if ($cast) {
|
||||
return $cast;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to raw default_cast
|
||||
$default = $this->config()->get('default_cast');
|
||||
if (empty($default)) {
|
||||
throw new Exception("No default_cast");
|
||||
throw new Exception('No default_cast');
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
@ -559,15 +581,25 @@ class ViewableData implements IteratorAggregate
|
||||
$value = $this->$fieldName;
|
||||
}
|
||||
|
||||
// Try to cast object if we have an explicit cast set
|
||||
if (!is_object($value)) {
|
||||
$castingHelper = $this->castingHelper($fieldName, false);
|
||||
if ($castingHelper !== null) {
|
||||
$valueObject = Injector::inst()->create($castingHelper, $fieldName);
|
||||
$valueObject->setValue($value, $this);
|
||||
$value = $valueObject;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap list arrays in ViewableData so templates can handle them
|
||||
if (is_array($value) && array_is_list($value)) {
|
||||
$value = ArrayList::create($value);
|
||||
}
|
||||
|
||||
// Cast object
|
||||
// Fallback on default casting
|
||||
if (!is_object($value)) {
|
||||
// Force cast
|
||||
$castingHelper = $this->castingHelper($fieldName);
|
||||
$castingHelper = $this->defaultCastingHelper($fieldName);
|
||||
$valueObject = Injector::inst()->create($castingHelper, $fieldName);
|
||||
$valueObject->setValue($value, $this);
|
||||
$value = $valueObject;
|
||||
|
@ -6,6 +6,7 @@ use ReflectionMethod;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\FieldType\DBText;
|
||||
use SilverStripe\View\ArrayData;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\View\Tests\ViewableDataTest\ViewableDataTestExtension;
|
||||
@ -59,6 +60,9 @@ class ViewableDataTest extends SapphireTest
|
||||
|
||||
$this->assertInstanceOf(ViewableDataTest\RequiresCasting::class, $caster->obj('alwaysCasted'));
|
||||
$this->assertInstanceOf(ViewableDataTest\Caster::class, $caster->obj('noCastingInformation'));
|
||||
|
||||
$this->assertInstanceOf(DBText::class, $caster->obj('arrayOne'));
|
||||
$this->assertInstanceOf(ArrayList::class, $caster->obj('arrayTwo'));
|
||||
}
|
||||
|
||||
public function testFailoverRequiresCasting()
|
||||
|
@ -7,13 +7,13 @@ use SilverStripe\View\ViewableData;
|
||||
|
||||
class Castable extends ViewableData implements TestOnly
|
||||
{
|
||||
|
||||
private static $default_cast = Caster::class;
|
||||
|
||||
private static $casting = [
|
||||
'alwaysCasted' => RequiresCasting::class,
|
||||
'castedUnsafeXML' => UnescapedCaster::class,
|
||||
'test' => 'Text',
|
||||
'arrayOne' => 'Text',
|
||||
];
|
||||
|
||||
public $test = 'test';
|
||||
@ -25,6 +25,16 @@ class Castable extends ViewableData implements TestOnly
|
||||
return 'alwaysCasted';
|
||||
}
|
||||
|
||||
public function arrayOne()
|
||||
{
|
||||
return ['value1', 'value2'];
|
||||
}
|
||||
|
||||
public function arrayTwo()
|
||||
{
|
||||
return ['value1', 'value2'];
|
||||
}
|
||||
|
||||
public function noCastingInformation()
|
||||
{
|
||||
return 'noCastingInformation';
|
||||
|
Loading…
Reference in New Issue
Block a user