From 7f2542c0fc59a2e312bdd0de836e13235b8be860 Mon Sep 17 00:00:00 2001 From: Eyad Abdullah Date: Mon, 23 Sep 2024 16:11:19 +0200 Subject: [PATCH] fix: reflection errors, and field mapping. --- Classes/Domain/Model/Event.php | 2 +- Classes/Mapping/AbstractMapping.php | 106 +++++++++++-------- Classes/Mapping/FalMapping.php | 33 +++--- Classes/Service/ApiClient.php | 2 +- Classes/Service/EventExecutionService.php | 10 +- Classes/Service/LoggingService.php | 3 +- Classes/Utility/ConstantsUtility.php | 2 +- Resources/Private/Templates/Event/Check.html | 20 +++- 8 files changed, 110 insertions(+), 68 deletions(-) diff --git a/Classes/Domain/Model/Event.php b/Classes/Domain/Model/Event.php index 261c137..1eb380b 100644 --- a/Classes/Domain/Model/Event.php +++ b/Classes/Domain/Model/Event.php @@ -220,7 +220,7 @@ public function setProcessing(bool $processing): void { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_fourallportal_domain_model_event'); $query = $queryBuilder->update('tx_fourallportal_domain_model_event') - ->set('processing', $processing, PDO::PARAM_INT) + ->set('processing', $processing ? 1 : 0,PDO::PARAM_INT) ->where($queryBuilder->expr()->eq('uid', $this->uid)); $query->executeStatement(); $this->processing = $processing; diff --git a/Classes/Mapping/AbstractMapping.php b/Classes/Mapping/AbstractMapping.php index f2f556c..962de85 100644 --- a/Classes/Mapping/AbstractMapping.php +++ b/Classes/Mapping/AbstractMapping.php @@ -14,9 +14,11 @@ use ReflectionClass; use ReflectionException; use ReflectionMethod; +use ReflectionProperty; use RuntimeException; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Domain\Repository\PageRepository; +use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\StorageRepository; use TYPO3\CMS\Core\TypoScript\FrontendTypoScript; @@ -62,11 +64,6 @@ public function injectPersistenceManager(PersistenceManager $persistenceManager) $this->persistenceManager = $persistenceManager; } - public function injectAccessiblePropertyMapper(AccessiblePropertyMapper $accessiblePropertyMapper) - { - $this->accessiblePropertyMapper = $accessiblePropertyMapper; - } - public function injectStorageRepository(StorageRepository $storageRepository) { $this->storageRepository = $storageRepository; @@ -292,19 +289,18 @@ protected function removeObjectFromRepository(DomainObjectInterface $object): vo /** * @param array $data - * @param AbstractEntity|FileInterface $object + * @param AbstractEntity|File $object * @param Module $module * @param DimensionMapping|null $dimensionMapping * @return bool - * @throws InvalidSourceException - * @throws ReflectionException - * @throws TypeConverterException + * @throws ApiException */ - protected function mapPropertiesFromDataToObject(array $data, AbstractEntity|FileInterface $object, Module $module, DimensionMapping $dimensionMapping = null): bool + protected function mapPropertiesFromDataToObject(array $data, AbstractEntity|File $object, Module $module, DimensionMapping $dimensionMapping = null): bool { if (!$data['result']) { return true; } + $map = MappingRegister::resolvePropertyMapForMapper(static::class); $properties = $data['result'][0]['properties']; $responseValueReader = new ResponseDataFieldValueReader(); @@ -328,10 +324,7 @@ protected function mapPropertiesFromDataToObject(array $data, AbstractEntity|Fil $propertyMappingProblemsOccurred = $this->mapPropertyValueToObject($targetPropertyName, $propertyValue, $object); $mappingProblemsOccurred = $mappingProblemsOccurred ?: $propertyMappingProblemsOccurred; } - } catch (PropertyNotAccessibleException $error) { - $message = 'Error mapping ' . $module->getModuleName() . ':' . $objectId . ':' . $importedName . ' - ' . $error->getMessage(); - $this->loggingService->logObjectActivity($data['result'][0]['id'], $message, 3 /*GeneralUtility::SYSLOG_SEVERITY_WARNING*/); - } catch (DeferralException $error) { + } catch (PropertyNotAccessibleException|DeferralException|ReflectionException|InvalidSourceException|TypeConverterException $error) { $message = 'Error mapping ' . $module->getModuleName() . ':' . $objectId . ':' . $importedName . ' - ' . $error->getMessage(); $this->loggingService->logObjectActivity($data['result'][0]['id'], $message, 3 /*GeneralUtility::SYSLOG_SEVERITY_WARNING*/); } @@ -373,10 +366,13 @@ protected function mapPropertyValueToObject(string $propertyName, mixed $propert $objectId = $object->getRemoteId(); } - $array = (new ReflectionMethod(get_class($object), 'set' . ucfirst($propertyName)))->getParameters(); - if ($propertyValue === null && reset($array)->allowsNull()) { - ObjectAccess::setProperty($object, $propertyName, null); - return false; + $setterName = 'set' . ucfirst($propertyName); + if (method_exists($objectId, $setterName)) { + $array = (new ReflectionMethod(get_class($object),))->getParameters(); + if ($propertyValue === null && reset($array)->allowsNull()) { + ObjectAccess::setProperty($object, $propertyName, null); + return false; + } } $configuration = new PropertyMappingConfiguration(); $mappingProblemsOccurred = false; @@ -388,7 +384,11 @@ protected function mapPropertyValueToObject(string $propertyName, mixed $propert $propertyMapper = $this->getAccessiblePropertyMapper(); $targetType = $this->determineDataTypeForProperty($propertyName, $object); - $array1 = (new ReflectionMethod(get_class($object), 'set' . ucfirst($propertyName)))->getParameters(); + if ($targetType === null) { + $classSchema = get_class($object); + $this->loggingService->logObjectActivity($objectId, "Type of property {$propertyName} on {$classSchema} could not be determined", 3); + return false; + } if (strpos($targetType, '<')) { $childType = substr($targetType, strpos($targetType, '<') + 1, -1); $childType = trim($childType, '\\'); @@ -471,15 +471,22 @@ protected function mapPropertyValueToObject(string $propertyName, mixed $propert } } } - } elseif ($propertyValue === null && !reset($array1)->allowsNull()) { - $message = sprintf( - 'Property "%s" on object "%s->%s" does not allow NULL as value, but NULL was resolved. Please verify PIM response data consistency!', - $propertyName, - get_class($object), - method_exists($object, 'getRemoteId') ? $object->getRemoteId() : $object->getUid() - ); - $this->loggingService->logObjectActivity($objectId, $message, 4 /*GeneralUtility::SYSLOG_SEVERITY_FATAL*/); - return false; + } elseif ($propertyValue === null) { + try { + $parameters = (new \ReflectionMethod(get_class($object), 'set' . ucfirst($propertyName)))->getParameters(); + } catch (\ReflectionException $e) { + // ignore + } + if (!empty($parameters) && !$parameters[0]->allowsNull()) { + $message = sprintf( + 'Property "%s" on object "%s->%s" does not allow NULL as value, but NULL was resolved. Please verify PIM response data consistency!', + $propertyName, + get_class($object), + method_exists($object, 'getRemoteId') ? $object->getRemoteId() : $object->getUid() + ); + $this->loggingService->logObjectActivity($objectId, $message, 4 /*GeneralUtility::SYSLOG_SEVERITY_FATAL*/); + return false; + } } $setOnObject = $object; @@ -506,33 +513,38 @@ protected function mapPropertyValueToObject(string $propertyName, mixed $propert * @throws NoSuchPropertyException * @throws UnknownClassException */ - protected function determineDataTypeForProperty($propertyName, $object): bool|string + protected function determineDataTypeForProperty(string $propertyName, object $object): string|null { - if (property_exists(get_class($object), $propertyName)) { - $property = GeneralUtility::makeInstance(ReflectionService::class); - $classSchema = $property->getClassSchema($object); - $varTags = $classSchema->getProperty('var'); - if ($property->getPrimaryType()->isCollection()) { - return strpos($varTags[0], ' ') !== false ? substr($varTags[0], 0, strpos($varTags[0], ' ')) : $varTags[0]; + + if (property_exists($object, $propertyName)) { + $property = new ReflectionProperty($object, $propertyName); + $type = $property->getType(); + + if ($type !== null) { + return $type->getName(); + } + + $varTags = $property->getAttributes('var'); + if (!empty($varTags)) { + return explode(' ', $varTags[0]->getArguments()[0])[0]; } } - if (method_exists(get_class($object), 'set' . ucfirst($propertyName))) { - /** @see .build/vendor/typo3/cms-core/Documentation/Changelog/9.0/Breaking-57594-OptimizeReflectionServiceCacheHandling.rst */ - $method = $classSchema->getMethod('set' . ucfirst($propertyName)); + $setterMethod = 'set' . ucfirst($propertyName); + if (method_exists($object, $setterMethod)) { + $method = new ReflectionMethod($object, $setterMethod); $parameters = $method->getParameters(); - if ($parameters[0]->getType() !== null) { - return (string)$parameters[0]->getType(); + + if (!empty($parameters) && $parameters[0]->hasType()) { + return $parameters[0]->getType()->getName(); } - $varTags = $method->getParameter('param'); - if (!empty($varTags)) { - $array = explode(' ', $varTags[0]); - return reset($array); + $paramTags = $method->getAttributes('param'); + if (!empty($paramTags)) { + return explode(' ', $paramTags[0]->getArguments()[0])[0]; } } - - throw new RuntimeException('Type of property ' . $propertyName . ' on ' . get_class($object) . ' could not be determined'); + return null; } /** @@ -558,7 +570,7 @@ public function getEntityClassName(): string */ protected function getAccessiblePropertyMapper(): AccessiblePropertyMapper { - return $this->accessiblePropertyMapper; + return GeneralUtility::makeInstance(AccessiblePropertyMapper::class); } /** diff --git a/Classes/Mapping/FalMapping.php b/Classes/Mapping/FalMapping.php index ae9295e..0453def 100644 --- a/Classes/Mapping/FalMapping.php +++ b/Classes/Mapping/FalMapping.php @@ -8,12 +8,13 @@ use Crossmedia\Fourallportal\Domain\Model\Server; use Crossmedia\Fourallportal\Error\ApiException; use Crossmedia\Fourallportal\Service\ApiClient; +use Crossmedia\Fourallportal\Service\LoggingService; use Crossmedia\Fourallportal\ValueReader\ResponseDataFieldValueReader; use DateTime; use Doctrine\DBAL\Exception; -use ReflectionException; use RuntimeException; use TYPO3\CMS\Core\Charset\CharsetConverter; +use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Resource\Driver\DriverInterface; use TYPO3\CMS\Core\Resource\Driver\LocalDriver; @@ -32,8 +33,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\FileReference; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; -use TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException; -use TYPO3\CMS\Extbase\Property\Exception\TypeConverterException; +use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface; use TYPO3\CMS\Extbase\Reflection\Exception\PropertyNotAccessibleException; use TYPO3\CMS\Extbase\Reflection\ObjectAccess; @@ -57,10 +57,7 @@ public function getEntityClassName(): string * @throws InsufficientFolderReadPermissionsException * @throws InsufficientFolderWritePermissionsException * @throws InvalidFileNameException - * @throws InvalidSourceException * @throws PropertyNotAccessibleException - * @throws ReflectionException - * @throws TypeConverterException */ public function import(array $data, Event $event): bool { @@ -77,6 +74,7 @@ public function import(array $data, Event $event): bool ->where($queryBuilder->expr()->eq('remote_id', $queryBuilder->quote($objectId))); $deferAfterProcessing = false; + $logging = $this->loggingService = GeneralUtility::makeInstance(LoggingService::class); switch ($event->getEventType()) { case 'delete': @@ -88,6 +86,7 @@ public function import(array $data, Event $event): bool } // handle multiple files in the system foreach ($records as $record) { + /** @var File $object */ $object = $repository->findByUid($record['uid']); if (!$object || !$record) { // Object is already deleted, return false meaning no deferral after processing. @@ -108,14 +107,24 @@ public function import(array $data, Event $event): bool break; case 'update': case 'create': + $message = sprintf("start creating object with id %s", $objectId); + $logging->logEventActivity($event, $message); + + /** @var File $object */ $object = $this->downloadFileAndGetFileObject($objectId, $data, $event); + $message = sprintf("object with id %s downloaded successfully", $objectId); + $logging->logEventActivity($event, $message); + $deferAfterProcessing = $this->mapPropertiesFromDataToObject($data, $object, $event->getModule()); + + $message = sprintf("object with id %s create successfully", $objectId); + $logging->logEventActivity($event, $message); break; default: throw new RuntimeException('Unknown event type: ' . $event->getEventType()); } - $this->persistenceManager->persistAll(); + GeneralUtility::makeInstance(PersistenceManagerInterface::class)->persistAll(); if ($object) { $this->processRelationships($object, $data, $event); @@ -221,16 +230,14 @@ protected function collectTablesAndRecordsWithRelationsToFileReferences(array $e /** * @param array $data - * @param AbstractEntity|FileInterface $object + * @param AbstractEntity|File $object * @param Module $module * @param DimensionMapping|null $dimensionMapping * @return bool + * @throws ApiException * @throws PropertyNotAccessibleException - * @throws ReflectionException - * @throws InvalidSourceException - * @throws TypeConverterException */ - protected function mapPropertiesFromDataToObject(array $data, AbstractEntity|FileInterface $object, Module $module, DimensionMapping $dimensionMapping = null): bool + protected function mapPropertiesFromDataToObject(array $data, AbstractEntity|File $object, Module $module, DimensionMapping $dimensionMapping = null): bool { $deferAfterProcessing = parent::mapPropertiesFromDataToObject($data, $object, $module, $dimensionMapping); $metadata = []; @@ -512,7 +519,7 @@ public function check(ApiClient $client, Module $module, array $status): array

', $ids[0]); } else { - $publicTempFile = 'typo3/typo3temp/assets/images/' . basename($temporaryFile); + $publicTempFile = Environment::getPublicPath() . '/typo3/typo3temp/assets/images/' . basename($temporaryFile); rename($temporaryFile, $publicTempFile); $temporaryFileRelativePath = substr($publicTempFile, strlen('typo3/') - 1); $receivedBytes = filesize($publicTempFile); diff --git a/Classes/Service/ApiClient.php b/Classes/Service/ApiClient.php index b557194..3c2e912 100644 --- a/Classes/Service/ApiClient.php +++ b/Classes/Service/ApiClient.php @@ -205,7 +205,7 @@ public function saveDerivate(string $filename, string $objectId, string $usage = curl_close($ch); fclose($fp); - if ($expectedFileSize > 0 && $expectedFileSize != filesize($temporaryFilename)) { + if (!empty($expectedFileSize) && $expectedFileSize > 0 && $expectedFileSize != filesize($temporaryFilename)) { unlink($temporaryFilename); $message = 'The downloaded file does not match the expected filesize'; $this->loggingService->logFileTransferActivity($uri, $temporaryFilename . ': ' . $message, 4 /*GeneralUtility::SYSLOG_SEVERITY_ERROR*/); diff --git a/Classes/Service/EventExecutionService.php b/Classes/Service/EventExecutionService.php index 6e507dc..306ade7 100644 --- a/Classes/Service/EventExecutionService.php +++ b/Classes/Service/EventExecutionService.php @@ -537,6 +537,11 @@ public function processEvent(Event $event, bool $updateEventId = true, ?SyncPara ); $event->setProcessing(true); + try { + $this->persistenceManager->update($event); + } catch (UnknownObjectException $e) { + // ignore + } if (method_exists($this->response, 'send')) { $this->response->send(); @@ -603,7 +608,7 @@ public function processEvent(Event $event, bool $updateEventId = true, ?SyncPara $event->setStatus('failed'); } $this->loggingService->logEventActivity($event, 'Event was deferred', 2 /* GeneralUtility::SYSLOG_SEVERITY_WARNING */); - } catch (Exception $exception) { + } catch (\Exception | \ReflectionException $exception) { $event->setStatus('failed'); $event->setRetries(0); $event->setMessage($exception->getMessage() . ' (code: ' . $exception->getCode() . ')' . $exception->getFile() . ':' . $exception->getLine()); @@ -611,6 +616,8 @@ public function processEvent(Event $event, bool $updateEventId = true, ?SyncPara $event->getModule()->setLastEventId(max($event->getEventId(), $event->getModule()->getLastEventId())); } $this->loggingService->logEventActivity($event, 'System error: ' . $exception->getMessage(), 2 /* GeneralUtility::SYSLOG_SEVERITY_WARNING */); + } finally { + $event->setProcessing(false); } // $responseMetadata = $client->getLastResponse(); // $event->setHeaders($responseMetadata['headers']); @@ -620,6 +627,7 @@ public function processEvent(Event $event, bool $updateEventId = true, ?SyncPara // $event->setProcessing(false); // $this->eventRepository->update($event); try { + $this->persistenceManager->update($event); $this->persistenceManager->persistAll(); } catch (Exception $exception) { $this->loggingService->logEventActivity($event, 'System error: ' . $exception->getMessage(), 2 /* GeneralUtility::SYSLOG_SEVERITY_WARNING */); diff --git a/Classes/Service/LoggingService.php b/Classes/Service/LoggingService.php index 36cc221..288b05f 100644 --- a/Classes/Service/LoggingService.php +++ b/Classes/Service/LoggingService.php @@ -5,6 +5,7 @@ use Crossmedia\Fourallportal\Domain\Model\Event; use Crossmedia\Fourallportal\Domain\Model\LogEntry; use Crossmedia\Fourallportal\Utility\ConstantsUtility; +use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -147,7 +148,7 @@ protected function writeEntry(string $logFile, string $message, int $severity): protected function resolveLogFilePath(string $type, string $identity = null): string { - $logFilePath = ConstantsUtility::LOG_BASEDIR . $type; + $logFilePath = Environment::getVarPath() . ConstantsUtility::LOG_BASEDIR . $type; if ($identity) { $logFilePath .= '/' . $identity . '.log'; } else { diff --git a/Classes/Utility/ConstantsUtility.php b/Classes/Utility/ConstantsUtility.php index 1d30b48..bf9c4b1 100644 --- a/Classes/Utility/ConstantsUtility.php +++ b/Classes/Utility/ConstantsUtility.php @@ -10,5 +10,5 @@ class ConstantsUtility public final const TEXT_SCHEMA = 'schema'; public final const TEXT_CONNECTION = 'connection'; public final const TEXT_ERRORS = 'error'; - public final const LOG_BASEDIR = 'typo3temp/var/logs/fourallportal/'; + public final const LOG_BASEDIR = 'logs/fourallportal/'; } \ No newline at end of file diff --git a/Resources/Private/Templates/Event/Check.html b/Resources/Private/Templates/Event/Check.html index 7aaa1b2..8b4caeb 100644 --- a/Resources/Private/Templates/Event/Check.html +++ b/Resources/Private/Templates/Event/Check.html @@ -7,6 +7,20 @@ + + .pre-4ap { + display: block; + padding: 8.5px; + font-size: 11px; + line-height: 1.5; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 2px; + } + +

Event history, object @@ -48,13 +62,13 @@

Object log

Request payload

- {event.payload} +
{event.payload -> f:format.raw()}

Response headers

- {event.headers} +
{event.headers -> f:format.raw()}

Response body

- {event.response} +
{event.response -> f:format.raw()}