API Strong typing for the view layer (#11351)

This commit is contained in:
Guy Sartorelli 2024-08-28 10:54:31 +12:00 committed by GitHub
parent e1428f27a2
commit ea93316d9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 692 additions and 1255 deletions

View File

@ -17,13 +17,6 @@ use BadMethodCallException;
*/ */
class RSSFeed_Entry extends ViewableData class RSSFeed_Entry extends ViewableData
{ {
/**
* The object that represents the item, it contains all the data.
*
* @var mixed
*/
protected $failover;
/** /**
* Name of the title field of feed entries * Name of the title field of feed entries
* *

View File

@ -514,7 +514,7 @@ class CompositeField extends FormField
return false; return false;
} }
public function debug() public function debug(): string
{ {
$class = static::class; $class = static::class;
$result = "$class ($this->name) <ul>"; $result = "$class ($this->name) <ul>";

View File

@ -521,7 +521,7 @@ class Form extends ViewableData implements HasRequestHandler
return $this; return $this;
} }
public function castingHelper($field, bool $useFallback = true) public function castingHelper(string $field, bool $useFallback = true): ?string
{ {
// Override casting for field message // Override casting for field message
if (strcasecmp($field ?? '', 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) { if (strcasecmp($field ?? '', 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
@ -1547,10 +1547,8 @@ class Form extends ViewableData implements HasRequestHandler
* *
* This is returned when you access a form as $FormObject rather * This is returned when you access a form as $FormObject rather
* than <% with FormObject %> * than <% with FormObject %>
*
* @return DBHTMLText
*/ */
public function forTemplate() public function forTemplate(): string
{ {
if (!$this->canBeCached()) { if (!$this->canBeCached()) {
HTTPCacheControlMiddleware::singleton()->disableCache(); HTTPCacheControlMiddleware::singleton()->disableCache();
@ -1750,7 +1748,7 @@ class Form extends ViewableData implements HasRequestHandler
return $this; return $this;
} }
public function debug() public function debug(): string
{ {
$class = static::class; $class = static::class;
$result = "<h3>$class</h3><ul>"; $result = "<h3>$class</h3><ul>";

View File

@ -790,7 +790,7 @@ class FormField extends RequestHandler
return $form->getSecurityToken()->isEnabled(); return $form->getSecurityToken()->isEnabled();
} }
public function castingHelper($field, bool $useFallback = true) public function castingHelper(string $field, bool $useFallback = true): ?string
{ {
// Override casting for field message // Override casting for field message
if (strcasecmp($field ?? '', 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) { if (strcasecmp($field ?? '', 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
@ -1269,7 +1269,7 @@ class FormField extends RequestHandler
/** /**
* @return string * @return string
*/ */
public function debug() public function debug(): string
{ {
$strValue = is_string($this->value) ? $this->value : print_r($this->value, true); $strValue = is_string($this->value) ? $this->value : print_r($this->value, true);
@ -1286,10 +1286,8 @@ class FormField extends RequestHandler
/** /**
* This function is used by the template processor. If you refer to a field as a $ variable, it * This function is used by the template processor. If you refer to a field as a $ variable, it
* will return the $Field value. * will return the $Field value.
*
* @return string
*/ */
public function forTemplate() public function forTemplate(): string
{ {
return $this->Field(); return $this->Field();
} }

View File

@ -504,7 +504,7 @@ class FormRequestHandler extends RequestHandler
return $result; return $result;
} }
public function forTemplate() public function forTemplate(): string
{ {
return $this->form->forTemplate(); return $this->form->forTemplate();
} }

View File

@ -968,7 +968,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler
* Get Pjax response negotiator so form submission mirrors other form submission in the CMS. * Get Pjax response negotiator so form submission mirrors other form submission in the CMS.
* See LeftAndMain::getResponseNegotiator() * See LeftAndMain::getResponseNegotiator()
*/ */
private function getResponseNegotiator(DBHTMLText $renderedForm): PjaxResponseNegotiator private function getResponseNegotiator(string $renderedForm): PjaxResponseNegotiator
{ {
return new PjaxResponseNegotiator([ return new PjaxResponseNegotiator([
'default' => function () use ($renderedForm) { 'default' => function () use ($renderedForm) {

View File

@ -166,7 +166,7 @@ class NullableField extends FormField
/** /**
* @return string * @return string
*/ */
public function debug() public function debug(): string
{ {
$result = sprintf( $result = sprintf(
'%s (%s: %s : <span style="color: red">%s</span>) = ', '%s (%s: %s : <span style="color: red">%s</span>) = ',

View File

@ -56,7 +56,7 @@ class ReadonlyField extends FormField
return 'readonly'; return 'readonly';
} }
public function castingHelper($field, bool $useFallback = true) public function castingHelper(string $field, bool $useFallback = true): ?string
{ {
// Get dynamic cast for 'Value' field // Get dynamic cast for 'Value' field
if (strcasecmp($field ?? '', 'Value') === 0) { if (strcasecmp($field ?? '', 'Value') === 0) {

View File

@ -58,7 +58,7 @@ class RequiredFields extends Validator
* Debug helper * Debug helper
* @return string * @return string
*/ */
public function debug() public function debug(): string
{ {
if (!is_array($this->required)) { if (!is_array($this->required)) {
return false; return false;

View File

@ -110,10 +110,8 @@ class ArrayList extends ViewableData implements SS_List, Filterable, Sortable, L
/** /**
* Returns true if this list has items * Returns true if this list has items
*
* @return bool
*/ */
public function exists() public function exists(): bool
{ {
return !empty($this->items); return !empty($this->items);
} }
@ -159,7 +157,7 @@ class ArrayList extends ViewableData implements SS_List, Filterable, Sortable, L
return $this; return $this;
} }
public function debug() public function debug(): string
{ {
$val = "<h2>" . static::class . "</h2><ul>"; $val = "<h2>" . static::class . "</h2><ul>";
foreach ($this->toNestedArray() as $item) { foreach ($this->toNestedArray() as $item) {

View File

@ -868,7 +868,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
return $this; return $this;
} }
public function debug() public function debug(): string
{ {
$val = "<h2>" . static::class . "</h2><ul>"; $val = "<h2>" . static::class . "</h2><ul>";
foreach ($this->toNestedArray() as $item) { foreach ($this->toNestedArray() as $item) {
@ -1702,10 +1702,8 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
/** /**
* Returns true if this DataList has items * Returns true if this DataList has items
*
* @return bool
*/ */
public function exists() public function exists(): bool
{ {
return $this->dataQuery->exists(); return $this->dataQuery->exists();
} }

View File

@ -816,10 +816,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Returns true if this object "exists", i.e., has a sensible value. * Returns true if this object "exists", i.e., has a sensible value.
* The default behaviour for a DataObject is to return true if * The default behaviour for a DataObject is to return true if
* the object exists in the database, you can override this in subclasses. * the object exists in the database, you can override this in subclasses.
*
* @return boolean true if this object exists
*/ */
public function exists() public function exists(): bool
{ {
return $this->isInDB(); return $this->isInDB();
} }
@ -2686,7 +2684,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $untabbedFields; return $untabbedFields;
} }
public function getViewerTemplates($suffix = '') public function getViewerTemplates(string $suffix = ''): array
{ {
return SSViewer::get_templates_by_class(static::class, $suffix, $this->baseClass()); return SSViewer::get_templates_by_class(static::class, $suffix, $this->baseClass());
} }
@ -2713,11 +2711,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/** /**
* Gets the value of a field. * Gets the value of a field.
* Called by {@link __get()} and any getFieldName() methods you might create. * Called by {@link __get()} and any getFieldName() methods you might create.
*
* @param string $field The name of the field
* @return mixed The field value
*/ */
public function getField($field) public function getField(string $field): mixed
{ {
// If we already have a value in $this->record, then we should just return that // If we already have a value in $this->record, then we should just return that
if (isset($this->record[$field])) { if (isset($this->record[$field])) {
@ -2928,12 +2923,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/** /**
* Set the value of the field * Set the value of the field
* Called by {@link __set()} and any setFieldName() methods you might create. * Called by {@link __set()} and any setFieldName() methods you might create.
*
* @param string $fieldName Name of the field
* @param mixed $val New field value
* @return $this
*/ */
public function setField($fieldName, $val) public function setField(string $fieldName, mixed $value): static
{ {
$this->objCacheClear(); $this->objCacheClear();
//if it's a has_one component, destroy the cache //if it's a has_one component, destroy the cache
@ -2952,42 +2943,42 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if ($schema->unaryComponent(static::class, $fieldName)) { if ($schema->unaryComponent(static::class, $fieldName)) {
unset($this->components[$fieldName]); unset($this->components[$fieldName]);
// Assign component directly // Assign component directly
if (is_null($val) || $val instanceof DataObject) { if (is_null($value) || $value instanceof DataObject) {
return $this->setComponent($fieldName, $val); return $this->setComponent($fieldName, $value);
} }
// Assign by ID instead of object // Assign by ID instead of object
if (is_numeric($val)) { if (is_numeric($value)) {
$fieldName .= 'ID'; $fieldName .= 'ID';
} }
} }
// Situation 1: Passing an DBField // Situation 1: Passing an DBField
if ($val instanceof DBField) { if ($value instanceof DBField) {
$val->setName($fieldName); $value->setName($fieldName);
$val->saveInto($this); $value->saveInto($this);
// Situation 1a: Composite fields should remain bound in case they are // Situation 1a: Composite fields should remain bound in case they are
// later referenced to update the parent dataobject // later referenced to update the parent dataobject
if ($val instanceof DBComposite) { if ($value instanceof DBComposite) {
$val->bindTo($this); $value->bindTo($this);
$this->setFieldValue($fieldName, $val); $this->setFieldValue($fieldName, $value);
} }
// Situation 2: Passing a literal or non-DBField object // Situation 2: Passing a literal or non-DBField object
} else { } else {
$this->setFieldValue($fieldName, $val); $this->setFieldValue($fieldName, $value);
} }
return $this; return $this;
} }
private function setFieldValue(string $fieldName, mixed $val): void private function setFieldValue(string $fieldName, mixed $value): void
{ {
$schema = static::getSchema(); $schema = static::getSchema();
// If this is a proper database field, we shouldn't be getting non-DBField objects // If this is a proper database field, we shouldn't be getting non-DBField objects
if (is_object($val) && !($val instanceof DBField) && $schema->fieldSpec(static::class, $fieldName)) { if (is_object($value) && !($value instanceof DBField) && $schema->fieldSpec(static::class, $fieldName)) {
throw new InvalidArgumentException('DataObject::setFieldValue: passed an object that is not a DBField'); throw new InvalidArgumentException('DataObject::setFieldValue: passed an object that is not a DBField');
} }
if (!empty($val) && !is_scalar($val)) { if (!empty($value) && !is_scalar($value)) {
$dbField = $this->dbObject($fieldName); $dbField = $this->dbObject($fieldName);
if ($dbField && $dbField->scalarValueOnly()) { if ($dbField && $dbField->scalarValueOnly()) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
@ -3000,12 +2991,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
// if a field is not existing or has strictly changed // if a field is not existing or has strictly changed
if (!array_key_exists($fieldName, $this->original ?? []) || $this->original[$fieldName] !== $val) { if (!array_key_exists($fieldName, $this->original ?? []) || $this->original[$fieldName] !== $value) {
// At the very least, the type has changed // At the very least, the type has changed
$this->changed[$fieldName] = DataObject::CHANGE_STRICT; $this->changed[$fieldName] = DataObject::CHANGE_STRICT;
if ((!array_key_exists($fieldName, $this->original ?? []) && $val) if ((!array_key_exists($fieldName, $this->original ?? []) && $value)
|| (array_key_exists($fieldName, $this->original ?? []) && $this->original[$fieldName] != $val) || (array_key_exists($fieldName, $this->original ?? []) && $this->original[$fieldName] != $value)
) { ) {
// Value has changed as well, not just the type // Value has changed as well, not just the type
$this->changed[$fieldName] = DataObject::CHANGE_VALUE; $this->changed[$fieldName] = DataObject::CHANGE_VALUE;
@ -3016,7 +3007,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
// Value is saved regardless, since the change detection relates to the last write // Value is saved regardless, since the change detection relates to the last write
$this->record[$fieldName] = $val; $this->record[$fieldName] = $value;
} }
/** /**
@ -3047,7 +3038,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function castingHelper($field, bool $useFallback = true) public function castingHelper(string $field, bool $useFallback = true): ?string
{ {
$fieldSpec = static::getSchema()->fieldSpec(static::class, $field); $fieldSpec = static::getSchema()->fieldSpec(static::class, $field);
if ($fieldSpec) { if ($fieldSpec) {
@ -3072,19 +3063,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* Returns true if the given field exists in a database column on any of * Returns true if the given field exists in a database column on any of
* the objects tables and optionally look up a dynamic getter with * the objects tables and optionally look up a dynamic getter with
* get<fieldName>(). * get<fieldName>().
*
* @param string $field Name of the field
* @return boolean True if the given field exists
*/ */
public function hasField($field) public function hasField(string $fieldName): bool
{ {
$schema = static::getSchema(); $schema = static::getSchema();
return ( return (
array_key_exists($field, $this->record ?? []) array_key_exists($fieldName, $this->record ?? [])
|| array_key_exists($field, $this->components ?? []) || array_key_exists($fieldName, $this->components ?? [])
|| $schema->fieldSpec(static::class, $field) || $schema->fieldSpec(static::class, $fieldName)
|| $schema->unaryComponent(static::class, $field) || $schema->unaryComponent(static::class, $fieldName)
|| $this->hasMethod("get{$field}") || $this->hasMethod("get{$fieldName}")
); );
} }
@ -3232,7 +3220,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* *
* @return string HTML data representing this object * @return string HTML data representing this object
*/ */
public function debug() public function debug(): string
{ {
$class = static::class; $class = static::class;
$val = "<h3>Database record: {$class}</h3>\n<ul>\n"; $val = "<h3>Database record: {$class}</h3>\n<ul>\n";
@ -4405,13 +4393,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/** /**
* Returns true if the given method/parameter has a value * Returns true if the given method/parameter has a value
* (Uses the DBField::hasValue if the parameter is a database field) * (Uses the DBField::hasValue if the parameter is a database field)
*
* @param string $field The field name
* @param array $arguments
* @param bool $cache
* @return boolean
*/ */
public function hasValue($field, $arguments = null, $cache = true) public function hasValue(string $field, array $arguments = [], bool $cache = true): bool
{ {
// has_one fields should not use dbObject to check if a value is given // has_one fields should not use dbObject to check if a value is given
$hasOne = static::getSchema()->hasOneComponent(static::class, $field); $hasOne = static::getSchema()->hasOneComponent(static::class, $field);

View File

@ -36,7 +36,7 @@ interface DataObjectInterface
* @param string $fieldName * @param string $fieldName
* @return mixed * @return mixed
*/ */
public function __get($fieldName); public function __get(string $property): mixed;
/** /**
* Save content from a form into a field on this data object. * Save content from a form into a field on this data object.

View File

@ -341,7 +341,7 @@ class EagerLoadedList extends ViewableData implements Relation, SS_List, Filtera
return $this; return $this;
} }
public function debug() public function debug(): string
{ {
// Same implementation as DataList::debug() // Same implementation as DataList::debug()
$val = '<h2>' . static::class . '</h2><ul>'; $val = '<h2>' . static::class . '</h2><ul>';

View File

@ -15,7 +15,7 @@ use SilverStripe\ORM\DB;
class DBBigInt extends DBInt class DBBigInt extends DBInt
{ {
public function requireField() public function requireField(): void
{ {
$parts = [ $parts = [
'datatype' => 'bigint', 'datatype' => 'bigint',

View File

@ -4,21 +4,23 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\View\ViewableData;
/** /**
* Represents a boolean field. * Represents a boolean field.
*/ */
class DBBoolean extends DBField class DBBoolean extends DBField
{ {
public function __construct($name = null, $defaultVal = 0) public function __construct(?string $name = null, bool|int $defaultVal = 0)
{ {
$this->defaultVal = ($defaultVal) ? 1 : 0; $this->defaultVal = ($defaultVal) ? 1 : 0;
parent::__construct($name); parent::__construct($name);
} }
public function requireField() public function requireField(): void
{ {
$parts = [ $parts = [
'datatype' => 'tinyint', 'datatype' => 'tinyint',
@ -32,17 +34,17 @@ class DBBoolean extends DBField
DB::require_field($this->tableName, $this->name, $values); DB::require_field($this->tableName, $this->name, $values);
} }
public function Nice() public function Nice(): string
{ {
return ($this->value) ? _t(__CLASS__ . '.YESANSWER', 'Yes') : _t(__CLASS__ . '.NOANSWER', 'No'); return ($this->value) ? _t(__CLASS__ . '.YESANSWER', 'Yes') : _t(__CLASS__ . '.NOANSWER', 'No');
} }
public function NiceAsBoolean() public function NiceAsBoolean(): string
{ {
return ($this->value) ? 'true' : 'false'; return ($this->value) ? 'true' : 'false';
} }
public function saveInto($dataObject) public function saveInto(ViewableData $dataObject): void
{ {
$fieldName = $this->name; $fieldName = $this->name;
if ($fieldName) { if ($fieldName) {
@ -57,12 +59,12 @@ class DBBoolean extends DBField
} }
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return CheckboxField::create($this->name, $title); return CheckboxField::create($this->name, $title);
} }
public function scaffoldSearchField($title = null) public function scaffoldSearchField(?string $title = null): ?FormField
{ {
$anyText = _t(__CLASS__ . '.ANY', 'Any'); $anyText = _t(__CLASS__ . '.ANY', 'Any');
$source = [ $source = [
@ -71,16 +73,16 @@ class DBBoolean extends DBField
0 => _t(__CLASS__ . '.NOANSWER', 'No') 0 => _t(__CLASS__ . '.NOANSWER', 'No')
]; ];
return (new DropdownField($this->name, $title, $source)) return DropdownField::create($this->name, $title, $source)
->setEmptyString($anyText); ->setEmptyString($anyText);
} }
public function nullValue() public function nullValue(): ?int
{ {
return 0; return 0;
} }
public function prepValueForDB($value) public function prepValueForDB(mixed $value): array|int|null
{ {
if (is_bool($value)) { if (is_bool($value)) {
return $value ? 1 : 0; return $value ? 1 : 0;

View File

@ -6,48 +6,40 @@ use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\View\ViewableData;
/** /**
* Represents a classname selector, which respects obsolete clasess. * Represents a classname selector, which respects obsolete clasess.
*/ */
class DBClassName extends DBEnum class DBClassName extends DBEnum
{ {
/** /**
* Base classname of class to enumerate. * Base classname of class to enumerate.
* If 'DataObject' then all classes are included. * If 'DataObject' then all classes are included.
* If empty, then the baseClass of the parent object will be used * If empty, then the baseClass of the parent object will be used
*
* @var string|null
*/ */
protected $baseClass = null; protected ?string $baseClass = null;
/** /**
* Parent object * Parent object
*
* @var DataObject|null
*/ */
protected $record = null; protected ?DataObject $record = null;
private static $index = true; private static string|bool $index = true;
/** /**
* Create a new DBClassName field * Create a new DBClassName field
* *
* @param string $name Name of field
* @param string|null $baseClass Optional base class to limit selections * @param string|null $baseClass Optional base class to limit selections
* @param array $options Optional parameters for this DBField instance * @param array $options Optional parameters for this DBField instance
*/ */
public function __construct($name = null, $baseClass = null, $options = []) public function __construct(?string $name = null, ?string $baseClass = null, array $options = [])
{ {
$this->setBaseClass($baseClass); $this->setBaseClass($baseClass);
parent::__construct($name, null, null, $options); parent::__construct($name, null, null, $options);
} }
/** public function requireField(): void
* @return void
*/
public function requireField()
{ {
$parts = [ $parts = [
'datatype' => 'enum', 'datatype' => 'enum',
@ -69,10 +61,8 @@ class DBClassName extends DBEnum
/** /**
* Get the base dataclass for the list of subclasses * Get the base dataclass for the list of subclasses
*
* @return string
*/ */
public function getBaseClass() public function getBaseClass(): string
{ {
// Use explicit base class // Use explicit base class
if ($this->baseClass) { if ($this->baseClass) {
@ -95,25 +85,20 @@ class DBClassName extends DBEnum
/** /**
* Get the base name of the current class * Get the base name of the current class
* Useful as a non-fully qualified CSS Class name in templates. * Useful as a non-fully qualified CSS Class name in templates.
*
* @return string|null
*/ */
public function getShortName() public function getShortName(): string
{ {
$value = $this->getValue(); $value = $this->getValue();
if (empty($value) || !ClassInfo::exists($value)) { if (empty($value) || !ClassInfo::exists($value)) {
return null; return '';
} }
return ClassInfo::shortName($value); return ClassInfo::shortName($value);
} }
/** /**
* Assign the base class * Assign the base class
*
* @param string $baseClass
* @return $this
*/ */
public function setBaseClass($baseClass) public function setBaseClass(?string $baseClass): static
{ {
$this->baseClass = $baseClass; $this->baseClass = $baseClass;
return $this; return $this;
@ -121,10 +106,8 @@ class DBClassName extends DBEnum
/** /**
* Get list of classnames that should be selectable * Get list of classnames that should be selectable
*
* @return array
*/ */
public function getEnum() public function getEnum(): array
{ {
$classNames = ClassInfo::subclassesFor($this->getBaseClass()); $classNames = ClassInfo::subclassesFor($this->getBaseClass());
$dataobject = strtolower(DataObject::class); $dataobject = strtolower(DataObject::class);
@ -132,7 +115,7 @@ class DBClassName extends DBEnum
return array_values($classNames ?? []); return array_values($classNames ?? []);
} }
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
parent::setValue($value, $record, $markChanged); parent::setValue($value, $record, $markChanged);
@ -143,7 +126,7 @@ class DBClassName extends DBEnum
return $this; return $this;
} }
public function getDefault() public function getDefault(): string
{ {
// Check for assigned default // Check for assigned default
$default = parent::getDefault(); $default = parent::getDefault();

View File

@ -7,6 +7,7 @@ use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\View\ViewableData;
/** /**
* Extend this class when designing a {@link DBField} that doesn't have a 1-1 mapping with a database field. * Extend this class when designing a {@link DBField} that doesn't have a 1-1 mapping with a database field.
@ -29,26 +30,21 @@ abstract class DBComposite extends DBField
* holds an array of composite field names. * holds an array of composite field names.
* Don't include the fields "main name", * Don't include the fields "main name",
* it will be prefixed in {@link requireField()}. * it will be prefixed in {@link requireField()}.
*
* @config
* @var array
*/ */
private static $composite_db = []; private static array $composite_db = [];
/** /**
* Marker as to whether this record has changed * Marker as to whether this record has changed
* Only used when deference to the parent object isn't possible * Only used when deference to the parent object isn't possible
*/ */
protected $isChanged = false; protected bool $isChanged = false;
/** /**
* Either the parent dataobject link, or a record of saved values for each field * Either the parent dataobject link, or a record of saved values for each field
*
* @var array|DataObject
*/ */
protected $record = []; protected array|ViewableData $record = [];
public function __set($property, $value) public function __set(string $property, mixed $value): void
{ {
// Prevent failover / extensions from hijacking composite field setters // Prevent failover / extensions from hijacking composite field setters
// by intentionally avoiding hasMethod() // by intentionally avoiding hasMethod()
@ -59,7 +55,7 @@ abstract class DBComposite extends DBField
parent::__set($property, $value); parent::__set($property, $value);
} }
public function __get($property) public function __get(string $property): mixed
{ {
// Prevent failover / extensions from hijacking composite field getters // Prevent failover / extensions from hijacking composite field getters
// by intentionally avoiding hasMethod() // by intentionally avoiding hasMethod()
@ -71,10 +67,8 @@ abstract class DBComposite extends DBField
/** /**
* Write all nested fields into a manipulation * Write all nested fields into a manipulation
*
* @param array $manipulation
*/ */
public function writeToManipulation(&$manipulation) public function writeToManipulation(array &$manipulation): void
{ {
foreach ($this->compositeDatabaseFields() as $field => $spec) { foreach ($this->compositeDatabaseFields() as $field => $spec) {
// Write sub-manipulation // Write sub-manipulation
@ -88,10 +82,8 @@ abstract class DBComposite extends DBField
* and {@link $composite_db}, or any additional SQL that is required * and {@link $composite_db}, or any additional SQL that is required
* to get to these columns. Will mostly just write to the {@link SQLSelect->select} * to get to these columns. Will mostly just write to the {@link SQLSelect->select}
* array. * array.
*
* @param SQLSelect $query
*/ */
public function addToQuery(&$query) public function addToQuery(SQLSelect &$query): void
{ {
parent::addToQuery($query); parent::addToQuery($query);
@ -109,12 +101,10 @@ abstract class DBComposite extends DBField
/** /**
* Return array in the format of {@link $composite_db}. * Return array in the format of {@link $composite_db}.
* Used by {@link DataObject->hasOwnDatabaseField()}. * Used by {@link DataObject->hasOwnDatabaseField()}.
*
* @return array
*/ */
public function compositeDatabaseFields() public function compositeDatabaseFields(): array
{ {
return $this->config()->composite_db; return static::config()->get('composite_db');
} }
@ -122,7 +112,7 @@ abstract class DBComposite extends DBField
* Returns true if this composite field has changed. * Returns true if this composite field has changed.
* For fields bound to a DataObject, this will be cleared when the DataObject is written. * For fields bound to a DataObject, this will be cleared when the DataObject is written.
*/ */
public function isChanged() public function isChanged(): bool
{ {
// When unbound, use the local changed flag // When unbound, use the local changed flag
if (!$this->record instanceof DataObject) { if (!$this->record instanceof DataObject) {
@ -141,10 +131,8 @@ abstract class DBComposite extends DBField
/** /**
* Composite field defaults to exists only if all fields have values * Composite field defaults to exists only if all fields have values
*
* @return boolean
*/ */
public function exists() public function exists(): bool
{ {
// By default all fields // By default all fields
foreach ($this->compositeDatabaseFields() as $field => $spec) { foreach ($this->compositeDatabaseFields() as $field => $spec) {
@ -156,7 +144,7 @@ abstract class DBComposite extends DBField
return true; return true;
} }
public function requireField() public function requireField(): void
{ {
foreach ($this->compositeDatabaseFields() as $field => $spec) { foreach ($this->compositeDatabaseFields() as $field => $spec) {
$key = $this->getName() . $field; $key = $this->getName() . $field;
@ -171,12 +159,9 @@ abstract class DBComposite extends DBField
* *
* {@see ViewableData::obj} * {@see ViewableData::obj}
* *
* @param mixed $value * @param null|array|ViewableData $record Parent object to this field, which could be a DataObject, record array, or other
* @param mixed $record Parent object to this field, which could be a DataObject, record array, or other
* @param bool $markChanged
* @return $this
*/ */
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
$this->isChanged = $markChanged; $this->isChanged = $markChanged;
@ -206,16 +191,14 @@ abstract class DBComposite extends DBField
} }
/** /**
* Bind this field to the dataobject, and set the underlying table to that of the owner * Bind this field to the model, and set the underlying table to that of the owner
*
* @param DataObject $dataObject
*/ */
public function bindTo($dataObject) public function bindTo(DataObject $dataObject): void
{ {
$this->record = $dataObject; $this->record = $dataObject;
} }
public function saveInto($dataObject) public function saveInto(ViewableData $dataObject): void
{ {
foreach ($this->compositeDatabaseFields() as $field => $spec) { foreach ($this->compositeDatabaseFields() as $field => $spec) {
// Save into record // Save into record
@ -230,52 +213,44 @@ abstract class DBComposite extends DBField
/** /**
* get value of a single composite field * get value of a single composite field
*
* @param string $field
* @return mixed
*/ */
public function getField($field) public function getField(string $fieldName): mixed
{ {
// Skip invalid fields // Skip invalid fields
$fields = $this->compositeDatabaseFields(); $fields = $this->compositeDatabaseFields();
if (!isset($fields[$field])) { if (!isset($fields[$fieldName])) {
return null; return null;
} }
// Check bound object // Check bound object
if ($this->record instanceof DataObject) { if ($this->record instanceof DataObject) {
$key = $this->getName() . $field; $key = $this->getName() . $fieldName;
return $this->record->getField($key); return $this->record->getField($key);
} }
// Check local record // Check local record
if (isset($this->record[$field])) { if (isset($this->record[$fieldName])) {
return $this->record[$field]; return $this->record[$fieldName];
} }
return null; return null;
} }
public function hasField($field) public function hasField(string $fieldName): bool
{ {
$fields = $this->compositeDatabaseFields(); $fields = $this->compositeDatabaseFields();
return isset($fields[$field]); return isset($fields[$fieldName]);
} }
/** /**
* Set value of a single composite field * Set value of a single composite field
*
* @param string $field
* @param mixed $value
* @param bool $markChanged
* @return $this
*/ */
public function setField($field, $value, $markChanged = true) public function setField(string $fieldName, mixed $value, bool $markChanged = true): static
{ {
$this->objCacheClear(); $this->objCacheClear();
if (!$this->hasField($field)) { if (!$this->hasField($fieldName)) {
throw new InvalidArgumentException(implode(' ', [ throw new InvalidArgumentException(implode(' ', [
"Field $field does not exist.", "Field $fieldName does not exist.",
'If this was accessed via a dynamic property then call setDynamicData() instead.' 'If this was accessed via a dynamic property then call setDynamicData() instead.'
])); ]));
} }
@ -287,23 +262,20 @@ abstract class DBComposite extends DBField
// Set bound object // Set bound object
if ($this->record instanceof DataObject) { if ($this->record instanceof DataObject) {
$key = $this->getName() . $field; $key = $this->getName() . $fieldName;
$this->record->setField($key, $value); $this->record->setField($key, $value);
return $this; return $this;
} }
// Set local record // Set local record
$this->record[$field] = $value; $this->record[$fieldName] = $value;
return $this; return $this;
} }
/** /**
* Get a db object for the named field * Get a db object for the named field
*
* @param string $field Field name
* @return DBField|null
*/ */
public function dbObject($field) public function dbObject(string $field): ?DBField
{ {
$fields = $this->compositeDatabaseFields(); $fields = $this->compositeDatabaseFields();
if (!isset($fields[$field])) { if (!isset($fields[$field])) {
@ -319,7 +291,7 @@ abstract class DBComposite extends DBField
return $fieldObject; return $fieldObject;
} }
public function castingHelper($field, bool $useFallback = true) public function castingHelper(string $field, bool $useFallback = true): ?string
{ {
$fields = $this->compositeDatabaseFields(); $fields = $this->compositeDatabaseFields();
if (isset($fields[$field])) { if (isset($fields[$field])) {
@ -329,7 +301,7 @@ abstract class DBComposite extends DBField
return parent::castingHelper($field, $useFallback); return parent::castingHelper($field, $useFallback);
} }
public function getIndexSpecs() public function getIndexSpecs(): array
{ {
if ($type = $this->getIndexType()) { if ($type = $this->getIndexType()) {
$columns = array_map(function ($name) { $columns = array_map(function ($name) {
@ -341,9 +313,10 @@ abstract class DBComposite extends DBField
'columns' => $columns, 'columns' => $columns,
]; ];
} }
return [];
} }
public function scalarValueOnly() public function scalarValueOnly(): bool
{ {
return false; return false;
} }

View File

@ -3,6 +3,8 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\CurrencyField; use SilverStripe\Forms\CurrencyField;
use SilverStripe\Forms\FormField;
use SilverStripe\View\ViewableData;
/** /**
* Represents a decimal field containing a currency amount. * Represents a decimal field containing a currency amount.
@ -20,22 +22,16 @@ use SilverStripe\Forms\CurrencyField;
class DBCurrency extends DBDecimal class DBCurrency extends DBDecimal
{ {
/** /**
* @config * The symbol that represents the currency
* @var string
*/ */
private static $currency_symbol = '$'; private static string $currency_symbol = '$';
public function __construct($name = null, $wholeSize = 9, $decimalSize = 2, $defaultValue = 0)
{
parent::__construct($name, $wholeSize, $decimalSize, $defaultValue);
}
/** /**
* Returns the number as a currency, eg $1,000.00. * Returns the number as a currency, eg $1,000.00.
*/ */
public function Nice() public function Nice(): string
{ {
$val = $this->config()->currency_symbol . number_format(abs($this->value ?? 0.0) ?? 0.0, 2); $val = static::config()->get('currency_symbol') . number_format(abs($this->value ?? 0.0) ?? 0.0, 2);
if ($this->value < 0) { if ($this->value < 0) {
return "($val)"; return "($val)";
} }
@ -46,22 +42,22 @@ class DBCurrency extends DBDecimal
/** /**
* Returns the number as a whole-number currency, eg $1,000. * Returns the number as a whole-number currency, eg $1,000.
*/ */
public function Whole() public function Whole(): string
{ {
$val = $this->config()->currency_symbol . number_format(abs($this->value ?? 0.0) ?? 0.0, 0); $val = static::config()->get('currency_symbol') . number_format(abs($this->value ?? 0.0) ?? 0.0, 0);
if ($this->value < 0) { if ($this->value < 0) {
return "($val)"; return "($val)";
} }
return $val; return $val;
} }
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
$matches = null; $matches = null;
if (is_numeric($value)) { if (is_numeric($value)) {
$this->value = $value; $this->value = $value;
} elseif (preg_match('/-?\$?[0-9,]+(.[0-9]+)?([Ee][0-9]+)?/', $value ?? '', $matches)) { } elseif (preg_match('/-?\$?[0-9,]+(.[0-9]+)?([Ee][0-9]+)?/', $value ?? '', $matches)) {
$this->value = str_replace(['$', ',', $this->config()->currency_symbol], '', $matches[0] ?? ''); $this->value = str_replace(['$', ',', static::config()->get('currency_symbol')], '', $matches[0] ?? '');
} else { } else {
$this->value = 0; $this->value = 0;
} }
@ -69,13 +65,7 @@ class DBCurrency extends DBDecimal
return $this; return $this;
} }
/** public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
* @param string $title
* @param array $params
*
* @return CurrencyField
*/
public function scaffoldFormField($title = null, $params = null)
{ {
return CurrencyField::create($this->getName(), $title); return CurrencyField::create($this->getName(), $title);
} }

View File

@ -6,10 +6,12 @@ use IntlDateFormatter;
use InvalidArgumentException; use InvalidArgumentException;
use NumberFormatter; use NumberFormatter;
use SilverStripe\Forms\DateField; use SilverStripe\Forms\DateField;
use SilverStripe\Forms\FormField;
use SilverStripe\i18n\i18n; use SilverStripe\i18n\i18n;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\View\ViewableData;
/** /**
* Represents a date field. * Represents a date field.
@ -32,15 +34,15 @@ class DBDate extends DBField
/** /**
* Standard ISO format string for date in CLDR standard format * Standard ISO format string for date in CLDR standard format
*/ */
const ISO_DATE = 'y-MM-dd'; public const ISO_DATE = 'y-MM-dd';
/** /**
* Fixed locale to use for ISO date formatting. This is necessary to prevent * Fixed locale to use for ISO date formatting. This is necessary to prevent
* locale-specific numeric localisation breaking internal date strings. * locale-specific numeric localisation breaking internal date strings.
*/ */
const ISO_LOCALE = 'en_US'; public const ISO_LOCALE = 'en_US';
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
$value = $this->parseDate($value); $value = $this->parseDate($value);
if ($value === false) { if ($value === false) {
@ -58,7 +60,7 @@ class DBDate extends DBField
* @param mixed $value * @param mixed $value
* @return string|null|false Formatted date, null if empty but valid, or false if invalid * @return string|null|false Formatted date, null if empty but valid, or false if invalid
*/ */
protected function parseDate($value) protected function parseDate(mixed $value): string|null|false
{ {
// Skip empty values // Skip empty values
if (empty($value) && !is_numeric($value)) { if (empty($value) && !is_numeric($value)) {
@ -89,13 +91,11 @@ class DBDate extends DBField
/** /**
* Returns the standard localised medium date * Returns the standard localised medium date
*
* @return ?string
*/ */
public function Nice() public function Nice(): string
{ {
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
$formatter = $this->getFormatter(); $formatter = $this->getFormatter();
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -103,40 +103,32 @@ class DBDate extends DBField
/** /**
* Returns the year from the given date * Returns the year from the given date
*
* @return string
*/ */
public function Year() public function Year(): string
{ {
return $this->Format('y'); return $this->Format('y');
} }
/** /**
* Returns the day of the week * Returns the day of the week
*
* @return string
*/ */
public function DayOfWeek() public function DayOfWeek(): string
{ {
return $this->Format('cccc'); return $this->Format('cccc');
} }
/** /**
* Returns a full textual representation of a month, such as January. * Returns a full textual representation of a month, such as January.
*
* @return string
*/ */
public function Month() public function Month(): string
{ {
return $this->Format('LLLL'); return $this->Format('LLLL');
} }
/** /**
* Returns the short version of the month such as Jan * Returns the short version of the month such as Jan
*
* @return string
*/ */
public function ShortMonth() public function ShortMonth(): string
{ {
return $this->Format('LLL'); return $this->Format('LLL');
} }
@ -145,9 +137,8 @@ class DBDate extends DBField
* Returns the day of the month. * Returns the day of the month.
* *
* @param bool $includeOrdinal Include ordinal suffix to day, e.g. "th" or "rd" * @param bool $includeOrdinal Include ordinal suffix to day, e.g. "th" or "rd"
* @return string
*/ */
public function DayOfMonth($includeOrdinal = false) public function DayOfMonth(bool $includeOrdinal = false): string
{ {
$number = $this->Format('d'); $number = $this->Format('d');
if ($includeOrdinal && $number) { if ($includeOrdinal && $number) {
@ -159,13 +150,11 @@ class DBDate extends DBField
/** /**
* Returns the date in the localised short format * Returns the date in the localised short format
*
* @return string
*/ */
public function Short() public function Short(): string
{ {
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
$formatter = $this->getFormatter(IntlDateFormatter::SHORT); $formatter = $this->getFormatter(IntlDateFormatter::SHORT);
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -173,13 +162,11 @@ class DBDate extends DBField
/** /**
* Returns the date in the localised long format * Returns the date in the localised long format
*
* @return string
*/ */
public function Long() public function Long(): string
{ {
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
$formatter = $this->getFormatter(IntlDateFormatter::LONG); $formatter = $this->getFormatter(IntlDateFormatter::LONG);
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -187,13 +174,11 @@ class DBDate extends DBField
/** /**
* Returns the date in the localised full format * Returns the date in the localised full format
*
* @return string
*/ */
public function Full() public function Full(): string
{ {
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
$formatter = $this->getFormatter(IntlDateFormatter::FULL); $formatter = $this->getFormatter(IntlDateFormatter::FULL);
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -201,12 +186,8 @@ class DBDate extends DBField
/** /**
* Get date formatter * Get date formatter
*
* @param int $dateLength
* @param int $timeLength
* @return IntlDateFormatter
*/ */
public function getFormatter($dateLength = IntlDateFormatter::MEDIUM, $timeLength = IntlDateFormatter::NONE) public function getFormatter(int $dateLength = IntlDateFormatter::MEDIUM, int $timeLength = IntlDateFormatter::NONE): IntlDateFormatter
{ {
return $this->getCustomFormatter(null, null, $dateLength, $timeLength); return $this->getCustomFormatter(null, null, $dateLength, $timeLength);
} }
@ -216,16 +197,13 @@ class DBDate extends DBField
* *
* @param string|null $locale The current locale, or null to use default * @param string|null $locale The current locale, or null to use default
* @param string|null $pattern Custom pattern to use for this, if required * @param string|null $pattern Custom pattern to use for this, if required
* @param int $dateLength
* @param int $timeLength
* @return IntlDateFormatter
*/ */
public function getCustomFormatter( public function getCustomFormatter(
$locale = null, ?string $locale = null,
$pattern = null, ?string $pattern = null,
$dateLength = IntlDateFormatter::MEDIUM, int $dateLength = IntlDateFormatter::MEDIUM,
$timeLength = IntlDateFormatter::NONE int $timeLength = IntlDateFormatter::NONE
) { ): IntlDateFormatter {
$locale = $locale ?: i18n::get_locale(); $locale = $locale ?: i18n::get_locale();
$formatter = IntlDateFormatter::create($locale, $dateLength, $timeLength); $formatter = IntlDateFormatter::create($locale, $dateLength, $timeLength);
if ($pattern) { if ($pattern) {
@ -238,9 +216,8 @@ class DBDate extends DBField
* Formatter used internally * Formatter used internally
* *
* @internal * @internal
* @return IntlDateFormatter
*/ */
protected function getInternalFormatter() protected function getInternalFormatter(): IntlDateFormatter
{ {
$formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDate::ISO_DATE); $formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDate::ISO_DATE);
$formatter->setLenient(false); $formatter->setLenient(false);
@ -249,10 +226,8 @@ class DBDate extends DBField
/** /**
* Get standard ISO date format string * Get standard ISO date format string
*
* @return string
*/ */
public function getISOFormat() public function getISOFormat(): string
{ {
return DBDate::ISO_DATE; return DBDate::ISO_DATE;
} }
@ -262,16 +237,13 @@ class DBDate extends DBField
* for the day of the month ("1st", "2nd", "3rd" etc) * for the day of the month ("1st", "2nd", "3rd" etc)
* *
* @param string $format Format code string. See https://unicode-org.github.io/icu/userguide/format_parse/datetime * @param string $format Format code string. See https://unicode-org.github.io/icu/userguide/format_parse/datetime
* @param string $locale Custom locale to use (add to signature in 5.0) * @param string|null $locale Custom locale to use
* @return ?string The date in the requested format * @return string The date in the requested format
*/ */
public function Format($format) public function Format(string $format, ?string $locale = null): string
{ {
// Note: soft-arg uses func_get_args() to respect semver. Add to signature in 5.0
$locale = func_num_args() > 1 ? func_get_arg(1) : null;
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
// Replace {o} with ordinal representation of day of the month // Replace {o} with ordinal representation of day of the month
@ -285,10 +257,8 @@ class DBDate extends DBField
/** /**
* Get unix timestamp for this date * Get unix timestamp for this date
*
* @return int
*/ */
public function getTimestamp() public function getTimestamp(): int
{ {
if ($this->value) { if ($this->value) {
return strtotime($this->value ?? ''); return strtotime($this->value ?? '');
@ -299,10 +269,9 @@ class DBDate extends DBField
/** /**
* Return a date formatted as per a CMS user's settings. * Return a date formatted as per a CMS user's settings.
* *
* @param Member $member * @return string A date formatted as per user-defined settings.
* @return boolean | string A date formatted as per user-defined settings.
*/ */
public function FormatFromSettings($member = null) public function FormatFromSettings(?Member $member = null): string
{ {
if (!$member) { if (!$member) {
$member = Security::getCurrentUser(); $member = Security::getCurrentUser();
@ -512,7 +481,7 @@ class DBDate extends DBField
} }
} }
public function requireField() public function requireField(): void
{ {
$parts = ['datatype' => 'date', 'arrayValue' => $this->arrayValue]; $parts = ['datatype' => 'date', 'arrayValue' => $this->arrayValue];
$values = ['type' => 'date', 'parts' => $parts]; $values = ['type' => 'date', 'parts' => $parts];
@ -575,7 +544,7 @@ class DBDate extends DBField
return rawurlencode($this->Format(DBDate::ISO_DATE, DBDate::ISO_LOCALE) ?? ''); return rawurlencode($this->Format(DBDate::ISO_DATE, DBDate::ISO_LOCALE) ?? '');
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
$field = DateField::create($this->name, $title); $field = DateField::create($this->name, $title);
$field->setHTML5(true); $field->setHTML5(true);

View File

@ -6,10 +6,12 @@ use Exception;
use IntlDateFormatter; use IntlDateFormatter;
use InvalidArgumentException; use InvalidArgumentException;
use SilverStripe\Forms\DatetimeField; use SilverStripe\Forms\DatetimeField;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\View\TemplateGlobalProvider; use SilverStripe\View\TemplateGlobalProvider;
use SilverStripe\View\ViewableData;
/** /**
* Represents a date-time field. * Represents a date-time field.
@ -37,35 +39,36 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* Standard ISO format string for date and time in CLDR standard format, * Standard ISO format string for date and time in CLDR standard format,
* with a whitespace separating date and time (common database representation, e.g. in MySQL). * with a whitespace separating date and time (common database representation, e.g. in MySQL).
*/ */
const ISO_DATETIME = 'y-MM-dd HH:mm:ss'; public const ISO_DATETIME = 'y-MM-dd HH:mm:ss';
/** /**
* Standard ISO format string for date and time in CLDR standard format, * Standard ISO format string for date and time in CLDR standard format,
* with a "T" separator between date and time (W3C standard, e.g. for HTML5 datetime-local fields). * with a "T" separator between date and time (W3C standard, e.g. for HTML5 datetime-local fields).
*/ */
const ISO_DATETIME_NORMALISED = 'y-MM-dd\'T\'HH:mm:ss'; public const ISO_DATETIME_NORMALISED = 'y-MM-dd\'T\'HH:mm:ss';
/** /**
* Flag idicating if this field is considered immutable * Flag idicating if this field is considered immutable
* when this is enabled setting the value of this field will return a new field instance * when this is enabled setting the value of this field will return a new field instance
* instead updatin the old one * instead updatin the old one
*
* @var bool
*/ */
protected $immutable = false; protected bool $immutable = false;
/** /**
* @param bool $immutable * Used to set a specific time for "now", useful for unit tests.
* @return $this
*/ */
public function setImmutable(bool $immutable): DBDatetime protected static ?DBDatetime $mock_now = null;
/**
* Set whether this field is mutable (can be modified) or immutable (cannot be modified)
*/
public function setImmutable(bool $immutable): static
{ {
$this->immutable = $immutable; $this->immutable = $immutable;
return $this; return $this;
} }
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
if ($this->immutable) { if ($this->immutable) {
// This field is set as immutable so we have to create a new field instance // This field is set as immutable so we have to create a new field instance
@ -87,10 +90,8 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/** /**
* Returns the standard localised date * Returns the standard localised date
*
* @return string Formatted date.
*/ */
public function Date() public function Date(): string
{ {
$formatter = $this->getFormatter(IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE); $formatter = $this->getFormatter(IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE);
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -98,10 +99,8 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/** /**
* Returns the standard localised time * Returns the standard localised time
*
* @return string Formatted time.
*/ */
public function Time() public function Time(): string
{ {
$formatter = $this->getFormatter(IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM); $formatter = $this->getFormatter(IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM);
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -109,20 +108,16 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/** /**
* Returns the time in 12-hour format using the format string 'h:mm a' e.g. '1:32 pm'. * Returns the time in 12-hour format using the format string 'h:mm a' e.g. '1:32 pm'.
*
* @return string Formatted time.
*/ */
public function Time12() public function Time12(): string
{ {
return $this->Format('h:mm a'); return $this->Format('h:mm a');
} }
/** /**
* Returns the time in 24-hour format using the format string 'H:mm' e.g. '13:32'. * Returns the time in 24-hour format using the format string 'H:mm' e.g. '13:32'.
*
* @return string Formatted time.
*/ */
public function Time24() public function Time24(): string
{ {
return $this->Format('H:mm'); return $this->Format('H:mm');
} }
@ -130,10 +125,9 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/** /**
* Return a date and time formatted as per a CMS user's settings. * Return a date and time formatted as per a CMS user's settings.
* *
* @param Member $member * @return string A time and date pair formatted as per user-defined settings.
* @return boolean|string A time and date pair formatted as per user-defined settings.
*/ */
public function FormatFromSettings($member = null) public function FormatFromSettings(?Member $member = null): string
{ {
if (!$member) { if (!$member) {
$member = Security::getCurrentUser(); $member = Security::getCurrentUser();
@ -151,7 +145,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
return $this->Format($dateFormat . ' ' . $timeFormat, $member->getLocale()); return $this->Format($dateFormat . ' ' . $timeFormat, $member->getLocale());
} }
public function requireField() public function requireField(): void
{ {
$parts = [ $parts = [
'datatype' => 'datetime', 'datatype' => 'datetime',
@ -167,15 +161,13 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/** /**
* Returns the url encoded date and time in ISO 6801 format using format * Returns the url encoded date and time in ISO 6801 format using format
* string 'y-MM-dd%20HH:mm:ss' e.g. '2014-02-28%2013:32:22'. * string 'y-MM-dd%20HH:mm:ss' e.g. '2014-02-28%2013:32:22'.
*
* @return string Formatted date and time.
*/ */
public function URLDatetime() public function URLDatetime(): string
{ {
return rawurlencode($this->Format(DBDatetime::ISO_DATETIME, DBDatetime::ISO_LOCALE) ?? ''); return rawurlencode($this->Format(DBDatetime::ISO_DATETIME, DBDatetime::ISO_LOCALE) ?? '');
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
$field = DatetimeField::create($this->name, $title); $field = DatetimeField::create($this->name, $title);
$dateTimeFormat = $field->getDatetimeFormat(); $dateTimeFormat = $field->getDatetimeFormat();
@ -195,18 +187,11 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
return $field; return $field;
} }
/**
*
*/
protected static $mock_now = null;
/** /**
* Returns either the current system date as determined * Returns either the current system date as determined
* by date(), or a mocked date through {@link set_mock_now()}. * by date(), or a mocked date through {@link set_mock_now()}.
*
* @return static
*/ */
public static function now() public static function now(): static
{ {
$time = DBDatetime::$mock_now ? DBDatetime::$mock_now->Value : time(); $time = DBDatetime::$mock_now ? DBDatetime::$mock_now->Value : time();
@ -224,7 +209,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* @param DBDatetime|string $datetime Either in object format, or as a DBDatetime compatible string. * @param DBDatetime|string $datetime Either in object format, or as a DBDatetime compatible string.
* @throws Exception * @throws Exception
*/ */
public static function set_mock_now($datetime) public static function set_mock_now(DBDatetime|string $datetime): void
{ {
if (!$datetime instanceof DBDatetime) { if (!$datetime instanceof DBDatetime) {
$value = $datetime; $value = $datetime;
@ -240,20 +225,15 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* Clear any mocked date, which causes * Clear any mocked date, which causes
* {@link Now()} to return the current system date. * {@link Now()} to return the current system date.
*/ */
public static function clear_mock_now() public static function clear_mock_now(): void
{ {
DBDatetime::$mock_now = null; DBDatetime::$mock_now = null;
} }
/** /**
* Run a callback with specific time, original mock value is retained after callback * Run a callback with specific time, original mock value is retained after callback
*
* @param DBDatetime|string $time
* @param callable $callback
* @return mixed
* @throws Exception
*/ */
public static function withFixedNow($time, $callback) public static function withFixedNow(DBDatetime|string $time, callable $callback): mixed
{ {
$original = DBDatetime::$mock_now; $original = DBDatetime::$mock_now;
@ -266,7 +246,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
} }
} }
public static function get_template_global_variables() public static function get_template_global_variables(): array
{ {
return [ return [
'Now' => ['method' => 'now', 'casting' => 'Datetime'], 'Now' => ['method' => 'now', 'casting' => 'Datetime'],
@ -275,13 +255,11 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/** /**
* Get date / time formatter for the current locale * Get date / time formatter for the current locale
*
* @param int $dateLength
* @param int $timeLength
* @return IntlDateFormatter
*/ */
public function getFormatter($dateLength = IntlDateFormatter::MEDIUM, $timeLength = IntlDateFormatter::SHORT) public function getFormatter(
{ int $dateLength = IntlDateFormatter::MEDIUM,
int $timeLength = IntlDateFormatter::SHORT
): IntlDateFormatter {
return parent::getFormatter($dateLength, $timeLength); return parent::getFormatter($dateLength, $timeLength);
} }
@ -291,16 +269,13 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* *
* @param string|null $locale The current locale, or null to use default * @param string|null $locale The current locale, or null to use default
* @param string|null $pattern Custom pattern to use for this, if required * @param string|null $pattern Custom pattern to use for this, if required
* @param int $dateLength
* @param int $timeLength
* @return IntlDateFormatter
*/ */
public function getCustomFormatter( public function getCustomFormatter(
$locale = null, ?string $locale = null,
$pattern = null, ?string $pattern = null,
$dateLength = IntlDateFormatter::MEDIUM, int $dateLength = IntlDateFormatter::MEDIUM,
$timeLength = IntlDateFormatter::MEDIUM int $timeLength = IntlDateFormatter::MEDIUM
) { ): IntlDateFormatter {
return parent::getCustomFormatter($locale, $pattern, $dateLength, $timeLength); return parent::getCustomFormatter($locale, $pattern, $dateLength, $timeLength);
} }
@ -308,9 +283,8 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* Formatter used internally * Formatter used internally
* *
* @internal * @internal
* @return IntlDateFormatter
*/ */
protected function getInternalFormatter() protected function getInternalFormatter(): IntlDateFormatter
{ {
$formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDatetime::ISO_DATETIME); $formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDatetime::ISO_DATETIME);
$formatter->setLenient(false); $formatter->setLenient(false);
@ -319,10 +293,8 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/** /**
* Get standard ISO date format string * Get standard ISO date format string
*
* @return string
*/ */
public function getISOFormat() public function getISOFormat(): string
{ {
return DBDatetime::ISO_DATETIME; return DBDatetime::ISO_DATETIME;
} }

View File

@ -2,71 +2,55 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\NumericField; use SilverStripe\Forms\NumericField;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\View\ViewableData;
/** /**
* Represents a Decimal field. * Represents a Decimal field.
*/ */
class DBDecimal extends DBField class DBDecimal extends DBField
{ {
/** /**
* Whole number size * Whole number size
*
* @var int
*/ */
protected $wholeSize = 9; protected int $wholeSize = 9;
/** /**
* Decimal scale * Decimal scale
*
* @var int
*/ */
protected $decimalSize = 2; protected int $decimalSize = 2;
/** /**
* Default value * Default value
*
* @var string
*/ */
protected $defaultValue = 0; protected float|int|string $defaultValue = 0;
/** /**
* Create a new Decimal field. * Create a new Decimal field.
*
* @param string $name
* @param int $wholeSize
* @param int $decimalSize
* @param float|int $defaultValue
*/ */
public function __construct($name = null, $wholeSize = 9, $decimalSize = 2, $defaultValue = 0) public function __construct(?string $name = null, ?int $wholeSize = 9, ?int $decimalSize = 2, float|int $defaultValue = 0)
{ {
$this->wholeSize = is_int($wholeSize) ? $wholeSize : 9; $this->wholeSize = is_int($wholeSize) ? $wholeSize : 9;
$this->decimalSize = is_int($decimalSize) ? $decimalSize : 2; $this->decimalSize = is_int($decimalSize) ? $decimalSize : 2;
$this->defaultValue = number_format((float) $defaultValue, $decimalSize ?? 0); $this->defaultValue = number_format((float) $defaultValue, $this->decimalSize);
parent::__construct($name); parent::__construct($name);
} }
/** public function Nice(): string
* @return float
*/
public function Nice()
{ {
return number_format($this->value ?? 0.0, $this->decimalSize ?? 0); return number_format($this->value ?? 0.0, $this->decimalSize ?? 0);
} }
/** public function Int(): int
* @return int
*/
public function Int()
{ {
return floor($this->value ?? 0.0); return floor($this->value ?? 0.0);
} }
public function requireField() public function requireField(): void
{ {
$parts = [ $parts = [
'datatype' => 'decimal', 'datatype' => 'decimal',
@ -83,16 +67,16 @@ class DBDecimal extends DBField
DB::require_field($this->tableName, $this->name, $values); DB::require_field($this->tableName, $this->name, $values);
} }
public function saveInto($dataObject) public function saveInto(ViewableData $model): void
{ {
$fieldName = $this->name; $fieldName = $this->name;
if ($fieldName) { if ($fieldName) {
if ($this->value instanceof DBField) { if ($this->value instanceof DBField) {
$this->value->saveInto($dataObject); $this->value->saveInto($model);
} else { } else {
$value = (float) preg_replace('/[^0-9.\-\+]/', '', $this->value ?? ''); $value = (float) preg_replace('/[^0-9.\-\+]/', '', $this->value ?? '');
$dataObject->__set($fieldName, $value); $model->__set($fieldName, $value);
} }
} else { } else {
throw new \UnexpectedValueException( throw new \UnexpectedValueException(
@ -101,27 +85,18 @@ class DBDecimal extends DBField
} }
} }
/** public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
* @param string $title
* @param array $params
*
* @return NumericField
*/
public function scaffoldFormField($title = null, $params = null)
{ {
return NumericField::create($this->name, $title) return NumericField::create($this->name, $title)
->setScale($this->decimalSize); ->setScale($this->decimalSize);
} }
/** public function nullValue(): ?int
* @return float
*/
public function nullValue()
{ {
return 0; return 0;
} }
public function prepValueForDB($value) public function prepValueForDB(mixed $value): array|float|int|null
{ {
if ($value === true) { if ($value === true) {
return 1; return 1;

View File

@ -11,7 +11,7 @@ use SilverStripe\ORM\DB;
class DBDouble extends DBFloat class DBDouble extends DBFloat
{ {
public function requireField() public function requireField(): void
{ {
// HACK: MSSQL does not support double so we're using float instead // HACK: MSSQL does not support double so we're using float instead
if (DB::get_conn() instanceof MySQLDatabase) { if (DB::get_conn() instanceof MySQLDatabase) {

View File

@ -4,6 +4,8 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\SelectField;
use SilverStripe\ORM\ArrayLib; use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\Connect\MySQLDatabase; use SilverStripe\ORM\Connect\MySQLDatabase;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
@ -15,35 +17,28 @@ use SilverStripe\ORM\DB;
*/ */
class DBEnum extends DBString class DBEnum extends DBString
{ {
/** /**
* List of enum values * List of enum values
*
* @var array
*/ */
protected $enum = []; protected array $enum = [];
/** /**
* Default value * Default value
*
* @var string|null
*/ */
protected $default = null; protected ?string $default = null;
private static $default_search_filter_class = 'ExactMatchFilter'; private static string $default_search_filter_class = 'ExactMatchFilter';
/** /**
* Internal cache for obsolete enum values. The top level keys are the table, each of which contains * Internal cache for obsolete enum values. The top level keys are the table, each of which contains
* nested arrays with keys mapped to field names. The values of the lowest level array are the enum values * nested arrays with keys mapped to field names. The values of the lowest level array are the enum values
*
* @var array
*/ */
protected static $enum_cache = []; protected static array $enum_cache = [];
/** /**
* Clear all cached enum values. * Clear all cached enum values.
*/ */
public static function flushCache() public static function flushCache(): void
{ {
DBEnum::$enum_cache = []; DBEnum::$enum_cache = [];
} }
@ -60,15 +55,18 @@ class DBEnum extends DBString
* "MyField" => "Enum(['Val1', 'Val2', 'Val3'], 'Val1')" // Supports array notation as well * "MyField" => "Enum(['Val1', 'Val2', 'Val3'], 'Val1')" // Supports array notation as well
* </code> * </code>
* *
* @param string $name
* @param string|array $enum A string containing a comma separated list of options or an array of Vals. * @param string|array $enum A string containing a comma separated list of options or an array of Vals.
* @param string|int|null $default The default option, which is either NULL or one of the items in the enumeration. * @param string|int|null $default The default option, which is either NULL or one of the items in the enumeration.
* If passing in an integer (non-string) it will default to the index of that item in the list. * If passing in an integer (non-string) it will default to the index of that item in the list.
* Set to null or empty string to allow empty values * Set to null or empty string to allow empty values
* @param array $options Optional parameters for this DB field * @param array $options Optional parameters for this DB field
*/ */
public function __construct($name = null, $enum = null, $default = 0, $options = []) public function __construct(
{ ?string $name = null,
string|array|null $enum = null,
string|int|null $default = 0,
array $options = []
) {
if ($enum) { if ($enum) {
$this->setEnum($enum); $this->setEnum($enum);
$enum = $this->getEnum(); $enum = $this->getEnum();
@ -94,10 +92,7 @@ class DBEnum extends DBString
parent::__construct($name, $options); parent::__construct($name, $options);
} }
/** public function requireField(): void
* @return void
*/
public function requireField()
{ {
$charset = Config::inst()->get(MySQLDatabase::class, 'charset'); $charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation'); $collation = Config::inst()->get(MySQLDatabase::class, 'collation');
@ -121,18 +116,15 @@ class DBEnum extends DBString
} }
/** /**
* Return a dropdown field suitable for editing this field. * Return a form field suitable for editing this field.
*
* @param string $title
* @param string $name
* @param bool $hasEmpty
* @param string $value
* @param string $emptyString
* @return DropdownField
*/ */
public function formField($title = null, $name = null, $hasEmpty = false, $value = '', $emptyString = null) public function formField(
{ ?string $title = null,
?string $name = null,
bool $hasEmpty = false,
?string $value = '',
?string $emptyString = null
): SelectField {
if (!$title) { if (!$title) {
$title = $this->getName(); $title = $this->getName();
} }
@ -148,16 +140,12 @@ class DBEnum extends DBString
return $field; return $field;
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return $this->formField($title); return $this->formField($title);
} }
/** public function scaffoldSearchField(?string $title = null): ?FormField
* @param string $title
* @return DropdownField
*/
public function scaffoldSearchField($title = null)
{ {
$anyText = _t(__CLASS__ . '.ANY', 'Any'); $anyText = _t(__CLASS__ . '.ANY', 'Any');
return $this->formField($title, null, true, '', "($anyText)"); return $this->formField($title, null, true, '', "($anyText)");
@ -166,12 +154,8 @@ class DBEnum extends DBString
/** /**
* Returns the values of this enum as an array, suitable for insertion into * Returns the values of this enum as an array, suitable for insertion into
* a {@link DropdownField} * a {@link DropdownField}
*
* @param bool $hasEmpty
*
* @return array
*/ */
public function enumValues($hasEmpty = false) public function enumValues(bool $hasEmpty = false): array
{ {
return ($hasEmpty) return ($hasEmpty)
? array_merge(['' => ''], ArrayLib::valuekey($this->getEnum())) ? array_merge(['' => ''], ArrayLib::valuekey($this->getEnum()))
@ -180,15 +164,12 @@ class DBEnum extends DBString
/** /**
* Get list of enum values * Get list of enum values
*
* @return array
*/ */
public function getEnum() public function getEnum(): array
{ {
return $this->enum; return $this->enum;
} }
/** /**
* Get the list of enum values, including obsolete values still present in the database * Get the list of enum values, including obsolete values still present in the database
* *
@ -196,10 +177,8 @@ class DBEnum extends DBString
* then only known enum values are returned. * then only known enum values are returned.
* *
* Values cached in this method can be cleared via `DBEnum::flushCache();` * Values cached in this method can be cleared via `DBEnum::flushCache();`
*
* @return array
*/ */
public function getEnumObsolete() public function getEnumObsolete(): array
{ {
// Without a table or field specified, we can only retrieve known enum values // Without a table or field specified, we can only retrieve known enum values
$table = $this->getTable(); $table = $this->getTable();
@ -232,11 +211,8 @@ class DBEnum extends DBString
/** /**
* Set enum options * Set enum options
*
* @param string|array $enum
* @return $this
*/ */
public function setEnum($enum) public function setEnum(string|array $enum): static
{ {
if (!is_array($enum)) { if (!is_array($enum)) {
$enum = preg_split( $enum = preg_split(
@ -250,22 +226,17 @@ class DBEnum extends DBString
} }
/** /**
* Get default vwalue * Get default value
*
* @return string|null
*/ */
public function getDefault() public function getDefault(): ?string
{ {
return $this->default; return $this->default;
} }
/** /**
* Set default value * Set default value
*
* @param string $default
* @return $this
*/ */
public function setDefault($default) public function setDefault(?string $default): static
{ {
$this->default = $default; $this->default = $default;
$this->setDefaultValue($default); $this->setDefaultValue($default);

View File

@ -7,7 +7,6 @@ use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FormField; use SilverStripe\Forms\FormField;
use SilverStripe\Forms\TextField; use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Filters\SearchFilter; use SilverStripe\ORM\Filters\SearchFilter;
use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\View\ViewableData; use SilverStripe\View\ViewableData;
@ -36,7 +35,7 @@ use SilverStripe\View\ViewableData;
* *
* <code> * <code>
* class Blob extends DBField { * class Blob extends DBField {
* function requireField() { * function requireField(): void {
* DB::require_field($this->tableName, $this->name, "blob"); * DB::require_field($this->tableName, $this->name, "blob");
* } * }
* } * }
@ -47,65 +46,47 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Raw value of this field * Raw value of this field
*
* @var mixed
*/ */
protected $value; protected mixed $value = null;
/** /**
* Table this field belongs to * Table this field belongs to
*
* @var string
*/ */
protected $tableName; protected ?string $tableName = null;
/** /**
* Name of this field * Name of this field
*
* @var string
*/ */
protected $name; protected ?string $name = null;
/** /**
* Used for generating DB schema. {@see DBSchemaManager} * Used for generating DB schema. {@see DBSchemaManager}
* * Despite its name, this seems to be a string
* @var array
*/ */
protected $arrayValue; protected $arrayValue;
/** /**
* Optional parameters for this field * Optional parameters for this field
*
* @var array
*/ */
protected $options = []; protected array $options = [];
/** /**
* The escape type for this field when inserted into a template - either "xml" or "raw". * The escape type for this field when inserted into a template - either "xml" or "raw".
*
* @var string
* @config
*/ */
private static $escape_type = 'raw'; private static string $escape_type = 'raw';
/** /**
* Subclass of {@link SearchFilter} for usage in {@link defaultSearchFilter()}. * Subclass of {@link SearchFilter} for usage in {@link defaultSearchFilter()}.
*
* @var string
* @config
*/ */
private static $default_search_filter_class = 'PartialMatchFilter'; private static string $default_search_filter_class = 'PartialMatchFilter';
/** /**
* The type of index to use for this field. Can either be a string (one of the DBIndexable type options) or a * The type of index to use for this field. Can either be a string (one of the DBIndexable type options) or a
* boolean. When a boolean is given, false will not index the field, and true will use the default index type. * boolean. When a boolean is given, false will not index the field, and true will use the default index type.
*
* @var string|bool
* @config
*/ */
private static $index = false; private static string|bool $index = false;
private static $casting = [ private static array $casting = [
'ATT' => 'HTMLFragment', 'ATT' => 'HTMLFragment',
'CDATA' => 'HTMLFragment', 'CDATA' => 'HTMLFragment',
'HTML' => 'HTMLFragment', 'HTML' => 'HTMLFragment',
@ -119,20 +100,18 @@ abstract class DBField extends ViewableData implements DBIndexable
]; ];
/** /**
* @var $default mixed Default-value in the database. * Default value in the database.
* Might be overridden on DataObject-level, but still useful for setting defaults on * Might be overridden on DataObject-level, but still useful for setting defaults on
* already existing records after a db-build. * already existing records after a db-build.
*/ */
protected $defaultVal; protected mixed $defaultVal = null;
/** /**
* Provide the DBField name and an array of options, e.g. ['index' => true], or ['nullifyEmpty' => false] * Provide the DBField name and an array of options, e.g. ['index' => true], or ['nullifyEmpty' => false]
* *
* @param string $name
* @param array $options
* @throws InvalidArgumentException If $options was passed by not an array * @throws InvalidArgumentException If $options was passed by not an array
*/ */
public function __construct($name = null, $options = []) public function __construct(?string $name = null, array $options = [])
{ {
$this->name = $name; $this->name = $name;
@ -154,12 +133,11 @@ abstract class DBField extends ViewableData implements DBIndexable
* @param string $spec Class specification to construct. May include both service name and additional * @param string $spec Class specification to construct. May include both service name and additional
* constructor arguments in the same format as DataObject.db config. * constructor arguments in the same format as DataObject.db config.
* @param mixed $value value of field * @param mixed $value value of field
* @param string $name Name of field * @param null|string $name Name of field
* @param mixed $args Additional arguments to pass to constructor if not using args in service $spec * @param mixed $args Additional arguments to pass to constructor if not using args in service $spec
* Note: Will raise a warning if using both * Note: Will raise a warning if using both
* @return static
*/ */
public static function create_field($spec, $value, $name = null, ...$args) public static function create_field(string $spec, mixed $value, ?string $name = null, mixed ...$args): static
{ {
// Raise warning if inconsistent with DataObject::dbObject() behaviour // Raise warning if inconsistent with DataObject::dbObject() behaviour
// This will cause spec args to be shifted down by the number of provided $args // This will cause spec args to be shifted down by the number of provided $args
@ -182,12 +160,8 @@ abstract class DBField extends ViewableData implements DBIndexable
* the first place you can set a name. * the first place you can set a name.
* *
* If you try an alter the name a warning will be thrown. * If you try an alter the name a warning will be thrown.
*
* @param string $name
*
* @return $this
*/ */
public function setName($name) public function setName(?string $name): static
{ {
if ($this->name && $this->name !== $name) { if ($this->name && $this->name !== $name) {
user_error("DBField::setName() shouldn't be called once a DBField already has a name." user_error("DBField::setName() shouldn't be called once a DBField already has a name."
@ -201,20 +175,16 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Returns the name of this field. * Returns the name of this field.
*
* @return string
*/ */
public function getName() public function getName(): string
{ {
return $this->name; return $this->name ?? '';
} }
/** /**
* Returns the value of this field. * Returns the value of this field.
*
* @return mixed
*/ */
public function getValue() public function getValue(): mixed
{ {
return $this->value; return $this->value;
} }
@ -228,14 +198,12 @@ abstract class DBField extends ViewableData implements DBIndexable
* and actually changing its values, it needs a {@link $markChanged} * and actually changing its values, it needs a {@link $markChanged}
* parameter. * parameter.
* *
* @param mixed $value * @param null|ViewableData|array $record An array or object that this field is part of
* @param DataObject|array $record An array or object that this field is part of
* @param bool $markChanged Indicate whether this field should be marked changed. * @param bool $markChanged Indicate whether this field should be marked changed.
* Set to FALSE if you are initializing this field after construction, rather * Set to FALSE if you are initializing this field after construction, rather
* than setting a new value. * than setting a new value.
* @return $this
*/ */
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
$this->value = $value; $this->value = $value;
return $this; return $this;
@ -243,21 +211,16 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Get default value assigned at the DB level * Get default value assigned at the DB level
*
* @return mixed
*/ */
public function getDefaultValue() public function getDefaultValue(): mixed
{ {
return $this->defaultVal; return $this->defaultVal;
} }
/** /**
* Set default value to use at the DB level * Set default value to use at the DB level
*
* @param mixed $defaultValue
* @return $this
*/ */
public function setDefaultValue($defaultValue) public function setDefaultValue(mixed $defaultValue): static
{ {
$this->defaultVal = $defaultValue; $this->defaultVal = $defaultValue;
return $this; return $this;
@ -265,11 +228,8 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Update the optional parameters for this field * Update the optional parameters for this field
*
* @param array $options Array of options
* @return $this
*/ */
public function setOptions(array $options = []) public function setOptions(array $options = []): static
{ {
$this->options = $options; $this->options = $options;
return $this; return $this;
@ -277,15 +237,13 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Get optional parameters for this field * Get optional parameters for this field
*
* @return array
*/ */
public function getOptions() public function getOptions(): array
{ {
return $this->options; return $this->options;
} }
public function setIndexType($type) public function setIndexType($type): string|bool
{ {
if (!is_bool($type) if (!is_bool($type)
&& !in_array($type, [DBIndexable::TYPE_INDEX, DBIndexable::TYPE_UNIQUE, DBIndexable::TYPE_FULLTEXT]) && !in_array($type, [DBIndexable::TYPE_INDEX, DBIndexable::TYPE_UNIQUE, DBIndexable::TYPE_FULLTEXT])
@ -320,10 +278,8 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Determines if the field has a value which is not considered to be 'null' * Determines if the field has a value which is not considered to be 'null'
* in a database context. * in a database context.
*
* @return boolean
*/ */
public function exists() public function exists(): bool
{ {
return (bool)$this->value; return (bool)$this->value;
} }
@ -336,7 +292,7 @@ abstract class DBField extends ViewableData implements DBIndexable
* @param mixed $value The value to check * @param mixed $value The value to check
* @return mixed The raw value, or escaped parameterised details * @return mixed The raw value, or escaped parameterised details
*/ */
public function prepValueForDB($value) public function prepValueForDB(mixed $value): mixed
{ {
if ($value === null || if ($value === null ||
$value === "" || $value === "" ||
@ -358,10 +314,8 @@ abstract class DBField extends ViewableData implements DBIndexable
* can also be used to apply special SQL-commands * can also be used to apply special SQL-commands
* to the raw value (e.g. for GIS functionality). * to the raw value (e.g. for GIS functionality).
* {@see prepValueForDB} * {@see prepValueForDB}
*
* @param array $manipulation
*/ */
public function writeToManipulation(&$manipulation) public function writeToManipulation(array &$manipulation): void
{ {
$manipulation['fields'][$this->name] = $this->exists() $manipulation['fields'][$this->name] = $this->exists()
? $this->prepValueForDB($this->value) : $this->nullValue(); ? $this->prepValueForDB($this->value) : $this->nullValue();
@ -375,20 +329,15 @@ abstract class DBField extends ViewableData implements DBIndexable
* SELECT <tablename>.* which * SELECT <tablename>.* which
* gets you the default representations * gets you the default representations
* of all columns. * of all columns.
*
* @param SQLSelect $query
*/ */
public function addToQuery(&$query) public function addToQuery(SQLSelect &$query)
{ {
} }
/** /**
* Assign this DBField to a table * Assign this DBField to a table
*
* @param string $tableName
* @return $this
*/ */
public function setTable($tableName) public function setTable(string $tableName): static
{ {
$this->tableName = $tableName; $this->tableName = $tableName;
return $this; return $this;
@ -396,20 +345,16 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Get the table this field belongs to, if assigned * Get the table this field belongs to, if assigned
*
* @return string|null
*/ */
public function getTable() public function getTable(): ?string
{ {
return $this->tableName; return $this->tableName;
} }
/** /**
* Determine 'default' casting for this field. * Determine 'default' casting for this field.
*
* @return string
*/ */
public function forTemplate() public function forTemplate(): string
{ {
// Default to XML encoding // Default to XML encoding
return $this->XML(); return $this->XML();
@ -417,40 +362,32 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Gets the value appropriate for a HTML attribute string * Gets the value appropriate for a HTML attribute string
*
* @return string
*/ */
public function HTMLATT() public function HTMLATT(): string
{ {
return Convert::raw2htmlatt($this->RAW()); return Convert::raw2htmlatt($this->RAW());
} }
/** /**
* urlencode this string * urlencode this string
*
* @return string
*/ */
public function URLATT() public function URLATT(): string
{ {
return urlencode($this->RAW() ?? ''); return urlencode($this->RAW() ?? '');
} }
/** /**
* rawurlencode this string * rawurlencode this string
*
* @return string
*/ */
public function RAWURLATT() public function RAWURLATT(): string
{ {
return rawurlencode($this->RAW() ?? ''); return rawurlencode($this->RAW() ?? '');
} }
/** /**
* Gets the value appropriate for a HTML attribute string * Gets the value appropriate for a HTML attribute string
*
* @return string
*/ */
public function ATT() public function ATT(): string
{ {
return Convert::raw2att($this->RAW()); return Convert::raw2att($this->RAW());
} }
@ -458,60 +395,48 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Gets the raw value for this field. * Gets the raw value for this field.
* Note: Skips processors implemented via forTemplate() * Note: Skips processors implemented via forTemplate()
*
* @return mixed
*/ */
public function RAW() public function RAW(): mixed
{ {
return $this->getValue(); return $this->getValue();
} }
/** /**
* Gets javascript string literal value * Gets javascript string literal value
*
* @return string
*/ */
public function JS() public function JS(): string
{ {
return Convert::raw2js($this->RAW()); return Convert::raw2js($this->RAW());
} }
/** /**
* Return JSON encoded value * Return JSON encoded value
*
* @return string
*/ */
public function JSON() public function JSON(): string
{ {
return json_encode($this->RAW()); return json_encode($this->RAW());
} }
/** /**
* Alias for {@see XML()} * Alias for {@see XML()}
*
* @return string
*/ */
public function HTML() public function HTML(): string
{ {
return $this->XML(); return $this->XML();
} }
/** /**
* XML encode this value * XML encode this value
*
* @return string
*/ */
public function XML() public function XML(): string
{ {
return Convert::raw2xml($this->RAW()); return Convert::raw2xml($this->RAW());
} }
/** /**
* Safely escape for XML string * Safely escape for XML string
*
* @return string
*/ */
public function CDATA() public function CDATA(): string
{ {
return $this->XML(); return $this->XML();
} }
@ -519,20 +444,16 @@ abstract class DBField extends ViewableData implements DBIndexable
/** /**
* Returns the value to be set in the database to blank this field. * Returns the value to be set in the database to blank this field.
* Usually it's a choice between null, 0, and '' * Usually it's a choice between null, 0, and ''
*
* @return mixed
*/ */
public function nullValue() public function nullValue(): mixed
{ {
return null; return null;
} }
/** /**
* Saves this field to the given data object. * Saves this field to the given data object.
*
* @param DataObject $dataObject
*/ */
public function saveInto($dataObject) public function saveInto(ViewableData $model): void
{ {
$fieldName = $this->name; $fieldName = $this->name;
if (empty($fieldName)) { if (empty($fieldName)) {
@ -541,9 +462,9 @@ abstract class DBField extends ViewableData implements DBIndexable
); );
} }
if ($this->value instanceof DBField) { if ($this->value instanceof DBField) {
$this->value->saveInto($dataObject); $this->value->saveInto($model);
} else { } else {
$dataObject->__set($fieldName, $this->value); $model->__set($fieldName, $this->value);
} }
} }
@ -554,10 +475,8 @@ abstract class DBField extends ViewableData implements DBIndexable
* Used by {@link SearchContext}, {@link ModelAdmin}, {@link DataObject::scaffoldFormFields()} * Used by {@link SearchContext}, {@link ModelAdmin}, {@link DataObject::scaffoldFormFields()}
* *
* @param string $title Optional. Localized title of the generated instance * @param string $title Optional. Localized title of the generated instance
* @param array $params
* @return FormField
*/ */
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return TextField::create($this->name, $title); return TextField::create($this->name, $title);
} }
@ -569,30 +488,28 @@ abstract class DBField extends ViewableData implements DBIndexable
* Used by {@link SearchContext}, {@link ModelAdmin}, {@link DataObject::scaffoldFormFields()}. * Used by {@link SearchContext}, {@link ModelAdmin}, {@link DataObject::scaffoldFormFields()}.
* *
* @param string $title Optional. Localized title of the generated instance * @param string $title Optional. Localized title of the generated instance
* @return FormField
*/ */
public function scaffoldSearchField($title = null) public function scaffoldSearchField(?string $title = null): ?FormField
{ {
return $this->scaffoldFormField($title); return $this->scaffoldFormField($title);
} }
/** /**
* @param string $name Override name of this field * @param string $name Override name of this field
* @return SearchFilter
*/ */
public function defaultSearchFilter($name = null) public function defaultSearchFilter(?string $name = null): SearchFilter
{ {
$name = ($name) ? $name : $this->name; $name = ($name) ? $name : $this->name;
$filterClass = $this->config()->get('default_search_filter_class'); $filterClass = static::config()->get('default_search_filter_class');
return Injector::inst()->create($filterClass, $name); return Injector::inst()->create($filterClass, $name);
} }
/** /**
* Add the field to the underlying database. * Add the field to the underlying database.
*/ */
abstract public function requireField(); abstract public function requireField(): void;
public function debug() public function debug(): string
{ {
return <<<DBG return <<<DBG
<ul> <ul>
@ -603,40 +520,31 @@ abstract class DBField extends ViewableData implements DBIndexable
DBG; DBG;
} }
public function __toString() public function __toString(): string
{ {
return (string)$this->forTemplate(); return (string)$this->forTemplate();
} }
/**
* @return array
*/
public function getArrayValue() public function getArrayValue()
{ {
return $this->arrayValue; return $this->arrayValue;
} }
/** public function setArrayValue($value): static
* @param array $value
* @return $this
*/
public function setArrayValue($value)
{ {
$this->arrayValue = $value; $this->arrayValue = $value;
return $this; return $this;
} }
/** /**
* Get formfield schema value * Get formfield schema value for use in formschema response
*
* @return string|array Encoded string for use in formschema response
*/ */
public function getSchemaValue() public function getSchemaValue(): mixed
{ {
return $this->RAW(); return $this->RAW();
} }
public function getIndexSpecs() public function getIndexSpecs(): ?array
{ {
$type = $this->getIndexType(); $type = $this->getIndexType();
if ($type) { if ($type) {
@ -652,9 +560,8 @@ DBG;
* Whether or not this DBField only accepts scalar values. * Whether or not this DBField only accepts scalar values.
* *
* Composite DBFields can override this method and return `false` so they can accept arrays of values. * Composite DBFields can override this method and return `false` so they can accept arrays of values.
* @return boolean
*/ */
public function scalarValueOnly() public function scalarValueOnly(): bool
{ {
return true; return true;
} }

View File

@ -2,6 +2,7 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\NumericField; use SilverStripe\Forms\NumericField;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
@ -10,15 +11,14 @@ use SilverStripe\ORM\DB;
*/ */
class DBFloat extends DBField class DBFloat extends DBField
{ {
public function __construct(?string $name = null, float|int $defaultVal = 0)
public function __construct($name = null, $defaultVal = 0)
{ {
$this->defaultVal = is_float($defaultVal) ? $defaultVal : (float) 0; $this->defaultVal = is_float($defaultVal) ? $defaultVal : (float) 0;
parent::__construct($name); parent::__construct($name);
} }
public function requireField() public function requireField(): void
{ {
$parts = [ $parts = [
'datatype' => 'float', 'datatype' => 'float',
@ -35,34 +35,34 @@ class DBFloat extends DBField
* *
* @uses number_format() * @uses number_format()
*/ */
public function Nice() public function Nice(): string
{ {
return number_format($this->value ?? 0.0, 2); return number_format($this->value ?? 0.0, 2);
} }
public function Round($precision = 3) public function Round($precision = 3): float
{ {
return round($this->value ?? 0.0, $precision ?? 0); return round($this->value ?? 0.0, $precision ?? 0);
} }
public function NiceRound($precision = 3) public function NiceRound($precision = 3): string
{ {
return number_format(round($this->value ?? 0.0, $precision ?? 0), $precision ?? 0); return number_format(round($this->value ?? 0.0, $precision ?? 0), $precision ?? 0);
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
$field = NumericField::create($this->name, $title); $field = NumericField::create($this->name, $title);
$field->setScale(null); // remove no-decimal restriction $field->setScale(null); // remove no-decimal restriction
return $field; return $field;
} }
public function nullValue() public function nullValue(): ?int
{ {
return 0; return 0;
} }
public function prepValueForDB($value) public function prepValueForDB(mixed $value): array|float|int|null
{ {
if ($value === true) { if ($value === true) {
return 1; return 1;

View File

@ -6,9 +6,11 @@ use SilverStripe\Assets\File;
use SilverStripe\Assets\Image; use SilverStripe\Assets\Image;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FileHandleField; use SilverStripe\Forms\FileHandleField;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\SearchableDropdownField; use SilverStripe\Forms\SearchableDropdownField;
use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\View\ViewableData;
/** /**
* A special type Int field used for foreign keys in has_one relationships. * A special type Int field used for foreign keys in has_one relationships.
@ -22,40 +24,26 @@ use SilverStripe\ORM\DataObject;
*/ */
class DBForeignKey extends DBInt class DBForeignKey extends DBInt
{ {
/** protected ?DataObject $object;
* @var DataObject
*/
protected $object;
/** /**
* Number of related objects to show in a scaffolded searchable dropdown field before it * Number of related objects to show in a scaffolded searchable dropdown field before it
* switches to using lazyloading. * switches to using lazyloading.
* This will also be used as the lazy load limit * This will also be used as the lazy load limit
*
* @config
* @var int
*/ */
private static $dropdown_field_threshold = 100; private static int $dropdown_field_threshold = 100;
private static $index = true; private static string|bool $index = true;
private static $default_search_filter_class = 'ExactMatchFilter'; private static string $default_search_filter_class = 'ExactMatchFilter';
/** public function __construct(?string $name, ?DataObject $object = null)
* Cache for multiple subsequent calls to scaffold form fields with the same foreign key object
*
* @var array
* @deprecated 5.2.0 Will be removed without equivalent functionality to replace it
*/
protected static $foreignListCache = [];
public function __construct($name, $object = null)
{ {
$this->object = $object; $this->object = $object;
parent::__construct($name); parent::__construct($name);
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
if (empty($this->object)) { if (empty($this->object)) {
return null; return null;
@ -70,11 +58,11 @@ class DBForeignKey extends DBInt
return $field; return $field;
} }
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
if ($record instanceof DataObject) { if ($record instanceof DataObject) {
$this->object = $record; $this->object = $record;
} }
parent::setValue($value, $record, $markChanged); return parent::setValue($value, $record, $markChanged);
} }
} }

View File

@ -4,6 +4,7 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\Control\HTTP; use SilverStripe\Control\HTTP;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\HTMLEditor\HTMLEditorField; use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\TextField; use SilverStripe\Forms\TextField;
use SilverStripe\View\Parsers\HTMLValue; use SilverStripe\View\Parsers\HTMLValue;
@ -25,9 +26,9 @@ use SilverStripe\View\Parsers\ShortcodeParser;
*/ */
class DBHTMLText extends DBText class DBHTMLText extends DBText
{ {
private static $escape_type = 'xml'; private static string $escape_type = 'xml';
private static $casting = [ private static array $casting = [
"AbsoluteLinks" => "HTMLFragment", "AbsoluteLinks" => "HTMLFragment",
// DBString conversion / summary methods // DBString conversion / summary methods
// Not overridden, but returns HTML instead of plain text. // Not overridden, but returns HTML instead of plain text.
@ -37,68 +38,52 @@ class DBHTMLText extends DBText
/** /**
* Enable shortcode parsing on this field * Enable shortcode parsing on this field
*
* @var bool
*/ */
protected $processShortcodes = false; protected bool $processShortcodes = false;
/**
* List of html properties to whitelist
*/
protected array $whitelist = [];
/** /**
* Check if shortcodes are enabled * Check if shortcodes are enabled
*
* @return bool
*/ */
public function getProcessShortcodes() public function getProcessShortcodes(): bool
{ {
return $this->processShortcodes; return $this->processShortcodes;
} }
/** /**
* Set shortcodes on or off by default * Set shortcodes on or off by default
*
* @param bool $process
* @return $this
*/ */
public function setProcessShortcodes($process) public function setProcessShortcodes(bool $process): static
{ {
$this->processShortcodes = (bool)$process; $this->processShortcodes = $process;
return $this; return $this;
} }
/** /**
* List of html properties to whitelist * List of html properties to whitelist
*
* @var array
*/ */
protected $whitelist = []; public function getWhitelist(): array
/**
* List of html properties to whitelist
*
* @return array
*/
public function getWhitelist()
{ {
return $this->whitelist; return $this->whitelist;
} }
/** /**
* Set list of html properties to whitelist * Set list of html properties to whitelist
*
* @param array $whitelist
* @return $this
*/ */
public function setWhitelist($whitelist) public function setWhitelist(string|array $whitelist): static
{ {
if (!is_array($whitelist)) { if (!is_array($whitelist)) {
$whitelist = preg_split('/\s*,\s*/', $whitelist ?? ''); $whitelist = preg_split('/\s*,\s*/', $whitelist);
} }
$this->whitelist = $whitelist; $this->whitelist = $whitelist;
return $this; return $this;
} }
/** /**
* @param array $options
*
* Options accepted in addition to those provided by Text: * Options accepted in addition to those provided by Text:
* *
* - shortcodes: If true, shortcodes will be turned into the appropriate HTML. * - shortcodes: If true, shortcodes will be turned into the appropriate HTML.
@ -110,10 +95,8 @@ class DBHTMLText extends DBText
* Text nodes outside of HTML tags are filtered out by default, but may be included by adding * Text nodes outside of HTML tags are filtered out by default, but may be included by adding
* the text() directive. E.g. 'link,meta,text()' will allow only <link /> <meta /> and text at * the text() directive. E.g. 'link,meta,text()' will allow only <link /> <meta /> and text at
* the root level. * the root level.
*
* @return $this
*/ */
public function setOptions(array $options = []) public function setOptions(array $options = []): static
{ {
if (array_key_exists("shortcodes", $options ?? [])) { if (array_key_exists("shortcodes", $options ?? [])) {
$this->setProcessShortcodes(!!$options["shortcodes"]); $this->setProcessShortcodes(!!$options["shortcodes"]);
@ -126,7 +109,7 @@ class DBHTMLText extends DBText
return parent::setOptions($options); return parent::setOptions($options);
} }
public function RAW() public function RAW(): ?string
{ {
if ($this->processShortcodes) { if ($this->processShortcodes) {
return ShortcodeParser::get_active()->parse($this->value); return ShortcodeParser::get_active()->parse($this->value);
@ -136,25 +119,22 @@ class DBHTMLText extends DBText
/** /**
* Return the value of the field with relative links converted to absolute urls (with placeholders parsed). * Return the value of the field with relative links converted to absolute urls (with placeholders parsed).
* @return string
*/ */
public function AbsoluteLinks() public function AbsoluteLinks(): string
{ {
return HTTP::absoluteURLs($this->forTemplate()); return HTTP::absoluteURLs($this->forTemplate());
} }
public function forTemplate() public function forTemplate(): string
{ {
// Suppress XML encoding for DBHtmlText // Suppress XML encoding for DBHtmlText
return $this->RAW(); return $this->RAW() ?? '';
} }
/** /**
* Safely escape for XML string * Safely escape for XML string
*
* @return string
*/ */
public function CDATA() public function CDATA(): string
{ {
return sprintf( return sprintf(
'<![CDATA[%s]]>', '<![CDATA[%s]]>',
@ -162,7 +142,7 @@ class DBHTMLText extends DBText
); );
} }
public function prepValueForDB($value) public function prepValueForDB(mixed $value): array|string|null
{ {
return parent::prepValueForDB($this->whitelistContent($value)); return parent::prepValueForDB($this->whitelistContent($value));
} }
@ -173,7 +153,7 @@ class DBHTMLText extends DBText
* @param string $value Input html content * @param string $value Input html content
* @return string Value with all non-whitelisted content stripped (if applicable) * @return string Value with all non-whitelisted content stripped (if applicable)
*/ */
public function whitelistContent($value) public function whitelistContent(mixed $value): mixed
{ {
if ($this->whitelist) { if ($this->whitelist) {
$dom = HTMLValue::create($value); $dom = HTMLValue::create($value);
@ -199,22 +179,20 @@ class DBHTMLText extends DBText
return $value; return $value;
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return HTMLEditorField::create($this->name, $title); return HTMLEditorField::create($this->name, $title);
} }
public function scaffoldSearchField($title = null) public function scaffoldSearchField(?string $title = null): ?FormField
{ {
return new TextField($this->name, $title); return new TextField($this->name, $title);
} }
/** /**
* Get plain-text version * Get plain-text version
*
* @return string
*/ */
public function Plain() public function Plain(): string
{ {
// Preserve line breaks // Preserve line breaks
$text = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $this->RAW() ?? ''); $text = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $this->RAW() ?? '');
@ -232,7 +210,7 @@ class DBHTMLText extends DBText
return trim(Convert::xml2raw($text) ?? ''); return trim(Convert::xml2raw($text) ?? '');
} }
public function getSchemaValue() public function getSchemaValue(): ?array
{ {
// Form schema format as HTML // Form schema format as HTML
$value = $this->RAW(); $value = $this->RAW();
@ -242,7 +220,7 @@ class DBHTMLText extends DBText
return null; return null;
} }
public function exists() public function exists(): bool
{ {
// Optimisation: don't process shortcode just for ->exists() // Optimisation: don't process shortcode just for ->exists()
$value = $this->getValue(); $value = $this->getValue();

View File

@ -3,6 +3,7 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\HTMLEditor\HTMLEditorField; use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\TextField; use SilverStripe\Forms\TextField;
use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\View\Parsers\ShortcodeParser;
@ -14,10 +15,9 @@ use SilverStripe\View\Parsers\ShortcodeParser;
*/ */
class DBHTMLVarchar extends DBVarchar class DBHTMLVarchar extends DBVarchar
{ {
private static string $escape_type = 'xml';
private static $escape_type = 'xml'; private static array $casting = [
private static $casting = [
// DBString conversion / summary methods // DBString conversion / summary methods
// Not overridden, but returns HTML instead of plain text. // Not overridden, but returns HTML instead of plain text.
"LowerCase" => "HTMLFragment", "LowerCase" => "HTMLFragment",
@ -26,35 +26,27 @@ class DBHTMLVarchar extends DBVarchar
/** /**
* Enable shortcode parsing on this field * Enable shortcode parsing on this field
*
* @var bool
*/ */
protected $processShortcodes = false; protected bool $processShortcodes = false;
/** /**
* Check if shortcodes are enabled * Check if shortcodes are enabled
*
* @return bool
*/ */
public function getProcessShortcodes() public function getProcessShortcodes(): bool
{ {
return $this->processShortcodes; return $this->processShortcodes;
} }
/** /**
* Set shortcodes on or off by default * Set shortcodes on or off by default
*
* @param bool $process
* @return $this
*/ */
public function setProcessShortcodes($process) public function setProcessShortcodes(bool $process): static
{ {
$this->processShortcodes = (bool)$process; $this->processShortcodes = $process;
return $this; return $this;
} }
/** /**
* @param array $options
*
* Options accepted in addition to those provided by Text: * Options accepted in addition to those provided by Text:
* *
* - shortcodes: If true, shortcodes will be turned into the appropriate HTML. * - shortcodes: If true, shortcodes will be turned into the appropriate HTML.
@ -66,10 +58,8 @@ class DBHTMLVarchar extends DBVarchar
* Text nodes outside of HTML tags are filtered out by default, but may be included by adding * Text nodes outside of HTML tags are filtered out by default, but may be included by adding
* the text() directive. E.g. 'link,meta,text()' will allow only <link /> <meta /> and text at * the text() directive. E.g. 'link,meta,text()' will allow only <link /> <meta /> and text at
* the root level. * the root level.
*
* @return $this
*/ */
public function setOptions(array $options = []) public function setOptions(array $options = []): static
{ {
if (array_key_exists("shortcodes", $options ?? [])) { if (array_key_exists("shortcodes", $options ?? [])) {
$this->setProcessShortcodes(!!$options["shortcodes"]); $this->setProcessShortcodes(!!$options["shortcodes"]);
@ -78,13 +68,13 @@ class DBHTMLVarchar extends DBVarchar
return parent::setOptions($options); return parent::setOptions($options);
} }
public function forTemplate() public function forTemplate(): string
{ {
// Suppress XML encoding for DBHtmlText // Suppress XML encoding for DBHtmlText
return $this->RAW(); return $this->RAW() ?? '';
} }
public function RAW() public function RAW(): ?string
{ {
if ($this->processShortcodes) { if ($this->processShortcodes) {
return ShortcodeParser::get_active()->parse($this->value); return ShortcodeParser::get_active()->parse($this->value);
@ -94,10 +84,8 @@ class DBHTMLVarchar extends DBVarchar
/** /**
* Safely escape for XML string * Safely escape for XML string
*
* @return string
*/ */
public function CDATA() public function CDATA(): string
{ {
return sprintf( return sprintf(
'<![CDATA[%s]]>', '<![CDATA[%s]]>',
@ -109,10 +97,8 @@ class DBHTMLVarchar extends DBVarchar
* Get plain-text version. * Get plain-text version.
* *
* Note: unlike DBHTMLText, this doesn't respect line breaks / paragraphs * Note: unlike DBHTMLText, this doesn't respect line breaks / paragraphs
*
* @return string
*/ */
public function Plain() public function Plain(): string
{ {
// Strip out HTML // Strip out HTML
$text = strip_tags($this->RAW() ?? ''); $text = strip_tags($this->RAW() ?? '');
@ -121,17 +107,17 @@ class DBHTMLVarchar extends DBVarchar
return trim(Convert::xml2raw($text) ?? ''); return trim(Convert::xml2raw($text) ?? '');
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return HTMLEditorField::create($this->name, $title); return HTMLEditorField::create($this->name, $title);
} }
public function scaffoldSearchField($title = null) public function scaffoldSearchField(?string $title = null): ?FormField
{ {
return TextField::create($this->name, $title); return TextField::create($this->name, $title);
} }
public function getSchemaValue() public function getSchemaValue(): ?array
{ {
// Form schema format as HTML // Form schema format as HTML
$value = $this->RAW(); $value = $this->RAW();
@ -141,7 +127,7 @@ class DBHTMLVarchar extends DBVarchar
return null; return null;
} }
public function exists() public function exists(): bool
{ {
// Optimisation: don't process shortcode just for ->exists() // Optimisation: don't process shortcode just for ->exists()
$value = $this->getValue(); $value = $this->getValue();

View File

@ -2,9 +2,11 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\NumericField; use SilverStripe\Forms\NumericField;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\ORM\SS_List;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
/** /**
@ -12,8 +14,7 @@ use SilverStripe\View\ArrayData;
*/ */
class DBInt extends DBField class DBInt extends DBField
{ {
public function __construct(?string $name = null, int $defaultVal = 0)
public function __construct($name = null, $defaultVal = 0)
{ {
$this->defaultVal = is_int($defaultVal) ? $defaultVal : 0; $this->defaultVal = is_int($defaultVal) ? $defaultVal : 0;
@ -24,7 +25,7 @@ class DBInt extends DBField
* Ensure int values are always returned. * Ensure int values are always returned.
* This is for mis-configured databases that return strings. * This is for mis-configured databases that return strings.
*/ */
public function getValue() public function getValue(): ?int
{ {
return (int) $this->value; return (int) $this->value;
} }
@ -32,12 +33,12 @@ class DBInt extends DBField
/** /**
* Returns the number, with commas added as appropriate, eg “1,000. * Returns the number, with commas added as appropriate, eg “1,000.
*/ */
public function Formatted() public function Formatted(): string
{ {
return number_format($this->value ?? 0.0); return number_format($this->value ?? 0.0);
} }
public function requireField() public function requireField(): void
{ {
$parts = [ $parts = [
'datatype' => 'int', 'datatype' => 'int',
@ -50,7 +51,7 @@ class DBInt extends DBField
DB::require_field($this->tableName, $this->name, $values); DB::require_field($this->tableName, $this->name, $values);
} }
public function Times() public function Times(): SS_List
{ {
$output = new ArrayList(); $output = new ArrayList();
for ($i = 0; $i < $this->value; $i++) { for ($i = 0; $i < $this->value; $i++) {
@ -60,22 +61,22 @@ class DBInt extends DBField
return $output; return $output;
} }
public function Nice() public function Nice(): string
{ {
return sprintf('%d', $this->value); return sprintf('%d', $this->value);
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return NumericField::create($this->name, $title); return NumericField::create($this->name, $title);
} }
public function nullValue() public function nullValue(): ?int
{ {
return 0; return 0;
} }
public function prepValueForDB($value) public function prepValueForDB(mixed $value): array|int|null
{ {
if ($value === true) { if ($value === true) {
return 1; return 1;

View File

@ -9,8 +9,7 @@ use SilverStripe\i18n\i18n;
*/ */
class DBLocale extends DBVarchar class DBLocale extends DBVarchar
{ {
public function __construct(?string $name = null, int $size = 16)
public function __construct($name = null, $size = 16)
{ {
parent::__construct($name, $size); parent::__construct($name, $size);
} }
@ -18,11 +17,10 @@ class DBLocale extends DBVarchar
/** /**
* See {@link getShortName()} and {@link getNativeName()}. * See {@link getShortName()} and {@link getNativeName()}.
* *
* @param Boolean $showNative Show a localized version of the name instead, based on the * @param bool $showNative Show a localized version of the name instead, based on the
* field's locale value. * field's locale value.
* @return String
*/ */
public function Nice($showNative = false) public function Nice(bool $showNative = false): string
{ {
if ($showNative) { if ($showNative) {
return $this->getNativeName(); return $this->getNativeName();
@ -30,7 +28,7 @@ class DBLocale extends DBVarchar
return $this->getShortName(); return $this->getShortName();
} }
public function RFC1766() public function RFC1766(): string
{ {
return i18n::convert_rfc1766($this->value); return i18n::convert_rfc1766($this->value);
} }
@ -38,18 +36,13 @@ class DBLocale extends DBVarchar
/** /**
* Resolves the locale to a common english-language * Resolves the locale to a common english-language
* name through {@link i18n::get_common_locales()}. * name through {@link i18n::get_common_locales()}.
*
* @return string
*/ */
public function getShortName() public function getShortName(): string
{ {
return i18n::getData()->languageName($this->value); return i18n::getData()->languageName($this->value);
} }
/** public function getLongName(): string
* @return string
*/
public function getLongName()
{ {
return i18n::getData()->localeName($this->value); return i18n::getData()->localeName($this->value);
} }
@ -57,10 +50,8 @@ class DBLocale extends DBVarchar
/** /**
* Returns the localized name based on the field's value. * Returns the localized name based on the field's value.
* Example: "de_DE" returns "Deutsch". * Example: "de_DE" returns "Deutsch".
*
* @return string
*/ */
public function getNativeName() public function getNativeName(): string
{ {
$locale = $this->value; $locale = $this->value;
return i18n::with_locale($locale, function () { return i18n::with_locale($locale, function () {

View File

@ -13,25 +13,17 @@ use SilverStripe\i18n\i18n;
*/ */
class DBMoney extends DBComposite class DBMoney extends DBComposite
{ {
/** protected ?string $locale = null;
* @var string $locale
*/
protected $locale = null;
/** private static array $composite_db = [
* @var array<string,string>
*/
private static $composite_db = [
'Currency' => 'Varchar(3)', 'Currency' => 'Varchar(3)',
'Amount' => 'Decimal(19,4)' 'Amount' => 'Decimal(19,4)'
]; ];
/** /**
* Get currency formatter * Get currency formatter
*
* @return NumberFormatter
*/ */
public function getFormatter() public function getFormatter(): NumberFormatter
{ {
$locale = $this->getLocale(); $locale = $this->getLocale();
$currency = $this->getCurrency(); $currency = $this->getCurrency();
@ -43,10 +35,8 @@ class DBMoney extends DBComposite
/** /**
* Get nicely formatted currency (based on current locale) * Get nicely formatted currency (based on current locale)
*
* @return string
*/ */
public function Nice() public function Nice(): string
{ {
if (!$this->exists()) { if (!$this->exists()) {
return null; return null;
@ -66,10 +56,8 @@ class DBMoney extends DBComposite
/** /**
* Standard '0.00 CUR' format (non-localised) * Standard '0.00 CUR' format (non-localised)
*
* @return string
*/ */
public function getValue() public function getValue(): ?string
{ {
if (!$this->exists()) { if (!$this->exists()) {
return null; return null;
@ -82,39 +70,23 @@ class DBMoney extends DBComposite
return $amount . ' ' . $currency; return $amount . ' ' . $currency;
} }
/** public function getCurrency(): ?string
* @return string
*/
public function getCurrency()
{ {
return $this->getField('Currency'); return $this->getField('Currency');
} }
/** public function setCurrency(?string $currency, bool $markChanged = true): static
* @param string $currency
* @param bool $markChanged
* @return $this
*/
public function setCurrency($currency, $markChanged = true)
{ {
$this->setField('Currency', $currency, $markChanged); $this->setField('Currency', $currency, $markChanged);
return $this; return $this;
} }
/** public function getAmount(): ?float
* @return float
*/
public function getAmount()
{ {
return $this->getField('Amount'); return $this->getField('Amount');
} }
/** public function setAmount(mixed $amount, bool $markChanged = true): static
* @param mixed $amount
* @param bool $markChanged
* @return $this
*/
public function setAmount($amount, $markChanged = true)
{ {
// Retain nullability to mark this field as empty // Retain nullability to mark this field as empty
if (isset($amount)) { if (isset($amount)) {
@ -124,49 +96,35 @@ class DBMoney extends DBComposite
return $this; return $this;
} }
/** public function exists(): bool
* @return boolean
*/
public function exists()
{ {
return is_numeric($this->getAmount()); return is_numeric($this->getAmount());
} }
/** /**
* Determine if this has a non-zero amount * Determine if this has a non-zero amount
*
* @return bool
*/ */
public function hasAmount() public function hasAmount(): bool
{ {
$a = $this->getAmount(); $a = $this->getAmount();
return (!empty($a) && is_numeric($a)); return (!empty($a) && is_numeric($a));
} }
/** public function setLocale(string $locale): static
* @param string $locale
* @return $this
*/
public function setLocale($locale)
{ {
$this->locale = $locale; $this->locale = $locale;
return $this; return $this;
} }
/** public function getLocale(): string
* @return string
*/
public function getLocale()
{ {
return $this->locale ?: i18n::get_locale(); return $this->locale ?: i18n::get_locale();
} }
/** /**
* Get currency symbol * Get currency symbol
*
* @return string
*/ */
public function getSymbol() public function getSymbol(): string
{ {
return $this->getFormatter()->getSymbol(NumberFormatter::CURRENCY_SYMBOL); return $this->getFormatter()->getSymbol(NumberFormatter::CURRENCY_SYMBOL);
} }
@ -178,10 +136,8 @@ class DBMoney extends DBComposite
* Used by {@link SearchContext}, {@link ModelAdmin}, {@link DataObject::scaffoldFormFields()} * Used by {@link SearchContext}, {@link ModelAdmin}, {@link DataObject::scaffoldFormFields()}
* *
* @param string $title Optional. Localized title of the generated instance * @param string $title Optional. Localized title of the generated instance
* @param array $params
* @return FormField
*/ */
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return MoneyField::create($this->getName(), $title) return MoneyField::create($this->getName(), $title)
->setLocale($this->getLocale()); ->setLocale($this->getLocale());

View File

@ -4,6 +4,7 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\CheckboxSetField; use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\MultiSelectField;
use SilverStripe\ORM\Connect\MySQLDatabase; use SilverStripe\ORM\Connect\MySQLDatabase;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
@ -33,7 +34,7 @@ class DBMultiEnum extends DBEnum
} }
} }
public function requireField() public function requireField(): void
{ {
$charset = Config::inst()->get(MySQLDatabase::class, 'charset'); $charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation'); $collation = Config::inst()->get(MySQLDatabase::class, 'collation');
@ -54,18 +55,15 @@ class DBMultiEnum extends DBEnum
/** /**
* Return a {@link CheckboxSetField} suitable for editing this field * Return a form field suitable for editing this field
*
* @param string $title
* @param string $name
* @param bool $hasEmpty
* @param string $value
* @param string $emptyString
* @return CheckboxSetField
*/ */
public function formField($title = null, $name = null, $hasEmpty = false, $value = '', $emptyString = null) public function formField(
{ ?string $title = null,
?string $name = null,
bool $hasEmpty = false,
?string $value = '',
?string $emptyString = null
): MultiSelectField {
if (!$title) { if (!$title) {
$title = $this->name; $title = $this->name;
} }
@ -73,6 +71,6 @@ class DBMultiEnum extends DBEnum
$name = $this->name; $name = $this->name;
} }
return new CheckboxSetField($name, $title, $this->enumValues($hasEmpty), $value); return CheckboxSetField::create($name, $title, $this->enumValues($hasEmpty), $value);
} }
} }

View File

@ -2,6 +2,8 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\View\ViewableData;
/** /**
* Represents a decimal field from 0-1 containing a percentage value. * Represents a decimal field from 0-1 containing a percentage value.
* *
@ -15,14 +17,10 @@ namespace SilverStripe\ORM\FieldType;
*/ */
class DBPercentage extends DBDecimal class DBPercentage extends DBDecimal
{ {
/** /**
* Create a new Decimal field. * Create a new Decimal field.
*
* @param string $name
* @param int $precision
*/ */
public function __construct($name = null, $precision = 4) public function __construct(?string $name = null, int $precision = 4)
{ {
if (!$precision) { if (!$precision) {
$precision = 4; $precision = 4;
@ -34,18 +32,18 @@ class DBPercentage extends DBDecimal
/** /**
* Returns the number, expressed as a percentage. For example, “36.30% * Returns the number, expressed as a percentage. For example, “36.30%
*/ */
public function Nice() public function Nice(): string
{ {
return number_format($this->value * 100, $this->decimalSize - 2) . '%'; return number_format($this->value * 100, $this->decimalSize - 2) . '%';
} }
public function saveInto($dataObject) public function saveInto(ViewableData $model): void
{ {
parent::saveInto($dataObject); parent::saveInto($model);
$fieldName = $this->name; $fieldName = $this->name;
if ($fieldName && $dataObject->$fieldName > 1.0) { if ($fieldName && $model->$fieldName > 1.0) {
$dataObject->__set($fieldName, 1.0); $model->__set($fieldName, 1.0);
} }
} }
} }

View File

@ -2,21 +2,23 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\View\ViewableData;
/** /**
* A special ForeignKey class that handles relations with arbitrary class types * A special ForeignKey class that handles relations with arbitrary class types
*/ */
class DBPolymorphicForeignKey extends DBComposite class DBPolymorphicForeignKey extends DBComposite
{ {
private static $index = true; private static bool $index = true;
private static $composite_db = [ private static array $composite_db = [
'ID' => 'Int', 'ID' => 'Int',
'Class' => "DBClassName('" . DataObject::class . "', ['index' => false])" 'Class' => "DBClassName('" . DataObject::class . "', ['index' => false])"
]; ];
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
// Don't provide scaffolded form field generation - Scaffolding should be performed on // Don't provide scaffolded form field generation - Scaffolding should be performed on
// the has_many end, or set programmatically. // the has_many end, or set programmatically.
@ -28,7 +30,7 @@ class DBPolymorphicForeignKey extends DBComposite
* *
* @return string Name of a subclass of DataObject * @return string Name of a subclass of DataObject
*/ */
public function getClassValue() public function getClassValue(): ?string
{ {
return $this->getField('Class'); return $this->getField('Class');
} }
@ -37,35 +39,29 @@ class DBPolymorphicForeignKey extends DBComposite
* Set the value of the "Class" this key points to * Set the value of the "Class" this key points to
* *
* @param string $value Name of a subclass of DataObject * @param string $value Name of a subclass of DataObject
* @param boolean $markChanged Mark this field as changed?
*/ */
public function setClassValue($value, $markChanged = true) public function setClassValue(string $value, bool $markChanged = true)
{ {
$this->setField('Class', $value, $markChanged); $this->setField('Class', $value, $markChanged);
} }
/** /**
* Gets the value of the "ID" this key points to * Gets the value of the "ID" this key points to
*
* @return integer
*/ */
public function getIDValue() public function getIDValue(): ?int
{ {
return $this->getField('ID'); return $this->getField('ID');
} }
/** /**
* Sets the value of the "ID" this key points to * Sets the value of the "ID" this key points to
*
* @param integer $value
* @param boolean $markChanged Mark this field as changed?
*/ */
public function setIDValue($value, $markChanged = true) public function setIDValue(int $value, bool $markChanged = true)
{ {
$this->setField('ID', $value, $markChanged); $this->setField('ID', $value, $markChanged);
} }
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
// Map dataobject value to array // Map dataobject value to array
if ($value instanceof DataObject) { if ($value instanceof DataObject) {
@ -75,10 +71,10 @@ class DBPolymorphicForeignKey extends DBComposite
]; ];
} }
parent::setValue($value, $record, $markChanged); return parent::setValue($value, $record, $markChanged);
} }
public function getValue() public function getValue(): ?DataObject
{ {
$id = $this->getIDValue(); $id = $this->getIDValue();
$class = $this->getClassValue(); $class = $this->getClassValue();

View File

@ -2,64 +2,59 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\View\ViewableData;
/** /**
* A special type Int field used for primary keys. * A special type Int field used for primary keys.
*/ */
class DBPrimaryKey extends DBInt class DBPrimaryKey extends DBInt
{ {
/** protected ?DataObject $object;
* @var DataObject
*/
protected $object;
private static $default_search_filter_class = 'ExactMatchFilter'; private static $default_search_filter_class = 'ExactMatchFilter';
/** protected bool $autoIncrement = true;
* @var bool
*/
protected $autoIncrement = true;
public function setAutoIncrement($autoIncrement)
{
$this->autoIncrement = $autoIncrement;
return $this;
}
public function getAutoIncrement()
{
return $this->autoIncrement;
}
public function requireField()
{
$spec = DB::get_schema()->IdColumn(false, $this->getAutoIncrement());
DB::require_field($this->getTable(), $this->getName(), $spec);
}
/** /**
* @param string $name
* @param DataObject $object The object that this is primary key for (should have a relation with $name) * @param DataObject $object The object that this is primary key for (should have a relation with $name)
*/ */
public function __construct($name, $object = null) public function __construct(?string $name, ?DataObject $object = null)
{ {
$this->object = $object; $this->object = $object;
parent::__construct($name); parent::__construct($name);
} }
public function scaffoldFormField($title = null, $params = null) public function setAutoIncrement(bool $autoIncrement): static
{
$this->autoIncrement = $autoIncrement;
return $this;
}
public function getAutoIncrement(): bool
{
return $this->autoIncrement;
}
public function requireField(): void
{
$spec = DB::get_schema()->IdColumn(false, $this->getAutoIncrement());
DB::require_field($this->getTable(), $this->getName(), $spec);
}
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return null; return null;
} }
public function scaffoldSearchField($title = null) public function scaffoldSearchField(?string $title = null): ?FormField
{ {
parent::scaffoldFormField($title); return parent::scaffoldFormField($title);
} }
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
parent::setValue($value, $record, $markChanged); parent::setValue($value, $record, $markChanged);

View File

@ -7,10 +7,7 @@ namespace SilverStripe\ORM\FieldType;
*/ */
abstract class DBString extends DBField abstract class DBString extends DBField
{ {
/** private static array $casting = [
* @var array
*/
private static $casting = [
'LimitCharacters' => 'Text', 'LimitCharacters' => 'Text',
'LimitCharactersToClosestWord' => 'Text', 'LimitCharactersToClosestWord' => 'Text',
'LimitWordCount' => 'Text', 'LimitWordCount' => 'Text',
@ -33,16 +30,14 @@ abstract class DBString extends DBField
/** /**
* Update the optional parameters for this field. * Update the optional parameters for this field.
* *
* @param array $options Array of options
* The options allowed are: * The options allowed are:
* <ul><li>"nullifyEmpty" * <ul><li>"nullifyEmpty"
* This is a boolean flag. * This is a boolean flag.
* True (the default) means that empty strings are automatically converted to nulls to be stored in * True (the default) means that empty strings are automatically converted to nulls to be stored in
* the database. Set it to false to ensure that nulls and empty strings are kept intact in the database. * the database. Set it to false to ensure that nulls and empty strings are kept intact in the database.
* </li></ul> * </li></ul>
* @return $this
*/ */
public function setOptions(array $options = []) public function setOptions(array $options = []): static
{ {
parent::setOptions($options); parent::setOptions($options);
@ -63,9 +58,9 @@ abstract class DBString extends DBField
* @param $value boolean True if empty strings are to be converted to null * @param $value boolean True if empty strings are to be converted to null
* @return $this * @return $this
*/ */
public function setNullifyEmpty($value) public function setNullifyEmpty(bool $value): static
{ {
$this->options['nullifyEmpty'] = (bool) $value; $this->options['nullifyEmpty'] = $value;
return $this; return $this;
} }
@ -75,23 +70,19 @@ abstract class DBString extends DBField
* *
* @return boolean True if empty strings are to be converted to null * @return boolean True if empty strings are to be converted to null
*/ */
public function getNullifyEmpty() public function getNullifyEmpty(): bool
{ {
return !empty($this->options['nullifyEmpty']); return !empty($this->options['nullifyEmpty']);
} }
/** public function exists(): bool
* (non-PHPdoc)
* @see DBField::exists()
*/
public function exists()
{ {
$value = $this->RAW(); $value = $this->RAW();
// All truthy values and non-empty strings exist ('0' but not (int)0) // All truthy values and non-empty strings exist ('0' but not (int)0)
return $value || (is_string($value) && strlen($value ?? '')); return $value || (is_string($value) && strlen($value ?? ''));
} }
public function prepValueForDB($value) public function prepValueForDB(mixed $value): array|string|null
{ {
// Cast non-empty value // Cast non-empty value
if (is_scalar($value) && strlen($value ?? '')) { if (is_scalar($value) && strlen($value ?? '')) {
@ -105,10 +96,7 @@ abstract class DBString extends DBField
return ''; return '';
} }
/** public function forTemplate(): string
* @return string
*/
public function forTemplate()
{ {
return nl2br(parent::forTemplate() ?? ''); return nl2br(parent::forTemplate() ?? '');
} }
@ -120,9 +108,8 @@ abstract class DBString extends DBField
* *
* @param int $limit Number of characters to limit by * @param int $limit Number of characters to limit by
* @param string|false $add Ellipsis to add to the end of truncated string * @param string|false $add Ellipsis to add to the end of truncated string
* @return string
*/ */
public function LimitCharacters($limit = 20, $add = false) public function LimitCharacters(int $limit = 20, string|false $add = false): string
{ {
$value = $this->Plain(); $value = $this->Plain();
if (mb_strlen($value ?? '') <= $limit) { if (mb_strlen($value ?? '') <= $limit) {
@ -140,7 +127,7 @@ abstract class DBString extends DBField
* @param string|false $add Ellipsis to add to the end of truncated string * @param string|false $add Ellipsis to add to the end of truncated string
* @return string Plain text value with limited characters * @return string Plain text value with limited characters
*/ */
public function LimitCharactersToClosestWord($limit = 20, $add = false) public function LimitCharactersToClosestWord(int $limit = 20, string|false $add = false): string
{ {
// Safely convert to plain text // Safely convert to plain text
$value = $this->Plain(); $value = $this->Plain();
@ -169,11 +156,9 @@ abstract class DBString extends DBField
* Limit this field's content by a number of words. * Limit this field's content by a number of words.
* *
* @param int $numWords Number of words to limit by. * @param int $numWords Number of words to limit by.
* @param false $add Ellipsis to add to the end of truncated string. * @param string|false $add Ellipsis to add to the end of truncated string.
*
* @return string
*/ */
public function LimitWordCount($numWords = 26, $add = false) public function LimitWordCount(int $numWords = 26, string|false $add = false): string
{ {
$value = $this->Plain(); $value = $this->Plain();
$words = explode(' ', $value ?? ''); $words = explode(' ', $value ?? '');
@ -191,7 +176,7 @@ abstract class DBString extends DBField
* *
* @return string Text with lowercase (HTML for some subclasses) * @return string Text with lowercase (HTML for some subclasses)
*/ */
public function LowerCase() public function LowerCase(): string
{ {
return mb_strtolower($this->RAW() ?? ''); return mb_strtolower($this->RAW() ?? '');
} }
@ -201,28 +186,23 @@ abstract class DBString extends DBField
* *
* @return string Text with uppercase (HTML for some subclasses) * @return string Text with uppercase (HTML for some subclasses)
*/ */
public function UpperCase() public function UpperCase(): string
{ {
return mb_strtoupper($this->RAW() ?? ''); return mb_strtoupper($this->RAW() ?? '');
} }
/** /**
* Plain text version of this string * Plain text version of this string
*
* @return string Plain text
*/ */
public function Plain() public function Plain(): string
{ {
return trim($this->RAW() ?? ''); return trim($this->RAW() ?? '');
} }
/** /**
* Swap add for defaultEllipsis if need be * Swap add for defaultEllipsis if need be
* @param string $string
* @param false|string $add
* @return string
*/ */
private function addEllipsis(string $string, $add): string private function addEllipsis(string $string, string|false $add): string
{ {
if ($add === false) { if ($add === false) {
$add = $this->defaultEllipsis(); $add = $this->defaultEllipsis();
@ -233,7 +213,6 @@ abstract class DBString extends DBField
/** /**
* Get the default string to indicate that a string was cut off. * Get the default string to indicate that a string was cut off.
* @return string
*/ */
public function defaultEllipsis(): string public function defaultEllipsis(): string
{ {

View File

@ -5,6 +5,7 @@ namespace SilverStripe\ORM\FieldType;
use InvalidArgumentException; use InvalidArgumentException;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\NullableField; use SilverStripe\Forms\NullableField;
use SilverStripe\Forms\TextareaField; use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\TextField; use SilverStripe\Forms\TextField;
@ -27,8 +28,7 @@ use SilverStripe\ORM\DB;
*/ */
class DBText extends DBString class DBText extends DBString
{ {
private static array $casting = [
private static $casting = [
'BigSummary' => 'Text', 'BigSummary' => 'Text',
'ContextSummary' => 'HTMLFragment', // Always returns HTML as it contains formatting and highlighting 'ContextSummary' => 'HTMLFragment', // Always returns HTML as it contains formatting and highlighting
'FirstParagraph' => 'Text', 'FirstParagraph' => 'Text',
@ -42,11 +42,7 @@ class DBText extends DBString
*/ */
private static array $summary_sentence_separators = ['.', '?', '!']; private static array $summary_sentence_separators = ['.', '?', '!'];
/** public function requireField(): void
* (non-PHPdoc)
* @see DBField::requireField()
*/
public function requireField()
{ {
$charset = Config::inst()->get(MySQLDatabase::class, 'charset'); $charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation'); $collation = Config::inst()->get(MySQLDatabase::class, 'collation');
@ -71,9 +67,8 @@ class DBText extends DBString
* Limit sentences, can be controlled by passing an integer. * Limit sentences, can be controlled by passing an integer.
* *
* @param int $maxSentences The amount of sentences you want. * @param int $maxSentences The amount of sentences you want.
* @return string
*/ */
public function LimitSentences($maxSentences = 2) public function LimitSentences(int $maxSentences = 2): string
{ {
if (!is_numeric($maxSentences)) { if (!is_numeric($maxSentences)) {
throw new InvalidArgumentException("Text::LimitSentence() expects one numeric argument"); throw new InvalidArgumentException("Text::LimitSentence() expects one numeric argument");
@ -107,22 +102,16 @@ class DBText extends DBString
/** /**
* Return the first string that finishes with a period (.) in this text. * Return the first string that finishes with a period (.) in this text.
*
* @return string
*/ */
public function FirstSentence() public function FirstSentence(): string
{ {
return $this->LimitSentences(1); return $this->LimitSentences(1);
} }
/** /**
* Builds a basic summary, up to a maximum number of words * Builds a basic summary, up to a maximum number of words
*
* @param int $maxWords
* @param string|false $add
* @return string
*/ */
public function Summary($maxWords = 50, $add = false) public function Summary(int $maxWords = 50, string|false $add = false): string
{ {
// Get plain-text version // Get plain-text version
$value = $this->Plain(); $value = $this->Plain();
@ -171,10 +160,8 @@ class DBText extends DBString
/** /**
* Get first paragraph * Get first paragraph
*
* @return string
*/ */
public function FirstParagraph() public function FirstParagraph(): string
{ {
$value = $this->Plain(); $value = $this->Plain();
if (empty($value)) { if (empty($value)) {
@ -193,17 +180,15 @@ class DBText extends DBString
* @param int $characters Number of characters in the summary * @param int $characters Number of characters in the summary
* @param string $keywords Supplied string ("keywords"). Will fall back to 'Search' querystring arg. * @param string $keywords Supplied string ("keywords"). Will fall back to 'Search' querystring arg.
* @param bool $highlight Add a highlight <mark> element around search query? * @param bool $highlight Add a highlight <mark> element around search query?
* @param string|false $prefix Prefix text
* @param string|false $suffix Suffix text
* @return string HTML string with context * @return string HTML string with context
*/ */
public function ContextSummary( public function ContextSummary(
$characters = 500, int $characters = 500,
$keywords = null, ?string $keywords = null,
$highlight = true, bool $highlight = true,
$prefix = false, string|false $prefix = false,
$suffix = false string|false $suffix = false
) { ): string {
if (!$keywords) { if (!$keywords) {
// Use the default "Search" request variable (from SearchForm) // Use the default "Search" request variable (from SearchForm)
@ -267,7 +252,7 @@ class DBText extends DBString
return nl2br($summary ?? ''); return nl2br($summary ?? '');
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
if (!$this->nullifyEmpty) { if (!$this->nullifyEmpty) {
// Allow the user to select if it's null instead of automatically assuming empty string is // Allow the user to select if it's null instead of automatically assuming empty string is
@ -277,7 +262,7 @@ class DBText extends DBString
return TextareaField::create($this->name, $title); return TextareaField::create($this->name, $title);
} }
public function scaffoldSearchField($title = null) public function scaffoldSearchField(?string $title = null): ?FormField
{ {
return new TextField($this->name, $title); return new TextField($this->name, $title);
} }

View File

@ -4,11 +4,13 @@ namespace SilverStripe\ORM\FieldType;
use IntlDateFormatter; use IntlDateFormatter;
use InvalidArgumentException; use InvalidArgumentException;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\TimeField; use SilverStripe\Forms\TimeField;
use SilverStripe\i18n\i18n; use SilverStripe\i18n\i18n;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\View\ViewableData;
/** /**
* Represents a column in the database with the type 'Time'. * Represents a column in the database with the type 'Time'.
@ -25,9 +27,9 @@ class DBTime extends DBField
/** /**
* Standard ISO format string for time in CLDR standard format * Standard ISO format string for time in CLDR standard format
*/ */
const ISO_TIME = 'HH:mm:ss'; public const ISO_TIME = 'HH:mm:ss';
public function setValue($value, $record = null, $markChanged = true) public function setValue(mixed $value, null|array|ViewableData $record = null, bool $markChanged = true): static
{ {
$value = $this->parseTime($value); $value = $this->parseTime($value);
if ($value === false) { if ($value === false) {
@ -42,10 +44,9 @@ class DBTime extends DBField
/** /**
* Parse timestamp or iso8601-ish date into standard iso8601 format * Parse timestamp or iso8601-ish date into standard iso8601 format
* *
* @param mixed $value
* @return string|null|false Formatted time, null if empty but valid, or false if invalid * @return string|null|false Formatted time, null if empty but valid, or false if invalid
*/ */
protected function parseTime($value) protected function parseTime(mixed $value): string|null|false
{ {
// Skip empty values // Skip empty values
if (empty($value) && !is_numeric($value)) { if (empty($value) && !is_numeric($value)) {
@ -73,24 +74,19 @@ class DBTime extends DBField
/** /**
* Get date / time formatter for the current locale * Get date / time formatter for the current locale
*
* @param int $timeLength
* @return IntlDateFormatter
*/ */
public function getFormatter($timeLength = IntlDateFormatter::MEDIUM) public function getFormatter(int $timeLength = IntlDateFormatter::MEDIUM): IntlDateFormatter
{ {
return IntlDateFormatter::create(i18n::get_locale(), IntlDateFormatter::NONE, $timeLength); return IntlDateFormatter::create(i18n::get_locale(), IntlDateFormatter::NONE, $timeLength);
} }
/** /**
* Returns the date in the localised short format * Returns the date in the localised short format
*
* @return string
*/ */
public function Short() public function Short(): string
{ {
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
$formatter = $this->getFormatter(IntlDateFormatter::SHORT); $formatter = $this->getFormatter(IntlDateFormatter::SHORT);
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -99,13 +95,11 @@ class DBTime extends DBField
/** /**
* Returns the standard localised medium time * Returns the standard localised medium time
* e.g. "3:15pm" * e.g. "3:15pm"
*
* @return string
*/ */
public function Nice() public function Nice(): string
{ {
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
$formatter = $this->getFormatter(); $formatter = $this->getFormatter();
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
@ -114,20 +108,19 @@ class DBTime extends DBField
/** /**
* Return the time using a particular formatting string. * Return the time using a particular formatting string.
* *
* @param string $format Format code string. See https://unicode-org.github.io/icu/userguide/format_parse/datetime * See https://unicode-org.github.io/icu/userguide/format_parse/datetime for valid formats
* @return string The time in the requested format
*/ */
public function Format($format) public function Format(string $format): string
{ {
if (!$this->value) { if (!$this->value) {
return null; return '';
} }
$formatter = $this->getFormatter(); $formatter = $this->getFormatter();
$formatter->setPattern($format); $formatter->setPattern($format);
return $formatter->format($this->getTimestamp()); return $formatter->format($this->getTimestamp());
} }
public function requireField() public function requireField(): void
{ {
$parts = [ $parts = [
'datatype' => 'time', 'datatype' => 'time',
@ -140,18 +133,15 @@ class DBTime extends DBField
DB::require_field($this->tableName, $this->name, $values); DB::require_field($this->tableName, $this->name, $values);
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
return TimeField::create($this->name, $title); return TimeField::create($this->name, $title);
} }
/** /**
* Return a time formatted as per a CMS user's settings. * Return a time formatted as per a CMS user's settings.
*
* @param Member $member
* @return string A time formatted as per user-defined settings.
*/ */
public function FormatFromSettings($member = null) public function FormatFromSettings(?Member $member = null): string
{ {
if (!$member) { if (!$member) {
$member = Security::getCurrentUser(); $member = Security::getCurrentUser();
@ -169,20 +159,16 @@ class DBTime extends DBField
/** /**
* Get standard ISO time format string * Get standard ISO time format string
*
* @return string
*/ */
public function getISOFormat() public function getISOFormat(): string
{ {
return DBTime::ISO_TIME; return DBTime::ISO_TIME;
} }
/** /**
* Get unix timestamp for this time * Get unix timestamp for this time
*
* @return int
*/ */
public function getTimestamp() public function getTimestamp(): int
{ {
if ($this->value) { if ($this->value) {
return strtotime($this->value ?? ''); return strtotime($this->value ?? '');

View File

@ -3,6 +3,7 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\NullableField; use SilverStripe\Forms\NullableField;
use SilverStripe\Forms\TextField; use SilverStripe\Forms\TextField;
use SilverStripe\ORM\Connect\MySQLDatabase; use SilverStripe\ORM\Connect\MySQLDatabase;
@ -17,18 +18,15 @@ use SilverStripe\ORM\DB;
*/ */
class DBVarchar extends DBString class DBVarchar extends DBString
{ {
private static array $casting = [
private static $casting = [
'Initial' => 'Text', 'Initial' => 'Text',
'URL' => 'Text', 'URL' => 'Text',
]; ];
/** /**
* Max size of this field * Max size of this field
*
* @var int
*/ */
protected $size; protected int $size;
/** /**
* Construct a new short text field * Construct a new short text field
@ -38,7 +36,7 @@ class DBVarchar extends DBString
* @param array $options Optional parameters, e.g. array("nullifyEmpty"=>false). * @param array $options Optional parameters, e.g. array("nullifyEmpty"=>false).
* See {@link StringField::setOptions()} for information on the available options * See {@link StringField::setOptions()} for information on the available options
*/ */
public function __construct($name = null, $size = 255, $options = []) public function __construct(?string $name = null, int $size = 255, array $options = [])
{ {
$this->size = $size ? $size : 255; $this->size = $size ? $size : 255;
parent::__construct($name, $options); parent::__construct($name, $options);
@ -53,16 +51,12 @@ class DBVarchar extends DBString
* *
* @return int The size of the field * @return int The size of the field
*/ */
public function getSize() public function getSize(): int
{ {
return $this->size; return $this->size;
} }
/** public function requireField(): void
* (non-PHPdoc)
* @see DBField::requireField()
*/
public function requireField()
{ {
$charset = Config::inst()->get(MySQLDatabase::class, 'charset'); $charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation'); $collation = Config::inst()->get(MySQLDatabase::class, 'collation');
@ -85,24 +79,20 @@ class DBVarchar extends DBString
/** /**
* Return the first letter of the string followed by a . * Return the first letter of the string followed by a .
*
* @return string
*/ */
public function Initial() public function Initial(): string
{ {
if ($this->exists()) { if ($this->exists()) {
$value = $this->RAW(); $value = $this->RAW();
return $value[0] . '.'; return $value[0] . '.';
} }
return null; return '';
} }
/** /**
* Ensure that the given value is an absolute URL. * Ensure that the given value is an absolute URL.
*
* @return string
*/ */
public function URL() public function URL(): string
{ {
$value = $this->RAW(); $value = $this->RAW();
if (preg_match('#^[a-zA-Z]+://#', $value ?? '')) { if (preg_match('#^[a-zA-Z]+://#', $value ?? '')) {
@ -113,14 +103,13 @@ class DBVarchar extends DBString
/** /**
* Return the value of the field in rich text format * Return the value of the field in rich text format
* @return string
*/ */
public function RTF() public function RTF(): string
{ {
return str_replace("\n", '\par ', $this->RAW() ?? ''); return str_replace("\n", '\par ', $this->RAW() ?? '');
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
// Set field with appropriate size // Set field with appropriate size
$field = TextField::create($this->name, $title); $field = TextField::create($this->name, $title);

View File

@ -3,6 +3,7 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
/** /**
@ -10,15 +11,14 @@ use SilverStripe\ORM\DB;
*/ */
class DBYear extends DBField class DBYear extends DBField
{ {
public function requireField(): void
public function requireField()
{ {
$parts = ['datatype' => 'year', 'precision' => 4, 'arrayValue' => $this->arrayValue]; $parts = ['datatype' => 'year', 'precision' => 4, 'arrayValue' => $this->arrayValue];
$values = ['type' => 'year', 'parts' => $parts]; $values = ['type' => 'year', 'parts' => $parts];
DB::require_field($this->tableName, $this->name, $values); DB::require_field($this->tableName, $this->name, $values);
} }
public function scaffoldFormField($title = null, $params = null) public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{ {
$selectBox = DropdownField::create($this->name, $title); $selectBox = DropdownField::create($this->name, $title);
$selectBox->setSource($this->getDefaultOptions()); $selectBox->setSource($this->getDefaultOptions());
@ -31,11 +31,10 @@ class DBYear extends DBField
* input values. Starts by default at the current year, * input values. Starts by default at the current year,
* and counts back to 1900. * and counts back to 1900.
* *
* @param int|bool $start starting date to count down from * @param int|null $start starting date to count down from
* @param int|bool $end end date to count down to * @param int|null $end end date to count down to
* @return array
*/ */
private function getDefaultOptions($start = null, $end = null) private function getDefaultOptions(?int $start = null, ?int $end = null): array
{ {
if (!$start) { if (!$start) {
$start = (int)date('Y'); $start = (int)date('Y');

View File

@ -112,7 +112,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable,
return $this->list->getIterator(); return $this->list->getIterator();
} }
public function exists() public function exists(): bool
{ {
return $this->list->exists(); return $this->list->exists();
} }
@ -140,7 +140,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable,
return $this->list->count(); return $this->list->count();
} }
public function forTemplate() public function forTemplate(): string
{ {
return $this->list->forTemplate(); return $this->list->forTemplate();
} }
@ -313,7 +313,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable,
return $this->list->exclude(...func_get_args()); return $this->list->exclude(...func_get_args());
} }
public function debug() public function debug(): string
{ {
return $this->list->debug(); return $this->list->debug();
} }

View File

@ -70,13 +70,10 @@ class ArrayData extends ViewableData
* *
* If the value is an associative array, it will likewise be * If the value is an associative array, it will likewise be
* converted recursively to an ArrayData. * converted recursively to an ArrayData.
*
* @param string $field
* @return mixed
*/ */
public function getField($field) public function getField(string $fieldName): mixed
{ {
$value = $this->array[$field]; $value = $this->array[$fieldName];
if (is_object($value) && !($value instanceof ViewableData) && !is_iterable($value)) { if (is_object($value) && !($value instanceof ViewableData) && !is_iterable($value)) {
return new ArrayData($value); return new ArrayData($value);
} elseif (ArrayLib::is_associative($value)) { } elseif (ArrayLib::is_associative($value)) {
@ -87,14 +84,10 @@ class ArrayData extends ViewableData
} }
/** /**
* Add or set a field on this object. * Add or set a field on this object.
*
* @param string $field
* @param mixed $value
* @return $this
*/ */
public function setField($field, $value) public function setField(string $fieldName, mixed $value): static
{ {
$this->array[$field] = $value; $this->array[$fieldName] = $value;
return $this; return $this;
} }
@ -104,9 +97,9 @@ class ArrayData extends ViewableData
* @param string $field Field Key * @param string $field Field Key
* @return bool * @return bool
*/ */
public function hasField($field) public function hasField(string $fieldName): bool
{ {
return isset($this->array[$field]); return isset($this->array[$fieldName]);
} }
/** /**

View File

@ -46,10 +46,7 @@ class HTMLValue extends ViewableData
return false; return false;
} }
/** public function getContent(): string
* @return string
*/
public function getContent()
{ {
$document = $this->getDocument(); $document = $this->getDocument();
if (!$document) { if (!$document) {
@ -98,7 +95,7 @@ class HTMLValue extends ViewableData
} }
/** @see HTMLValue::getContent() */ /** @see HTMLValue::getContent() */
public function forTemplate() public function forTemplate(): string
{ {
return $this->getContent(); return $this->getContent();
} }

View File

@ -733,6 +733,6 @@ class ShortcodeParser
$this->extend('onAfterParse', $content); $this->extend('onAfterParse', $content);
return $content; return $content ?? '';
} }
} }

View File

@ -288,7 +288,7 @@ class SSTemplateParser extends Parser implements TemplateParser
$arguments = $sub['Call']['CallArguments']['php']; $arguments = $sub['Call']['CallArguments']['php'];
$res['php'] .= "->$method('$property', [$arguments], true)"; $res['php'] .= "->$method('$property', [$arguments], true)";
} else { } else {
$res['php'] .= "->$method('$property', null, true)"; $res['php'] .= "->$method('$property', [], true)";
} }
} }
@ -1037,7 +1037,7 @@ class SSTemplateParser extends Parser implements TemplateParser
//loop without arguments loops on the current scope //loop without arguments loops on the current scope
if ($res['ArgumentCount'] == 0) { if ($res['ArgumentCount'] == 0) {
$on = '$scope->locally()->obj(\'Me\', null, true)'; $on = '$scope->locally()->obj(\'Me\', [], true)';
} else { //loop in the normal way } else { //loop in the normal way
$arg = $res['Arguments'][0]; $arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') { if ($arg['ArgumentMode'] == 'string') {

View File

@ -779,7 +779,7 @@ class SSTemplateParser extends Parser implements TemplateParser
$arguments = $sub['Call']['CallArguments']['php']; $arguments = $sub['Call']['CallArguments']['php'];
$res['php'] .= "->$method('$property', [$arguments], true)"; $res['php'] .= "->$method('$property', [$arguments], true)";
} else { } else {
$res['php'] .= "->$method('$property', null, true)"; $res['php'] .= "->$method('$property', [], true)";
} }
} }
@ -1886,6 +1886,8 @@ class SSTemplateParser extends Parser implements TemplateParser
$res['php'] .= '((bool)'.$sub['php'].')'; $res['php'] .= '((bool)'.$sub['php'].')';
} else { } else {
$php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']); $php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']);
// TODO: kinda hacky - maybe we need a way to pass state down the parse chain so
// Lookup_LastLookupStep and Argument_BareWord can produce hasValue instead of XML_val
$res['php'] .= str_replace('$$FINAL', 'hasValue', $php ?? ''); $res['php'] .= str_replace('$$FINAL', 'hasValue', $php ?? '');
} }
} }
@ -4263,7 +4265,7 @@ class SSTemplateParser extends Parser implements TemplateParser
//loop without arguments loops on the current scope //loop without arguments loops on the current scope
if ($res['ArgumentCount'] == 0) { if ($res['ArgumentCount'] == 0) {
$on = '$scope->locally()->obj(\'Me\', null, true)'; $on = '$scope->locally()->obj(\'Me\', [], true)';
} else { //loop in the normal way } else { //loop in the normal way
$arg = $res['Arguments'][0]; $arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') { if ($arg['ArgumentMode'] == 'string') {
@ -5290,6 +5292,8 @@ class SSTemplateParser extends Parser implements TemplateParser
$text = stripslashes($text ?? ''); $text = stripslashes($text ?? '');
$text = addcslashes($text ?? '', '\'\\'); $text = addcslashes($text ?? '', '\'\\');
// TODO: This is pretty ugly & gets applied on all files not just html. I wonder if we can make this
// non-dynamically calculated
$code = <<<'EOC' $code = <<<'EOC'
(\SilverStripe\View\SSViewer::getRewriteHashLinksDefault() (\SilverStripe\View\SSViewer::getRewriteHashLinksDefault()
? \SilverStripe\Core\Convert::raw2att( preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'] ) ) ? \SilverStripe\Core\Convert::raw2att( preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'] ) )
@ -5328,7 +5332,8 @@ EOC;
$this->includeDebuggingComments = $includeDebuggingComments; $this->includeDebuggingComments = $includeDebuggingComments;
// Ignore UTF8 BOM at beginning of string. // Ignore UTF8 BOM at beginning of string. TODO: Confirm this is needed, make sure SSViewer handles UTF
// (and other encodings) properly
if (substr($string ?? '', 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) { if (substr($string ?? '', 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
$this->pos = 3; $this->pos = 3;
} }

View File

@ -3,6 +3,7 @@
namespace SilverStripe\View; namespace SilverStripe\View;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\FieldType\DBField;
/** /**
* Special SSViewer that will process a template passed as a string, rather than a filename. * Special SSViewer that will process a template passed as a string, rather than a filename.
@ -71,7 +72,9 @@ class SSViewer_FromString extends SSViewer
unlink($cacheFile ?? ''); unlink($cacheFile ?? '');
} }
return $val; $html = DBField::create_field('HTMLFragment', $val);
return $html;
} }
/** /**

View File

@ -2,29 +2,23 @@
namespace SilverStripe\View; namespace SilverStripe\View;
use ArrayIterator;
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
use IteratorAggregate;
use LogicException; use LogicException;
use ReflectionMethod; use ReflectionMethod;
use ReflectionObject;
use ReflectionProperty; use ReflectionProperty;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\Extensible; use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Debug; use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\ArrayLib; use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
use Traversable;
use UnexpectedValueException; use UnexpectedValueException;
/** /**
@ -34,7 +28,7 @@ use UnexpectedValueException;
* is provided and automatically escaped by ViewableData. Any class that needs to be available to a view (controllers, * is provided and automatically escaped by ViewableData. Any class that needs to be available to a view (controllers,
* {@link DataObject}s, page controls) should inherit from this class. * {@link DataObject}s, page controls) should inherit from this class.
*/ */
class ViewableData implements IteratorAggregate class ViewableData
{ {
use Extensible { use Extensible {
defineMethods as extensibleDefineMethods; defineMethods as extensibleDefineMethods;
@ -50,27 +44,18 @@ class ViewableData implements IteratorAggregate
* 'FieldName' => 'ClassToCastTo(Arguments)' * 'FieldName' => 'ClassToCastTo(Arguments)'
* ); * );
* </code> * </code>
*
* @var array
* @config
*/ */
private static $casting = [ private static array $casting = [
'CSSClasses' => 'Varchar' 'CSSClasses' => 'Varchar'
]; ];
/** /**
* The default object to cast scalar fields to if casting information is not specified, and casting to an object * The default object to cast scalar fields to if casting information is not specified, and casting to an object
* is required. * is required.
*
* @var string
* @config
*/ */
private static $default_cast = 'Text'; private static string $default_cast = 'Text';
/** private static array $casting_cache = [];
* @var array
*/
private static $casting_cache = [];
/** /**
* Acts as a PHP 8.2+ compliant replacement for dynamic properties * Acts as a PHP 8.2+ compliant replacement for dynamic properties
@ -81,20 +66,12 @@ class ViewableData implements IteratorAggregate
/** /**
* A failover object to attempt to get data from if it is not present on this object. * A failover object to attempt to get data from if it is not present on this object.
*
* @var ViewableData
*/ */
protected $failover; protected ?ViewableData $failover = null;
/** protected ?ViewableData $customisedObject = null;
* @var ViewableData
*/
protected $customisedObject;
/** private array $objCache = [];
* @var array
*/
private $objCache = [];
public function __construct() public function __construct()
{ {
@ -108,11 +85,8 @@ class ViewableData implements IteratorAggregate
* Check if a field exists on this object or its failover. * Check if a field exists on this object or its failover.
* Note that, unlike the core isset() implementation, this will return true if the property is defined * Note that, unlike the core isset() implementation, this will return true if the property is defined
* and set to null. * and set to null.
*
* @param string $property
* @return bool
*/ */
public function __isset($property) public function __isset(string $property): bool
{ {
// getField() isn't a field-specific getter and shouldn't be treated as such // getField() isn't a field-specific getter and shouldn't be treated as such
if (strtolower($property ?? '') !== 'field' && $this->hasMethod("get$property")) { if (strtolower($property ?? '') !== 'field' && $this->hasMethod("get$property")) {
@ -131,11 +105,8 @@ class ViewableData implements IteratorAggregate
/** /**
* Get the value of a property/field on this object. This will check if a method called get{$property} exists, then * Get the value of a property/field on this object. This will check if a method called get{$property} exists, then
* check if a field is available using {@link ViewableData::getField()}, then fall back on a failover object. * check if a field is available using {@link ViewableData::getField()}, then fall back on a failover object.
*
* @param string $property
* @return mixed
*/ */
public function __get($property) public function __get(string $property): mixed
{ {
// getField() isn't a field-specific getter and shouldn't be treated as such // getField() isn't a field-specific getter and shouldn't be treated as such
$method = "get$property"; $method = "get$property";
@ -155,11 +126,8 @@ class ViewableData implements IteratorAggregate
/** /**
* Set a property/field on this object. This will check for the existence of a method called set{$property}, then * Set a property/field on this object. This will check for the existence of a method called set{$property}, then
* use the {@link ViewableData::setField()} method. * use the {@link ViewableData::setField()} method.
*
* @param string $property
* @param mixed $value
*/ */
public function __set($property, $value) public function __set(string $property, mixed $value): void
{ {
$this->objCacheClear(); $this->objCacheClear();
$method = "set$property"; $method = "set$property";
@ -173,10 +141,8 @@ class ViewableData implements IteratorAggregate
/** /**
* Set a failover object to attempt to get data from if it is not present on this object. * Set a failover object to attempt to get data from if it is not present on this object.
*
* @param ViewableData $failover
*/ */
public function setFailover(ViewableData $failover) public function setFailover(ViewableData $failover): void
{ {
// Ensure cached methods from previous failover are removed // Ensure cached methods from previous failover are removed
if ($this->failover) { if ($this->failover) {
@ -189,56 +155,44 @@ class ViewableData implements IteratorAggregate
/** /**
* Get the current failover object if set * Get the current failover object if set
*
* @return ViewableData|null
*/ */
public function getFailover() public function getFailover(): ?ViewableData
{ {
return $this->failover; return $this->failover;
} }
/** /**
* Check if a field exists on this object. This should be overloaded in child classes. * Check if a field exists on this object. This should be overloaded in child classes.
*
* @param string $field
* @return bool
*/ */
public function hasField($field) public function hasField(string $fieldName): bool
{ {
return property_exists($this, $field) || $this->hasDynamicData($field); return property_exists($this, $fieldName) || $this->hasDynamicData($fieldName);
} }
/** /**
* Get the value of a field on this object. This should be overloaded in child classes. * Get the value of a field on this object. This should be overloaded in child classes.
*
* @param string $field
* @return mixed
*/ */
public function getField($field) public function getField(string $fieldName): mixed
{ {
if ($this->isAccessibleProperty($field)) { if ($this->isAccessibleProperty($fieldName)) {
return $this->$field; return $this->$fieldName;
} }
return $this->getDynamicData($field); return $this->getDynamicData($fieldName);
} }
/** /**
* Set a field on this object. This should be overloaded in child classes. * Set a field on this object. This should be overloaded in child classes.
*
* @param string $field
* @param mixed $value
* @return $this
*/ */
public function setField($field, $value) public function setField(string $fieldName, mixed $value): static
{ {
$this->objCacheClear(); $this->objCacheClear();
// prior to PHP 8.2 support ViewableData::setField() simply used `$this->field = $value;` // prior to PHP 8.2 support ViewableData::setField() simply used `$this->field = $value;`
// so the following logic essentially mimics this behaviour, though without the use // so the following logic essentially mimics this behaviour, though without the use
// of now deprecated dynamic properties // of now deprecated dynamic properties
if ($this->isAccessibleProperty($field)) { if ($this->isAccessibleProperty($fieldName)) {
$this->$field = $value; $this->$fieldName = $value;
} }
return $this->setDynamicData($field, $value); return $this->setDynamicData($fieldName, $value);
} }
public function getDynamicData(string $field): mixed public function getDynamicData(string $field): mixed
@ -322,11 +276,8 @@ class ViewableData implements IteratorAggregate
* with references to both this and the new custom data. * with references to both this and the new custom data.
* *
* Note that any fields you specify will take precedence over the fields on this object. * Note that any fields you specify will take precedence over the fields on this object.
*
* @param array|ViewableData $data
* @return ViewableData_Customised
*/ */
public function customise($data) public function customise(array|ViewableData $data): ViewableData
{ {
if (is_array($data) && (empty($data) || ArrayLib::is_associative($data))) { if (is_array($data) && (empty($data) || ArrayLib::is_associative($data))) {
$data = new ArrayData($data); $data = new ArrayData($data);
@ -346,33 +297,25 @@ class ViewableData implements IteratorAggregate
* *
* This method should be overridden in subclasses to provide more context about the classes state. For example, a * This method should be overridden in subclasses to provide more context about the classes state. For example, a
* {@link DataObject} class could return false when it is deleted from the database * {@link DataObject} class could return false when it is deleted from the database
*
* @return bool
*/ */
public function exists() public function exists(): bool
{ {
return true; return true;
} }
/** /**
* @return string the class name * Return the class name (though subclasses may return something else)
*/ */
public function __toString() public function __toString(): string
{ {
return static::class; return static::class;
} }
/** public function getCustomisedObj(): ?ViewableData
* @return ViewableData
*/
public function getCustomisedObj()
{ {
return $this->customisedObject; return $this->customisedObject;
} }
/**
* @param ViewableData $object
*/
public function setCustomisedObj(ViewableData $object) public function setCustomisedObj(ViewableData $object)
{ {
$this->customisedObject = $object; $this->customisedObject = $object;
@ -384,12 +327,11 @@ class ViewableData implements IteratorAggregate
* Return the "casting helper" (a piece of PHP code that when evaluated creates a casted value object) * Return the "casting helper" (a piece of PHP code that when evaluated creates a casted value object)
* for a field on this object. This helper will be a subclass of DBField. * for a field on this object. This helper will be a subclass of DBField.
* *
* @param string $field
* @param bool $useFallback If true, fall back on the default casting helper if there isn't an explicit one. * @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. * @return string|null Casting helper As a constructor pattern, and may include arguments.
* @throws Exception * @throws Exception
*/ */
public function castingHelper($field, bool $useFallback = true) public function castingHelper(string $field, bool $useFallback = true): ?string
{ {
// Get casting if it has been configured. // Get casting if it has been configured.
// DB fields and PHP methods are all case insensitive so we normalise casing before checking. // DB fields and PHP methods are all case insensitive so we normalise casing before checking.
@ -441,11 +383,8 @@ class ViewableData implements IteratorAggregate
/** /**
* Get the class name a field on this object will be casted to. * Get the class name a field on this object will be casted to.
*
* @param string $field
* @return string
*/ */
public function castingClass($field) public function castingClass(string $field): string
{ {
// Strip arguments // Strip arguments
$spec = $this->castingHelper($field); $spec = $this->castingHelper($field);
@ -455,10 +394,9 @@ class ViewableData implements IteratorAggregate
/** /**
* Return the string-format type for the given field. * Return the string-format type for the given field.
* *
* @param string $field
* @return string 'xml'|'raw' * @return string 'xml'|'raw'
*/ */
public function escapeTypeForField($field) public function escapeTypeForField(string $field): string
{ {
$class = $this->castingClass($field) ?: $this->config()->get('default_cast'); $class = $this->castingClass($field) ?: $this->config()->get('default_cast');
@ -477,10 +415,9 @@ class ViewableData implements IteratorAggregate
* - an SSViewer instance * - an SSViewer instance
* *
* @param string|array|SSViewer $template the template to render into * @param string|array|SSViewer $template the template to render into
* @param array $customFields fields to customise() the object with before rendering * @param ViewableData|array|null $customFields fields to customise() the object with before rendering
* @return DBHTMLText
*/ */
public function renderWith($template, $customFields = null) public function renderWith($template, ViewableData|array|null $customFields = null): DBHTMLText
{ {
if (!is_object($template)) { if (!is_object($template)) {
$template = SSViewer::create($template); $template = SSViewer::create($template);
@ -556,14 +493,18 @@ class ViewableData implements IteratorAggregate
* Get the value of a field on this object, automatically inserting the value into any available casting objects * Get the value of a field on this object, automatically inserting the value into any available casting objects
* that have been specified. * that have been specified.
* *
* @param string $fieldName * @return object|DBField|null The specific object representing the field, or null if there is no
* @param array $arguments * property, method, or dynamic data available for that field.
* @param bool $cache Cache this object * Note that if there is a property or method that returns null, a relevant DBField instance will
* @param string $cacheName a custom cache name * be returned.
* @return object|DBField
*/ */
public function obj($fieldName, $arguments = [], $cache = false, $cacheName = null) public function obj(
{ string $fieldName,
array $arguments = [],
bool $cache = false,
?string $cacheName = null
): ?object {
$hasObj = false;
if (!$cacheName && $cache) { if (!$cacheName && $cache) {
$cacheName = $this->objCacheName($fieldName, $arguments); $cacheName = $this->objCacheName($fieldName, $arguments);
} }
@ -576,11 +517,19 @@ class ViewableData implements IteratorAggregate
// Load value from record // Load value from record
if ($this->hasMethod($fieldName)) { if ($this->hasMethod($fieldName)) {
$hasObj = true;
$value = call_user_func_array([$this, $fieldName], $arguments ?: []); $value = call_user_func_array([$this, $fieldName], $arguments ?: []);
} else { } else {
$hasObj = $this->hasField($fieldName) || ($this->hasMethod("get{$fieldName}") && $this->isAccessibleMethod("get{$fieldName}"));
$value = $this->$fieldName; $value = $this->$fieldName;
} }
// Return null early if there's no backing for this field
// i.e. no poperty, no method, etc - it just doesn't exist on this model.
if (!$hasObj && $value === null) {
return null;
}
// Try to cast object if we have an explicit cast set // Try to cast object if we have an explicit cast set
if (!is_object($value)) { if (!is_object($value)) {
$castingHelper = $this->castingHelper($fieldName, false); $castingHelper = $this->castingHelper($fieldName, false);
@ -617,26 +566,18 @@ class ViewableData implements IteratorAggregate
* A simple wrapper around {@link ViewableData::obj()} that automatically caches the result so it can be used again * A simple wrapper around {@link ViewableData::obj()} that automatically caches the result so it can be used again
* without re-running the method. * without re-running the method.
* *
* @param string $fieldName
* @param array $arguments
* @param string $identifier an optional custom cache identifier
* @return Object|DBField * @return Object|DBField
*/ */
public function cachedCall($fieldName, $arguments = [], $identifier = null) public function cachedCall(string $fieldName, array $arguments = [], ?string $cacheName = null): object
{ {
return $this->obj($fieldName, $arguments, true, $identifier); return $this->obj($fieldName, $arguments, true, $cacheName);
} }
/** /**
* Checks if a given method/field has a valid value. If the result is an object, this will return the result of the * Checks if a given method/field has a valid value. If the result is an object, this will return the result of the
* exists method, otherwise will check if the result is not just an empty paragraph tag. * exists method, otherwise will check if the result is not just an empty paragraph tag.
*
* @param string $field
* @param array $arguments
* @param bool $cache
* @return bool
*/ */
public function hasValue($field, $arguments = [], $cache = true) public function hasValue(string $field, array $arguments = [], bool $cache = true): bool
{ {
$result = $this->obj($field, $arguments, $cache); $result = $this->obj($field, $arguments, $cache);
if ($result instanceof ViewableData) { if ($result instanceof ViewableData) {
@ -648,15 +589,13 @@ class ViewableData implements IteratorAggregate
/** /**
* Get the string value of a field on this object that has been suitable escaped to be inserted directly into a * Get the string value of a field on this object that has been suitable escaped to be inserted directly into a
* template. * template.
*
* @param string $field
* @param array $arguments
* @param bool $cache
* @return string
*/ */
public function XML_val($field, $arguments = [], $cache = false) public function XML_val(string $field, array $arguments = [], bool $cache = false): string
{ {
$result = $this->obj($field, $arguments, $cache); $result = $this->obj($field, $arguments, $cache);
if (!$result) {
return '';
}
// Might contain additional formatting over ->XML(). E.g. parse shortcodes, nl2br() // Might contain additional formatting over ->XML(). E.g. parse shortcodes, nl2br()
return $result->forTemplate(); return $result->forTemplate();
} }
@ -665,9 +604,8 @@ class ViewableData implements IteratorAggregate
* Get an array of XML-escaped values by field name * Get an array of XML-escaped values by field name
* *
* @param array $fields an array of field names * @param array $fields an array of field names
* @return array
*/ */
public function getXMLValues($fields) public function getXMLValues(array $fields): array
{ {
$result = []; $result = [];
@ -678,33 +616,12 @@ class ViewableData implements IteratorAggregate
return $result; return $result;
} }
// ITERATOR SUPPORT ------------------------------------------------------------------------------------------------
/**
* Return a single-item iterator so you can iterate over the fields of a single record.
*
* This is useful so you can use a single record inside a <% control %> block in a template - and then use
* to access individual fields on this object.
*
* @deprecated 5.2.0 Will be removed without equivalent functionality
*
* @return ArrayIterator
*/
public function getIterator(): Traversable
{
Deprecation::notice('5.2.0', 'Will be removed without equivalent functionality');
return new ArrayIterator([$this]);
}
// UTILITY METHODS ------------------------------------------------------------------------------------------------- // UTILITY METHODS -------------------------------------------------------------------------------------------------
/** /**
* Find appropriate templates for SSViewer to use to render this object * Find appropriate templates for SSViewer to use to render this object
*
* @param string $suffix
* @return array
*/ */
public function getViewerTemplates($suffix = '') public function getViewerTemplates(string $suffix = ''): array
{ {
return SSViewer::get_templates_by_class(static::class, $suffix, ViewableData::class); return SSViewer::get_templates_by_class(static::class, $suffix, ViewableData::class);
} }
@ -725,10 +642,9 @@ class ViewableData implements IteratorAggregate
* stop point - e.g. "Page DataObject ViewableData". * stop point - e.g. "Page DataObject ViewableData".
* *
* @param string $stopAtClass the class to stop at (default: ViewableData) * @param string $stopAtClass the class to stop at (default: ViewableData)
* @return string
* @uses ClassInfo * @uses ClassInfo
*/ */
public function CSSClasses($stopAtClass = ViewableData::class) public function CSSClasses(string $stopAtClass = ViewableData::class): string
{ {
$classes = []; $classes = [];
$classAncestry = array_reverse(ClassInfo::ancestry(static::class) ?? []); $classAncestry = array_reverse(ClassInfo::ancestry(static::class) ?? []);
@ -754,11 +670,9 @@ class ViewableData implements IteratorAggregate
/** /**
* Return debug information about this object that can be rendered into a template * Return debug information about this object that can be rendered into a template
*
* @return ViewableData_Debugger
*/ */
public function Debug() public function Debug(): ViewableData|string
{ {
return new ViewableData_Debugger($this); return ViewableData_Debugger::create($this);
} }
} }

View File

@ -4,17 +4,12 @@ namespace SilverStripe\View;
class ViewableData_Customised extends ViewableData class ViewableData_Customised extends ViewableData
{ {
protected ViewableData $original;
/** protected ViewableData $customised;
* @var ViewableData
*/
protected $original, $customised;
/** /**
* Instantiate a new customised ViewableData object * Instantiate a new customised ViewableData object
*
* @param ViewableData $originalObject
* @param ViewableData $customisedObject
*/ */
public function __construct(ViewableData $originalObject, ViewableData $customisedObject) public function __construct(ViewableData $originalObject, ViewableData $customisedObject)
{ {
@ -35,7 +30,7 @@ class ViewableData_Customised extends ViewableData
return call_user_func_array([$this->original, $method], $arguments ?? []); return call_user_func_array([$this->original, $method], $arguments ?? []);
} }
public function __get($property) public function __get(string $property): mixed
{ {
if (isset($this->customised->$property)) { if (isset($this->customised->$property)) {
return $this->customised->$property; return $this->customised->$property;
@ -44,12 +39,12 @@ class ViewableData_Customised extends ViewableData
return $this->original->$property; return $this->original->$property;
} }
public function __set($property, $value) public function __set(string $property, mixed $value): void
{ {
$this->customised->$property = $this->original->$property = $value; $this->customised->$property = $this->original->$property = $value;
} }
public function __isset($property) public function __isset(string $property): bool
{ {
return isset($this->customised->$property) || isset($this->original->$property) || parent::__isset($property); return isset($this->customised->$property) || isset($this->original->$property) || parent::__isset($property);
} }
@ -59,16 +54,20 @@ class ViewableData_Customised extends ViewableData
return $this->customised->hasMethod($method) || $this->original->hasMethod($method); return $this->customised->hasMethod($method) || $this->original->hasMethod($method);
} }
public function cachedCall($fieldName, $arguments = null, $identifier = null) public function cachedCall(string $fieldName, array $arguments = [], ?string $cacheName = null): object
{ {
if ($this->customisedHas($fieldName)) { if ($this->customisedHas($fieldName)) {
return $this->customised->cachedCall($fieldName, $arguments, $identifier); return $this->customised->cachedCall($fieldName, $arguments, $cacheName);
} }
return $this->original->cachedCall($fieldName, $arguments, $identifier); return $this->original->cachedCall($fieldName, $arguments, $cacheName);
} }
public function obj($fieldName, $arguments = null, $cache = false, $cacheName = null) public function obj(
{ string $fieldName,
array $arguments = [],
bool $cache = false,
?string $cacheName = null
): ?object {
if ($this->customisedHas($fieldName)) { if ($this->customisedHas($fieldName)) {
return $this->customised->obj($fieldName, $arguments, $cache, $cacheName); return $this->customised->obj($fieldName, $arguments, $cache, $cacheName);
} }

View File

@ -9,15 +9,8 @@ use ReflectionObject;
*/ */
class ViewableData_Debugger extends ViewableData class ViewableData_Debugger extends ViewableData
{ {
protected ViewableData $object;
/**
* @var ViewableData
*/
protected $object;
/**
* @param ViewableData $object
*/
public function __construct(ViewableData $object) public function __construct(ViewableData $object)
{ {
$this->object = $object; $this->object = $object;
@ -25,9 +18,9 @@ class ViewableData_Debugger extends ViewableData
} }
/** /**
* @return string The rendered debugger * Returns the rendered debugger
*/ */
public function __toString() public function __toString(): string
{ {
return (string)$this->forTemplate(); return (string)$this->forTemplate();
} }
@ -35,11 +28,8 @@ class ViewableData_Debugger extends ViewableData
/** /**
* Return debugging information, as XHTML. If a field name is passed, it will show debugging information on that * Return debugging information, as XHTML. If a field name is passed, it will show debugging information on that
* field, otherwise it will show information on all methods and fields. * field, otherwise it will show information on all methods and fields.
*
* @param string $field the field name
* @return string
*/ */
public function forTemplate($field = null) public function forTemplate(?string $field = null): string
{ {
// debugging info for a specific field // debugging info for a specific field
$class = get_class($this->object); $class = get_class($this->object);

View File

@ -14,14 +14,14 @@ class TestObject extends ViewableData implements TestOnly
$this->data = $data; $this->data = $data;
} }
public function hasField($name) public function hasField(string $fieldName): bool
{ {
return isset($this->data[$name]); return isset($this->data[$fieldName]);
} }
public function getField($name) public function getField(string $fieldName): mixed
{ {
return isset($this->data[$name]) ?: null; return isset($this->data[$fieldName]) ?: null;
} }
public function getSomething() public function getSomething()

View File

@ -129,9 +129,9 @@ class DBClassNameTest extends SapphireTest
$this->assertEquals('stdClass', $test3->getShortName()); $this->assertEquals('stdClass', $test3->getShortName());
$test4 = DBField::create_field('DBClassName', null); $test4 = DBField::create_field('DBClassName', null);
$this->assertNull($test4->getShortName()); $this->assertSame('', $test4->getShortName());
$test5 = DBField::create_field('DBClassName', 'not a class'); $test5 = DBField::create_field('DBClassName', 'not a class');
$this->assertNull($test5->getShortName()); $this->assertSame('', $test5->getShortName());
} }
} }

View File

@ -7,7 +7,7 @@ use SilverStripe\ORM\FieldType\DBMoney;
class DBDoubleMoney extends DBMoney implements TestOnly class DBDoubleMoney extends DBMoney implements TestOnly
{ {
public function writeToManipulation(&$manipulation) public function writeToManipulation(array &$manipulation): void
{ {
// Duplicate the amount before writing // Duplicate the amount before writing
$this->setAmount($this->getAmount() * 2); $this->setAmount($this->getAmount() * 2);

View File

@ -16,10 +16,10 @@ class TestDataObject extends DataObject implements TestOnly
public $setFieldCalledCount = 0; public $setFieldCalledCount = 0;
public function setField($fieldName, $val) public function setField(string $fieldName, mixed $value): static
{ {
$this->setFieldCalledCount++; $this->setFieldCalledCount++;
return parent::setField($fieldName, $val); return parent::setField($fieldName, $value);
} }
public function setMyTestField($val) public function setMyTestField($val)

View File

@ -6,10 +6,11 @@ use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\TestOnly; use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\View\ViewableData;
class TestDbField extends DBField implements TestOnly class TestDbField extends DBField implements TestOnly
{ {
public function requireField() public function requireField(): void
{ {
// Basically the same as DBVarchar but we don't want to test with DBVarchar in case something // Basically the same as DBVarchar but we don't want to test with DBVarchar in case something
// changes in that class eventually. // changes in that class eventually.
@ -34,9 +35,9 @@ class TestDbField extends DBField implements TestOnly
public $saveIntoCalledCount = 0; public $saveIntoCalledCount = 0;
public function saveInto($dataObject) public function saveInto(ViewableData $model): void
{ {
$this->saveIntoCalledCount++; $this->saveIntoCalledCount++;
return parent::saveInto($dataObject); parent::saveInto($model);
} }
} }

View File

@ -7,7 +7,7 @@ use SilverStripe\ORM\FieldType\DBString;
class MyStringField extends DBString implements TestOnly class MyStringField extends DBString implements TestOnly
{ {
public function requireField() public function requireField(): void
{ {
} }
} }

View File

@ -739,15 +739,15 @@ class DataObjectTest extends SapphireTest
$fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1'); $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
// Test relation probing // Test relation probing
$this->assertFalse((bool)$team1->hasValue('Captain', null, false)); $this->assertFalse((bool)$team1->hasValue('Captain', [], false));
$this->assertFalse((bool)$team1->hasValue('CaptainID', null, false)); $this->assertFalse((bool)$team1->hasValue('CaptainID', [], false));
// Add a captain to team 1 // Add a captain to team 1
$team1->setField('CaptainID', $player1->ID); $team1->setField('CaptainID', $player1->ID);
$team1->write(); $team1->write();
$this->assertTrue((bool)$team1->hasValue('Captain', null, false)); $this->assertTrue((bool)$team1->hasValue('Captain', [], false));
$this->assertTrue((bool)$team1->hasValue('CaptainID', null, false)); $this->assertTrue((bool)$team1->hasValue('CaptainID', [], false));
$this->assertEquals( $this->assertEquals(
$player1->ID, $player1->ID,
@ -2075,22 +2075,22 @@ class DataObjectTest extends SapphireTest
public function testHasValue() public function testHasValue()
{ {
$team = new DataObjectTest\Team(); $team = new DataObjectTest\Team();
$this->assertFalse($team->hasValue('Title', null, false)); $this->assertFalse($team->hasValue('Title', [], false));
$this->assertFalse($team->hasValue('DatabaseField', null, false)); $this->assertFalse($team->hasValue('DatabaseField', [], false));
$team->Title = 'hasValue'; $team->Title = 'hasValue';
$this->assertTrue($team->hasValue('Title', null, false)); $this->assertTrue($team->hasValue('Title', [], false));
$this->assertFalse($team->hasValue('DatabaseField', null, false)); $this->assertFalse($team->hasValue('DatabaseField', [], false));
$team->Title = '<p></p>'; $team->Title = '<p></p>';
$this->assertTrue( $this->assertTrue(
$team->hasValue('Title', null, false), $team->hasValue('Title', [], false),
'Test that an empty paragraph is a value for non-HTML fields.' 'Test that an empty paragraph is a value for non-HTML fields.'
); );
$team->DatabaseField = 'hasValue'; $team->DatabaseField = 'hasValue';
$this->assertTrue($team->hasValue('Title', null, false)); $this->assertTrue($team->hasValue('Title', [], false));
$this->assertTrue($team->hasValue('DatabaseField', null, false)); $this->assertTrue($team->hasValue('DatabaseField', [], false));
} }
public function testHasMany() public function testHasMany()

View File

@ -34,10 +34,8 @@ class MockDynamicAssignmentDBField extends DBBoolean implements TestOnly
/** /**
* If the field value and $dynamicAssignment are true, we'll try to do a dynamic assignment. * If the field value and $dynamicAssignment are true, we'll try to do a dynamic assignment.
* @param $value
* @return array|int
*/ */
public function prepValueForDB($value) public function prepValueForDB(mixed $value): array|int|null
{ {
if ($value) { if ($value) {
return $this->dynamicAssignment return $this->dynamicAssignment
@ -48,7 +46,7 @@ class MockDynamicAssignmentDBField extends DBBoolean implements TestOnly
return 0; return 0;
} }
public function scalarValueOnly() public function scalarValueOnly(): bool
{ {
return $this->scalarOnly; return $this->scalarOnly;
} }

View File

@ -4,6 +4,7 @@ namespace SilverStripe\ORM\Tests;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\FieldType\DBDecimal; use SilverStripe\ORM\FieldType\DBDecimal;
use TypeError;
class DecimalTest extends SapphireTest class DecimalTest extends SapphireTest
{ {
@ -45,11 +46,8 @@ class DecimalTest extends SapphireTest
public function testInvalidSpecifiedDefaultValue() public function testInvalidSpecifiedDefaultValue()
{ {
$this->assertEquals( $this->expectException(TypeError::class);
$this->testDataObject->MyDecimal3, new DBDecimal(defaultValue: 'Invalid');
0,
'Invalid default value for Decimal type is casted to 0'
);
} }
public function testSpecifiedDefaultValueInDefaultsArray() public function testSpecifiedDefaultValueInDefaultsArray()

View File

@ -13,7 +13,6 @@ class TestObject extends DataObject implements TestOnly
'Name' => 'Varchar', 'Name' => 'Varchar',
'MyDecimal1' => 'Decimal', 'MyDecimal1' => 'Decimal',
'MyDecimal2' => 'Decimal(5,3,2.5)', 'MyDecimal2' => 'Decimal(5,3,2.5)',
'MyDecimal3' => 'Decimal(4,2,"Invalid default value")',
'MyDecimal4' => 'Decimal', 'MyDecimal4' => 'Decimal',
'MyDecimal5' => 'Decimal(20,18,0.99999999999999999999)', 'MyDecimal5' => 'Decimal(20,18,0.99999999999999999999)',
'MyDecimal6' => 'Decimal', 'MyDecimal6' => 'Decimal',

View File

@ -11,7 +11,7 @@ use stdClass;
class ArrayDataTest extends SapphireTest class ArrayDataTest extends SapphireTest
{ {
public function testViewabledataItemsInsideArraydataArePreserved() public function testViewableDataItemsInsideArraydataArePreserved()
{ {
/* ViewableData objects will be preserved, but other objects will be converted */ /* ViewableData objects will be preserved, but other objects will be converted */
$arrayData = new ArrayData( $arrayData = new ArrayData(

View File

@ -653,12 +653,14 @@ SS;
public function testLoopWhitespace() public function testLoopWhitespace()
{ {
$data = new ArrayList([new SSViewerTest\TestFixture()]);
$this->assertEquals( $this->assertEquals(
'before[out:SingleItem.Test]after 'before[out:Test]after
beforeTestafter', beforeTestafter',
$this->render( $this->render(
'before<% loop SingleItem %>$Test<% end_loop %>after 'before<% loop %>$Test<% end_loop %>after
before<% loop SingleItem %>Test<% end_loop %>after' before<% loop %>Test<% end_loop %>after',
$data
) )
); );
@ -668,15 +670,16 @@ SS;
$this->assertEquals( $this->assertEquals(
'before 'before
[out:SingleItem.ItemOnItsOwnLine] [out:ItemOnItsOwnLine]
after', after',
$this->render( $this->render(
'before 'before
<% loop SingleItem %> <% loop %>
$ItemOnItsOwnLine $ItemOnItsOwnLine
<% end_loop %> <% end_loop %>
after' after',
$data
) )
); );
@ -2369,7 +2372,7 @@ EOC;
public function testMe(): void public function testMe(): void
{ {
$mockArrayData = $this->getMockBuilder(ArrayData::class)->addMethods(['forTemplate'])->getMock(); $mockArrayData = $this->getMockBuilder(ArrayData::class)->addMethods(['forTemplate'])->getMock();
$mockArrayData->expects($this->once())->method('forTemplate'); $mockArrayData->expects($this->once())->method('forTemplate')->willReturn('');
$this->render('$Me', $mockArrayData); $this->render('$Me', $mockArrayData);
} }
} }

View File

@ -18,7 +18,6 @@ class TestFixture extends ViewableData
parent::__construct(); parent::__construct();
} }
private function argedName($fieldName, $arguments) private function argedName($fieldName, $arguments)
{ {
$childName = $this->name ? "$this->name.$fieldName" : $fieldName; $childName = $this->name ? "$this->name.$fieldName" : $fieldName;
@ -29,8 +28,12 @@ class TestFixture extends ViewableData
} }
} }
public function obj($fieldName, $arguments = null, $cache = false, $cacheName = null) public function obj(
{ string $fieldName,
array $arguments = [],
bool $cache = false,
?string $cacheName = null
): ?object {
$childName = $this->argedName($fieldName, $arguments); $childName = $this->argedName($fieldName, $arguments);
// Special field name Loop### to create a list // Special field name Loop### to create a list
@ -49,8 +52,7 @@ class TestFixture extends ViewableData
} }
} }
public function XML_val(string $fieldName, array $arguments = [], bool $cache = false): string
public function XML_val($fieldName, $arguments = null, $cache = false)
{ {
if (preg_match('/NotSet/i', $fieldName ?? '')) { if (preg_match('/NotSet/i', $fieldName ?? '')) {
return ''; return '';
@ -63,7 +65,7 @@ class TestFixture extends ViewableData
} }
} }
public function hasValue($fieldName, $arguments = null, $cache = true) public function hasValue(string $fieldName, array $arguments = [], bool $cache = true): bool
{ {
return (bool)$this->XML_val($fieldName, $arguments); return (bool)$this->XML_val($fieldName, $arguments);
} }

View File

@ -133,7 +133,7 @@ class ViewableDataTest extends SapphireTest
$this->assertFalse($data->hasValue('SomethingElse')); $this->assertFalse($data->hasValue('SomethingElse'));
// this should cast the raw string to a StringField since we are // this should cast the raw string to a StringField since we are
// passing true as the third argument: // passing true as the third argument:
$obj = $data->obj('Title', null, true); $obj = $data->obj('Title', [], true);
$this->assertTrue(is_object($obj)); $this->assertTrue(is_object($obj));
// and the string field should have the value of the raw string: // and the string field should have the value of the raw string:
$this->assertEquals('SomeTitleValue', $obj->forTemplate()); $this->assertEquals('SomeTitleValue', $obj->forTemplate());
@ -164,11 +164,11 @@ class ViewableDataTest extends SapphireTest
// Save a literal string into cache // Save a literal string into cache
$cache = true; $cache = true;
$uncastedData = $obj->obj('noCastingInformation', null, false, $cache); $uncastedData = $obj->obj('noCastingInformation', [], false, $cache);
// Fetch the cached string as an object // Fetch the cached string as an object
$forceReturnedObject = true; $forceReturnedObject = true;
$castedData = $obj->obj('noCastingInformation', null, $forceReturnedObject); $castedData = $obj->obj('noCastingInformation', [], $forceReturnedObject);
// Uncasted data should always be the nonempty string // Uncasted data should always be the nonempty string
$this->assertNotEmpty($uncastedData, 'Uncasted data was empty.'); $this->assertNotEmpty($uncastedData, 'Uncasted data was empty.');
@ -189,15 +189,15 @@ class ViewableDataTest extends SapphireTest
$objCached->Test = 'AAA'; $objCached->Test = 'AAA';
$objNotCached->Test = 'AAA'; $objNotCached->Test = 'AAA';
$this->assertEquals('AAA', $objCached->obj('Test', null, true, true)); $this->assertEquals('AAA', $objCached->obj('Test', [], true, true));
$this->assertEquals('AAA', $objNotCached->obj('Test', null, true, true)); $this->assertEquals('AAA', $objNotCached->obj('Test', [], true, true));
$objCached->Test = 'BBB'; $objCached->Test = 'BBB';
$objNotCached->Test = 'BBB'; $objNotCached->Test = 'BBB';
// Cached data must be always the same // Cached data must be always the same
$this->assertEquals('AAA', $objCached->obj('Test', null, true, true)); $this->assertEquals('AAA', $objCached->obj('Test', [], true, true));
$this->assertEquals('BBB', $objNotCached->obj('Test', null, true, true)); $this->assertEquals('BBB', $objNotCached->obj('Test', [], true, true));
} }
public function testSetFailover() public function testSetFailover()

View File

@ -50,7 +50,7 @@ class Castable extends ViewableData implements TestOnly
return $this->unsafeXML(); return $this->unsafeXML();
} }
public function forTemplate() public function forTemplate(): string
{ {
return 'castable'; return 'castable';
} }

View File

@ -8,7 +8,7 @@ use SilverStripe\View\ViewableData;
class Caster extends ViewableData implements TestOnly class Caster extends ViewableData implements TestOnly
{ {
public function forTemplate() public function forTemplate(): string
{ {
return 'casted'; return 'casted';
} }

View File

@ -10,7 +10,7 @@ class RequiresCasting extends ViewableData implements TestOnly
public $test = 'overwritten'; public $test = 'overwritten';
public function forTemplate() public function forTemplate(): string
{ {
return 'casted'; return 'casted';
} }

View File

@ -15,7 +15,7 @@ class UnescapedCaster extends ViewableData implements TestOnly
$this->value = $value; $this->value = $value;
} }
public function forTemplate() public function forTemplate(): string
{ {
return Convert::raw2xml($this->value); return Convert::raw2xml($this->value);
} }