From fef4b863b7874206be2cd8f35cb983aa66ba6ba0 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Thu, 22 Aug 2024 14:24:43 +1200 Subject: [PATCH] API Strong typing for the view layer --- .../Exception/NotImplementedException.php | 19 ++ src/Dev/TaskRunner.php | 2 +- src/Forms/Form.php | 6 +- src/Forms/FormField.php | 6 +- src/Forms/FormRequestHandler.php | 2 +- src/Forms/ReadonlyField.php | 2 +- src/ORM/ArrayList.php | 4 +- src/ORM/DataList.php | 4 +- src/ORM/DataObject.php | 77 +++---- src/ORM/DataObjectInterface.php | 2 +- src/ORM/FieldType/DBBigInt.php | 2 +- src/ORM/FieldType/DBBoolean.php | 22 +- src/ORM/FieldType/DBClassName.php | 45 ++-- src/ORM/FieldType/DBComposite.php | 97 +++----- src/ORM/FieldType/DBCurrency.php | 32 +-- src/ORM/FieldType/DBDate.php | 103 +++------ src/ORM/FieldType/DBDatetime.php | 104 ++++----- src/ORM/FieldType/DBDecimal.php | 57 ++--- src/ORM/FieldType/DBDouble.php | 2 +- src/ORM/FieldType/DBEnum.php | 94 +++----- src/ORM/FieldType/DBField.php | 210 +++++------------- src/ORM/FieldType/DBFloat.php | 18 +- src/ORM/FieldType/DBForeignKey.php | 32 +-- src/ORM/FieldType/DBHTMLText.php | 76 +++---- src/ORM/FieldType/DBHTMLVarchar.php | 48 ++-- src/ORM/FieldType/DBInt.php | 21 +- src/ORM/FieldType/DBLocale.php | 23 +- src/ORM/FieldType/DBMoney.php | 74 ++---- src/ORM/FieldType/DBMultiEnum.php | 24 +- src/ORM/FieldType/DBPercentage.php | 18 +- src/ORM/FieldType/DBPolymorphicForeignKey.php | 28 +-- src/ORM/FieldType/DBPrimaryKey.php | 41 ++-- src/ORM/FieldType/DBString.php | 53 ++--- src/ORM/FieldType/DBText.php | 45 ++-- src/ORM/FieldType/DBTime.php | 50 ++--- src/ORM/FieldType/DBVarchar.php | 33 +-- src/ORM/FieldType/DBYear.php | 13 +- src/ORM/ListDecorator.php | 4 +- src/View/ArrayData.php | 19 +- src/View/Parsers/HTMLValue.php | 7 +- src/View/SSTemplateParser.php | 18 +- src/View/ViewableData.php | 207 +++++------------ src/View/ViewableData_Customised.php | 15 +- src/View/ViewableData_Debugger.php | 22 +- tests/php/Control/Email/EmailTest.php | 6 +- tests/php/Core/ClassInfoTest.php | 2 +- .../ViewableDataContainsTest/TestObject.php | 8 +- tests/php/ORM/DBFieldTest/TestDataObject.php | 4 +- tests/php/ORM/DBFieldTest/TestDbField.php | 2 +- tests/php/ORM/DBStringTest/MyStringField.php | 2 +- .../MockDynamicAssignmentDBField.php | 6 +- tests/php/View/ArrayDataTest.php | 2 +- tests/php/View/ViewableDataTest.php | 42 ++-- tests/php/View/ViewableDataTest/Castable.php | 2 +- tests/php/View/ViewableDataTest/Caster.php | 2 +- .../View/ViewableDataTest/RequiresCasting.php | 2 +- .../View/ViewableDataTest/UnescapedCaster.php | 2 +- 57 files changed, 650 insertions(+), 1213 deletions(-) create mode 100644 src/Core/Exception/NotImplementedException.php diff --git a/src/Core/Exception/NotImplementedException.php b/src/Core/Exception/NotImplementedException.php new file mode 100644 index 00000000000..fa1e6b44980 --- /dev/null +++ b/src/Core/Exception/NotImplementedException.php @@ -0,0 +1,19 @@ +getTaskList()) > 0; } - + public function providePermissions(): array { return [ diff --git a/src/Forms/Form.php b/src/Forms/Form.php index 22338c02ce9..79278bfaa90 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -521,7 +521,7 @@ public function setFieldMessage( 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 @@ public function getData() * * 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(); diff --git a/src/Forms/FormField.php b/src/Forms/FormField.php index 3c47b5ff92c..3950d3ae332 100644 --- a/src/Forms/FormField.php +++ b/src/Forms/FormField.php @@ -790,7 +790,7 @@ public function securityTokenEnabled() 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())) { @@ -1286,10 +1286,8 @@ public function debug() /** * 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(); } diff --git a/src/Forms/FormRequestHandler.php b/src/Forms/FormRequestHandler.php index 1071f92aba8..62298b44492 100644 --- a/src/Forms/FormRequestHandler.php +++ b/src/Forms/FormRequestHandler.php @@ -504,7 +504,7 @@ public function validationResult() return $result; } - public function forTemplate() + public function forTemplate(): ?string { return $this->form->forTemplate(); } diff --git a/src/Forms/ReadonlyField.php b/src/Forms/ReadonlyField.php index ef31f3d7b55..68a496a5d61 100644 --- a/src/Forms/ReadonlyField.php +++ b/src/Forms/ReadonlyField.php @@ -56,7 +56,7 @@ public function Type() 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) { diff --git a/src/ORM/ArrayList.php b/src/ORM/ArrayList.php index ed20230086a..4aca7a0c704 100644 --- a/src/ORM/ArrayList.php +++ b/src/ORM/ArrayList.php @@ -110,10 +110,8 @@ public function count(): int /** * Returns true if this list has items - * - * @return bool */ - public function exists() + public function exists(): bool { return !empty($this->items); } diff --git a/src/ORM/DataList.php b/src/ORM/DataList.php index 72ccacd7113..94233c7d02e 100644 --- a/src/ORM/DataList.php +++ b/src/ORM/DataList.php @@ -1702,10 +1702,8 @@ public function last() /** * Returns true if this DataList has items - * - * @return bool */ - public function exists() + public function exists(): bool { return $this->dataQuery->exists(); } diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 3852d640c0c..f6908686a09 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -816,10 +816,8 @@ public function defineMethods() * 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(); } @@ -2687,7 +2685,7 @@ public function getFrontEndFields($params = null) return $untabbedFields; } - public function getViewerTemplates($suffix = '') + public function getViewerTemplates(string $suffix = ''): array { return SSViewer::get_templates_by_class(static::class, $suffix, $this->baseClass()); } @@ -2695,11 +2693,8 @@ public function getViewerTemplates($suffix = '') /** * 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])) { @@ -2910,12 +2905,8 @@ public function isChanged($fieldName = null, $changeLevel = DataObject::CHANGE_S /** * 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 @@ -2934,42 +2925,42 @@ public function setField($fieldName, $val) 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( @@ -2982,12 +2973,12 @@ private function setFieldValue(string $fieldName, mixed $val): void } // 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; @@ -2998,7 +2989,7 @@ private function setFieldValue(string $fieldName, mixed $val): void } // Value is saved regardless, since the change detection relates to the last write - $this->record[$fieldName] = $val; + $this->record[$fieldName] = $value; } /** @@ -3029,7 +3020,7 @@ public function setCastedField($fieldName, $value) /** * {@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) { @@ -3054,19 +3045,16 @@ public function castingHelper($field, bool $useFallback = true) * 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(). - * - * @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}") ); } @@ -3214,7 +3202,7 @@ public function canCreate($member = null, $context = []) * * @return string HTML data representing this object */ - public function debug() + public function debug(): string { $class = static::class; $val = "

Database record: {$class}

\n