Bei der Entwicklung von Frontend-Anwendungen in TYPO3, die CRUD-Operationen (Erstellen, Lesen, Aktualisieren, Löschen) für eigene Datensätze ermöglichen, stehen Entwickler oft vor der Herausforderung, korrekt mit den enableFields
(wie hidden
, starttime
, endtime
) umzugehen. Besonders beim Aktualisieren von Datensätzen kann es vorkommen, dass das Property Mapping ein Objekt nicht findet, wenn es basierend auf diesen Feldern gerade „unsichtbar“ ist. Dieser Beitrag beleuchtet das Problem und präsentiert eine bewährte Lösung: einen benutzerdefinierten Objektkonverter.
Das Problem:
TYPO3s Property Mapping ist ein mächtiges Werkzeug, das die Konvertierung von eingehenden Request-Daten in Domain-Modelle vereinfacht. Standardmäßig berücksichtigt der eingebaute PersistentObjectConverter
jedoch die enableFields
. Wenn Sie versuchen, ein Objekt anhand seiner UID zu laden, das aufgrund von starttime
, endtime
oder dem hidden
-Flag ausgeblendet ist, wird das Property Mapping es nicht finden. Dies führt dazu, dass beispielsweise eine updateAction
Ihr erwartetes Objekt nicht als Parameter erhält, obwohl der Datensatz in der Datenbank existiert.
Die Lösung: Ein benutzerdefinierter Objektkonverter
Der Schlüssel zur Umgehung dieses Problems liegt in der Erstellung eines eigenen Objektkonverters, der vom Standard PersistentObjectConverter
erbt, aber dessen Verhalten beim Laden von Objekten anpasst.
Hier ist ein anonymisiertes Beispiel, wie ein solcher Konverter aussehen könnte:
*/
protected $sourceTypes = ['array'];
/**
* @var string
*/
protected $targetType = YourModel::class;
/**
* @var int
*/
protected $priority = 20; // Höhere Priorität als der Standard-Converter (10)
/**
* Lädt ein Objekt aus der Persistenz-Schicht unter Ignorierung der enableFields
*
* @param mixed $identity Die UID des zu ladenden Objekts
* @param string $targetType Der Ziel-Typ
* @throws TargetNotFoundException
* @throws InvalidSourceException
* @return object Das geladene Objekt
*/
protected function fetchObjectFromPersistence($identity, $targetType): object
{
if (ctype_digit((string)$identity)) {
$query = $this->persistenceManager->createQueryForType($targetType);
$query->getQuerySettings()->setIgnoreEnableFields(true);
$query->getQuerySettings()->setIncludeDeleted(false); // Gelöschte Einträge nicht laden
$constraints = $query->equals('uid', $identity);
$object = $query->matching($constraints)->execute()->getFirst();
} else {
throw new InvalidSourceException('Die Identitätseigenschaft "' . $identity . '" ist keine UID.', 1297931020);
}
if ($object === null) {
throw new TargetNotFoundException('Objekt mit der Identität "' . print_r($identity, true) . '" wurde nicht gefunden.', 1297933823);
}
return $object;
}
/**
* Konvertiert ein Array in ein YourModel-Objekt
*
* @param mixed $source
* @param string $targetType
* @param array $convertedChildProperties
* @param PropertyMappingConfigurationInterface|null $configuration
* @return object|null
*/
public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null): ?object
{
// Hier können wir spezielle Anpassungen vornehmen, bevor die eigentliche Konvertierung stattfindet
// Wir erlauben alle Eigenschaften für die Konvertierung
if ($configuration !== null) {
$configuration->setTypeConverterOptions(
PersistentObjectConverter::class,
[
PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true,
PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED => true
]
);
// Alle Eigenschaften des YourModel-Objekts erlauben
$configuration->allowAllProperties();
}
// Rufe die Elternmethode auf, um die eigentliche Konvertierung durchzuführen
return parent::convertFrom($source, $targetType, $convertedChildProperties, $configuration);
}
/**
* Gibt den Typ einer Kind-Eigenschaft zurück
*
* @param string $targetType
* @param string $propertyName
* @param PropertyMappingConfigurationInterface $configuration
* @return string
*/
public function getTypeOfChildProperty($targetType, $propertyName, PropertyMappingConfigurationInterface $configuration): string
{
// Spezielle Behandlung für bestimmte Eigenschaften (anpassen nach Bedarf)
switch ($propertyName) {
case 'categories':
return \TYPO3\CMS\Extbase\Persistence\ObjectStorage::class . '<' . \TYPO3\CMS\Extbase\Domain\Model\Category::class . '>';
// ... weitere Fälle für ObjectStorage oder spezielle Typen
default:
return parent::getTypeOfChildProperty($targetType, $propertyName, $configuration);
}
}
}
In diesem benutzerdefinierten Konverter überschreiben wir die Methode fetchObjectFromPersistence
. Innerhalb dieser Methode erstellen wir eine Query für den Zieltyp (YourModel
) und setzen über setIgnoreEnableFields(true)
explizit die Anweisung, die enableFields
bei dieser Abfrage zu ignorieren. Gleichzeitig stellen wir sicher, dass gelöschte Einträge mit setIncludeDeleted(false)
weiterhin ausgeschlossen werden.
Die convertFrom
-Methode wird ebenfalls überschrieben, um sicherzustellen, dass alle Eigenschaften des Modells für das Property Mapping zugelassen sind. Die getTypeOfChildProperty
-Methode kann bei Bedarf angepasst werden, um spezielle Property-Typen wie ObjectStorage
korrekt zu behandeln.
protected function initializeUpdateAction()
{
$this->arguments->getArgument('yourModelArgumentName')->getPropertyMappingConfiguration()
->setTypeConverter($this->yourModelObjectConverter);
// Optional: Spezifische TypeConverter für Datumsfelder, falls benötigt
$this->arguments->getArgument('yourModelArgumentName')
->getPropertyMappingConfiguration()
->forProperty('starttime')
->setTypeConverterOption(
DateTimeConverter::class,
DateTimeConverter::CONFIGURATION_DATE_FORMAT,
'd.m.Y' // Anpassen an Ihr Datumsformat
);
// ... ähnliche Konfigurationen für endtime, officialdate etc.
}
Dar Argument yourModelArgumentName
wird durch den tatsächlichen Namen des Arguments in der Action-Methode ersetztz (z.B. news
). Stellen Sie sicher, dass der benutzerdefinierte Konverter in Ihrer Klasse verfügbar ist (z.B. durch Dependency Injection)
Fazit:
Die Implementierung eines benutzerdefinierten Objektkonverters ist eine saubere und effektive Methode, um die Herausforderungen im Umgang mit enableFields
bei Frontend-CRUD-Operationen in TYPO3 zu meistern. Dieser Ansatz ermöglicht es Ihnen, Datensätze unabhängig von ihrem hidden
, starttime
oder endtime
-Status im Backend zu bearbeiten, während die Anzeige im Frontend weiterhin die enableFields
respektiert. Durch die Anpassung des Property Mappings stellen Sie sicher, dass Ihre Controller-Aktionen die benötigten Objekte korrekt erhalten und verarbeiten können.