Bug #103641
closedNot possible so set nullable relation values in extbase domain models
100%
Description
When an extbase domain Model has a property, which implements DomainObjectInterface
and is nullable, it is impossible to set the property of an existing object to null
, because the database field is defined does not accept NULL
as value. When saved through DataHandler, a nullable (e.g. tca=group) field saves 0
as value for those fields.
Example:
protected ?Person $author = null;
If the object with the $author
property is persisted with a valid relation to a Person
model, Extbase persists the UID of the person. Setting the field to null
e.g. in controller (e.g. $blog->setAuthor(null)
), results in the following exception Column 'author cannot be null
, since Extbase persistence tries to save a NULL
value to the not nullable database field.
Extbase should consider this scenario and save 0
for nullable properties which implement DomainObjectInterface
The same applies to nullable DateTime
fields, which show the same behavior. (see #88515)
Updated by Torben Hansen 7 months ago
- Related to Bug #88515: Cannot unset DateTime value via null added
Updated by Gerrit Code Review 7 months ago
- Status changed from New to Under Review
Patch set 1 for branch main of project Packages/TYPO3.CMS has been pushed to the review server.
It is available at https://review.typo3.org/c/Packages/TYPO3.CMS/+/83839
Updated by Gerrit Code Review 7 months ago
Patch set 2 for branch main of project Packages/TYPO3.CMS has been pushed to the review server.
It is available at https://review.typo3.org/c/Packages/TYPO3.CMS/+/83839
Updated by Gerrit Code Review 7 months ago
Patch set 3 for branch main of project Packages/TYPO3.CMS has been pushed to the review server.
It is available at https://review.typo3.org/c/Packages/TYPO3.CMS/+/83839
Updated by Gerrit Code Review 7 months ago
Patch set 4 for branch main of project Packages/TYPO3.CMS has been pushed to the review server.
It is available at https://review.typo3.org/c/Packages/TYPO3.CMS/+/83839
Updated by Gerrit Code Review 7 months ago
Patch set 5 for branch main of project Packages/TYPO3.CMS has been pushed to the review server.
It is available at https://review.typo3.org/c/Packages/TYPO3.CMS/+/83839
Updated by Gerrit Code Review 7 months ago
Patch set 6 for branch main of project Packages/TYPO3.CMS has been pushed to the review server.
It is available at https://review.typo3.org/c/Packages/TYPO3.CMS/+/83839
Updated by Gerrit Code Review 7 months ago
Patch set 7 for branch main of project Packages/TYPO3.CMS has been pushed to the review server.
It is available at https://review.typo3.org/c/Packages/TYPO3.CMS/+/83839
Updated by Torben Hansen 7 months ago
- Status changed from Under Review to Resolved
- % Done changed from 0 to 100
Applied in changeset 30399b0eb7d9b08b31f35e1e23cdcfb676ae6aff.
Updated by Marcin Sągol 3 months ago
If someone would like to have it in version 12, you can use the composer patch (cweagans/composer-patches). For example (adjust the path and name if needed):
"extra": {
"patches": {
"typo3/cms-extbase": {
"Allow persisting nullable properties in extbase": "patches/83839-bugfix-allow-persisting-nullable-properties-in-extbase-30399b0.diff"
}
}
}
and the updated patch content (small fix was required due to code changes in v12):
diff --git a/Classes/Persistence/Generic/Backend.php b/Classes/Persistence/Generic/Backend.php
index 24815d1..2cfff89 100644
--- a/Classes/Persistence/Generic/Backend.php
+++ b/Classes/Persistence/Generic/Backend.php
@@ -19,6 +19,7 @@
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Context\LanguageAspect;
+use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\ReferenceIndex;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -41,6 +42,7 @@
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Extbase\Reflection\ClassSchema\Property;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
@@ -319,11 +321,11 @@
if ($propertyValue->_isNew()) {
$this->insertObject($propertyValue, $object, $propertyName);
}
- $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
+ $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, null, $property);
}
$queue[] = $propertyValue;
} elseif ($object->_isNew() || $object->_isDirty($propertyName)) {
- $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap);
+ $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap, $property);
}
}
if (!empty($row)) {
@@ -596,7 +598,7 @@
$row[$columnMap->getColumnName()] = 0;
}
} elseif ($propertyValue !== null) {
- $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap);
+ $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap, $property);
}
}
$this->addCommonFieldsToRow($object, $row);
@@ -930,19 +932,48 @@
}
/**
- * Returns a plain value
- *
- * i.e. objects are flattened out if possible.
- * Checks explicitly for null values as DataMapper's getPlainValue would convert this to 'NULL'
+ * Returns a plain value, i.e. objects are flattened out if possible.
+ * Checks explicitly for null values as DataMapper's getPlainValue would convert this to 'NULL'.
+ * For null values, the expected DB null value will be considered.
*
* @param mixed $input The value that will be converted
* @param ColumnMap|null $columnMap Optional column map for retrieving the date storage format
+ * @param Property|null $property The current property
* @return int|string|null
*/
- protected function getPlainValue($input, ?ColumnMap $columnMap = null)
+ protected function getPlainValue(mixed $input, ColumnMap $columnMap = null, Property $property = null)
{
- return $input !== null
- ? GeneralUtility::makeInstance(DataMapper::class)->getPlainValue($input, $columnMap)
- : null;
+ if ($input !== null) {
+ return GeneralUtility::makeInstance(DataMapper::class)->getPlainValue($input, $columnMap);
+ }
+
+ if (!$property) {
+ return null;
+ }
+
+ $className = $property->getPrimaryType()->getClassName();
+
+ // Nullable domain model property
+ if (is_subclass_of($className, DomainObjectInterface::class)) {
+ return 0;
+ }
+
+ // Nullable DateTime property
+ if ($columnMap && is_subclass_of($className, \DateTimeInterface::class)) {
+ $datetimeFormats = QueryHelper::getDateTimeFormats();
+ $dateFormat = $columnMap->getDateTimeStorageFormat();
+ if (!$dateFormat) {
+ // Datetime property with no TCA dbType
+ return 0;
+ }
+
+ if (isset($datetimeFormats[$dateFormat])) {
+ // Datetime property with TCA dbType defined. Nullable fields will be saved with the empty value
+ // (e.g. "00:00:00" for dbType = time) as well, but DataMapper will correctly map those values to null
+ return $datetimeFormats[$dateFormat]['empty'];
+ }
+ }
+
+ return null;
}
}