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
{
/**
* The object that represents the item, it contains all the data.
*
* @var mixed
*/
protected $failover;
/**
* Name of the title field of feed entries
*

View File

@ -236,7 +236,7 @@ class TaskRunner extends Controller implements PermissionProvider
}
return count($this->getTaskList()) > 0;
}
public function providePermissions(): array
{
return [

View File

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

View File

@ -521,7 +521,7 @@ class Form extends ViewableData implements HasRequestHandler
return $this;
}
public function castingHelper($field, bool $useFallback = true)
public function castingHelper(string $field, bool $useFallback = true): ?string
{
// Override casting for field message
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
* than <% with FormObject %>
*
* @return DBHTMLText
*/
public function forTemplate()
public function forTemplate(): string
{
if (!$this->canBeCached()) {
HTTPCacheControlMiddleware::singleton()->disableCache();
@ -1750,7 +1748,7 @@ class Form extends ViewableData implements HasRequestHandler
return $this;
}
public function debug()
public function debug(): string
{
$class = static::class;
$result = "<h3>$class</h3><ul>";

View File

@ -790,7 +790,7 @@ class FormField extends RequestHandler
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
if (strcasecmp($field ?? '', 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
@ -1269,7 +1269,7 @@ class FormField extends RequestHandler
/**
* @return string
*/
public function debug()
public function debug(): string
{
$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
* will return the $Field value.
*
* @return string
*/
public function forTemplate()
public function forTemplate(): string
{
return $this->Field();
}

View File

@ -504,7 +504,7 @@ class FormRequestHandler extends RequestHandler
return $result;
}
public function forTemplate()
public function forTemplate(): string
{
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.
* See LeftAndMain::getResponseNegotiator()
*/
private function getResponseNegotiator(DBHTMLText $renderedForm): PjaxResponseNegotiator
private function getResponseNegotiator(string $renderedForm): PjaxResponseNegotiator
{
return new PjaxResponseNegotiator([
'default' => function () use ($renderedForm) {

View File

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

View File

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

View File

@ -58,7 +58,7 @@ class RequiredFields extends Validator
* Debug helper
* @return string
*/
public function debug()
public function debug(): string
{
if (!is_array($this->required)) {
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
*
* @return bool
*/
public function exists()
public function exists(): bool
{
return !empty($this->items);
}
@ -159,7 +157,7 @@ class ArrayList extends ViewableData implements SS_List, Filterable, Sortable, L
return $this;
}
public function debug()
public function debug(): string
{
$val = "<h2>" . static::class . "</h2><ul>";
foreach ($this->toNestedArray() as $item) {

View File

@ -868,7 +868,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
return $this;
}
public function debug()
public function debug(): string
{
$val = "<h2>" . static::class . "</h2><ul>";
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
*
* @return bool
*/
public function exists()
public function exists(): bool
{
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.
* The default behaviour for a DataObject is to return true if
* 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();
}
@ -2686,7 +2684,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $untabbedFields;
}
public function getViewerTemplates($suffix = '')
public function getViewerTemplates(string $suffix = ''): array
{
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.
* 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 (isset($this->record[$field])) {
@ -2928,12 +2923,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Set the value of the field
* 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();
//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)) {
unset($this->components[$fieldName]);
// Assign component directly
if (is_null($val) || $val instanceof DataObject) {
return $this->setComponent($fieldName, $val);
if (is_null($value) || $value instanceof DataObject) {
return $this->setComponent($fieldName, $value);
}
// Assign by ID instead of object
if (is_numeric($val)) {
if (is_numeric($value)) {
$fieldName .= 'ID';
}
}
// Situation 1: Passing an DBField
if ($val instanceof DBField) {
$val->setName($fieldName);
$val->saveInto($this);
if ($value instanceof DBField) {
$value->setName($fieldName);
$value->saveInto($this);
// Situation 1a: Composite fields should remain bound in case they are
// later referenced to update the parent dataobject
if ($val instanceof DBComposite) {
$val->bindTo($this);
$this->setFieldValue($fieldName, $val);
if ($value instanceof DBComposite) {
$value->bindTo($this);
$this->setFieldValue($fieldName, $value);
}
// Situation 2: Passing a literal or non-DBField object
} else {
$this->setFieldValue($fieldName, $val);
$this->setFieldValue($fieldName, $value);
}
return $this;
}
private function setFieldValue(string $fieldName, mixed $val): void
private function setFieldValue(string $fieldName, mixed $value): void
{
$schema = static::getSchema();
// 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');
}
if (!empty($val) && !is_scalar($val)) {
if (!empty($value) && !is_scalar($value)) {
$dbField = $this->dbObject($fieldName);
if ($dbField && $dbField->scalarValueOnly()) {
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 (!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
$this->changed[$fieldName] = DataObject::CHANGE_STRICT;
if ((!array_key_exists($fieldName, $this->original ?? []) && $val)
|| (array_key_exists($fieldName, $this->original ?? []) && $this->original[$fieldName] != $val)
if ((!array_key_exists($fieldName, $this->original ?? []) && $value)
|| (array_key_exists($fieldName, $this->original ?? []) && $this->original[$fieldName] != $value)
) {
// Value has changed as well, not just the type
$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
$this->record[$fieldName] = $val;
$this->record[$fieldName] = $value;
}
/**
@ -3047,7 +3038,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* {@inheritdoc}
*/
public function castingHelper($field, bool $useFallback = true)
public function castingHelper(string $field, bool $useFallback = true): ?string
{
$fieldSpec = static::getSchema()->fieldSpec(static::class, $field);
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
* the objects tables and optionally look up a dynamic getter with
* 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();
return (
array_key_exists($field, $this->record ?? [])
|| array_key_exists($field, $this->components ?? [])
|| $schema->fieldSpec(static::class, $field)
|| $schema->unaryComponent(static::class, $field)
|| $this->hasMethod("get{$field}")
array_key_exists($fieldName, $this->record ?? [])
|| array_key_exists($fieldName, $this->components ?? [])
|| $schema->fieldSpec(static::class, $fieldName)
|| $schema->unaryComponent(static::class, $fieldName)
|| $this->hasMethod("get{$fieldName}")
);
}
@ -3232,7 +3220,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*
* @return string HTML data representing this object
*/
public function debug()
public function debug(): string
{
$class = static::class;
$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
* (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
$hasOne = static::getSchema()->hasOneComponent(static::class, $field);

View File

@ -36,7 +36,7 @@ interface DataObjectInterface
* @param string $fieldName
* @return mixed
*/
public function __get($fieldName);
public function __get(string $property): mixed;
/**
* 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;
}
public function debug()
public function debug(): string
{
// Same implementation as DataList::debug()
$val = '<h2>' . static::class . '</h2><ul>';

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
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.
@ -29,26 +30,21 @@ abstract class DBComposite extends DBField
* holds an array of composite field names.
* Don't include the fields "main name",
* 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
* 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
*
* @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
// by intentionally avoiding hasMethod()
@ -59,7 +55,7 @@ abstract class DBComposite extends DBField
parent::__set($property, $value);
}
public function __get($property)
public function __get(string $property): mixed
{
// Prevent failover / extensions from hijacking composite field getters
// by intentionally avoiding hasMethod()
@ -71,10 +67,8 @@ abstract class DBComposite extends DBField
/**
* 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) {
// Write sub-manipulation
@ -88,10 +82,8 @@ abstract class DBComposite extends DBField
* 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}
* array.
*
* @param SQLSelect $query
*/
public function addToQuery(&$query)
public function addToQuery(SQLSelect &$query): void
{
parent::addToQuery($query);
@ -109,12 +101,10 @@ abstract class DBComposite extends DBField
/**
* Return array in the format of {@link $composite_db}.
* 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.
* 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
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
*
* @return boolean
*/
public function exists()
public function exists(): bool
{
// By default all fields
foreach ($this->compositeDatabaseFields() as $field => $spec) {
@ -156,7 +144,7 @@ abstract class DBComposite extends DBField
return true;
}
public function requireField()
public function requireField(): void
{
foreach ($this->compositeDatabaseFields() as $field => $spec) {
$key = $this->getName() . $field;
@ -171,12 +159,9 @@ abstract class DBComposite extends DBField
*
* {@see ViewableData::obj}
*
* @param mixed $value
* @param mixed $record Parent object to this field, which could be a DataObject, record array, or other
* @param bool $markChanged
* @return $this
* @param null|array|ViewableData $record Parent object to this field, which could be a DataObject, record array, or other
*/
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;
@ -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
*
* @param DataObject $dataObject
* Bind this field to the model, and set the underlying table to that of the owner
*/
public function bindTo($dataObject)
public function bindTo(DataObject $dataObject): void
{
$this->record = $dataObject;
}
public function saveInto($dataObject)
public function saveInto(ViewableData $dataObject): void
{
foreach ($this->compositeDatabaseFields() as $field => $spec) {
// Save into record
@ -230,52 +213,44 @@ abstract class DBComposite extends DBField
/**
* 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
$fields = $this->compositeDatabaseFields();
if (!isset($fields[$field])) {
if (!isset($fields[$fieldName])) {
return null;
}
// Check bound object
if ($this->record instanceof DataObject) {
$key = $this->getName() . $field;
$key = $this->getName() . $fieldName;
return $this->record->getField($key);
}
// Check local record
if (isset($this->record[$field])) {
return $this->record[$field];
if (isset($this->record[$fieldName])) {
return $this->record[$fieldName];
}
return null;
}
public function hasField($field)
public function hasField(string $fieldName): bool
{
$fields = $this->compositeDatabaseFields();
return isset($fields[$field]);
return isset($fields[$fieldName]);
}
/**
* 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();
if (!$this->hasField($field)) {
if (!$this->hasField($fieldName)) {
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.'
]));
}
@ -287,23 +262,20 @@ abstract class DBComposite extends DBField
// Set bound object
if ($this->record instanceof DataObject) {
$key = $this->getName() . $field;
$key = $this->getName() . $fieldName;
$this->record->setField($key, $value);
return $this;
}
// Set local record
$this->record[$field] = $value;
$this->record[$fieldName] = $value;
return $this;
}
/**
* 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();
if (!isset($fields[$field])) {
@ -319,7 +291,7 @@ abstract class DBComposite extends DBField
return $fieldObject;
}
public function castingHelper($field, bool $useFallback = true)
public function castingHelper(string $field, bool $useFallback = true): ?string
{
$fields = $this->compositeDatabaseFields();
if (isset($fields[$field])) {
@ -329,7 +301,7 @@ abstract class DBComposite extends DBField
return parent::castingHelper($field, $useFallback);
}
public function getIndexSpecs()
public function getIndexSpecs(): array
{
if ($type = $this->getIndexType()) {
$columns = array_map(function ($name) {
@ -341,9 +313,10 @@ abstract class DBComposite extends DBField
'columns' => $columns,
];
}
return [];
}
public function scalarValueOnly()
public function scalarValueOnly(): bool
{
return false;
}

View File

@ -3,6 +3,8 @@
namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\CurrencyField;
use SilverStripe\Forms\FormField;
use SilverStripe\View\ViewableData;
/**
* Represents a decimal field containing a currency amount.
@ -20,22 +22,16 @@ use SilverStripe\Forms\CurrencyField;
class DBCurrency extends DBDecimal
{
/**
* @config
* @var string
* The symbol that represents the currency
*/
private static $currency_symbol = '$';
public function __construct($name = null, $wholeSize = 9, $decimalSize = 2, $defaultValue = 0)
{
parent::__construct($name, $wholeSize, $decimalSize, $defaultValue);
}
private static string $currency_symbol = '$';
/**
* 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) {
return "($val)";
}
@ -46,22 +42,22 @@ class DBCurrency extends DBDecimal
/**
* 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) {
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;
if (is_numeric($value)) {
$this->value = $value;
} 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 {
$this->value = 0;
}
@ -69,13 +65,7 @@ class DBCurrency extends DBDecimal
return $this;
}
/**
* @param string $title
* @param array $params
*
* @return CurrencyField
*/
public function scaffoldFormField($title = null, $params = null)
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{
return CurrencyField::create($this->getName(), $title);
}

View File

@ -6,10 +6,12 @@ use IntlDateFormatter;
use InvalidArgumentException;
use NumberFormatter;
use SilverStripe\Forms\DateField;
use SilverStripe\Forms\FormField;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\DB;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\View\ViewableData;
/**
* Represents a date field.
@ -32,15 +34,15 @@ class DBDate extends DBField
/**
* 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
* 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);
if ($value === false) {
@ -58,7 +60,7 @@ class DBDate extends DBField
* @param mixed $value
* @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
if (empty($value) && !is_numeric($value)) {
@ -89,13 +91,11 @@ class DBDate extends DBField
/**
* Returns the standard localised medium date
*
* @return ?string
*/
public function Nice()
public function Nice(): string
{
if (!$this->value) {
return null;
return '';
}
$formatter = $this->getFormatter();
return $formatter->format($this->getTimestamp());
@ -103,40 +103,32 @@ class DBDate extends DBField
/**
* Returns the year from the given date
*
* @return string
*/
public function Year()
public function Year(): string
{
return $this->Format('y');
}
/**
* Returns the day of the week
*
* @return string
*/
public function DayOfWeek()
public function DayOfWeek(): string
{
return $this->Format('cccc');
}
/**
* Returns a full textual representation of a month, such as January.
*
* @return string
*/
public function Month()
public function Month(): string
{
return $this->Format('LLLL');
}
/**
* Returns the short version of the month such as Jan
*
* @return string
*/
public function ShortMonth()
public function ShortMonth(): string
{
return $this->Format('LLL');
}
@ -145,9 +137,8 @@ class DBDate extends DBField
* Returns the day of the month.
*
* @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');
if ($includeOrdinal && $number) {
@ -159,13 +150,11 @@ class DBDate extends DBField
/**
* Returns the date in the localised short format
*
* @return string
*/
public function Short()
public function Short(): string
{
if (!$this->value) {
return null;
return '';
}
$formatter = $this->getFormatter(IntlDateFormatter::SHORT);
return $formatter->format($this->getTimestamp());
@ -173,13 +162,11 @@ class DBDate extends DBField
/**
* Returns the date in the localised long format
*
* @return string
*/
public function Long()
public function Long(): string
{
if (!$this->value) {
return null;
return '';
}
$formatter = $this->getFormatter(IntlDateFormatter::LONG);
return $formatter->format($this->getTimestamp());
@ -187,13 +174,11 @@ class DBDate extends DBField
/**
* Returns the date in the localised full format
*
* @return string
*/
public function Full()
public function Full(): string
{
if (!$this->value) {
return null;
return '';
}
$formatter = $this->getFormatter(IntlDateFormatter::FULL);
return $formatter->format($this->getTimestamp());
@ -201,12 +186,8 @@ class DBDate extends DBField
/**
* 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);
}
@ -216,16 +197,13 @@ class DBDate extends DBField
*
* @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 int $dateLength
* @param int $timeLength
* @return IntlDateFormatter
*/
public function getCustomFormatter(
$locale = null,
$pattern = null,
$dateLength = IntlDateFormatter::MEDIUM,
$timeLength = IntlDateFormatter::NONE
) {
?string $locale = null,
?string $pattern = null,
int $dateLength = IntlDateFormatter::MEDIUM,
int $timeLength = IntlDateFormatter::NONE
): IntlDateFormatter {
$locale = $locale ?: i18n::get_locale();
$formatter = IntlDateFormatter::create($locale, $dateLength, $timeLength);
if ($pattern) {
@ -238,9 +216,8 @@ class DBDate extends DBField
* Formatter used internally
*
* @internal
* @return IntlDateFormatter
*/
protected function getInternalFormatter()
protected function getInternalFormatter(): IntlDateFormatter
{
$formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDate::ISO_DATE);
$formatter->setLenient(false);
@ -249,10 +226,8 @@ class DBDate extends DBField
/**
* Get standard ISO date format string
*
* @return string
*/
public function getISOFormat()
public function getISOFormat(): string
{
return DBDate::ISO_DATE;
}
@ -262,16 +237,13 @@ class DBDate extends DBField
* 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 $locale Custom locale to use (add to signature in 5.0)
* @return ?string The date in the requested format
* @param string|null $locale Custom locale to use
* @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) {
return null;
return '';
}
// Replace {o} with ordinal representation of day of the month
@ -285,10 +257,8 @@ class DBDate extends DBField
/**
* Get unix timestamp for this date
*
* @return int
*/
public function getTimestamp()
public function getTimestamp(): int
{
if ($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.
*
* @param Member $member
* @return boolean | string A date formatted as per user-defined settings.
* @return string A date formatted as per user-defined settings.
*/
public function FormatFromSettings($member = null)
public function FormatFromSettings(?Member $member = null): string
{
if (!$member) {
$member = Security::getCurrentUser();
@ -512,7 +481,7 @@ class DBDate extends DBField
}
}
public function requireField()
public function requireField(): void
{
$parts = ['datatype' => 'date', 'arrayValue' => $this->arrayValue];
$values = ['type' => 'date', 'parts' => $parts];
@ -575,7 +544,7 @@ class DBDate extends DBField
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->setHTML5(true);

View File

@ -6,10 +6,12 @@ use Exception;
use IntlDateFormatter;
use InvalidArgumentException;
use SilverStripe\Forms\DatetimeField;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DB;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\View\TemplateGlobalProvider;
use SilverStripe\View\ViewableData;
/**
* 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,
* 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,
* 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
* when this is enabled setting the value of this field will return a new field instance
* instead updatin the old one
*
* @var bool
*/
protected $immutable = false;
protected bool $immutable = false;
/**
* @param bool $immutable
* @return $this
* Used to set a specific time for "now", useful for unit tests.
*/
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;
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) {
// 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
*
* @return string Formatted date.
*/
public function Date()
public function Date(): string
{
$formatter = $this->getFormatter(IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE);
return $formatter->format($this->getTimestamp());
@ -98,10 +99,8 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/**
* Returns the standard localised time
*
* @return string Formatted time.
*/
public function Time()
public function Time(): string
{
$formatter = $this->getFormatter(IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM);
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'.
*
* @return string Formatted time.
*/
public function Time12()
public function Time12(): string
{
return $this->Format('h:mm a');
}
/**
* 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');
}
@ -130,10 +125,9 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/**
* Return a date and time formatted as per a CMS user's settings.
*
* @param Member $member
* @return boolean|string A time and date pair formatted as per user-defined settings.
* @return 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) {
$member = Security::getCurrentUser();
@ -151,7 +145,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
return $this->Format($dateFormat . ' ' . $timeFormat, $member->getLocale());
}
public function requireField()
public function requireField(): void
{
$parts = [
'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
* 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) ?? '');
}
public function scaffoldFormField($title = null, $params = null)
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{
$field = DatetimeField::create($this->name, $title);
$dateTimeFormat = $field->getDatetimeFormat();
@ -195,18 +187,11 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
return $field;
}
/**
*
*/
protected static $mock_now = null;
/**
* Returns either the current system date as determined
* 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();
@ -224,7 +209,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* @param DBDatetime|string $datetime Either in object format, or as a DBDatetime compatible string.
* @throws Exception
*/
public static function set_mock_now($datetime)
public static function set_mock_now(DBDatetime|string $datetime): void
{
if (!$datetime instanceof DBDatetime) {
$value = $datetime;
@ -240,20 +225,15 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* Clear any mocked date, which causes
* {@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;
}
/**
* 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;
@ -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 [
'Now' => ['method' => 'now', 'casting' => 'Datetime'],
@ -275,13 +255,11 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/**
* 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);
}
@ -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 $pattern Custom pattern to use for this, if required
* @param int $dateLength
* @param int $timeLength
* @return IntlDateFormatter
*/
public function getCustomFormatter(
$locale = null,
$pattern = null,
$dateLength = IntlDateFormatter::MEDIUM,
$timeLength = IntlDateFormatter::MEDIUM
) {
?string $locale = null,
?string $pattern = null,
int $dateLength = IntlDateFormatter::MEDIUM,
int $timeLength = IntlDateFormatter::MEDIUM
): IntlDateFormatter {
return parent::getCustomFormatter($locale, $pattern, $dateLength, $timeLength);
}
@ -308,9 +283,8 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
* Formatter used internally
*
* @internal
* @return IntlDateFormatter
*/
protected function getInternalFormatter()
protected function getInternalFormatter(): IntlDateFormatter
{
$formatter = $this->getCustomFormatter(DBDate::ISO_LOCALE, DBDatetime::ISO_DATETIME);
$formatter->setLenient(false);
@ -319,10 +293,8 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
/**
* Get standard ISO date format string
*
* @return string
*/
public function getISOFormat()
public function getISOFormat(): string
{
return DBDatetime::ISO_DATETIME;
}

View File

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

View File

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

View File

@ -4,6 +4,8 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\SelectField;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\Connect\MySQLDatabase;
use SilverStripe\ORM\DB;
@ -15,35 +17,28 @@ use SilverStripe\ORM\DB;
*/
class DBEnum extends DBString
{
/**
* List of enum values
*
* @var array
*/
protected $enum = [];
protected array $enum = [];
/**
* 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
* 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.
*/
public static function flushCache()
public static function flushCache(): void
{
DBEnum::$enum_cache = [];
}
@ -60,15 +55,18 @@ class DBEnum extends DBString
* "MyField" => "Enum(['Val1', 'Val2', 'Val3'], 'Val1')" // Supports array notation as well
* </code>
*
* @param string $name
* @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.
* 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
* @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) {
$this->setEnum($enum);
$enum = $this->getEnum();
@ -94,10 +92,7 @@ class DBEnum extends DBString
parent::__construct($name, $options);
}
/**
* @return void
*/
public function requireField()
public function requireField(): void
{
$charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation');
@ -121,18 +116,15 @@ class DBEnum extends DBString
}
/**
* Return a dropdown field suitable for editing this field.
*
* @param string $title
* @param string $name
* @param bool $hasEmpty
* @param string $value
* @param string $emptyString
* @return DropdownField
* Return a form field suitable for editing this field.
*/
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) {
$title = $this->getName();
}
@ -148,16 +140,12 @@ class DBEnum extends DBString
return $field;
}
public function scaffoldFormField($title = null, $params = null)
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{
return $this->formField($title);
}
/**
* @param string $title
* @return DropdownField
*/
public function scaffoldSearchField($title = null)
public function scaffoldSearchField(?string $title = null): ?FormField
{
$anyText = _t(__CLASS__ . '.ANY', 'Any');
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
* a {@link DropdownField}
*
* @param bool $hasEmpty
*
* @return array
*/
public function enumValues($hasEmpty = false)
public function enumValues(bool $hasEmpty = false): array
{
return ($hasEmpty)
? array_merge(['' => ''], ArrayLib::valuekey($this->getEnum()))
@ -180,15 +164,12 @@ class DBEnum extends DBString
/**
* Get list of enum values
*
* @return array
*/
public function getEnum()
public function getEnum(): array
{
return $this->enum;
}
/**
* 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.
*
* 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
$table = $this->getTable();
@ -232,11 +211,8 @@ class DBEnum extends DBString
/**
* Set enum options
*
* @param string|array $enum
* @return $this
*/
public function setEnum($enum)
public function setEnum(string|array $enum): static
{
if (!is_array($enum)) {
$enum = preg_split(
@ -250,22 +226,17 @@ class DBEnum extends DBString
}
/**
* Get default vwalue
*
* @return string|null
* Get default value
*/
public function getDefault()
public function getDefault(): ?string
{
return $this->default;
}
/**
* Set default value
*
* @param string $default
* @return $this
*/
public function setDefault($default)
public function setDefault(?string $default): static
{
$this->default = $default;
$this->setDefaultValue($default);

View File

@ -7,7 +7,6 @@ use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Filters\SearchFilter;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\View\ViewableData;
@ -36,7 +35,7 @@ use SilverStripe\View\ViewableData;
*
* <code>
* class Blob extends DBField {
* function requireField() {
* function requireField(): void {
* DB::require_field($this->tableName, $this->name, "blob");
* }
* }
@ -47,65 +46,47 @@ abstract class DBField extends ViewableData implements DBIndexable
/**
* Raw value of this field
*
* @var mixed
*/
protected $value;
protected mixed $value = null;
/**
* Table this field belongs to
*
* @var string
*/
protected $tableName;
protected ?string $tableName = null;
/**
* Name of this field
*
* @var string
*/
protected $name;
protected ?string $name = null;
/**
* Used for generating DB schema. {@see DBSchemaManager}
*
* @var array
* Despite its name, this seems to be a string
*/
protected $arrayValue;
/**
* 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".
*
* @var string
* @config
*/
private static $escape_type = 'raw';
private static string $escape_type = 'raw';
/**
* 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
* 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',
'CDATA' => '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
* 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]
*
* @param string $name
* @param array $options
* @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;
@ -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
* constructor arguments in the same format as DataObject.db config.
* @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
* 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
// 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.
*
* 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) {
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.
*
* @return string
*/
public function getName()
public function getName(): string
{
return $this->name;
return $this->name ?? '';
}
/**
* Returns the value of this field.
*
* @return mixed
*/
public function getValue()
public function getValue(): mixed
{
return $this->value;
}
@ -228,14 +198,12 @@ abstract class DBField extends ViewableData implements DBIndexable
* and actually changing its values, it needs a {@link $markChanged}
* parameter.
*
* @param mixed $value
* @param DataObject|array $record An array or object that this field is part of
* @param null|ViewableData|array $record An array or object that this field is part of
* @param bool $markChanged Indicate whether this field should be marked changed.
* Set to FALSE if you are initializing this field after construction, rather
* 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;
return $this;
@ -243,21 +211,16 @@ abstract class DBField extends ViewableData implements DBIndexable
/**
* Get default value assigned at the DB level
*
* @return mixed
*/
public function getDefaultValue()
public function getDefaultValue(): mixed
{
return $this->defaultVal;
}
/**
* 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;
return $this;
@ -265,11 +228,8 @@ abstract class DBField extends ViewableData implements DBIndexable
/**
* 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;
return $this;
@ -277,15 +237,13 @@ abstract class DBField extends ViewableData implements DBIndexable
/**
* Get optional parameters for this field
*
* @return array
*/
public function getOptions()
public function getOptions(): array
{
return $this->options;
}
public function setIndexType($type)
public function setIndexType($type): string|bool
{
if (!is_bool($type)
&& !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'
* in a database context.
*
* @return boolean
*/
public function exists()
public function exists(): bool
{
return (bool)$this->value;
}
@ -336,7 +292,7 @@ abstract class DBField extends ViewableData implements DBIndexable
* @param mixed $value The value to check
* @return mixed The raw value, or escaped parameterised details
*/
public function prepValueForDB($value)
public function prepValueForDB(mixed $value): mixed
{
if ($value === null ||
$value === "" ||
@ -358,10 +314,8 @@ abstract class DBField extends ViewableData implements DBIndexable
* can also be used to apply special SQL-commands
* to the raw value (e.g. for GIS functionality).
* {@see prepValueForDB}
*
* @param array $manipulation
*/
public function writeToManipulation(&$manipulation)
public function writeToManipulation(array &$manipulation): void
{
$manipulation['fields'][$this->name] = $this->exists()
? $this->prepValueForDB($this->value) : $this->nullValue();
@ -375,20 +329,15 @@ abstract class DBField extends ViewableData implements DBIndexable
* SELECT <tablename>.* which
* gets you the default representations
* of all columns.
*
* @param SQLSelect $query
*/
public function addToQuery(&$query)
public function addToQuery(SQLSelect &$query)
{
}
/**
* Assign this DBField to a table
*
* @param string $tableName
* @return $this
*/
public function setTable($tableName)
public function setTable(string $tableName): static
{
$this->tableName = $tableName;
return $this;
@ -396,20 +345,16 @@ abstract class DBField extends ViewableData implements DBIndexable
/**
* Get the table this field belongs to, if assigned
*
* @return string|null
*/
public function getTable()
public function getTable(): ?string
{
return $this->tableName;
}
/**
* Determine 'default' casting for this field.
*
* @return string
*/
public function forTemplate()
public function forTemplate(): string
{
// Default to XML encoding
return $this->XML();
@ -417,40 +362,32 @@ abstract class DBField extends ViewableData implements DBIndexable
/**
* Gets the value appropriate for a HTML attribute string
*
* @return string
*/
public function HTMLATT()
public function HTMLATT(): string
{
return Convert::raw2htmlatt($this->RAW());
}
/**
* urlencode this string
*
* @return string
*/
public function URLATT()
public function URLATT(): string
{
return urlencode($this->RAW() ?? '');
}
/**
* rawurlencode this string
*
* @return string
*/
public function RAWURLATT()
public function RAWURLATT(): string
{
return rawurlencode($this->RAW() ?? '');
}
/**
* Gets the value appropriate for a HTML attribute string
*
* @return string
*/
public function ATT()
public function ATT(): string
{
return Convert::raw2att($this->RAW());
}
@ -458,60 +395,48 @@ abstract class DBField extends ViewableData implements DBIndexable
/**
* Gets the raw value for this field.
* Note: Skips processors implemented via forTemplate()
*
* @return mixed
*/
public function RAW()
public function RAW(): mixed
{
return $this->getValue();
}
/**
* Gets javascript string literal value
*
* @return string
*/
public function JS()
public function JS(): string
{
return Convert::raw2js($this->RAW());
}
/**
* Return JSON encoded value
*
* @return string
*/
public function JSON()
public function JSON(): string
{
return json_encode($this->RAW());
}
/**
* Alias for {@see XML()}
*
* @return string
*/
public function HTML()
public function HTML(): string
{
return $this->XML();
}
/**
* XML encode this value
*
* @return string
*/
public function XML()
public function XML(): string
{
return Convert::raw2xml($this->RAW());
}
/**
* Safely escape for XML string
*
* @return string
*/
public function CDATA()
public function CDATA(): string
{
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.
* Usually it's a choice between null, 0, and ''
*
* @return mixed
*/
public function nullValue()
public function nullValue(): mixed
{
return null;
}
/**
* Saves this field to the given data object.
*
* @param DataObject $dataObject
*/
public function saveInto($dataObject)
public function saveInto(ViewableData $model): void
{
$fieldName = $this->name;
if (empty($fieldName)) {
@ -541,9 +462,9 @@ abstract class DBField extends ViewableData implements DBIndexable
);
}
if ($this->value instanceof DBField) {
$this->value->saveInto($dataObject);
$this->value->saveInto($model);
} 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()}
*
* @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);
}
@ -569,30 +488,28 @@ abstract class DBField extends ViewableData implements DBIndexable
* Used by {@link SearchContext}, {@link ModelAdmin}, {@link DataObject::scaffoldFormFields()}.
*
* @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);
}
/**
* @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;
$filterClass = $this->config()->get('default_search_filter_class');
$filterClass = static::config()->get('default_search_filter_class');
return Injector::inst()->create($filterClass, $name);
}
/**
* 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
<ul>
@ -603,40 +520,31 @@ abstract class DBField extends ViewableData implements DBIndexable
DBG;
}
public function __toString()
public function __toString(): string
{
return (string)$this->forTemplate();
}
/**
* @return array
*/
public function getArrayValue()
{
return $this->arrayValue;
}
/**
* @param array $value
* @return $this
*/
public function setArrayValue($value)
public function setArrayValue($value): static
{
$this->arrayValue = $value;
return $this;
}
/**
* Get formfield schema value
*
* @return string|array Encoded string for use in formschema response
* Get formfield schema value for use in formschema response
*/
public function getSchemaValue()
public function getSchemaValue(): mixed
{
return $this->RAW();
}
public function getIndexSpecs()
public function getIndexSpecs(): ?array
{
$type = $this->getIndexType();
if ($type) {
@ -652,9 +560,8 @@ DBG;
* 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.
* @return boolean
*/
public function scalarValueOnly()
public function scalarValueOnly(): bool
{
return true;
}

View File

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

View File

@ -6,9 +6,11 @@ use SilverStripe\Assets\File;
use SilverStripe\Assets\Image;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FileHandleField;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\SearchableDropdownField;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
use SilverStripe\View\ViewableData;
/**
* 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
{
/**
* @var DataObject
*/
protected $object;
protected ?DataObject $object;
/**
* Number of related objects to show in a scaffolded searchable dropdown field before it
* switches to using lazyloading.
* 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';
/**
* 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)
public function __construct(?string $name, ?DataObject $object = null)
{
$this->object = $object;
parent::__construct($name);
}
public function scaffoldFormField($title = null, $params = null)
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{
if (empty($this->object)) {
return null;
@ -70,11 +58,11 @@ class DBForeignKey extends DBInt
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) {
$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\Core\Convert;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\TextField;
use SilverStripe\View\Parsers\HTMLValue;
@ -25,9 +26,9 @@ use SilverStripe\View\Parsers\ShortcodeParser;
*/
class DBHTMLText extends DBText
{
private static $escape_type = 'xml';
private static string $escape_type = 'xml';
private static $casting = [
private static array $casting = [
"AbsoluteLinks" => "HTMLFragment",
// DBString conversion / summary methods
// Not overridden, but returns HTML instead of plain text.
@ -37,68 +38,52 @@ class DBHTMLText extends DBText
/**
* 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
*
* @return bool
*/
public function getProcessShortcodes()
public function getProcessShortcodes(): bool
{
return $this->processShortcodes;
}
/**
* 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;
}
/**
* List of html properties to whitelist
*
* @var array
*/
protected $whitelist = [];
/**
* List of html properties to whitelist
*
* @return array
*/
public function getWhitelist()
public function getWhitelist(): array
{
return $this->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)) {
$whitelist = preg_split('/\s*,\s*/', $whitelist ?? '');
$whitelist = preg_split('/\s*,\s*/', $whitelist);
}
$this->whitelist = $whitelist;
return $this;
}
/**
* @param array $options
*
* Options accepted in addition to those provided by Text:
*
* - 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
* the text() directive. E.g. 'link,meta,text()' will allow only <link /> <meta /> and text at
* the root level.
*
* @return $this
*/
public function setOptions(array $options = [])
public function setOptions(array $options = []): static
{
if (array_key_exists("shortcodes", $options ?? [])) {
$this->setProcessShortcodes(!!$options["shortcodes"]);
@ -126,7 +109,7 @@ class DBHTMLText extends DBText
return parent::setOptions($options);
}
public function RAW()
public function RAW(): ?string
{
if ($this->processShortcodes) {
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 string
*/
public function AbsoluteLinks()
public function AbsoluteLinks(): string
{
return HTTP::absoluteURLs($this->forTemplate());
}
public function forTemplate()
public function forTemplate(): string
{
// Suppress XML encoding for DBHtmlText
return $this->RAW();
return $this->RAW() ?? '';
}
/**
* Safely escape for XML string
*
* @return string
*/
public function CDATA()
public function CDATA(): string
{
return sprintf(
'<![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));
}
@ -173,7 +153,7 @@ class DBHTMLText extends DBText
* @param string $value Input html content
* @return string Value with all non-whitelisted content stripped (if applicable)
*/
public function whitelistContent($value)
public function whitelistContent(mixed $value): mixed
{
if ($this->whitelist) {
$dom = HTMLValue::create($value);
@ -199,22 +179,20 @@ class DBHTMLText extends DBText
return $value;
}
public function scaffoldFormField($title = null, $params = null)
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{
return HTMLEditorField::create($this->name, $title);
}
public function scaffoldSearchField($title = null)
public function scaffoldSearchField(?string $title = null): ?FormField
{
return new TextField($this->name, $title);
}
/**
* Get plain-text version
*
* @return string
*/
public function Plain()
public function Plain(): string
{
// Preserve line breaks
$text = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $this->RAW() ?? '');
@ -232,7 +210,7 @@ class DBHTMLText extends DBText
return trim(Convert::xml2raw($text) ?? '');
}
public function getSchemaValue()
public function getSchemaValue(): ?array
{
// Form schema format as HTML
$value = $this->RAW();
@ -242,7 +220,7 @@ class DBHTMLText extends DBText
return null;
}
public function exists()
public function exists(): bool
{
// Optimisation: don't process shortcode just for ->exists()
$value = $this->getValue();

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\MultiSelectField;
use SilverStripe\ORM\Connect\MySQLDatabase;
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');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation');
@ -54,18 +55,15 @@ class DBMultiEnum extends DBEnum
/**
* Return a {@link CheckboxSetField} suitable for editing this field
*
* @param string $title
* @param string $name
* @param bool $hasEmpty
* @param string $value
* @param string $emptyString
* @return CheckboxSetField
* Return a form field suitable for editing this field
*/
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) {
$title = $this->name;
}
@ -73,6 +71,6 @@ class DBMultiEnum extends DBEnum
$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;
use SilverStripe\View\ViewableData;
/**
* Represents a decimal field from 0-1 containing a percentage value.
*
@ -15,14 +17,10 @@ namespace SilverStripe\ORM\FieldType;
*/
class DBPercentage extends DBDecimal
{
/**
* 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) {
$precision = 4;
@ -34,18 +32,18 @@ class DBPercentage extends DBDecimal
/**
* 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) . '%';
}
public function saveInto($dataObject)
public function saveInto(ViewableData $model): void
{
parent::saveInto($dataObject);
parent::saveInto($model);
$fieldName = $this->name;
if ($fieldName && $dataObject->$fieldName > 1.0) {
$dataObject->__set($fieldName, 1.0);
if ($fieldName && $model->$fieldName > 1.0) {
$model->__set($fieldName, 1.0);
}
}
}

View File

@ -2,21 +2,23 @@
namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DataObject;
use SilverStripe\View\ViewableData;
/**
* A special ForeignKey class that handles relations with arbitrary class types
*/
class DBPolymorphicForeignKey extends DBComposite
{
private static $index = true;
private static bool $index = true;
private static $composite_db = [
private static array $composite_db = [
'ID' => 'Int',
'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
// the has_many end, or set programmatically.
@ -28,7 +30,7 @@ class DBPolymorphicForeignKey extends DBComposite
*
* @return string Name of a subclass of DataObject
*/
public function getClassValue()
public function getClassValue(): ?string
{
return $this->getField('Class');
}
@ -37,35 +39,29 @@ class DBPolymorphicForeignKey extends DBComposite
* Set the value of the "Class" this key points to
*
* @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);
}
/**
* Gets the value of the "ID" this key points to
*
* @return integer
*/
public function getIDValue()
public function getIDValue(): ?int
{
return $this->getField('ID');
}
/**
* 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);
}
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
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();
$class = $this->getClassValue();

View File

@ -2,64 +2,59 @@
namespace SilverStripe\ORM\FieldType;
use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\View\ViewableData;
/**
* A special type Int field used for primary keys.
*/
class DBPrimaryKey extends DBInt
{
/**
* @var DataObject
*/
protected $object;
protected ?DataObject $object;
private static $default_search_filter_class = 'ExactMatchFilter';
/**
* @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);
}
protected bool $autoIncrement = true;
/**
* @param string $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;
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;
}
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);

View File

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

View File

@ -5,6 +5,7 @@ namespace SilverStripe\ORM\FieldType;
use InvalidArgumentException;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\NullableField;
use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\TextField;
@ -27,8 +28,7 @@ use SilverStripe\ORM\DB;
*/
class DBText extends DBString
{
private static $casting = [
private static array $casting = [
'BigSummary' => 'Text',
'ContextSummary' => 'HTMLFragment', // Always returns HTML as it contains formatting and highlighting
'FirstParagraph' => 'Text',
@ -42,11 +42,7 @@ class DBText extends DBString
*/
private static array $summary_sentence_separators = ['.', '?', '!'];
/**
* (non-PHPdoc)
* @see DBField::requireField()
*/
public function requireField()
public function requireField(): void
{
$charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$collation = Config::inst()->get(MySQLDatabase::class, 'collation');
@ -71,9 +67,8 @@ class DBText extends DBString
* Limit sentences, can be controlled by passing an integer.
*
* @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)) {
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 string
*/
public function FirstSentence()
public function FirstSentence(): string
{
return $this->LimitSentences(1);
}
/**
* 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
$value = $this->Plain();
@ -171,10 +160,8 @@ class DBText extends DBString
/**
* Get first paragraph
*
* @return string
*/
public function FirstParagraph()
public function FirstParagraph(): string
{
$value = $this->Plain();
if (empty($value)) {
@ -193,17 +180,15 @@ class DBText extends DBString
* @param int $characters Number of characters in the summary
* @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 string|false $prefix Prefix text
* @param string|false $suffix Suffix text
* @return string HTML string with context
*/
public function ContextSummary(
$characters = 500,
$keywords = null,
$highlight = true,
$prefix = false,
$suffix = false
) {
int $characters = 500,
?string $keywords = null,
bool $highlight = true,
string|false $prefix = false,
string|false $suffix = false
): string {
if (!$keywords) {
// Use the default "Search" request variable (from SearchForm)
@ -267,7 +252,7 @@ class DBText extends DBString
return nl2br($summary ?? '');
}
public function scaffoldFormField($title = null, $params = null)
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
{
if (!$this->nullifyEmpty) {
// 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);
}
public function scaffoldSearchField($title = null)
public function scaffoldSearchField(?string $title = null): ?FormField
{
return new TextField($this->name, $title);
}

View File

@ -4,11 +4,13 @@ namespace SilverStripe\ORM\FieldType;
use IntlDateFormatter;
use InvalidArgumentException;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\TimeField;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\DB;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\View\ViewableData;
/**
* 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
*/
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);
if ($value === false) {
@ -42,10 +44,9 @@ class DBTime extends DBField
/**
* 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
*/
protected function parseTime($value)
protected function parseTime(mixed $value): string|null|false
{
// Skip empty values
if (empty($value) && !is_numeric($value)) {
@ -73,24 +74,19 @@ class DBTime extends DBField
/**
* 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);
}
/**
* Returns the date in the localised short format
*
* @return string
*/
public function Short()
public function Short(): string
{
if (!$this->value) {
return null;
return '';
}
$formatter = $this->getFormatter(IntlDateFormatter::SHORT);
return $formatter->format($this->getTimestamp());
@ -99,13 +95,11 @@ class DBTime extends DBField
/**
* Returns the standard localised medium time
* e.g. "3:15pm"
*
* @return string
*/
public function Nice()
public function Nice(): string
{
if (!$this->value) {
return null;
return '';
}
$formatter = $this->getFormatter();
return $formatter->format($this->getTimestamp());
@ -114,20 +108,19 @@ class DBTime extends DBField
/**
* 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
* @return string The time in the requested format
* See https://unicode-org.github.io/icu/userguide/format_parse/datetime for valid formats
*/
public function Format($format)
public function Format(string $format): string
{
if (!$this->value) {
return null;
return '';
}
$formatter = $this->getFormatter();
$formatter->setPattern($format);
return $formatter->format($this->getTimestamp());
}
public function requireField()
public function requireField(): void
{
$parts = [
'datatype' => 'time',
@ -140,18 +133,15 @@ class DBTime extends DBField
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 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) {
$member = Security::getCurrentUser();
@ -169,20 +159,16 @@ class DBTime extends DBField
/**
* Get standard ISO time format string
*
* @return string
*/
public function getISOFormat()
public function getISOFormat(): string
{
return DBTime::ISO_TIME;
}
/**
* Get unix timestamp for this time
*
* @return int
*/
public function getTimestamp()
public function getTimestamp(): int
{
if ($this->value) {
return strtotime($this->value ?? '');

View File

@ -3,6 +3,7 @@
namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Config\Config;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\NullableField;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\Connect\MySQLDatabase;
@ -17,18 +18,15 @@ use SilverStripe\ORM\DB;
*/
class DBVarchar extends DBString
{
private static $casting = [
private static array $casting = [
'Initial' => 'Text',
'URL' => 'Text',
];
/**
* Max size of this field
*
* @var int
*/
protected $size;
protected int $size;
/**
* Construct a new short text field
@ -38,7 +36,7 @@ class DBVarchar extends DBString
* @param array $options Optional parameters, e.g. array("nullifyEmpty"=>false).
* 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;
parent::__construct($name, $options);
@ -53,16 +51,12 @@ class DBVarchar extends DBString
*
* @return int The size of the field
*/
public function getSize()
public function getSize(): int
{
return $this->size;
}
/**
* (non-PHPdoc)
* @see DBField::requireField()
*/
public function requireField()
public function requireField(): void
{
$charset = Config::inst()->get(MySQLDatabase::class, 'charset');
$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 string
*/
public function Initial()
public function Initial(): string
{
if ($this->exists()) {
$value = $this->RAW();
return $value[0] . '.';
}
return null;
return '';
}
/**
* Ensure that the given value is an absolute URL.
*
* @return string
*/
public function URL()
public function URL(): string
{
$value = $this->RAW();
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 string
*/
public function RTF()
public function RTF(): string
{
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
$field = TextField::create($this->name, $title);

View File

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

View File

@ -112,7 +112,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable,
return $this->list->getIterator();
}
public function exists()
public function exists(): bool
{
return $this->list->exists();
}
@ -140,7 +140,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable,
return $this->list->count();
}
public function forTemplate()
public function forTemplate(): string
{
return $this->list->forTemplate();
}
@ -313,7 +313,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable,
return $this->list->exclude(...func_get_args());
}
public function debug()
public function debug(): string
{
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
* 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)) {
return new ArrayData($value);
} elseif (ArrayLib::is_associative($value)) {
@ -87,14 +84,10 @@ class ArrayData extends ViewableData
}
/**
* 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;
}
@ -104,9 +97,9 @@ class ArrayData extends ViewableData
* @param string $field Field Key
* @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 string
*/
public function getContent()
public function getContent(): string
{
$document = $this->getDocument();
if (!$document) {
@ -98,7 +95,7 @@ class HTMLValue extends ViewableData
}
/** @see HTMLValue::getContent() */
public function forTemplate()
public function forTemplate(): string
{
return $this->getContent();
}

View File

@ -733,6 +733,6 @@ class ShortcodeParser
$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'];
$res['php'] .= "->$method('$property', [$arguments], true)";
} 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
if ($res['ArgumentCount'] == 0) {
$on = '$scope->locally()->obj(\'Me\', null, true)';
$on = '$scope->locally()->obj(\'Me\', [], true)';
} else { //loop in the normal way
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {

View File

@ -779,7 +779,7 @@ class SSTemplateParser extends Parser implements TemplateParser
$arguments = $sub['Call']['CallArguments']['php'];
$res['php'] .= "->$method('$property', [$arguments], true)";
} 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'].')';
} else {
$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 ?? '');
}
}
@ -4263,7 +4265,7 @@ class SSTemplateParser extends Parser implements TemplateParser
//loop without arguments loops on the current scope
if ($res['ArgumentCount'] == 0) {
$on = '$scope->locally()->obj(\'Me\', null, true)';
$on = '$scope->locally()->obj(\'Me\', [], true)';
} else { //loop in the normal way
$arg = $res['Arguments'][0];
if ($arg['ArgumentMode'] == 'string') {
@ -5290,6 +5292,8 @@ class SSTemplateParser extends Parser implements TemplateParser
$text = stripslashes($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'
(\SilverStripe\View\SSViewer::getRewriteHashLinksDefault()
? \SilverStripe\Core\Convert::raw2att( preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'] ) )
@ -5328,7 +5332,8 @@ EOC;
$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)) {
$this->pos = 3;
}

View File

@ -3,6 +3,7 @@
namespace SilverStripe\View;
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.
@ -71,7 +72,9 @@ class SSViewer_FromString extends SSViewer
unlink($cacheFile ?? '');
}
return $val;
$html = DBField::create_field('HTMLFragment', $val);
return $html;
}
/**

View File

@ -2,29 +2,23 @@
namespace SilverStripe\View;
use ArrayIterator;
use Exception;
use InvalidArgumentException;
use IteratorAggregate;
use LogicException;
use ReflectionMethod;
use ReflectionObject;
use ReflectionProperty;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\View\SSViewer;
use Traversable;
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,
* {@link DataObject}s, page controls) should inherit from this class.
*/
class ViewableData implements IteratorAggregate
class ViewableData
{
use Extensible {
defineMethods as extensibleDefineMethods;
@ -50,27 +44,18 @@ class ViewableData implements IteratorAggregate
* 'FieldName' => 'ClassToCastTo(Arguments)'
* );
* </code>
*
* @var array
* @config
*/
private static $casting = [
private static array $casting = [
'CSSClasses' => 'Varchar'
];
/**
* The default object to cast scalar fields to if casting information is not specified, and casting to an object
* is required.
*
* @var string
* @config
*/
private static $default_cast = 'Text';
private static string $default_cast = 'Text';
/**
* @var array
*/
private static $casting_cache = [];
private static array $casting_cache = [];
/**
* 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.
*
* @var ViewableData
*/
protected $failover;
protected ?ViewableData $failover = null;
/**
* @var ViewableData
*/
protected $customisedObject;
protected ?ViewableData $customisedObject = null;
/**
* @var array
*/
private $objCache = [];
private array $objCache = [];
public function __construct()
{
@ -108,11 +85,8 @@ class ViewableData implements IteratorAggregate
* 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
* 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
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
* 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
$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
* 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();
$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.
*
* @param ViewableData $failover
*/
public function setFailover(ViewableData $failover)
public function setFailover(ViewableData $failover): void
{
// Ensure cached methods from previous failover are removed
if ($this->failover) {
@ -189,56 +155,44 @@ class ViewableData implements IteratorAggregate
/**
* Get the current failover object if set
*
* @return ViewableData|null
*/
public function getFailover()
public function getFailover(): ?ViewableData
{
return $this->failover;
}
/**
* 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.
*
* @param string $field
* @return mixed
*/
public function getField($field)
public function getField(string $fieldName): mixed
{
if ($this->isAccessibleProperty($field)) {
return $this->$field;
if ($this->isAccessibleProperty($fieldName)) {
return $this->$fieldName;
}
return $this->getDynamicData($field);
return $this->getDynamicData($fieldName);
}
/**
* 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();
// 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
// of now deprecated dynamic properties
if ($this->isAccessibleProperty($field)) {
$this->$field = $value;
if ($this->isAccessibleProperty($fieldName)) {
$this->$fieldName = $value;
}
return $this->setDynamicData($field, $value);
return $this->setDynamicData($fieldName, $value);
}
public function getDynamicData(string $field): mixed
@ -322,11 +276,8 @@ class ViewableData implements IteratorAggregate
* 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.
*
* @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))) {
$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
* {@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 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 ViewableData
*/
public function getCustomisedObj()
public function getCustomisedObj(): ?ViewableData
{
return $this->customisedObject;
}
/**
* @param ViewableData $object
*/
public function setCustomisedObj(ViewableData $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)
* 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.
* @return string|null Casting helper As a constructor pattern, and may include arguments.
* @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.
// 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.
*
* @param string $field
* @return string
*/
public function castingClass($field)
public function castingClass(string $field): string
{
// Strip arguments
$spec = $this->castingHelper($field);
@ -455,10 +394,9 @@ class ViewableData implements IteratorAggregate
/**
* Return the string-format type for the given field.
*
* @param string $field
* @return string 'xml'|'raw'
*/
public function escapeTypeForField($field)
public function escapeTypeForField(string $field): string
{
$class = $this->castingClass($field) ?: $this->config()->get('default_cast');
@ -477,10 +415,9 @@ class ViewableData implements IteratorAggregate
* - an SSViewer instance
*
* @param string|array|SSViewer $template the template to render into
* @param array $customFields fields to customise() the object with before rendering
* @return DBHTMLText
* @param ViewableData|array|null $customFields fields to customise() the object with before rendering
*/
public function renderWith($template, $customFields = null)
public function renderWith($template, ViewableData|array|null $customFields = null): DBHTMLText
{
if (!is_object($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
* that have been specified.
*
* @param string $fieldName
* @param array $arguments
* @param bool $cache Cache this object
* @param string $cacheName a custom cache name
* @return object|DBField
* @return object|DBField|null The specific object representing the field, or null if there is no
* property, method, or dynamic data available for that field.
* Note that if there is a property or method that returns null, a relevant DBField instance will
* be returned.
*/
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) {
$cacheName = $this->objCacheName($fieldName, $arguments);
}
@ -576,11 +517,19 @@ class ViewableData implements IteratorAggregate
// Load value from record
if ($this->hasMethod($fieldName)) {
$hasObj = true;
$value = call_user_func_array([$this, $fieldName], $arguments ?: []);
} else {
$hasObj = $this->hasField($fieldName) || ($this->hasMethod("get{$fieldName}") && $this->isAccessibleMethod("get{$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
if (!is_object($value)) {
$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
* without re-running the method.
*
* @param string $fieldName
* @param array $arguments
* @param string $identifier an optional custom cache identifier
* @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
* 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);
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
* 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);
if (!$result) {
return '';
}
// Might contain additional formatting over ->XML(). E.g. parse shortcodes, nl2br()
return $result->forTemplate();
}
@ -665,9 +604,8 @@ class ViewableData implements IteratorAggregate
* Get an array of XML-escaped values by field name
*
* @param array $fields an array of field names
* @return array
*/
public function getXMLValues($fields)
public function getXMLValues(array $fields): array
{
$result = [];
@ -678,33 +616,12 @@ class ViewableData implements IteratorAggregate
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 -------------------------------------------------------------------------------------------------
/**
* 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);
}
@ -725,10 +642,9 @@ class ViewableData implements IteratorAggregate
* stop point - e.g. "Page DataObject ViewableData".
*
* @param string $stopAtClass the class to stop at (default: ViewableData)
* @return string
* @uses ClassInfo
*/
public function CSSClasses($stopAtClass = ViewableData::class)
public function CSSClasses(string $stopAtClass = ViewableData::class): string
{
$classes = [];
$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 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
{
protected ViewableData $original;
/**
* @var ViewableData
*/
protected $original, $customised;
protected ViewableData $customised;
/**
* Instantiate a new customised ViewableData object
*
* @param ViewableData $originalObject
* @param 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 ?? []);
}
public function __get($property)
public function __get(string $property): mixed
{
if (isset($this->customised->$property)) {
return $this->customised->$property;
@ -44,12 +39,12 @@ class ViewableData_Customised extends ViewableData
return $this->original->$property;
}
public function __set($property, $value)
public function __set(string $property, mixed $value): void
{
$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);
}
@ -59,16 +54,20 @@ class ViewableData_Customised extends ViewableData
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)) {
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)) {
return $this->customised->obj($fieldName, $arguments, $cache, $cacheName);
}

View File

@ -9,15 +9,8 @@ use ReflectionObject;
*/
class ViewableData_Debugger extends ViewableData
{
protected ViewableData $object;
/**
* @var ViewableData
*/
protected $object;
/**
* @param ViewableData $object
*/
public function __construct(ViewableData $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();
}
@ -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
* 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
$class = get_class($this->object);

View File

@ -14,14 +14,14 @@ class TestObject extends ViewableData implements TestOnly
$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()

View File

@ -129,9 +129,9 @@ class DBClassNameTest extends SapphireTest
$this->assertEquals('stdClass', $test3->getShortName());
$test4 = DBField::create_field('DBClassName', null);
$this->assertNull($test4->getShortName());
$this->assertSame('', $test4->getShortName());
$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
{
public function writeToManipulation(&$manipulation)
public function writeToManipulation(array &$manipulation): void
{
// Duplicate the amount before writing
$this->setAmount($this->getAmount() * 2);

View File

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

View File

@ -6,10 +6,11 @@ use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\View\ViewableData;
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
// changes in that class eventually.
@ -34,9 +35,9 @@ class TestDbField extends DBField implements TestOnly
public $saveIntoCalledCount = 0;
public function saveInto($dataObject)
public function saveInto(ViewableData $model): void
{
$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
{
public function requireField()
public function requireField(): void
{
}
}

View File

@ -739,15 +739,15 @@ class DataObjectTest extends SapphireTest
$fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
// Test relation probing
$this->assertFalse((bool)$team1->hasValue('Captain', null, false));
$this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
$this->assertFalse((bool)$team1->hasValue('Captain', [], false));
$this->assertFalse((bool)$team1->hasValue('CaptainID', [], false));
// Add a captain to team 1
$team1->setField('CaptainID', $player1->ID);
$team1->write();
$this->assertTrue((bool)$team1->hasValue('Captain', null, false));
$this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
$this->assertTrue((bool)$team1->hasValue('Captain', [], false));
$this->assertTrue((bool)$team1->hasValue('CaptainID', [], false));
$this->assertEquals(
$player1->ID,
@ -2075,22 +2075,22 @@ class DataObjectTest extends SapphireTest
public function testHasValue()
{
$team = new DataObjectTest\Team();
$this->assertFalse($team->hasValue('Title', null, false));
$this->assertFalse($team->hasValue('DatabaseField', null, false));
$this->assertFalse($team->hasValue('Title', [], false));
$this->assertFalse($team->hasValue('DatabaseField', [], false));
$team->Title = 'hasValue';
$this->assertTrue($team->hasValue('Title', null, false));
$this->assertFalse($team->hasValue('DatabaseField', null, false));
$this->assertTrue($team->hasValue('Title', [], false));
$this->assertFalse($team->hasValue('DatabaseField', [], false));
$team->Title = '<p></p>';
$this->assertTrue(
$team->hasValue('Title', null, false),
$team->hasValue('Title', [], false),
'Test that an empty paragraph is a value for non-HTML fields.'
);
$team->DatabaseField = 'hasValue';
$this->assertTrue($team->hasValue('Title', null, false));
$this->assertTrue($team->hasValue('DatabaseField', null, false));
$this->assertTrue($team->hasValue('Title', [], false));
$this->assertTrue($team->hasValue('DatabaseField', [], false));
}
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.
* @param $value
* @return array|int
*/
public function prepValueForDB($value)
public function prepValueForDB(mixed $value): array|int|null
{
if ($value) {
return $this->dynamicAssignment
@ -48,7 +46,7 @@ class MockDynamicAssignmentDBField extends DBBoolean implements TestOnly
return 0;
}
public function scalarValueOnly()
public function scalarValueOnly(): bool
{
return $this->scalarOnly;
}

View File

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

View File

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

View File

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

View File

@ -653,12 +653,14 @@ SS;
public function testLoopWhitespace()
{
$data = new ArrayList([new SSViewerTest\TestFixture()]);
$this->assertEquals(
'before[out:SingleItem.Test]after
'before[out:Test]after
beforeTestafter',
$this->render(
'before<% loop SingleItem %>$Test<% end_loop %>after
before<% loop SingleItem %>Test<% end_loop %>after'
'before<% loop %>$Test<% end_loop %>after
before<% loop %>Test<% end_loop %>after',
$data
)
);
@ -668,15 +670,16 @@ SS;
$this->assertEquals(
'before
[out:SingleItem.ItemOnItsOwnLine]
[out:ItemOnItsOwnLine]
after',
$this->render(
'before
<% loop SingleItem %>
<% loop %>
$ItemOnItsOwnLine
<% end_loop %>
after'
after',
$data
)
);
@ -2369,7 +2372,7 @@ EOC;
public function testMe(): void
{
$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);
}
}

View File

@ -18,7 +18,6 @@ class TestFixture extends ViewableData
parent::__construct();
}
private function argedName($fieldName, $arguments)
{
$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);
// Special field name Loop### to create a list
@ -49,8 +52,7 @@ class TestFixture extends ViewableData
}
}
public function XML_val($fieldName, $arguments = null, $cache = false)
public function XML_val(string $fieldName, array $arguments = [], bool $cache = false): string
{
if (preg_match('/NotSet/i', $fieldName ?? '')) {
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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