Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 54 additions & 31 deletions Classes/Serializer/Handler/AbstractDomainObjectHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
use SourceBroker\T3api\Service\PropertyInfoService;
use SourceBroker\T3api\Service\SerializerService;
use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;

class AbstractDomainObjectHandler extends AbstractHandler implements DeserializeHandlerInterface
{
use InlineRelationServiceTrait;

/**
* @var string
*/
Expand Down Expand Up @@ -51,11 +53,26 @@ public function deserialize(
$targetObjectType = $type['params']['targetType'];

if (is_numeric($data)) {
return $this->persistenceManager->getObjectByIdentifier(
// `0` clears a single relation (in a collection it is rejected by `ObjectStorageHandler`).
if ((int)$data === 0) {
return null;
}

$object = $this->persistenceManager->getObjectByIdentifier(
(int)$data,
$targetObjectType,
false
);
if (!$object instanceof AbstractDomainObject) {
throw new BadRequestException(
'relation_not_found',
[(string)$data, implode('.', $context->getCurrentPath())],
1793670001
);
}
$this->getInlineRelationService()->assertAssignable($object, $context, 1793660001);

return $object;
}

if (is_array($data)) {
Expand Down Expand Up @@ -103,7 +120,8 @@ protected function processCascadePersistence(
);
}

if (isset($data['uid'])) {
// UID `0` is a new record as well, same as in `FileReferenceHandler`.
if (!empty($data['uid'])) {
return $this->processCascadeUpdate(
(int)$data['uid'],
$targetObjectType,
Expand Down Expand Up @@ -141,24 +159,18 @@ private function processCascadeUpdate(
) {
$object = $this->persistenceManager->getObjectByIdentifier($uid, $targetObjectType, false);

if ($object === null) {
throw new \InvalidArgumentException(
sprintf(
'Path `%s`: Entity of type `%s` with UID `%s` could not be found',
implode('.', $context->getCurrentPath()),
$targetObjectType,
$uid
),
if (!$object instanceof AbstractDomainObject) {
throw new BadRequestException(
'relation_not_found',
[(string)$uid, implode('.', $context->getCurrentPath())],
1588278656829
);
}

if (!$this->isObjectInContextScope($context, $propertyMetadata, $object)) {
throw new \InvalidArgumentException(
sprintf(
'Object in path `%s` is out of scope. You can not update objects which are not related already. To create new object pass it without `uid` property.',
implode('.', $context->getCurrentPath())
),
throw new BadRequestException(
'relation_not_assignable',
[(string)$uid, implode('.', $context->getCurrentPath())],
1589811402076
);
}
Expand All @@ -172,33 +184,44 @@ private function processCascadeUpdate(
);
}

/**
* Relations stored in the related table are checked by `InlineRelationService`. Other relations are compared with
* the state loaded from the database (clean state) - the current value may be already reset (PUT).
*/
private function isObjectInContextScope(
DeserializationContext $context,
PropertyMetadata $propertyMetadata,
$object
AbstractDomainObject $object
): bool {
$parentObject = $context->getAttribute('target');
$parentObject = $this->getInlineRelationService()->getContextParent($context);
if ($parentObject === null) {
return false;
}

if (empty($parentObject)) {
throw new \RuntimeException(
'It is not possible to check if object is in context scope without parent object. This message should never be thrown',
1589811502043
);
$assignable = $this->getInlineRelationService()->checkAssignable($parentObject, $propertyMetadata->name, $object);
if ($assignable !== null) {
return $assignable;
}

$property = ObjectAccess::getProperty($parentObject, $propertyMetadata->name);
$property = $parentObject->_getCleanProperty($propertyMetadata->name);

if (
$propertyMetadata->type['name'] === ObjectStorage::class
|| is_subclass_of($propertyMetadata->type['name'], ObjectStorage::class)
) {
return $property->contains($object);
// Compared by UID - the related object may be an instance of another class (e.g. an API resource class
// declared as `type` in YAML metadata) than the one in the relation.
if ($property instanceof ObjectStorage) {
foreach ($property as $relatedObject) {
if ($relatedObject instanceof AbstractDomainObject && $relatedObject->getUid() === $object->getUid()) {
return true;
}
}

return false;
}

if ($property instanceof AbstractDomainObject) {
// `LazyLoadingProxy::getUid()` does not load the related object.
if ($property instanceof AbstractDomainObject || $property instanceof LazyLoadingProxy) {
return $object->getUid() === $property->getUid();
}

throw new \RuntimeException('Unsupported object type', 1589811431286);
return false;
}
}
22 changes: 20 additions & 2 deletions Classes/Serializer/Handler/FileReferenceHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use JMS\Serializer\SerializationContext;
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
use JMS\Serializer\Visitor\SerializationVisitorInterface;
use SourceBroker\T3api\Exception\BadRequestException;
use SourceBroker\T3api\Exception\ValidationException;
use SourceBroker\T3api\Service\FileReferenceService;
use SourceBroker\T3api\Service\SerializerService;
Expand All @@ -30,6 +31,8 @@

class FileReferenceHandler extends AbstractHandler implements SerializeHandlerInterface, DeserializeHandlerInterface
{
use InlineRelationServiceTrait;

/**
* @var string
*/
Expand Down Expand Up @@ -140,7 +143,8 @@ public function deserialize(
);
}

if ($data === 0) {
// `0` (also as string) removes the reference of a single file property
if (is_numeric($data) && (int)$data === 0) {
$this->removeExistingFileReference($context);

return null;
Expand All @@ -155,11 +159,25 @@ public function deserialize(
$uid = (int)(is_numeric($data) ? $data : $data['uid']);

if ($uid > 0) {
return $this->persistenceManager->getObjectByIdentifier(
$fileReference = $this->persistenceManager->getObjectByIdentifier(
$uid,
ExtbaseFileReference::class,
false
);
// @todo `File` and `Folder` properties are looked up as file references as well - kept unchanged here
if (!is_subclass_of($type['params']['targetType'], FileReference::class)) {
return $fileReference;
}
if (!$fileReference instanceof AbstractDomainObject) {
throw new BadRequestException(
'relation_not_found',
[(string)$uid, implode('.', $context->getCurrentPath())],
1793670002
);
}
$this->getInlineRelationService()->assertAssignable($fileReference, $context, 1793660002);

return $fileReference;
}
return null;
}
Expand Down
30 changes: 30 additions & 0 deletions Classes/Serializer/Handler/InlineRelationServiceTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace SourceBroker\T3api\Serializer\Handler;

use SourceBroker\T3api\Service\InlineRelationService;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
* For handlers which assign existing records given by UID.
*/
trait InlineRelationServiceTrait
{
protected ?InlineRelationService $inlineRelationService = null;

/**
* Injected by method (not constructor) so that adding new dependency does not break handlers which extend this
* class and override the constructor.
*/
public function injectInlineRelationService(InlineRelationService $inlineRelationService): void
{
$this->inlineRelationService = $inlineRelationService;
}

protected function getInlineRelationService(): InlineRelationService
{
return $this->inlineRelationService ??= GeneralUtility::makeInstance(InlineRelationService::class);
}
}
8 changes: 8 additions & 0 deletions Classes/Serializer/Handler/ObjectStorageHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ public function deserialize(
$items = $visitor->visitArray($data, $type);

foreach ($items as $item) {
// `null` or `0` (which clears a single relation) can not be an item of a collection
if ($item === null) {
throw new BadRequestException(
'empty_collection_item',
[implode('.', $context->getCurrentPath())],
1793680001
);
}
$objectStorage->attach($item);
}

Expand Down
Loading
Loading