From 1f135d6286d7f1fa8cc3f55637e8d011aa771d43 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Wed, 25 Feb 2026 23:16:49 +0100 Subject: [PATCH 01/56] Added file rotation cache --- .../transfer-generator.transfer.yml | 8 +++ .../transfer-object.list.csv | 1 + .../transfer-object.list.csv | 8 +++ .../transfer-object.list.csv | 4 ++ src/Generated/ConfigContentTransfer.php | 14 +++- .../TransferGeneratorContentTransfer.php | 14 +++- src/Generated/transfer-object.list.csv | 20 ++++++ .../Exception/FileCacheReaderException.php | 11 ++++ src/Shared/Filesystem/FileCacheAppender.php | 23 +++++++ .../Filesystem/FileCacheAppenderInterface.php | 18 +++++ src/Shared/Reader/FileCacheReader.php | 57 ++++++++++++++++ .../Reader/FileCacheReaderInterface.php | 17 +++++ src/Shared/SharedFactoryTrait.php | 20 ++++++ .../Config/Config/Config.php | 5 ++ .../Config/Config/ConfigInterface.php | 5 ++ .../Config/Config/ConfigProxy.php | 5 ++ .../Config/Enum/ConfigKeyEnum.php | 2 +- .../Parser/Builder/ConfigContentBuilder.php | 15 ++++- .../Generator/Enum/FilesystemEnum.php | 12 ++++ .../Generator/Filesystem/CacheFilesystem.php | 66 +++++++++++++++++++ .../Filesystem/CacheFilesystemInterface.php | 24 +++++++ .../Generator/Filesystem/FilesystemTrait.php | 39 +++++++++++ .../Filesystem/GeneratorFilesystem.php | 46 +++++++------ .../GeneratorFilesystemInterface.php | 4 +- .../Generator/Generator/GeneratorFactory.php | 16 +++++ .../Processor/Command/PostProcessCommand.php | 29 +++++++- .../Processor/Command/ProcessCommand.php | 9 +++ .../Generator/Render/TemplateRender.php | 8 ++- .../Success/transfer-object.list.csv | 2 + .../Destatis/transfer-object.list.csv | 7 ++ .../Frankfurter/transfer-object.list.csv | 2 + .../transfer-object.list.csv | 2 + .../NasaNeo/transfer-object.list.csv | 12 ++++ .../OpenWeather/transfer-object.list.csv | 7 ++ .../Tagesschau/transfer-object.list.csv | 7 ++ .../Generated/Wero/transfer-object.list.csv | 5 ++ .../Generated/BcMath/transfer-object.list.csv | 1 + .../Generated/transfer-object.list.csv | 11 ++++ .../Success/transfer-object.list.csv | 4 ++ .../Command/PostProcessCommandTest.php | 35 ++++++++++ 40 files changed, 567 insertions(+), 28 deletions(-) create mode 100644 examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv create mode 100644 examples/Generated/DefinitionGenerator/transfer-object.list.csv create mode 100644 examples/Generated/TransferGenerator/transfer-object.list.csv create mode 100644 src/Generated/transfer-object.list.csv create mode 100644 src/Shared/Exception/FileCacheReaderException.php create mode 100644 src/Shared/Filesystem/FileCacheAppender.php create mode 100644 src/Shared/Filesystem/FileCacheAppenderInterface.php create mode 100644 src/Shared/Reader/FileCacheReader.php create mode 100644 src/Shared/Reader/FileCacheReaderInterface.php create mode 100644 src/TransferGenerator/Generator/Enum/FilesystemEnum.php create mode 100644 src/TransferGenerator/Generator/Filesystem/CacheFilesystem.php create mode 100644 src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php create mode 100644 src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php create mode 100644 tests/integration/Command/Generated/Success/transfer-object.list.csv create mode 100644 tests/integration/DefinitionGenerator/Generated/Destatis/transfer-object.list.csv create mode 100644 tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer-object.list.csv create mode 100644 tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer-object.list.csv create mode 100644 tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer-object.list.csv create mode 100644 tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer-object.list.csv create mode 100644 tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer-object.list.csv create mode 100644 tests/integration/DefinitionGenerator/Generated/Wero/transfer-object.list.csv create mode 100644 tests/integration/Transfer/Generated/BcMath/transfer-object.list.csv create mode 100644 tests/integration/Transfer/Generated/transfer-object.list.csv create mode 100644 tests/integration/TransferGenerator/Generated/Success/transfer-object.list.csv diff --git a/config/definition/transfer-generator.transfer.yml b/config/definition/transfer-generator.transfer.yml index 81597837..160559aa 100644 --- a/config/definition/transfer-generator.transfer.yml +++ b/config/definition/transfer-generator.transfer.yml @@ -20,6 +20,10 @@ ConfigContent: relativeDefinitionPath: type: string required: + uuid: + type: string + required: + protected: Definition: fileName: @@ -116,6 +120,10 @@ TransferGeneratorContent: type: string required: protected: + hash: + type: string + required: + protected: TransferGeneratorBulk: progress: diff --git a/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv b/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv new file mode 100644 index 00000000..d9a73bef --- /dev/null +++ b/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv @@ -0,0 +1 @@ +AdvancedCustomerTransfer,e95f378cb17b10a5f5bacc253d684f2e406526cb9179da626f2ae589770b7351 diff --git a/examples/Generated/DefinitionGenerator/transfer-object.list.csv b/examples/Generated/DefinitionGenerator/transfer-object.list.csv new file mode 100644 index 00000000..3271c31b --- /dev/null +++ b/examples/Generated/DefinitionGenerator/transfer-object.list.csv @@ -0,0 +1,8 @@ +ProductTransfer,4b971a742f409a8f34474cc398ae3a421a170da2af27fadb758419f55d67558c +DeliveryOptionsTransfer,ae3c027988c0aff9cd9ca4a4c75806c5570cccea47f11b740bf66b8587e686b2 +DetailsTransfer,a71bdbbd8934a1a4bbc2f6b87e582e45e299c3beebea7f015e7479262e9b5dfb +LabelsTransfer,d4b058fdeb18278361c47cb129e9f0d194dce6be97d8a10672dd987f7b026091 +AvailabilitiesTransfer,18871357c1130789f629ec1e8d836042f5c3f02d88c90c997a25a743253bfc64 +MeasurementUnitTransfer,4da9dffe333a556436bb6dfd4c94a01e19ebe00c935e5773a9b32011a72a61a9 +PaletteTransfer,080031bc3844e2505f97fba584a2652e5f9719e2c730e00ebacffb0b17673ddc +BoxTransfer,119ddf5e9e460935f949eb86d047e91142b6e5b58c3862e4e6ede2f5cf7f61d6 diff --git a/examples/Generated/TransferGenerator/transfer-object.list.csv b/examples/Generated/TransferGenerator/transfer-object.list.csv new file mode 100644 index 00000000..a33f552a --- /dev/null +++ b/examples/Generated/TransferGenerator/transfer-object.list.csv @@ -0,0 +1,4 @@ +CredentialsTransfer,4c9e3a5af952bb41f1d90f55262d56f9eebb5fe7a296fd8b9b00f74269bb3fa5 +CustomerTransfer,e7fa9a90f41ef578b42d4f8c42d07202f62edb7da389adf72c523c798b00252c +AgentTransfer,88d58cf9383287603ea138ae39629eda250f33209381fa8232597456922b7389 +MerchantTransfer,715ed4d116f7ad2d92398aed0baf2031b4ee391663df9ba7a2e1bd9350f0b4e4 diff --git a/src/Generated/ConfigContentTransfer.php b/src/Generated/ConfigContentTransfer.php index 19bbe22d..e4becc6b 100644 --- a/src/Generated/ConfigContentTransfer.php +++ b/src/Generated/ConfigContentTransfer.php @@ -17,13 +17,14 @@ */ final class ConfigContentTransfer extends AbstractTransfer { - protected const int META_DATA_SIZE = 4; + protected const int META_DATA_SIZE = 5; protected const array META_DATA = [ self::DEFINITION_PATH_PROP => self::DEFINITION_PATH_INDEX, self::RELATIVE_DEFINITION_PATH_PROP => self::RELATIVE_DEFINITION_PATH_INDEX, self::TRANSFER_NAMESPACE_PROP => self::TRANSFER_NAMESPACE_INDEX, self::TRANSFER_PATH_PROP => self::TRANSFER_PATH_INDEX, + self::UUID_PROP => self::UUID_INDEX, ]; // definitionPath @@ -69,4 +70,15 @@ final class ConfigContentTransfer extends AbstractTransfer $this->setData(self::TRANSFER_PATH_INDEX, $value); } } + + // uuid + public const string UUID_PROP = 'uuid'; + private const int UUID_INDEX = 4; + + public protected(set) string $uuid { + get => $this->getData(self::UUID_INDEX); + set { + $this->setData(self::UUID_INDEX, $value); + } + } } diff --git a/src/Generated/TransferGeneratorContentTransfer.php b/src/Generated/TransferGeneratorContentTransfer.php index c849833f..040bf618 100644 --- a/src/Generated/TransferGeneratorContentTransfer.php +++ b/src/Generated/TransferGeneratorContentTransfer.php @@ -17,11 +17,12 @@ */ final class TransferGeneratorContentTransfer extends AbstractTransfer { - protected const int META_DATA_SIZE = 2; + protected const int META_DATA_SIZE = 3; protected const array META_DATA = [ self::CLASS_NAME_PROP => self::CLASS_NAME_INDEX, self::CONTENT_PROP => self::CONTENT_INDEX, + self::HASH_PROP => self::HASH_INDEX, ]; // className @@ -45,4 +46,15 @@ final class TransferGeneratorContentTransfer extends AbstractTransfer $this->setData(self::CONTENT_INDEX, $value); } } + + // hash + public const string HASH_PROP = 'hash'; + private const int HASH_INDEX = 2; + + public protected(set) string $hash { + get => $this->getData(self::HASH_INDEX); + set { + $this->setData(self::HASH_INDEX, $value); + } + } } diff --git a/src/Generated/transfer-object.list.csv b/src/Generated/transfer-object.list.csv new file mode 100644 index 00000000..5f6e4e6f --- /dev/null +++ b/src/Generated/transfer-object.list.csv @@ -0,0 +1,20 @@ +ConfigTransfer,fa113db0d8da9557305bc223b5058f1159f5265e13ee6f3d3f241ce6dea211cf +ConfigContentTransfer,7e1da0bdc465582b63c45fed1d9c42808e8c148bcd84d353ac59f390e037ee00 +DefinitionTransfer,2f3aee2d9280769303b79b3188a48409b9c515e61e7318408239c12e2a18bbb4 +DefinitionContentTransfer,f9091ac813bf472677ee58d39fcced7c192ac19300059e9788fd79b8e3f93077 +DefinitionPropertyTransfer,df7f49080dcca259103f571b2c656c416aec7616769c54ed024980349762e7ac +DefinitionAttributeTransfer,dd8933e2d0cb072b0fed8e8a25a333f4f0a0d0915ed873864ca1879651beb089 +DefinitionBuiltInTypeTransfer,e39bce896d4c7087e0b5b83f5fe8899413c313b855b94dd287bdc101b960cafd +DefinitionEmbeddedTypeTransfer,7c77e18e2db40b7fec2113771872ebd56b299dbd2976358af857e0ed1cd7d5f7 +DefinitionNamespaceTransfer,4169380f244fb5fb139f4174bc913c9975bd27c6d0b304a50694c1566817a1cf +TransferGeneratorTransfer,6b2f8deecd6f95a38d66dd4ededed882b156c2f99e235deeaf87a4e9c1a8d703 +TransferGeneratorContentTransfer,51bd33f51f7d22da97b2c940d573acba8e7afa47e84c9f5552aeae05c9e1f537 +TransferGeneratorBulkTransfer,a0f33353da9725bb6e38e306c9ad8f7454aaa648d2821130406de43b8632ad67 +ValidatorTransfer,ec88c3635d5173ead19a291b626dadf3f675200314147938a2bffdb1b7df1b78 +ValidatorMessageTransfer,e579192f13bf4cdc9fab844dbefaf8a6a8246ad05f893d9723328870ffc974c3 +FileReaderProgressTransfer,7ec9bb1a892f0594535cca1709515f465aadb15ea2dac393256ae75a201db404 +DefinitionGeneratorTransfer,b54540b65d2766439be5cf0a7566580b78e1241134289c28ba30a7f56be0d8f5 +DefinitionGeneratorContentTransfer,4ef671b1a35f0cde54246d2cf8e7b0feaa3faa25352e44abd3d504a6d6d48e17 +DefinitionBuilderTransfer,d5899c829de6422e636803b720f68fb7dc79165cf13e4611b7d33951bc7e0a94 +DefinitionFilesystemTransfer,6726bd810cc3c76c8c2f5791f883b27bab9ac4aebcf4c7d094f814527442f0dd +TemplateTransfer,5f95485e1bf24759188a0fc318f8bc8aa24c7b35cc4d47d21e39baf7eed01179 diff --git a/src/Shared/Exception/FileCacheReaderException.php b/src/Shared/Exception/FileCacheReaderException.php new file mode 100644 index 00000000..3191bd26 --- /dev/null +++ b/src/Shared/Exception/FileCacheReaderException.php @@ -0,0 +1,11 @@ +fileAppender->appendToFile($filename, $content); + } + + public function closeFile(string $filename): void + { + $this->fileAppender->closeFile($filename); + } +} diff --git a/src/Shared/Filesystem/FileCacheAppenderInterface.php b/src/Shared/Filesystem/FileCacheAppenderInterface.php new file mode 100644 index 00000000..8448e1f9 --- /dev/null +++ b/src/Shared/Filesystem/FileCacheAppenderInterface.php @@ -0,0 +1,18 @@ +fileExists($path)) { + return new ArrayObject(); + } + + $content = []; + foreach ($this->fileReader->readFile($path) as $line) { + if ($line === '') { + continue; + } + + $content = array_merge($content, $this->parseLine($line)); + } + + return new ArrayObject($content); + } + + /** + * @return array + */ + private function parseLine(string $line): array + { + /** @var array $parsedLine */ + $parsedLine = explode(',', $line); + $parsedLineCount = count($parsedLine); + + if ($parsedLineCount === 2) { + return [ + $parsedLine[0] => $parsedLine[1], + ]; + } + + throw new FileCacheReaderException('CSV file contains invalid line.'); + } + + protected function fileExists(string $filename): bool + { + return file_exists($filename); + } +} diff --git a/src/Shared/Reader/FileCacheReaderInterface.php b/src/Shared/Reader/FileCacheReaderInterface.php new file mode 100644 index 00000000..b48caef4 --- /dev/null +++ b/src/Shared/Reader/FileCacheReaderInterface.php @@ -0,0 +1,17 @@ + + */ + public function readFile(string $path): ArrayObject; +} diff --git a/src/Shared/SharedFactoryTrait.php b/src/Shared/SharedFactoryTrait.php index 245e7a11..847c994d 100644 --- a/src/Shared/SharedFactoryTrait.php +++ b/src/Shared/SharedFactoryTrait.php @@ -9,9 +9,13 @@ use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; use Picamator\TransferObject\Shared\Filesystem\FileAppender; use Picamator\TransferObject\Shared\Filesystem\FileAppenderInterface; +use Picamator\TransferObject\Shared\Filesystem\FileCacheAppender; +use Picamator\TransferObject\Shared\Filesystem\FileCacheAppenderInterface; use Picamator\TransferObject\Shared\Filesystem\FileReader; use Picamator\TransferObject\Shared\Filesystem\FileReaderInterface; use Picamator\TransferObject\Shared\Initializer\LazyGhostInitializerTrait; +use Picamator\TransferObject\Shared\Reader\FileCacheReader; +use Picamator\TransferObject\Shared\Reader\FileCacheReaderInterface; use Picamator\TransferObject\Shared\Reader\FileReaderProgress; use Picamator\TransferObject\Shared\Reader\FileReaderProgressInterface; use Picamator\TransferObject\Shared\Reader\JsonReader; @@ -112,4 +116,20 @@ final protected function createEnvironmentReader(): EnvironmentReaderInterface factory: fn(): EnvironmentReaderInterface => new EnvironmentReader(), ); } + + final protected function createFileCacheReader(): FileCacheReaderInterface + { + return $this->getCached( + key: 'shared:FileCacheReader', + factory: fn(): FileCacheReaderInterface => new FileCacheReader($this->createFileReader()), + ); + } + + final protected function createFileCacheAppender(): FileCacheAppenderInterface + { + return $this->getCached( + key: 'shared:FileCacheAppender', + factory: fn(): FileCacheAppenderInterface => new FileCacheAppender($this->createFileAppender()), + ); + } } diff --git a/src/TransferGenerator/Config/Config/Config.php b/src/TransferGenerator/Config/Config/Config.php index bf4e61fa..ffa6f08b 100644 --- a/src/TransferGenerator/Config/Config/Config.php +++ b/src/TransferGenerator/Config/Config/Config.php @@ -32,4 +32,9 @@ public function getRelativeDefinitionPath(): string { return $this->configTransfer->relativeDefinitionPath; } + + public function getUuid(): string + { + return $this->configTransfer->uuid; + } } diff --git a/src/TransferGenerator/Config/Config/ConfigInterface.php b/src/TransferGenerator/Config/Config/ConfigInterface.php index 8b6ae9db..19985b76 100644 --- a/src/TransferGenerator/Config/Config/ConfigInterface.php +++ b/src/TransferGenerator/Config/Config/ConfigInterface.php @@ -25,4 +25,9 @@ public function getDefinitionPath(): string; * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ public function getRelativeDefinitionPath(): string; + + /** + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + */ + public function getUuid(): string; } diff --git a/src/TransferGenerator/Config/Config/ConfigProxy.php b/src/TransferGenerator/Config/Config/ConfigProxy.php index feed87b6..674e60c9 100644 --- a/src/TransferGenerator/Config/Config/ConfigProxy.php +++ b/src/TransferGenerator/Config/Config/ConfigProxy.php @@ -30,6 +30,11 @@ public function getRelativeDefinitionPath(): string return $this->getConfig()->getRelativeDefinitionPath(); } + public function getUuid(): string + { + return $this->getConfig()->getUuid(); + } + public static function loadConfig(ConfigInterface $config): void { self::$config = $config; diff --git a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php index 266d1deb..48c09aad 100644 --- a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php +++ b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php @@ -17,7 +17,7 @@ enum ConfigKeyEnum: string */ public static function getDefaultConfig(): array { - $defaultContent = []; + $defaultContent[ConfigContentTransfer::UUID_PROP] = ''; foreach (self::cases() as $keyEnum) { $defaultContent[$keyEnum->value] = ''; } diff --git a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php index 298fd341..6b590cc1 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php @@ -17,8 +17,21 @@ public function __construct(private EnvironmentReaderInterface $environmentReade public function createContentTransfer(array $configData): ConfigContentTransfer { + $configData[ConfigContentTransfer::UUID_PROP] = $this->getUuid(); $contentTransfer = new ConfigContentTransfer($configData); + $this->parseContentPath($contentTransfer); + + return $contentTransfer; + } + + private function getUuid(): string + { + return uniqid('', true); + } + + private function parseContentPath(ConfigContentTransfer $contentTransfer): void + { $relativeDefinitionPath = $this->filterPath($contentTransfer->definitionPath); $contentTransfer->relativeDefinitionPath = $this->replacePlaceholder($relativeDefinitionPath, ''); @@ -29,8 +42,6 @@ public function createContentTransfer(array $configData): ConfigContentTransfer $transferPath = $this->replacePlaceholder($contentTransfer->transferPath, $projectRoot); $contentTransfer->transferPath = $transferPath; - - return $contentTransfer; } private function filterPath(string $path): string diff --git a/src/TransferGenerator/Generator/Enum/FilesystemEnum.php b/src/TransferGenerator/Generator/Enum/FilesystemEnum.php new file mode 100644 index 00000000..e7a7ffb8 --- /dev/null +++ b/src/TransferGenerator/Generator/Enum/FilesystemEnum.php @@ -0,0 +1,12 @@ +value; + + /** + * @var array> + */ + private array $cachePerConfiguration = []; + + public function __construct( + private readonly FileCacheReaderInterface $fileReader, + private readonly FileCacheAppenderInterface $fileAppender, + private readonly ConfigInterface $config, + ) { + } + + public function readFromCache(): ArrayObject + { + $uuid = $this->config->getUuid(); + $fromCache = $this->cachePerConfiguration[$uuid] ?? null; + if ($fromCache !== null) { + return $fromCache; + } + + // resetting cache + $this->cachePerConfiguration = []; + + $filePath = $this->getTransferPath(self::FILE_NAME); + $this->cachePerConfiguration[$uuid] = $this->fileReader->readFile($filePath); + + return $this->cachePerConfiguration[$uuid]; + } + + public function readFromTempCache(): ArrayObject + { + $filePath = $this->getTemporaryPath(self::FILE_NAME); + + return $this->fileReader->readFile($filePath); + } + + public function appendToTempCache(string $className, string $hash): void + { + $filePath = $this->getTemporaryPath(self::FILE_NAME); + $this->fileAppender->appendToFile($filePath, $className, $hash); + } + + public function closeTempCache(): void + { + $filePath = $this->getTemporaryPath(self::FILE_NAME); + $this->fileAppender->closeFile($filePath); + } +} diff --git a/src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php new file mode 100644 index 00000000..d248cace --- /dev/null +++ b/src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php @@ -0,0 +1,24 @@ + + */ + public function readFromCache(): ArrayObject; + + /** + * @return \ArrayObject + */ + public function readFromTempCache(): ArrayObject; + + public function appendToTempCache(string $className, string $hash): void; + + public function closeTempCache(): void; +} diff --git a/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php new file mode 100644 index 00000000..6d736373 --- /dev/null +++ b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php @@ -0,0 +1,39 @@ +config->getTransferPath(); + if ($filename !== null) { + $path .= DIRECTORY_SEPARATOR . $filename; + } + + return $path; + } + + /** + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + */ + final protected function getTemporaryPath(?string $filename = null): string + { + $path = $this->config->getTransferPath() . DIRECTORY_SEPARATOR . self::TEMPORARY_DIR; + if ($filename !== null) { + $path .= DIRECTORY_SEPARATOR . $filename; + } + + return $path; + } +} diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php index f1a89a6f..88abfd10 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php @@ -9,13 +9,16 @@ use Picamator\TransferObject\Generated\TransferGeneratorContentTransfer; use Picamator\TransferObject\TransferGenerator\Config\Config\ConfigInterface; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException; +use Picamator\TransferObject\TransferGenerator\Generator\Enum\FilesystemEnum; readonly class GeneratorFilesystem implements GeneratorFilesystemInterface { - private const string TEMPORARY_DIR = '_tmp'; + use FilesystemTrait; - private const string FILE_EXTENSION = '.php'; - private const string FILE_NAME_PATTERN = '*Transfer.php'; + private const string TRANSFER_FILE_EXTENSION = FilesystemEnum::TRANSFER_FILE_EXTENSION->value; + private const string TRANSFER_FILE_NAME_PATTERN = FilesystemEnum::TRANSFER_FILE_NAME_PATTERN->value; + + private const string CACHE_FILE_NAME = FilesystemEnum::CACHE_FILE_NAME->value; public function __construct( private FilesystemInterface $filesystem, @@ -43,9 +46,12 @@ public function deleteTempDir(): void } } - public function rotateTempDir(): void + public function rotateTempDir(array $deleteClassNames): void { - $this->deleteOldFiles(); + if (count($deleteClassNames) !== 0) { + $this->deleteOldFiles($deleteClassNames); + } + $this->copyTempFiles(); $this->deleteTempDir(); } @@ -55,7 +61,7 @@ public function writeFile(TransferGeneratorContentTransfer $contentTransfer): vo $filePath = $this->getTemporaryPath() . DIRECTORY_SEPARATOR . $contentTransfer->className - . self::FILE_EXTENSION; + . self::TRANSFER_FILE_EXTENSION; if ($this->filesystem->exists($filePath)) { throw new TransferGeneratorException( @@ -67,19 +73,22 @@ public function writeFile(TransferGeneratorContentTransfer $contentTransfer): vo } /** + * @param array $deleteClassNames + * * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException * @throws \Picamator\TransferObject\Dependency\Exception\FinderException * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ - private function deleteOldFiles(): void + private function deleteOldFiles(array $deleteClassNames): void { - $finder = $this->finder->findFilesInDirectoryExclude( - filePattern: self::FILE_NAME_PATTERN, - dirName: $this->config->getTransferPath(), - exclude: self::TEMPORARY_DIR, + /** @var array $deleteFiles */ + $deleteFiles = array_map( + fn (string $className): string + => $this->config->getTransferPath() . DIRECTORY_SEPARATOR . $className . self::TRANSFER_FILE_EXTENSION, + $deleteClassNames, ); - $this->filesystem->remove($finder); + $this->filesystem->remove($deleteFiles); } /** @@ -90,7 +99,7 @@ private function deleteOldFiles(): void private function copyTempFiles(): void { $finder = $this->finder->findFilesInDirectory( - filePattern: self::FILE_NAME_PATTERN, + filePattern: self::TRANSFER_FILE_NAME_PATTERN, dirName: $this->getTemporaryPath(), ); @@ -99,13 +108,10 @@ private function copyTempFiles(): void $targetFile = $destinationPath . $file->getFilename(); $this->filesystem->copy($file->getRealPath(), $targetFile); } - } - /** - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException - */ - private function getTemporaryPath(): string - { - return $this->config->getTransferPath() . DIRECTORY_SEPARATOR . self::TEMPORARY_DIR; + $this->filesystem->copy( + $this->getTemporaryPath(self::CACHE_FILE_NAME), + $this->getTransferPath(self::CACHE_FILE_NAME), + ); } } diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php index e88efc71..9b5d54b7 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php @@ -19,11 +19,13 @@ public function createTempDir(): void; public function deleteTempDir(): void; /** + * @param array $deleteClassNames + * * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException * @throws \Picamator\TransferObject\Dependency\Exception\FinderException * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ - public function rotateTempDir(): void; + public function rotateTempDir(array $deleteClassNames): void; /** * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException diff --git a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php index f5f71eec..b0ed49aa 100644 --- a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php +++ b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php @@ -10,6 +10,8 @@ use Picamator\TransferObject\TransferGenerator\Config\Loader\ConfigLoaderInterface; use Picamator\TransferObject\TransferGenerator\Definition\DefinitionFactory; use Picamator\TransferObject\TransferGenerator\Definition\Reader\DefinitionReaderInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystem; +use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystem; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilder; @@ -45,6 +47,7 @@ protected function createPostProcessCommand(): PostProcessCommandInterface { return new PostProcessCommand( $this->createTransferGeneratorBuilder(), + $this->createCacheFilesystem(), $this->createGeneratorFilesystem(), ); } @@ -62,6 +65,7 @@ protected function createProcessCommand(): ProcessCommandInterface return new ProcessCommand( $this->createTransferGeneratorBuilder(), $this->createTemplateRender(), + $this->createCacheFilesystem(), $this->createGeneratorFilesystem(), ); } @@ -100,6 +104,18 @@ protected function createTransferGeneratorBuilder(): TransferGeneratorBuilderInt ); } + protected function createCacheFilesystem(): CacheFilesystemInterface + { + return $this->getCached( + key: 'transfer-generator:CacheFilesystem', + factory: fn() => new CacheFilesystem( + $this->createFileCacheReader(), + $this->createFileCacheAppender(), + $this->getConfig(), + ), + ); + } + protected function createTemplateRender(): TemplateRenderInterface { return $this->getCached( diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php index c233622d..9aa490c2 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php @@ -8,6 +8,7 @@ use Picamator\TransferObject\Dependency\Exception\FinderException; use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException; +use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; @@ -15,6 +16,7 @@ { public function __construct( private TransferGeneratorBuilderInterface $builder, + private CacheFilesystemInterface $cacheFilesystem, private GeneratorFilesystemInterface $filesystem, ) { } @@ -31,7 +33,10 @@ public function postProcess(bool $isSuccessful): TransferGeneratorTransfer private function postProcessSuccess(): TransferGeneratorTransfer { try { - $this->filesystem->rotateTempDir(); + $this->cacheFilesystem->closeTempCache(); + $deleteClassNames = $this->getDeleteClassNames(); + + $this->filesystem->rotateTempDir($deleteClassNames); } catch (FilesystemException | FinderException | TransferGeneratorConfigNotFoundException $e) { return $this->builder->createErrorGeneratorTransfer($e->getMessage()); } @@ -39,9 +44,31 @@ private function postProcessSuccess(): TransferGeneratorTransfer return $this->builder->createSuccessGeneratorTransfer(); } + /** + * @return array + */ + private function getDeleteClassNames(): array + { + $tempCache = $this->cacheFilesystem->readFromTempCache(); + $cache = $this->cacheFilesystem->readFromCache(); + + $deletedClasses = []; + // phpcs:disable SlevomatCodingStandard.Variables.UnusedVariable + foreach ($cache as $className => $hash) { + if (isset($tempCache[$className])) { + continue; + } + + $deletedClasses[] = $className; + } + + return $deletedClasses; + } + private function postProcessError(): TransferGeneratorTransfer { try { + $this->cacheFilesystem->closeTempCache(); $this->filesystem->deleteTempDir(); } catch (FilesystemException $e) { return $this->builder->createErrorGeneratorTransfer($e->getMessage()); diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php index c9557ad8..6fde8b77 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php @@ -9,6 +9,7 @@ use Picamator\TransferObject\Generated\TransferGeneratorContentTransfer; use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException; +use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Render\TemplateRenderInterface; @@ -18,6 +19,7 @@ public function __construct( private TransferGeneratorBuilderInterface $builder, private TemplateRenderInterface $render, + private CacheFilesystemInterface $cacheFilesystem, private GeneratorFilesystemInterface $filesystem, ) { } @@ -45,6 +47,13 @@ private function generateTransfer(DefinitionTransfer $definitionTransfer): Trans private function saveContent(TransferGeneratorContentTransfer $contentTransfer): void { + $this->cacheFilesystem->appendToTempCache($contentTransfer->className, $contentTransfer->hash); + + $cachedContentHash = $this->cacheFilesystem->readFromCache()[$contentTransfer->className] ?? null; + if ($cachedContentHash !== null && hash_equals($cachedContentHash, $contentTransfer->hash)) { + return; + } + $this->filesystem->writeFile($contentTransfer); } diff --git a/src/TransferGenerator/Generator/Render/TemplateRender.php b/src/TransferGenerator/Generator/Render/TemplateRender.php index ef0f1074..a6ec3ce9 100644 --- a/src/TransferGenerator/Generator/Render/TemplateRender.php +++ b/src/TransferGenerator/Generator/Render/TemplateRender.php @@ -19,12 +19,18 @@ public function __construct( public function renderTemplate(DefinitionTransfer $definitionTransfer): TransferGeneratorContentTransfer { $templateTransfer = $this->templateBuilder->createTemplateTransfer($definitionTransfer); - $content = $this->template->render($templateTransfer); + $hash = $this->getContentHash($content); return new TransferGeneratorContentTransfer([ TransferGeneratorContentTransfer::CLASS_NAME_PROP => $definitionTransfer->content->className, TransferGeneratorContentTransfer::CONTENT_PROP => $content, + TransferGeneratorContentTransfer::HASH_PROP => $hash, ]); } + + private function getContentHash(string $content): string + { + return hash('sha256', $content); + } } diff --git a/tests/integration/Command/Generated/Success/transfer-object.list.csv b/tests/integration/Command/Generated/Success/transfer-object.list.csv new file mode 100644 index 00000000..986c94e3 --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.list.csv @@ -0,0 +1,2 @@ +CommandSecondTransfer,74cefcbf8a44fa6b6981c11e6b8c8dec8b2da152755d6c70bafd38f1eb5c847c +CommandFirstTransfer,08c25060e2d59248791d1f727d85bd2a6a6a6186289430c6421cc96db090b1c7 diff --git a/tests/integration/DefinitionGenerator/Generated/Destatis/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Destatis/transfer-object.list.csv new file mode 100644 index 00000000..82cf7b30 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Destatis/transfer-object.list.csv @@ -0,0 +1,7 @@ +DestatisTransfer,c3cd5e792d4fa83a91e9ce9f67c9e63576cc157f87492eae1553363d8aa8bee7 +IdentTransfer,d34e5a4643a1c396df5e9ebaca2b98b14df78a72c6aea16fe04cc37dd20c76c6 +StatusTransfer,30aee249decbf14511b71f79970a944faffa8acb803e90bf2b242ac57462da2a +ParameterTransfer,e6f42137bb0fea339a4df47b54a20179d160d938378338f9044dac488378722f +StatisticsTransfer,31fdb8a195ecabf52428713a2a861758bcb048e65ff1deaafd118470a82626cf +TablesTransfer,26b53d666cd7129ac38fb6b8f13459b9e08fd38b52783d6aadf59751a549a364 +VariablesTransfer,e3fa5bc5233220c3df3eea08215b3c11298f9102abc168b8517f640fd74563da diff --git a/tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer-object.list.csv new file mode 100644 index 00000000..2c8c12e4 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer-object.list.csv @@ -0,0 +1,2 @@ +ExchangeRateTransfer,11dd4fb491f625dbcf4ca057fd0497d70062f91bc085655bc4bcb8c6cdd08f23 +RatesTransfer,eef143d206a3955d653ea38e40742a8a45b0e9b169fad9b46d3ed00450c918fb diff --git a/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer-object.list.csv new file mode 100644 index 00000000..42e25166 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer-object.list.csv @@ -0,0 +1,2 @@ +ProductTransfer,16260d759f0a7e1d30d3eac313430d750a8fec967937d2eced1b87fa86cfd575 +PriceTransfer,0d567e2578ccfb9b5712a516436b2b42e4d486cace8815b04185f2851c8f9e24 diff --git a/tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer-object.list.csv new file mode 100644 index 00000000..11f88a5a --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer-object.list.csv @@ -0,0 +1,12 @@ +AsteroidTransfer,4a117e1e7a81235e4f06513fc79707b65548c2bd5bdbad5fb3245e3e9e4d492a +LinksTransfer,5c693a0357cddc67908f32bb52b0ad58d3961a70977e28ec7e4ec63fbc431f6b +EstimatedDiameterTransfer,9eb7bb99e1880038980c7eb113fd06e57fc31fe7c35e234a0fd970e4bcd15244 +KilometersTransfer,a8318153283e7a3a31d17724d20322201e43ebeaaeb4f20e05786ffbd11d97bc +MetersTransfer,a1569450ff71a867fc0ac2f1d06dc3e2f310645f95ee2ac0cadb9632b946e39a +MilesTransfer,c987ac69a00e3c22ad890ee70ca06b05ef39551cab76f31811308d4d9c97eb97 +FeetTransfer,0d8afe4180551f1ee4edbd617edfa3a4da68fe16a7037c073e20f66d5dc73c56 +CloseApproachDataTransfer,a534ede7349450b1fab8518ca7361523d450633a6df8b29842d69647d83a840b +RelativeVelocityTransfer,b576460323ab0a590fd08f3031a4e855c516e8afe1ef3359dd5c22e295cedab0 +MissDistanceTransfer,b0fb22d8bc49cecffe1a74ba717286b8783582d2f2e920181c5e133f96636d20 +OrbitalDataTransfer,b5601cf93317a0cf48b503d0d7eebf261a97c4c8615edc9935793416eb6e67ff +OrbitClassTransfer,578856d9d4de968d19843d33022963bcbfcef4afaa65a044f7e481804b369410 diff --git a/tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer-object.list.csv new file mode 100644 index 00000000..9661688e --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer-object.list.csv @@ -0,0 +1,7 @@ +ForecastTransfer,d932242b15fc3d15dad99bea0aa40648ccd76b4c96535dd5700df7f7830bcdc4 +CoordTransfer,747a9b38bb0290a2db5bbfe37d7036a800fa2db2ca053f6353703aa3848bead9 +WeatherTransfer,c20a83c5338f8de3cd2cb15e6450abefa476f79424caeab206fe72a04c2418ac +MainTransfer,3e8798af1fd0a2686fa8fe665caeed58ef5e5ff4986f4852a2e79fa20316768c +WindTransfer,f247ce8a520e7a21a50e3e353c506a7b5af82a91762ea6d02246bf0f00e655ee +CloudsTransfer,e7af5ba3aca69a69bf521a01aa4ffb0491c7513e2745aaa7e8ded14bac7ab030 +SysTransfer,4410b52347e3e8847850c79088b8a8b5d68ae404027e95a4d888f322898d2834 diff --git a/tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer-object.list.csv new file mode 100644 index 00000000..4f31d6fd --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer-object.list.csv @@ -0,0 +1,7 @@ +ArdNewsTransfer,362bf99002cdc2149f0ecabf287dced5fa4b4e84d56a71a107facd63ab98cefd +NewsTransfer,5d91a9b4fdb54d2cb8871fc5b5799fb6cf9919183b9f88183c83bfd17638ce76 +TeaserImageTransfer,ca3dc39cdd919011864d212d5daa674f3e93672cb0f2faa04a11939b389a6d0f +TagsTransfer,9d581cd4cff899645e12436a3053d2daba1e5c19752faec930e363d3d3871927 +TrackingTransfer,7b9258e1369e675b03a1b1f46b295299637ba8999da1c2e5637b3894264897d8 +BrandingImageTransfer,50138d66b1aaba6857130974daffeedd326a7aea96336cfddef9b65487eb6527 +ImageVariantsTransfer,2ddddd5dc57c167681f04b1703a5e2d9d5efd4afcf54188ee38ca3055b63d064 diff --git a/tests/integration/DefinitionGenerator/Generated/Wero/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Wero/transfer-object.list.csv new file mode 100644 index 00000000..7a40570b --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Wero/transfer-object.list.csv @@ -0,0 +1,5 @@ +PaymentChargesTransfer,4083b1be36b95116290ae78c951aef9a7bd0fb07bd0b47a34cc54942e8708f12 +AmountTransfer,2711d68a6899a374b653ad85ec39c80d9327513a461bedc50aa6aa2607c6853c +ConsumerTransfer,32e5293f1c8bf58256f9f7698923f2ca6e0b5b6d5a51b463c1b95e0a3086fb7b +AuthenticationSettingsTransfer,afeaf7f28dc031f535e8b2a3a204b2cfcb1d48999966802bd4a7781de2bd569e +SettingsTransfer,0e6f04b58eefaf95723cafa1becb35ea6a448571d99e97f0e3a21fd50558bf82 diff --git a/tests/integration/Transfer/Generated/BcMath/transfer-object.list.csv b/tests/integration/Transfer/Generated/BcMath/transfer-object.list.csv new file mode 100644 index 00000000..e2c403ca --- /dev/null +++ b/tests/integration/Transfer/Generated/BcMath/transfer-object.list.csv @@ -0,0 +1 @@ +BcMathNumberTransfer,08592943e5993454c0e18fae20d4808423e9e764d8593f5f95637d733ade4d83 diff --git a/tests/integration/Transfer/Generated/transfer-object.list.csv b/tests/integration/Transfer/Generated/transfer-object.list.csv new file mode 100644 index 00000000..b1f52912 --- /dev/null +++ b/tests/integration/Transfer/Generated/transfer-object.list.csv @@ -0,0 +1,11 @@ +RequiredTransfer,7c1d9b8a602966a46d1563f974f06f0117b882f87885c6ed4f7cb746542d8181 +ReservedAdvancedTransfer,054b877dc6063510f4066d2ae6b63b9dc14565aa1dad8ff2acd1ad457bfc2f90 +BookTransfer,ec0a384bb5243e3a5419cabac88632f8585b86e3a02fd6bfaf5c1484cefd4776 +NamespaceTransfer,f92f70b83028f2beb4df404221e928dbdb060bb9c6f3b0288e5f23c62676a434 +ItemCollectionTransfer,06b8dc9ad9d5e7a4c7c71be61a11388d5124cd391790468b25902159aff51ac4 +EnumTransfer,a8c61922e613f9f8694f67d496b7009e908428e818b660715f2f624308c23ff0 +SymfonyAttributeTransfer,841ea6c83341d4991714a67f6b4566e87b2034886dcf566a5b9cac442b0e1b7f +ItemTransfer,7a402f94bbe046ba039d9c65825e21b02ddd1d2879f2dbb4f057f868a3f28da8 +ProtectedTransfer,9fc044256cf718f1c864ff328c54d8cb9d178d1f1ee8f8c508584324c6a1a658 +ReservedConstantTransfer,1614b67809f0c985b484475cabdaa6aec70b2773c5bd3277d0db8b56da61b615 +AuthorTransfer,af0bd2bb1129732f3193aa4e1d3d3df2e853c9f5c2867ac51172c5ce8eed07a5 diff --git a/tests/integration/TransferGenerator/Generated/Success/transfer-object.list.csv b/tests/integration/TransferGenerator/Generated/Success/transfer-object.list.csv new file mode 100644 index 00000000..f64b4ad1 --- /dev/null +++ b/tests/integration/TransferGenerator/Generated/Success/transfer-object.list.csv @@ -0,0 +1,4 @@ +AddressTransfer,1119ef41f62193712329476757d26b0419cacc74f578c12196a8e5fb674d6ed1 +CountryTransfer,6e9bc668f5e502a80c3b741e548b65728c84435a421105d6a4998d39fbd9ff42 +AddressStatisticsTransfer,067635074458269d46b8c6b612c977b33754365ba771b0aa84f1a895192a0db4 +AddressBookTransfer,ac3f65a9f5bf54d514c2d5b03792f2d923f9d1f2fd82c38aa4645c6d0736e499 diff --git a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php index b950909d..674780c1 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php @@ -4,11 +4,14 @@ namespace Picamator\Tests\Unit\TransferObject\TransferGenerator\Generator\Generator\Processor\Command; +use ArrayObject; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Picamator\TransferObject\Dependency\Exception\FilesystemException; +use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilder; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PostProcessCommand; @@ -21,14 +24,18 @@ final class PostProcessCommandTest extends TestCase private GeneratorFilesystemInterface&Stub $filesystemStub; + private CacheFilesystemInterface&MockObject $cacheFilesystemMock; + protected function setUp(): void { $builder = new TransferGeneratorBuilder(); $this->filesystemStub = $this->createStub(GeneratorFilesystemInterface::class); + $this->cacheFilesystemMock = $this->createMock(CacheFilesystemInterface::class); $this->command = new PostProcessCommand( $builder, + $this->cacheFilesystemMock, $this->filesystemStub, ); } @@ -37,11 +44,34 @@ protected function setUp(): void public function testFilesystemExceptionShouldBeHandledOnPostProcessSuccess(): void { // Arrange + $tempCache = new ArrayObject([ + 'className' => 'some-hash', + ]); + + $cache = new ArrayObject([ + 'className' => 'some-hash', + 'classNameToDelete' => 'some-hash', + ]); + + $this->filesystemStub ->method('rotateTempDir') ->willThrowException(new FilesystemException()) ->seal(); + // Expect + $this->cacheFilesystemMock->expects($this->once()) + ->method('closeTempCache'); + + $this->cacheFilesystemMock->expects($this->once()) + ->method('readFromTempCache') + ->willReturn($tempCache); + + $this->cacheFilesystemMock->expects($this->once()) + ->method('readFromCache') + ->willReturn($cache) + ->seal(); + // Act $actual = $this->command->postProcess(true); @@ -58,6 +88,11 @@ public function testFilesystemExceptionShouldBeHandledOnPostProcessError(): void ->willThrowException(new FilesystemException()) ->seal(); + // Expect + $this->cacheFilesystemMock->expects($this->once()) + ->method('closeTempCache') + ->seal(); + // Act $actual = $this->command->postProcess(false); From fbcbfb35e83e4bc9cafe6789030ec9b6c6e466e2 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Thu, 26 Feb 2026 22:49:59 +0100 Subject: [PATCH 02/56] Optimized the file rotation cache: added specified transfer object, replaced file finder with file list copy, the hash file is saved only when needed --- .../transfer-generator.transfer.yml | 17 +++ src/Generated/ConfigContentTransfer.php | 22 +++- src/Generated/TransferHashTransfer.php | 96 +++++++++++++++ src/Generated/transfer-object.list.csv | 3 +- .../Exception/FileCacheReaderException.php | 11 -- src/Shared/Filesystem/FileCacheAppender.php | 23 ---- .../Filesystem/FileCacheAppenderInterface.php | 18 --- src/Shared/Hash/HashFileReader.php | 59 +++++++++ src/Shared/Hash/HashFileReaderInterface.php | 15 +++ src/Shared/Hash/HashFileWriter.php | 36 ++++++ src/Shared/Hash/HashFileWriterInterface.php | 17 +++ src/Shared/Reader/FileCacheReader.php | 57 --------- .../Reader/FileCacheReaderInterface.php | 17 --- src/Shared/SharedFactoryTrait.php | 20 +-- .../Config/Config/Config.php | 5 + .../Config/Config/ConfigInterface.php | 5 + .../Config/Config/ConfigProxy.php | 5 + .../Config/Enum/ConfigKeyEnum.php | 2 + .../Parser/Builder/ConfigContentBuilder.php | 4 + .../Generator/Enum/FilesystemEnum.php | 12 -- .../Generator/Filesystem/CacheFilesystem.php | 66 ---------- .../Filesystem/CacheFilesystemInterface.php | 24 ---- .../Filesystem/GeneratorFilesystem.php | 116 ++++++++++-------- .../GeneratorFilesystemInterface.php | 15 +-- .../Generator/Filesystem/HashFilesystem.php | 54 ++++++++ .../Filesystem/HashFilesystemInterface.php | 17 +++ .../Generator/Generator/GeneratorFactory.php | 60 +++++++-- .../Processor/Command/PostProcessCommand.php | 32 +---- .../Processor/Command/ProcessCommand.php | 15 +-- .../Generator/Reader/TransferHashReader.php | 45 +++++++ .../Reader/TransferHashReaderInterface.php | 12 ++ .../Generator/Writer/TransferRotator.php | 53 ++++++++ .../Writer/TransferRotatorInterface.php | 10 ++ .../Generator/Writer/TransferWriter.php | 42 +++++++ .../Writer/TransferWriterInterface.php | 12 ++ .../Filesystem/GeneratorFilesystemTest.php | 6 +- .../Command/PostProcessCommandTest.php | 37 ++---- 37 files changed, 672 insertions(+), 388 deletions(-) create mode 100644 src/Generated/TransferHashTransfer.php delete mode 100644 src/Shared/Exception/FileCacheReaderException.php delete mode 100644 src/Shared/Filesystem/FileCacheAppender.php delete mode 100644 src/Shared/Filesystem/FileCacheAppenderInterface.php create mode 100644 src/Shared/Hash/HashFileReader.php create mode 100644 src/Shared/Hash/HashFileReaderInterface.php create mode 100644 src/Shared/Hash/HashFileWriter.php create mode 100644 src/Shared/Hash/HashFileWriterInterface.php delete mode 100644 src/Shared/Reader/FileCacheReader.php delete mode 100644 src/Shared/Reader/FileCacheReaderInterface.php delete mode 100644 src/TransferGenerator/Generator/Enum/FilesystemEnum.php delete mode 100644 src/TransferGenerator/Generator/Filesystem/CacheFilesystem.php delete mode 100644 src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php create mode 100644 src/TransferGenerator/Generator/Filesystem/HashFilesystem.php create mode 100644 src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php create mode 100644 src/TransferGenerator/Generator/Reader/TransferHashReader.php create mode 100644 src/TransferGenerator/Generator/Reader/TransferHashReaderInterface.php create mode 100644 src/TransferGenerator/Generator/Writer/TransferRotator.php create mode 100644 src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php create mode 100644 src/TransferGenerator/Generator/Writer/TransferWriter.php create mode 100644 src/TransferGenerator/Generator/Writer/TransferWriterInterface.php diff --git a/config/definition/transfer-generator.transfer.yml b/config/definition/transfer-generator.transfer.yml index 160559aa..9c27f52a 100644 --- a/config/definition/transfer-generator.transfer.yml +++ b/config/definition/transfer-generator.transfer.yml @@ -20,6 +20,10 @@ ConfigContent: relativeDefinitionPath: type: string required: + hashFileName: + type: string + required: + protected: uuid: type: string required: @@ -125,6 +129,19 @@ TransferGeneratorContent: required: protected: +TransferHash: + hashes: + type: ArrayObject + protected: + configUuid: + type: string + required: + protected: + actualHashes: + type: ArrayObject + toCopyClassNames: + type: ArrayObject + TransferGeneratorBulk: progress: type: FileReaderProgress diff --git a/src/Generated/ConfigContentTransfer.php b/src/Generated/ConfigContentTransfer.php index e4becc6b..4cd8c18e 100644 --- a/src/Generated/ConfigContentTransfer.php +++ b/src/Generated/ConfigContentTransfer.php @@ -17,10 +17,11 @@ */ final class ConfigContentTransfer extends AbstractTransfer { - protected const int META_DATA_SIZE = 5; + protected const int META_DATA_SIZE = 6; protected const array META_DATA = [ self::DEFINITION_PATH_PROP => self::DEFINITION_PATH_INDEX, + self::HASH_FILE_NAME_PROP => self::HASH_FILE_NAME_INDEX, self::RELATIVE_DEFINITION_PATH_PROP => self::RELATIVE_DEFINITION_PATH_INDEX, self::TRANSFER_NAMESPACE_PROP => self::TRANSFER_NAMESPACE_INDEX, self::TRANSFER_PATH_PROP => self::TRANSFER_PATH_INDEX, @@ -38,9 +39,20 @@ final class ConfigContentTransfer extends AbstractTransfer } } + // hashFileName + public const string HASH_FILE_NAME_PROP = 'hashFileName'; + private const int HASH_FILE_NAME_INDEX = 1; + + public protected(set) string $hashFileName { + get => $this->getData(self::HASH_FILE_NAME_INDEX); + set { + $this->setData(self::HASH_FILE_NAME_INDEX, $value); + } + } + // relativeDefinitionPath public const string RELATIVE_DEFINITION_PATH_PROP = 'relativeDefinitionPath'; - private const int RELATIVE_DEFINITION_PATH_INDEX = 1; + private const int RELATIVE_DEFINITION_PATH_INDEX = 2; public string $relativeDefinitionPath { get => $this->getData(self::RELATIVE_DEFINITION_PATH_INDEX); @@ -51,7 +63,7 @@ final class ConfigContentTransfer extends AbstractTransfer // transferNamespace public const string TRANSFER_NAMESPACE_PROP = 'transferNamespace'; - private const int TRANSFER_NAMESPACE_INDEX = 2; + private const int TRANSFER_NAMESPACE_INDEX = 3; public string $transferNamespace { get => $this->getData(self::TRANSFER_NAMESPACE_INDEX); @@ -62,7 +74,7 @@ final class ConfigContentTransfer extends AbstractTransfer // transferPath public const string TRANSFER_PATH_PROP = 'transferPath'; - private const int TRANSFER_PATH_INDEX = 3; + private const int TRANSFER_PATH_INDEX = 4; public string $transferPath { get => $this->getData(self::TRANSFER_PATH_INDEX); @@ -73,7 +85,7 @@ final class ConfigContentTransfer extends AbstractTransfer // uuid public const string UUID_PROP = 'uuid'; - private const int UUID_INDEX = 4; + private const int UUID_INDEX = 5; public protected(set) string $uuid { get => $this->getData(self::UUID_INDEX); diff --git a/src/Generated/TransferHashTransfer.php b/src/Generated/TransferHashTransfer.php new file mode 100644 index 00000000..eb023602 --- /dev/null +++ b/src/Generated/TransferHashTransfer.php @@ -0,0 +1,96 @@ + self::ACTUAL_HASHES_INDEX, + self::CONFIG_UUID_PROP => self::CONFIG_UUID_INDEX, + self::HASHES_PROP => self::HASHES_INDEX, + self::TO_COPY_CLASS_NAMES_PROP => self::TO_COPY_CLASS_NAMES_INDEX, + ]; + + protected const array META_INITIATORS = [ + self::ACTUAL_HASHES_PROP => 'ACTUAL_HASHES_PROP', + self::HASHES_PROP => 'HASHES_PROP', + self::TO_COPY_CLASS_NAMES_PROP => 'TO_COPY_CLASS_NAMES_PROP', + ]; + + protected const array META_TRANSFORMERS = [ + self::ACTUAL_HASHES_PROP => 'ACTUAL_HASHES_PROP', + self::HASHES_PROP => 'HASHES_PROP', + self::TO_COPY_CLASS_NAMES_PROP => 'TO_COPY_CLASS_NAMES_PROP', + ]; + + // actualHashes + #[ArrayObjectInitiatorAttribute] + #[ArrayObjectTransformerAttribute] + public const string ACTUAL_HASHES_PROP = 'actualHashes'; + private const int ACTUAL_HASHES_INDEX = 0; + + /** @var \ArrayObject */ + public ArrayObject $actualHashes { + get => $this->getData(self::ACTUAL_HASHES_INDEX); + set { + $this->setData(self::ACTUAL_HASHES_INDEX, $value); + } + } + + // configUuid + public const string CONFIG_UUID_PROP = 'configUuid'; + private const int CONFIG_UUID_INDEX = 1; + + public protected(set) string $configUuid { + get => $this->getData(self::CONFIG_UUID_INDEX); + set { + $this->setData(self::CONFIG_UUID_INDEX, $value); + } + } + + // hashes + #[ArrayObjectInitiatorAttribute] + #[ArrayObjectTransformerAttribute] + public const string HASHES_PROP = 'hashes'; + private const int HASHES_INDEX = 2; + + /** @var \ArrayObject */ + public protected(set) ArrayObject $hashes { + get => $this->getData(self::HASHES_INDEX); + set { + $this->setData(self::HASHES_INDEX, $value); + } + } + + // toCopyClassNames + #[ArrayObjectInitiatorAttribute] + #[ArrayObjectTransformerAttribute] + public const string TO_COPY_CLASS_NAMES_PROP = 'toCopyClassNames'; + private const int TO_COPY_CLASS_NAMES_INDEX = 3; + + /** @var \ArrayObject */ + public ArrayObject $toCopyClassNames { + get => $this->getData(self::TO_COPY_CLASS_NAMES_INDEX); + set { + $this->setData(self::TO_COPY_CLASS_NAMES_INDEX, $value); + } + } +} diff --git a/src/Generated/transfer-object.list.csv b/src/Generated/transfer-object.list.csv index 5f6e4e6f..8810caec 100644 --- a/src/Generated/transfer-object.list.csv +++ b/src/Generated/transfer-object.list.csv @@ -1,5 +1,5 @@ ConfigTransfer,fa113db0d8da9557305bc223b5058f1159f5265e13ee6f3d3f241ce6dea211cf -ConfigContentTransfer,7e1da0bdc465582b63c45fed1d9c42808e8c148bcd84d353ac59f390e037ee00 +ConfigContentTransfer,90d55471965eb3de22fb9cc4b74b80b8435d9d96281617729c900e44cd828613 DefinitionTransfer,2f3aee2d9280769303b79b3188a48409b9c515e61e7318408239c12e2a18bbb4 DefinitionContentTransfer,f9091ac813bf472677ee58d39fcced7c192ac19300059e9788fd79b8e3f93077 DefinitionPropertyTransfer,df7f49080dcca259103f571b2c656c416aec7616769c54ed024980349762e7ac @@ -9,6 +9,7 @@ DefinitionEmbeddedTypeTransfer,7c77e18e2db40b7fec2113771872ebd56b299dbd2976358af DefinitionNamespaceTransfer,4169380f244fb5fb139f4174bc913c9975bd27c6d0b304a50694c1566817a1cf TransferGeneratorTransfer,6b2f8deecd6f95a38d66dd4ededed882b156c2f99e235deeaf87a4e9c1a8d703 TransferGeneratorContentTransfer,51bd33f51f7d22da97b2c940d573acba8e7afa47e84c9f5552aeae05c9e1f537 +TransferHashTransfer,9f6c2ed84c3db81d394dee94af03388c31483eef6fa7d367fdc5487a6d88e40b TransferGeneratorBulkTransfer,a0f33353da9725bb6e38e306c9ad8f7454aaa648d2821130406de43b8632ad67 ValidatorTransfer,ec88c3635d5173ead19a291b626dadf3f675200314147938a2bffdb1b7df1b78 ValidatorMessageTransfer,e579192f13bf4cdc9fab844dbefaf8a6a8246ad05f893d9723328870ffc974c3 diff --git a/src/Shared/Exception/FileCacheReaderException.php b/src/Shared/Exception/FileCacheReaderException.php deleted file mode 100644 index 3191bd26..00000000 --- a/src/Shared/Exception/FileCacheReaderException.php +++ /dev/null @@ -1,11 +0,0 @@ -fileAppender->appendToFile($filename, $content); - } - - public function closeFile(string $filename): void - { - $this->fileAppender->closeFile($filename); - } -} diff --git a/src/Shared/Filesystem/FileCacheAppenderInterface.php b/src/Shared/Filesystem/FileCacheAppenderInterface.php deleted file mode 100644 index 8448e1f9..00000000 --- a/src/Shared/Filesystem/FileCacheAppenderInterface.php +++ /dev/null @@ -1,18 +0,0 @@ -fileExists($path)) { + return []; + } + + $content = []; + foreach ($this->fileReader->readFile($path) as $line) { + if ($line === '') { + continue; + } + + $content += $this->parseLine($line); + } + + return $content; + } + + /** + * @return array + */ + private function parseLine(string $line): array + { + $separatorPos = strpos($line, ','); + if ($separatorPos === false || $separatorPos === 0) { + return []; + } + + $className = substr($line, 0, $separatorPos) + |>trim(...); + + /** @var string $className */ + $className = pathinfo($className, flags: PATHINFO_BASENAME); + + $hash = substr($line, $separatorPos + 1) + |>trim(...); + + return [$className => $hash]; + } + + protected function fileExists(string $filename): bool + { + return file_exists($filename); + } +} diff --git a/src/Shared/Hash/HashFileReaderInterface.php b/src/Shared/Hash/HashFileReaderInterface.php new file mode 100644 index 00000000..5653e7de --- /dev/null +++ b/src/Shared/Hash/HashFileReaderInterface.php @@ -0,0 +1,15 @@ + + */ + public function readFile(string $path): array; +} diff --git a/src/Shared/Hash/HashFileWriter.php b/src/Shared/Hash/HashFileWriter.php new file mode 100644 index 00000000..7f258c9b --- /dev/null +++ b/src/Shared/Hash/HashFileWriter.php @@ -0,0 +1,36 @@ +renderContent($data); + $this->filesystem->dumpFile($filename, $content); + } + + /** + * @param \ArrayObject $data + * + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + private function renderContent(ArrayObject $data): string + { + $content = ''; + foreach ($data as $key => $value) { + $content .= $key . ',' . $value . PHP_EOL; + } + + return $content; + } +} diff --git a/src/Shared/Hash/HashFileWriterInterface.php b/src/Shared/Hash/HashFileWriterInterface.php new file mode 100644 index 00000000..26f81bb1 --- /dev/null +++ b/src/Shared/Hash/HashFileWriterInterface.php @@ -0,0 +1,17 @@ + $data + * + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + public function writeFile(string $filename, ArrayObject $data): void; +} diff --git a/src/Shared/Reader/FileCacheReader.php b/src/Shared/Reader/FileCacheReader.php deleted file mode 100644 index d7089c0f..00000000 --- a/src/Shared/Reader/FileCacheReader.php +++ /dev/null @@ -1,57 +0,0 @@ -fileExists($path)) { - return new ArrayObject(); - } - - $content = []; - foreach ($this->fileReader->readFile($path) as $line) { - if ($line === '') { - continue; - } - - $content = array_merge($content, $this->parseLine($line)); - } - - return new ArrayObject($content); - } - - /** - * @return array - */ - private function parseLine(string $line): array - { - /** @var array $parsedLine */ - $parsedLine = explode(',', $line); - $parsedLineCount = count($parsedLine); - - if ($parsedLineCount === 2) { - return [ - $parsedLine[0] => $parsedLine[1], - ]; - } - - throw new FileCacheReaderException('CSV file contains invalid line.'); - } - - protected function fileExists(string $filename): bool - { - return file_exists($filename); - } -} diff --git a/src/Shared/Reader/FileCacheReaderInterface.php b/src/Shared/Reader/FileCacheReaderInterface.php deleted file mode 100644 index b48caef4..00000000 --- a/src/Shared/Reader/FileCacheReaderInterface.php +++ /dev/null @@ -1,17 +0,0 @@ - - */ - public function readFile(string $path): ArrayObject; -} diff --git a/src/Shared/SharedFactoryTrait.php b/src/Shared/SharedFactoryTrait.php index 847c994d..b4c8883c 100644 --- a/src/Shared/SharedFactoryTrait.php +++ b/src/Shared/SharedFactoryTrait.php @@ -9,13 +9,13 @@ use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; use Picamator\TransferObject\Shared\Filesystem\FileAppender; use Picamator\TransferObject\Shared\Filesystem\FileAppenderInterface; -use Picamator\TransferObject\Shared\Filesystem\FileCacheAppender; -use Picamator\TransferObject\Shared\Filesystem\FileCacheAppenderInterface; use Picamator\TransferObject\Shared\Filesystem\FileReader; use Picamator\TransferObject\Shared\Filesystem\FileReaderInterface; +use Picamator\TransferObject\Shared\Hash\HashFileReader; +use Picamator\TransferObject\Shared\Hash\HashFileReaderInterface; +use Picamator\TransferObject\Shared\Hash\HashFileWriter; +use Picamator\TransferObject\Shared\Hash\HashFileWriterInterface; use Picamator\TransferObject\Shared\Initializer\LazyGhostInitializerTrait; -use Picamator\TransferObject\Shared\Reader\FileCacheReader; -use Picamator\TransferObject\Shared\Reader\FileCacheReaderInterface; use Picamator\TransferObject\Shared\Reader\FileReaderProgress; use Picamator\TransferObject\Shared\Reader\FileReaderProgressInterface; use Picamator\TransferObject\Shared\Reader\JsonReader; @@ -117,19 +117,19 @@ final protected function createEnvironmentReader(): EnvironmentReaderInterface ); } - final protected function createFileCacheReader(): FileCacheReaderInterface + final protected function createHashFileReader(): HashFileReaderInterface { return $this->getCached( - key: 'shared:FileCacheReader', - factory: fn(): FileCacheReaderInterface => new FileCacheReader($this->createFileReader()), + key: 'shared:HashFileReader', + factory: fn(): HashFileReaderInterface => new HashFileReader($this->createFileReader()), ); } - final protected function createFileCacheAppender(): FileCacheAppenderInterface + final protected function createHashFileWriter(): HashFileWriterInterface { return $this->getCached( - key: 'shared:FileCacheAppender', - factory: fn(): FileCacheAppenderInterface => new FileCacheAppender($this->createFileAppender()), + key: 'shared:HashFileWriter', + factory: fn(): HashFileWriterInterface => new HashFileWriter($this->createFilesystem()), ); } } diff --git a/src/TransferGenerator/Config/Config/Config.php b/src/TransferGenerator/Config/Config/Config.php index ffa6f08b..f697279c 100644 --- a/src/TransferGenerator/Config/Config/Config.php +++ b/src/TransferGenerator/Config/Config/Config.php @@ -37,4 +37,9 @@ public function getUuid(): string { return $this->configTransfer->uuid; } + + public function getHashFileName(): string + { + return $this->configTransfer->hashFileName; + } } diff --git a/src/TransferGenerator/Config/Config/ConfigInterface.php b/src/TransferGenerator/Config/Config/ConfigInterface.php index 19985b76..446c47bd 100644 --- a/src/TransferGenerator/Config/Config/ConfigInterface.php +++ b/src/TransferGenerator/Config/Config/ConfigInterface.php @@ -30,4 +30,9 @@ public function getRelativeDefinitionPath(): string; * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ public function getUuid(): string; + + /** + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + */ + public function getHashFileName(): string; } diff --git a/src/TransferGenerator/Config/Config/ConfigProxy.php b/src/TransferGenerator/Config/Config/ConfigProxy.php index 674e60c9..8029d17a 100644 --- a/src/TransferGenerator/Config/Config/ConfigProxy.php +++ b/src/TransferGenerator/Config/Config/ConfigProxy.php @@ -35,6 +35,11 @@ public function getUuid(): string return $this->getConfig()->getUuid(); } + public function getHashFileName(): string + { + return $this->getConfig()->getHashFileName(); + } + public static function loadConfig(ConfigInterface $config): void { self::$config = $config; diff --git a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php index 48c09aad..ac80bf80 100644 --- a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php +++ b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php @@ -18,6 +18,8 @@ enum ConfigKeyEnum: string public static function getDefaultConfig(): array { $defaultContent[ConfigContentTransfer::UUID_PROP] = ''; + $defaultContent[ConfigContentTransfer::HASH_FILE_NAME_PROP] = ''; + foreach (self::cases() as $keyEnum) { $defaultContent[$keyEnum->value] = ''; } diff --git a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php index 6b590cc1..d1a67801 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php @@ -11,6 +11,8 @@ { protected const string PLACEHOLDER = '${PROJECT_ROOT}'; + protected const string HASH_FILE_NAME = 'transfer-object.list.csv'; + public function __construct(private EnvironmentReaderInterface $environmentReader) { } @@ -18,6 +20,8 @@ public function __construct(private EnvironmentReaderInterface $environmentReade public function createContentTransfer(array $configData): ConfigContentTransfer { $configData[ConfigContentTransfer::UUID_PROP] = $this->getUuid(); + $configData[ConfigContentTransfer::HASH_FILE_NAME_PROP] = static::HASH_FILE_NAME; + $contentTransfer = new ConfigContentTransfer($configData); $this->parseContentPath($contentTransfer); diff --git a/src/TransferGenerator/Generator/Enum/FilesystemEnum.php b/src/TransferGenerator/Generator/Enum/FilesystemEnum.php deleted file mode 100644 index e7a7ffb8..00000000 --- a/src/TransferGenerator/Generator/Enum/FilesystemEnum.php +++ /dev/null @@ -1,12 +0,0 @@ -value; - - /** - * @var array> - */ - private array $cachePerConfiguration = []; - - public function __construct( - private readonly FileCacheReaderInterface $fileReader, - private readonly FileCacheAppenderInterface $fileAppender, - private readonly ConfigInterface $config, - ) { - } - - public function readFromCache(): ArrayObject - { - $uuid = $this->config->getUuid(); - $fromCache = $this->cachePerConfiguration[$uuid] ?? null; - if ($fromCache !== null) { - return $fromCache; - } - - // resetting cache - $this->cachePerConfiguration = []; - - $filePath = $this->getTransferPath(self::FILE_NAME); - $this->cachePerConfiguration[$uuid] = $this->fileReader->readFile($filePath); - - return $this->cachePerConfiguration[$uuid]; - } - - public function readFromTempCache(): ArrayObject - { - $filePath = $this->getTemporaryPath(self::FILE_NAME); - - return $this->fileReader->readFile($filePath); - } - - public function appendToTempCache(string $className, string $hash): void - { - $filePath = $this->getTemporaryPath(self::FILE_NAME); - $this->fileAppender->appendToFile($filePath, $className, $hash); - } - - public function closeTempCache(): void - { - $filePath = $this->getTemporaryPath(self::FILE_NAME); - $this->fileAppender->closeFile($filePath); - } -} diff --git a/src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php deleted file mode 100644 index d248cace..00000000 --- a/src/TransferGenerator/Generator/Filesystem/CacheFilesystemInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - public function readFromCache(): ArrayObject; - - /** - * @return \ArrayObject - */ - public function readFromTempCache(): ArrayObject; - - public function appendToTempCache(string $className, string $hash): void; - - public function closeTempCache(): void; -} diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php index 88abfd10..1c838ea2 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php @@ -4,25 +4,20 @@ namespace Picamator\TransferObject\TransferGenerator\Generator\Filesystem; +use ArrayObject; use Picamator\TransferObject\Dependency\Filesystem\FilesystemInterface; -use Picamator\TransferObject\Dependency\Finder\FinderInterface; use Picamator\TransferObject\Generated\TransferGeneratorContentTransfer; use Picamator\TransferObject\TransferGenerator\Config\Config\ConfigInterface; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException; -use Picamator\TransferObject\TransferGenerator\Generator\Enum\FilesystemEnum; readonly class GeneratorFilesystem implements GeneratorFilesystemInterface { use FilesystemTrait; - private const string TRANSFER_FILE_EXTENSION = FilesystemEnum::TRANSFER_FILE_EXTENSION->value; - private const string TRANSFER_FILE_NAME_PATTERN = FilesystemEnum::TRANSFER_FILE_NAME_PATTERN->value; - - private const string CACHE_FILE_NAME = FilesystemEnum::CACHE_FILE_NAME->value; + private const string TRANSFER_FILE_EXTENSION = '.php'; public function __construct( private FilesystemInterface $filesystem, - private FinderInterface $finder, private ConfigInterface $config, ) { } @@ -46,22 +41,10 @@ public function deleteTempDir(): void } } - public function rotateTempDir(array $deleteClassNames): void + public function writeTempFile(TransferGeneratorContentTransfer $contentTransfer): void { - if (count($deleteClassNames) !== 0) { - $this->deleteOldFiles($deleteClassNames); - } - - $this->copyTempFiles(); - $this->deleteTempDir(); - } - - public function writeFile(TransferGeneratorContentTransfer $contentTransfer): void - { - $filePath = $this->getTemporaryPath() - . DIRECTORY_SEPARATOR - . $contentTransfer->className - . self::TRANSFER_FILE_EXTENSION; + $fileName = $this->getFileName($contentTransfer->className); + $filePath = $this->getTemporaryPath($fileName); if ($this->filesystem->exists($filePath)) { throw new TransferGeneratorException( @@ -72,46 +55,73 @@ public function writeFile(TransferGeneratorContentTransfer $contentTransfer): vo $this->filesystem->dumpFile($filePath, $contentTransfer->content); } + public function rotateFiles(ArrayObject $toCopyClassNames, ArrayObject $toDeleteClassNames): void + { + if ($toCopyClassNames->count() > 0) { + $this->copyTempFiles($toCopyClassNames); + $this->deleteTempFiles($toCopyClassNames); + } + + if ($toDeleteClassNames->count() > 0) { + $this->deleteFiles($toDeleteClassNames); + } + } + /** - * @param array $deleteClassNames - * - * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\Dependency\Exception\FinderException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + * @param \ArrayObject $classNames */ - private function deleteOldFiles(array $deleteClassNames): void + private function deleteFiles(ArrayObject $classNames): void { - /** @var array $deleteFiles */ - $deleteFiles = array_map( - fn (string $className): string - => $this->config->getTransferPath() . DIRECTORY_SEPARATOR . $className . self::TRANSFER_FILE_EXTENSION, - $deleteClassNames, - ); - - $this->filesystem->remove($deleteFiles); + $fileNames = $this->getFileNames($classNames); + foreach ($fileNames as $fileName) { + $filePath = $this->getTransferPath($fileName); + $this->filesystem->remove($filePath); + } } /** - * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\Dependency\Exception\FinderException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + * @param \ArrayObject $classNames */ - private function copyTempFiles(): void + private function copyTempFiles(ArrayObject $classNames): void { - $finder = $this->finder->findFilesInDirectory( - filePattern: self::TRANSFER_FILE_NAME_PATTERN, - dirName: $this->getTemporaryPath(), - ); - - $destinationPath = $this->config->getTransferPath() . DIRECTORY_SEPARATOR; - foreach ($finder as $file) { - $targetFile = $destinationPath . $file->getFilename(); - $this->filesystem->copy($file->getRealPath(), $targetFile); + $fileNames = $this->getFileNames($classNames); + foreach ($fileNames as $fileName) { + $originalFile = $this->getTemporaryPath($fileName); + $targetFile = $this->getTransferPath($fileName); + + $this->filesystem->copy($originalFile, $targetFile); } + } - $this->filesystem->copy( - $this->getTemporaryPath(self::CACHE_FILE_NAME), - $this->getTransferPath(self::CACHE_FILE_NAME), - ); + /** + * @param \ArrayObject $classNames + */ + private function deleteTempFiles(ArrayObject $classNames): void + { + $fileNames = $this->getFileNames($classNames); + foreach ($fileNames as $fileName) { + $filePath = $this->getTemporaryPath($fileName); + $this->filesystem->remove($filePath); + } + } + + /** + * @param \ArrayObject $classNames + * + * @return array + */ + private function getFileNames(ArrayObject $classNames): array + { + $fileNames = []; + foreach ($classNames as $className) { + $fileNames[] = $this->getFileName($className); + } + + return $fileNames; + } + + private function getFileName(string $className): string + { + return $className . self::TRANSFER_FILE_EXTENSION; } } diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php index 9b5d54b7..7871ffc3 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php @@ -4,6 +4,7 @@ namespace Picamator\TransferObject\TransferGenerator\Generator\Filesystem; +use ArrayObject; use Picamator\TransferObject\Generated\TransferGeneratorContentTransfer; interface GeneratorFilesystemInterface @@ -19,17 +20,17 @@ public function createTempDir(): void; public function deleteTempDir(): void; /** - * @param array $deleteClassNames - * * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\Dependency\Exception\FinderException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException */ - public function rotateTempDir(array $deleteClassNames): void; + public function writeTempFile(TransferGeneratorContentTransfer $contentTransfer): void; /** + * @param \ArrayObject $toCopyClassNames + * @param \ArrayObject $toDeleteClassNames + * * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ - public function writeFile(TransferGeneratorContentTransfer $contentTransfer): void; + public function rotateFiles(ArrayObject $toCopyClassNames, ArrayObject $toDeleteClassNames): void; } diff --git a/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php b/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php new file mode 100644 index 00000000..03b4a652 --- /dev/null +++ b/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php @@ -0,0 +1,54 @@ +writeHashTmpFile($hashes); + $this->copyHashTmpFile(); + $this->deleteHashTmpFile(); + } + + private function copyHashTmpFile(): void + { + $fileName = $this->config->getHashFileName(); + + $originalFile = $this->getTemporaryPath($fileName); + $targetFile = $this->getTransferPath($fileName); + + $this->filesystem->copy($originalFile, $targetFile); + } + + private function deleteHashTmpFile(): void + { + $filePath = $this->getTemporaryPath($this->config->getHashFileName()); + $this->filesystem->remove($filePath); + } + + /** + * @param \ArrayObject $hashes + */ + private function writeHashTmpFile(ArrayObject $hashes): void + { + $filePath = $this->getTemporaryPath($this->config->getHashFileName()); + $this->fileWriter->writeFile($filePath, $hashes); + } +} diff --git a/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php new file mode 100644 index 00000000..7ef1f680 --- /dev/null +++ b/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php @@ -0,0 +1,17 @@ + $hashes + * + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + public function rotateHashFile(ArrayObject $hashes): void; +} diff --git a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php index b0ed49aa..3bd5797c 100644 --- a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php +++ b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php @@ -10,10 +10,10 @@ use Picamator\TransferObject\TransferGenerator\Config\Loader\ConfigLoaderInterface; use Picamator\TransferObject\TransferGenerator\Definition\DefinitionFactory; use Picamator\TransferObject\TransferGenerator\Definition\Reader\DefinitionReaderInterface; -use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystem; -use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystem; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\HashFilesystem; +use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\HashFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilder; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\BulkProcessCommand; @@ -26,8 +26,14 @@ use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\ProcessCommandInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\GeneratorProcessor; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\GeneratorProcessorInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Reader\TransferHashReader; +use Picamator\TransferObject\TransferGenerator\Generator\Reader\TransferHashReaderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Render\RenderFactory; use Picamator\TransferObject\TransferGenerator\Generator\Render\TemplateRenderInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotator; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferWriter; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferWriterInterface; class GeneratorFactory { @@ -47,8 +53,8 @@ protected function createPostProcessCommand(): PostProcessCommandInterface { return new PostProcessCommand( $this->createTransferGeneratorBuilder(), - $this->createCacheFilesystem(), $this->createGeneratorFilesystem(), + $this->createTransferRotator(), ); } @@ -65,8 +71,7 @@ protected function createProcessCommand(): ProcessCommandInterface return new ProcessCommand( $this->createTransferGeneratorBuilder(), $this->createTemplateRender(), - $this->createCacheFilesystem(), - $this->createGeneratorFilesystem(), + $this->createTransferWriter(), ); } @@ -87,7 +92,6 @@ className: GeneratorFilesystem::class, initializer: function (GeneratorFilesystem $ghost): void { $ghost->__construct( $this->createFilesystem(), - $this->createFinder(), $this->getConfig(), ); } @@ -96,6 +100,18 @@ className: GeneratorFilesystem::class, return $generatorFilesystem; } + protected function createTransferRotator(): TransferRotatorInterface + { + return $this->getCached( + key: 'transfer-generator:TransferRotator', + factory: fn(): TransferRotatorInterface => new TransferRotator( + $this->createTransferHashReader(), + $this->createHashFilesystem(), + $this->createGeneratorFilesystem(), + ), + ); + } + protected function createTransferGeneratorBuilder(): TransferGeneratorBuilderInterface { return $this->getCached( @@ -104,13 +120,35 @@ protected function createTransferGeneratorBuilder(): TransferGeneratorBuilderInt ); } - protected function createCacheFilesystem(): CacheFilesystemInterface + protected function createHashFilesystem(): HashFilesystemInterface + { + return $this->getCached( + key: 'transfer-generator:HashFilesystem', + factory: fn() => new HashFilesystem( + $this->createFilesystem(), + $this->createHashFileWriter(), + $this->getConfig(), + ), + ); + } + + protected function createTransferWriter(): TransferWriterInterface + { + return $this->getCached( + key: 'transfer-generator:TransferWriter', + factory: fn(): TransferWriterInterface => new TransferWriter( + $this->createTransferHashReader(), + $this->createGeneratorFilesystem(), + ), + ); + } + + protected function createTransferHashReader(): TransferHashReaderInterface { return $this->getCached( - key: 'transfer-generator:CacheFilesystem', - factory: fn() => new CacheFilesystem( - $this->createFileCacheReader(), - $this->createFileCacheAppender(), + key: 'transfer-generator:TransferHashReader', + factory: fn(): TransferHashReaderInterface => new TransferHashReader( + $this->createHashFileReader(), $this->getConfig(), ), ); diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php index 9aa490c2..c3ed993a 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php @@ -8,16 +8,16 @@ use Picamator\TransferObject\Dependency\Exception\FinderException; use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException; -use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; readonly class PostProcessCommand implements PostProcessCommandInterface { public function __construct( private TransferGeneratorBuilderInterface $builder, - private CacheFilesystemInterface $cacheFilesystem, private GeneratorFilesystemInterface $filesystem, + private TransferRotatorInterface $transferRotator, ) { } @@ -33,10 +33,8 @@ public function postProcess(bool $isSuccessful): TransferGeneratorTransfer private function postProcessSuccess(): TransferGeneratorTransfer { try { - $this->cacheFilesystem->closeTempCache(); - $deleteClassNames = $this->getDeleteClassNames(); - - $this->filesystem->rotateTempDir($deleteClassNames); + $this->transferRotator->rotateFiles(); + $this->filesystem->deleteTempDir(); } catch (FilesystemException | FinderException | TransferGeneratorConfigNotFoundException $e) { return $this->builder->createErrorGeneratorTransfer($e->getMessage()); } @@ -44,31 +42,9 @@ private function postProcessSuccess(): TransferGeneratorTransfer return $this->builder->createSuccessGeneratorTransfer(); } - /** - * @return array - */ - private function getDeleteClassNames(): array - { - $tempCache = $this->cacheFilesystem->readFromTempCache(); - $cache = $this->cacheFilesystem->readFromCache(); - - $deletedClasses = []; - // phpcs:disable SlevomatCodingStandard.Variables.UnusedVariable - foreach ($cache as $className => $hash) { - if (isset($tempCache[$className])) { - continue; - } - - $deletedClasses[] = $className; - } - - return $deletedClasses; - } - private function postProcessError(): TransferGeneratorTransfer { try { - $this->cacheFilesystem->closeTempCache(); $this->filesystem->deleteTempDir(); } catch (FilesystemException $e) { return $this->builder->createErrorGeneratorTransfer($e->getMessage()); diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php index 6fde8b77..a201c881 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php @@ -9,18 +9,16 @@ use Picamator\TransferObject\Generated\TransferGeneratorContentTransfer; use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException; -use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; -use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Render\TemplateRenderInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferWriterInterface; readonly class ProcessCommand implements ProcessCommandInterface { public function __construct( private TransferGeneratorBuilderInterface $builder, private TemplateRenderInterface $render, - private CacheFilesystemInterface $cacheFilesystem, - private GeneratorFilesystemInterface $filesystem, + private TransferWriterInterface $transferWriter, ) { } @@ -47,14 +45,7 @@ private function generateTransfer(DefinitionTransfer $definitionTransfer): Trans private function saveContent(TransferGeneratorContentTransfer $contentTransfer): void { - $this->cacheFilesystem->appendToTempCache($contentTransfer->className, $contentTransfer->hash); - - $cachedContentHash = $this->cacheFilesystem->readFromCache()[$contentTransfer->className] ?? null; - if ($cachedContentHash !== null && hash_equals($cachedContentHash, $contentTransfer->hash)) { - return; - } - - $this->filesystem->writeFile($contentTransfer); + $this->transferWriter->writeFile($contentTransfer); } private function renderContent(DefinitionTransfer $definitionTransfer): TransferGeneratorContentTransfer diff --git a/src/TransferGenerator/Generator/Reader/TransferHashReader.php b/src/TransferGenerator/Generator/Reader/TransferHashReader.php new file mode 100644 index 00000000..763855aa --- /dev/null +++ b/src/TransferGenerator/Generator/Reader/TransferHashReader.php @@ -0,0 +1,45 @@ +hashTransfer) || $this->hashTransfer->configUuid !== $this->config->getUuid()) { + $this->reloadHashFileCache(); + } + + return $this->hashTransfer; + } + + private function reloadHashFileCache(): void + { + $filePath = $this->getFilePath(); + $hashes = $this->fileReader->readFile($filePath); + + $this->hashTransfer = new TransferHashTransfer([ + TransferHashTransfer::HASHES_PROP => $hashes, + TransferHashTransfer::CONFIG_UUID_PROP => $this->config->getUuid(), + ]); + } + + private function getFilePath(): string + { + return $this->config->getTransferPath() . DIRECTORY_SEPARATOR . $this->config->getHashFileName(); + } +} diff --git a/src/TransferGenerator/Generator/Reader/TransferHashReaderInterface.php b/src/TransferGenerator/Generator/Reader/TransferHashReaderInterface.php new file mode 100644 index 00000000..cfcff05d --- /dev/null +++ b/src/TransferGenerator/Generator/Reader/TransferHashReaderInterface.php @@ -0,0 +1,12 @@ +hashReader->readHashFile(); + $toDeleteClassNames = $this->getToDeleteClassNames($hashTransfer); + + if ($toDeleteClassNames->count() === 0 && $hashTransfer->toCopyClassNames->count() === 0) { + $this->filesystem->deleteTempDir(); + } + + $this->filesystem->rotateFiles($hashTransfer->toCopyClassNames, $toDeleteClassNames); + $this->hashFilesystem->rotateHashFile($hashTransfer->actualHashes); + } + + /** + * @return \ArrayObject + */ + private function getToDeleteClassNames(TransferHashTransfer $hashTransfer): ArrayObject + { + /** @var \ArrayObject $classesNames */ + $classesNames = new ArrayObject(); + // phpcs:disable SlevomatCodingStandard.Variables.UnusedVariable + foreach ($hashTransfer->hashes as $className => $hash) { + if (isset($hashTransfer->actualHashes[$className])) { + continue; + } + + $classesNames[] = $className; + } + + return $classesNames; + } +} diff --git a/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php b/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php new file mode 100644 index 00000000..485bca3d --- /dev/null +++ b/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php @@ -0,0 +1,10 @@ +hashReader->readHashFile(); + $hashTransfer->actualHashes[$contentTransfer->className] = $contentTransfer->hash; + + if (!$this->isFileChanged($contentTransfer, $hashTransfer)) { + return; + } + + $hashTransfer->toCopyClassNames[] = $contentTransfer->className; + + $this->filesystem->writeTempFile($contentTransfer); + } + + private function isFileChanged( + TransferGeneratorContentTransfer $contentTransfer, + TransferHashTransfer $hashTransfer, + ): bool { + $previousHash = $hashTransfer->hashes[$contentTransfer->className] ?? null; + + return $previousHash === null || !hash_equals($previousHash, $contentTransfer->hash); + } +} diff --git a/src/TransferGenerator/Generator/Writer/TransferWriterInterface.php b/src/TransferGenerator/Generator/Writer/TransferWriterInterface.php new file mode 100644 index 00000000..f0eba7cd --- /dev/null +++ b/src/TransferGenerator/Generator/Writer/TransferWriterInterface.php @@ -0,0 +1,12 @@ +filesystemStub = $this->createStub(FilesystemInterface::class); - $finderStub = $this->createStub(FinderInterface::class); - $this->configStub = $this->createStub(ConfigInterface::class); $this->generatorFilesystem = new GeneratorFilesystem( $this->filesystemStub, - $finderStub, $this->configStub, ); } @@ -65,6 +61,6 @@ public function testDuplicateFileWriteShouldThrowException(): void $this->expectException(TransferGeneratorException::class); // Act - $this->generatorFilesystem->writeFile($contentTransfer); + $this->generatorFilesystem->writeTempFile($contentTransfer); } } diff --git a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php index 674780c1..8d055502 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php @@ -4,18 +4,17 @@ namespace Picamator\Tests\Unit\TransferObject\TransferGenerator\Generator\Generator\Processor\Command; -use ArrayObject; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Picamator\TransferObject\Dependency\Exception\FilesystemException; -use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\CacheFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilder; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PostProcessCommand; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PostProcessCommandInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; #[Group('transfer-generator')] final class PostProcessCommandTest extends TestCase @@ -24,19 +23,19 @@ final class PostProcessCommandTest extends TestCase private GeneratorFilesystemInterface&Stub $filesystemStub; - private CacheFilesystemInterface&MockObject $cacheFilesystemMock; + private TransferRotatorInterface&MockObject $transferRotatorMock; protected function setUp(): void { $builder = new TransferGeneratorBuilder(); $this->filesystemStub = $this->createStub(GeneratorFilesystemInterface::class); - $this->cacheFilesystemMock = $this->createMock(CacheFilesystemInterface::class); + $this->transferRotatorMock = $this->createMock(TransferRotatorInterface::class); $this->command = new PostProcessCommand( $builder, - $this->cacheFilesystemMock, $this->filesystemStub, + $this->transferRotatorMock, ); } @@ -44,32 +43,14 @@ protected function setUp(): void public function testFilesystemExceptionShouldBeHandledOnPostProcessSuccess(): void { // Arrange - $tempCache = new ArrayObject([ - 'className' => 'some-hash', - ]); - - $cache = new ArrayObject([ - 'className' => 'some-hash', - 'classNameToDelete' => 'some-hash', - ]); - - $this->filesystemStub - ->method('rotateTempDir') + ->method('deleteTempDir') ->willThrowException(new FilesystemException()) ->seal(); // Expect - $this->cacheFilesystemMock->expects($this->once()) - ->method('closeTempCache'); - - $this->cacheFilesystemMock->expects($this->once()) - ->method('readFromTempCache') - ->willReturn($tempCache); - - $this->cacheFilesystemMock->expects($this->once()) - ->method('readFromCache') - ->willReturn($cache) + $this->transferRotatorMock->expects($this->once()) + ->method('rotateFiles') ->seal(); // Act @@ -89,8 +70,8 @@ public function testFilesystemExceptionShouldBeHandledOnPostProcessError(): void ->seal(); // Expect - $this->cacheFilesystemMock->expects($this->once()) - ->method('closeTempCache') + $this->transferRotatorMock->expects($this->never()) + ->method('rotateFiles') ->seal(); // Act From a31a9aa4a0c529e5700c4581a01410bed8108dad Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Thu, 26 Feb 2026 22:56:50 +0100 Subject: [PATCH 03/56] Optimized getToDeleteClassNames() --- .../Generator/Writer/TransferRotator.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/TransferGenerator/Generator/Writer/TransferRotator.php b/src/TransferGenerator/Generator/Writer/TransferRotator.php index 26d66e8b..177202ca 100644 --- a/src/TransferGenerator/Generator/Writer/TransferRotator.php +++ b/src/TransferGenerator/Generator/Writer/TransferRotator.php @@ -39,13 +39,15 @@ private function getToDeleteClassNames(TransferHashTransfer $hashTransfer): Arra { /** @var \ArrayObject $classesNames */ $classesNames = new ArrayObject(); - // phpcs:disable SlevomatCodingStandard.Variables.UnusedVariable - foreach ($hashTransfer->hashes as $className => $hash) { - if (isset($hashTransfer->actualHashes[$className])) { - continue; - } + $hashesIterator = $hashTransfer->hashes->getIterator(); + + while ($hashesIterator->valid()) { + $className = $hashesIterator->key(); + $hashesIterator->next(); - $classesNames[] = $className; + if (!isset($hashTransfer->actualHashes[$className])) { + $classesNames[] = $className; + } } return $classesNames; From 3b58e979aa31e5d51cf7e7b8143d0d9ca3beed58 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Thu, 26 Feb 2026 23:53:04 +0100 Subject: [PATCH 04/56] Replaced copy-delete file to rename, added try-finally apporach to clear up temporary directory --- .../Filesystem/FilesystemBridge.php | 19 +++++++++++++++++++ .../Filesystem/FilesystemInterface.php | 5 +++++ .../Filesystem/GeneratorFilesystem.php | 19 +++---------------- .../Generator/Filesystem/HashFilesystem.php | 13 +++---------- .../Processor/Command/PostProcessCommand.php | 11 ++++++----- .../Writer/TransferRotatorInterface.php | 4 ++++ .../Command/PostProcessCommandTest.php | 2 +- 7 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/Dependency/Filesystem/FilesystemBridge.php b/src/Dependency/Filesystem/FilesystemBridge.php index 549e61ba..4cbced9b 100644 --- a/src/Dependency/Filesystem/FilesystemBridge.php +++ b/src/Dependency/Filesystem/FilesystemBridge.php @@ -34,6 +34,25 @@ public function copy(string $originFile, string $targetFile): void // @codeCoverageIgnoreEnd } + public function rename(string $origin, string $target, bool $overwrite = false): void + { + try { + $this->filesystem->rename($origin, $target, $overwrite); + // @codeCoverageIgnoreStart + } catch (Throwable $e) { + throw new FilesystemException( + sprintf( + 'Failed to rename "%s" to "%s". Error: "%s".', + $origin, + $target, + $e->getMessage(), + ), + previous: $e, + ); + } + // @codeCoverageIgnoreEnd + } + public function mkdir(string $dir): void { try { diff --git a/src/Dependency/Filesystem/FilesystemInterface.php b/src/Dependency/Filesystem/FilesystemInterface.php index 8a19a4d9..f51a939a 100644 --- a/src/Dependency/Filesystem/FilesystemInterface.php +++ b/src/Dependency/Filesystem/FilesystemInterface.php @@ -11,6 +11,11 @@ interface FilesystemInterface */ public function copy(string $originFile, string $targetFile): void; + /** + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + public function rename(string $origin, string $target, bool $overwrite = false): void; + /** * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException */ diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php index 1c838ea2..b31c77cb 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php @@ -58,8 +58,7 @@ public function writeTempFile(TransferGeneratorContentTransfer $contentTransfer) public function rotateFiles(ArrayObject $toCopyClassNames, ArrayObject $toDeleteClassNames): void { if ($toCopyClassNames->count() > 0) { - $this->copyTempFiles($toCopyClassNames); - $this->deleteTempFiles($toCopyClassNames); + $this->renameTempFiles($toCopyClassNames); } if ($toDeleteClassNames->count() > 0) { @@ -82,26 +81,14 @@ private function deleteFiles(ArrayObject $classNames): void /** * @param \ArrayObject $classNames */ - private function copyTempFiles(ArrayObject $classNames): void + private function renameTempFiles(ArrayObject $classNames): void { $fileNames = $this->getFileNames($classNames); foreach ($fileNames as $fileName) { $originalFile = $this->getTemporaryPath($fileName); $targetFile = $this->getTransferPath($fileName); - $this->filesystem->copy($originalFile, $targetFile); - } - } - - /** - * @param \ArrayObject $classNames - */ - private function deleteTempFiles(ArrayObject $classNames): void - { - $fileNames = $this->getFileNames($classNames); - foreach ($fileNames as $fileName) { - $filePath = $this->getTemporaryPath($fileName); - $this->filesystem->remove($filePath); + $this->filesystem->rename($originalFile, $targetFile, overwrite: true); } } diff --git a/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php b/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php index 03b4a652..0ab525f0 100644 --- a/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php @@ -23,24 +23,17 @@ public function __construct( public function rotateHashFile(ArrayObject $hashes): void { $this->writeHashTmpFile($hashes); - $this->copyHashTmpFile(); - $this->deleteHashTmpFile(); + $this->renameHashTmpFile(); } - private function copyHashTmpFile(): void + private function renameHashTmpFile(): void { $fileName = $this->config->getHashFileName(); $originalFile = $this->getTemporaryPath($fileName); $targetFile = $this->getTransferPath($fileName); - $this->filesystem->copy($originalFile, $targetFile); - } - - private function deleteHashTmpFile(): void - { - $filePath = $this->getTemporaryPath($this->config->getHashFileName()); - $this->filesystem->remove($filePath); + $this->filesystem->rename($originalFile, $targetFile, overwrite: true); } /** diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php index c3ed993a..b8d6fa37 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php @@ -5,12 +5,11 @@ namespace Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command; use Picamator\TransferObject\Dependency\Exception\FilesystemException; -use Picamator\TransferObject\Dependency\Exception\FinderException; use Picamator\TransferObject\Generated\TransferGeneratorTransfer; -use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; +use Throwable; readonly class PostProcessCommand implements PostProcessCommandInterface { @@ -34,12 +33,14 @@ private function postProcessSuccess(): TransferGeneratorTransfer { try { $this->transferRotator->rotateFiles(); + $generatorTransfer = $this->builder->createSuccessGeneratorTransfer(); + } catch (Throwable $e) { + $generatorTransfer = $this->builder->createErrorGeneratorTransfer($e->getMessage()); + } finally { $this->filesystem->deleteTempDir(); - } catch (FilesystemException | FinderException | TransferGeneratorConfigNotFoundException $e) { - return $this->builder->createErrorGeneratorTransfer($e->getMessage()); } - return $this->builder->createSuccessGeneratorTransfer(); + return $generatorTransfer; } private function postProcessError(): TransferGeneratorTransfer diff --git a/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php b/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php index 485bca3d..e150e0e9 100644 --- a/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php +++ b/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php @@ -6,5 +6,9 @@ interface TransferRotatorInterface { + /** + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + */ public function rotateFiles(): void; } diff --git a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php index 8d055502..3869804c 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php @@ -45,12 +45,12 @@ public function testFilesystemExceptionShouldBeHandledOnPostProcessSuccess(): vo // Arrange $this->filesystemStub ->method('deleteTempDir') - ->willThrowException(new FilesystemException()) ->seal(); // Expect $this->transferRotatorMock->expects($this->once()) ->method('rotateFiles') + ->willThrowException(new FilesystemException()) ->seal(); // Act From 719607a6788b4a73177dbb4cea142e1e77983e9e Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 17:12:35 +0100 Subject: [PATCH 05/56] Optimized hash reader, changed argument order on hash_equals --- src/Shared/Hash/HashFileReader.php | 14 ++------------ .../Generator/Writer/TransferRotator.php | 8 ++++++-- .../Generator/Writer/TransferWriter.php | 3 ++- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Shared/Hash/HashFileReader.php b/src/Shared/Hash/HashFileReader.php index 00323b37..d6e2d27d 100644 --- a/src/Shared/Hash/HashFileReader.php +++ b/src/Shared/Hash/HashFileReader.php @@ -20,10 +20,6 @@ public function readFile(string $path): array $content = []; foreach ($this->fileReader->readFile($path) as $line) { - if ($line === '') { - continue; - } - $content += $this->parseLine($line); } @@ -40,14 +36,8 @@ private function parseLine(string $line): array return []; } - $className = substr($line, 0, $separatorPos) - |>trim(...); - - /** @var string $className */ - $className = pathinfo($className, flags: PATHINFO_BASENAME); - - $hash = substr($line, $separatorPos + 1) - |>trim(...); + $className = substr($line, 0, $separatorPos); + $hash = substr($line, $separatorPos + 1); return [$className => $hash]; } diff --git a/src/TransferGenerator/Generator/Writer/TransferRotator.php b/src/TransferGenerator/Generator/Writer/TransferRotator.php index 177202ca..03037036 100644 --- a/src/TransferGenerator/Generator/Writer/TransferRotator.php +++ b/src/TransferGenerator/Generator/Writer/TransferRotator.php @@ -45,9 +45,13 @@ private function getToDeleteClassNames(TransferHashTransfer $hashTransfer): Arra $className = $hashesIterator->key(); $hashesIterator->next(); - if (!isset($hashTransfer->actualHashes[$className])) { - $classesNames[] = $className; + if (isset($hashTransfer->actualHashes[$className])) { + continue; } + + /** @var string $className */ + $className = pathinfo($className, flags: PATHINFO_BASENAME); + $classesNames[] = $className; } return $classesNames; diff --git a/src/TransferGenerator/Generator/Writer/TransferWriter.php b/src/TransferGenerator/Generator/Writer/TransferWriter.php index bbd26f44..6ea42b12 100644 --- a/src/TransferGenerator/Generator/Writer/TransferWriter.php +++ b/src/TransferGenerator/Generator/Writer/TransferWriter.php @@ -37,6 +37,7 @@ private function isFileChanged( ): bool { $previousHash = $hashTransfer->hashes[$contentTransfer->className] ?? null; - return $previousHash === null || !hash_equals($previousHash, $contentTransfer->hash); + return $previousHash === null + || !hash_equals(known_string: $contentTransfer->hash, user_string: $previousHash); } } From 596f558badd40d476dfb31f75195dd60f7c2f384 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 17:48:49 +0100 Subject: [PATCH 06/56] Refactored hash reader and writer, covered by unit tests --- .../transfer-object.list.csv | 2 +- .../transfer-object.list.csv | 2 +- .../transfer-object.list.csv | 2 +- src/Shared/Hash/HashFileWriter.php | 7 +- .../Success/transfer-object.list.csv | 2 +- .../Destatis/transfer-object.list.csv | 2 +- .../Frankfurter/transfer-object.list.csv | 2 +- .../transfer-object.list.csv | 2 +- .../NasaNeo/transfer-object.list.csv | 2 +- .../OpenWeather/transfer-object.list.csv | 2 +- .../Tagesschau/transfer-object.list.csv | 2 +- .../Generated/Wero/transfer-object.list.csv | 2 +- .../Generated/BcMath/transfer-object.list.csv | 2 +- .../Generated/transfer-object.list.csv | 2 +- .../Success/transfer-object.list.csv | 2 +- tests/unit/Shared/Hash/HashFileReaderTest.php | 97 +++++++++++++++++++ tests/unit/Shared/Hash/HashFileWriterTest.php | 51 ++++++++++ 17 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 tests/unit/Shared/Hash/HashFileReaderTest.php create mode 100644 tests/unit/Shared/Hash/HashFileWriterTest.php diff --git a/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv b/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv index d9a73bef..7a663fe2 100644 --- a/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv +++ b/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv @@ -1 +1 @@ -AdvancedCustomerTransfer,e95f378cb17b10a5f5bacc253d684f2e406526cb9179da626f2ae589770b7351 +AdvancedCustomerTransfer,e95f378cb17b10a5f5bacc253d684f2e406526cb9179da626f2ae589770b7351 \ No newline at end of file diff --git a/examples/Generated/DefinitionGenerator/transfer-object.list.csv b/examples/Generated/DefinitionGenerator/transfer-object.list.csv index 3271c31b..461b2e0a 100644 --- a/examples/Generated/DefinitionGenerator/transfer-object.list.csv +++ b/examples/Generated/DefinitionGenerator/transfer-object.list.csv @@ -5,4 +5,4 @@ LabelsTransfer,d4b058fdeb18278361c47cb129e9f0d194dce6be97d8a10672dd987f7b026091 AvailabilitiesTransfer,18871357c1130789f629ec1e8d836042f5c3f02d88c90c997a25a743253bfc64 MeasurementUnitTransfer,4da9dffe333a556436bb6dfd4c94a01e19ebe00c935e5773a9b32011a72a61a9 PaletteTransfer,080031bc3844e2505f97fba584a2652e5f9719e2c730e00ebacffb0b17673ddc -BoxTransfer,119ddf5e9e460935f949eb86d047e91142b6e5b58c3862e4e6ede2f5cf7f61d6 +BoxTransfer,119ddf5e9e460935f949eb86d047e91142b6e5b58c3862e4e6ede2f5cf7f61d6 \ No newline at end of file diff --git a/examples/Generated/TransferGenerator/transfer-object.list.csv b/examples/Generated/TransferGenerator/transfer-object.list.csv index a33f552a..86fb5ac4 100644 --- a/examples/Generated/TransferGenerator/transfer-object.list.csv +++ b/examples/Generated/TransferGenerator/transfer-object.list.csv @@ -1,4 +1,4 @@ CredentialsTransfer,4c9e3a5af952bb41f1d90f55262d56f9eebb5fe7a296fd8b9b00f74269bb3fa5 CustomerTransfer,e7fa9a90f41ef578b42d4f8c42d07202f62edb7da389adf72c523c798b00252c AgentTransfer,88d58cf9383287603ea138ae39629eda250f33209381fa8232597456922b7389 -MerchantTransfer,715ed4d116f7ad2d92398aed0baf2031b4ee391663df9ba7a2e1bd9350f0b4e4 +MerchantTransfer,715ed4d116f7ad2d92398aed0baf2031b4ee391663df9ba7a2e1bd9350f0b4e4 \ No newline at end of file diff --git a/src/Shared/Hash/HashFileWriter.php b/src/Shared/Hash/HashFileWriter.php index 7f258c9b..69adae7b 100644 --- a/src/Shared/Hash/HashFileWriter.php +++ b/src/Shared/Hash/HashFileWriter.php @@ -28,9 +28,12 @@ private function renderContent(ArrayObject $data): string { $content = ''; foreach ($data as $key => $value) { - $content .= $key . ',' . $value . PHP_EOL; + $content .= <<filerReaderMock = $this->createMock(FileReaderInterface::class); + + $this->hashReaderMock = $this->getMockBuilder(HashFileReader::class) + ->onlyMethods(['fileExists']) + ->setConstructorArgs([ + $this->filerReaderMock, + ]) + ->getMock(); + } + + #[TestDox('File does not exist should return empty array')] + public function testFileDoesNotExistShouldReturnEmptyArray(): void + { + // Arrange + $path = 'some-path/test.txt'; + + // Expect + $this->hashReaderMock->expects($this->once()) + ->method('fileExists') + ->with($path) + ->willReturn(false) + ->seal(); + + $this->filerReaderMock->expects($this->never()) + ->method('readFile') + ->seal(); + + // Act + $actual = $this->hashReaderMock->readFile($path); + + // Assert + $this->assertSame([], $actual); + } + + /** + * @param array $expected + */ + #[TestDox('Hash file line "$line" is read as "$expected"')] + #[TestWith(['', []])] + #[TestWith(['test', []])] + #[TestWith([',', []])] + #[TestWith([', ', []])] + #[TestWith([',test', []])] + #[TestWith(['CustomerTransfer,hash-string', ['CustomerTransfer' => 'hash-string']])] + #[TestWith(['CustomerTransfer,hash-string,some-text', ['CustomerTransfer' => 'hash-string,some-text']])] + public function testReadFile(string $line, array $expected): void + { + // Arrange + $path = 'some-path/test.txt'; + + // Expect + $this->hashReaderMock->expects($this->once()) + ->method('fileExists') + ->with($path) + ->willReturn(true) + ->seal(); + + $this->filerReaderMock->expects($this->once()) + ->method('readFile') + ->with($path) + ->willReturnCallback(function () use ($line): Generator { + yield $line; + }) + ->seal(); + + // Act + $actual = $this->hashReaderMock->readFile($path); + + // Assert + $this->assertSame($expected, $actual); + } +} diff --git a/tests/unit/Shared/Hash/HashFileWriterTest.php b/tests/unit/Shared/Hash/HashFileWriterTest.php new file mode 100644 index 00000000..baf7ba6c --- /dev/null +++ b/tests/unit/Shared/Hash/HashFileWriterTest.php @@ -0,0 +1,51 @@ +filesystemMock = $this->createMock(FilesystemInterface::class); + + $this->writer = new HashFileWriter($this->filesystemMock); + } + + #[TestDox('Write hash file data')] + public function testWriteFile(): void + { + // Arrange + $path = '/some/path'; + $data = new ArrayObject([ + 'CustomerTransfer' => 'some-hash', + ]); + + $expected = 'CustomerTransfer,some-hash'; + + // Expect + $this->filesystemMock + ->expects($this->once()) + ->method('dumpFile') + ->with($path, $expected) + ->seal(); + + // Act + $this->writer->writeFile($path, $data); + } +} From 4e6ecafef91a25904f193f76533ae38e6a6bf878 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 19:03:16 +0100 Subject: [PATCH 07/56] Specified returned command error --- src/Command/TransferGeneratorBulkCommand.php | 2 +- src/Command/TransferGeneratorCommand.php | 2 +- tests/integration/Command/TransferGeneratorBulkCommandTest.php | 2 +- tests/integration/Command/TransferGeneratorCommandTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Command/TransferGeneratorBulkCommand.php b/src/Command/TransferGeneratorBulkCommand.php index c958d922..0cb6cb2d 100644 --- a/src/Command/TransferGeneratorBulkCommand.php +++ b/src/Command/TransferGeneratorBulkCommand.php @@ -68,7 +68,7 @@ public function __invoke( if ($configListPath === '') { $io->error(self::ERROR_MISSED_OPTION_BULK_MESSAGE); - return Command::FAILURE; + return Command::INVALID; } $io->writeln(sprintf(self::CONFIGURATION_LIST_MESSAGE_TEMPLATE, $configListPath)); diff --git a/src/Command/TransferGeneratorCommand.php b/src/Command/TransferGeneratorCommand.php index 60877576..0380aaaa 100644 --- a/src/Command/TransferGeneratorCommand.php +++ b/src/Command/TransferGeneratorCommand.php @@ -79,7 +79,7 @@ public function __invoke( if ($configPath === '') { $io->error(self::ERROR_MISSED_OPTION_CONFIGURATION_MESSAGE); - return Command::FAILURE; + return Command::INVALID; } $io->writeln(sprintf(self::CONFIGURATION_MESSAGE_TEMPLATE, $configPath)); diff --git a/tests/integration/Command/TransferGeneratorBulkCommandTest.php b/tests/integration/Command/TransferGeneratorBulkCommandTest.php index 547e8a47..24c76dda 100644 --- a/tests/integration/Command/TransferGeneratorBulkCommandTest.php +++ b/tests/integration/Command/TransferGeneratorBulkCommandTest.php @@ -40,7 +40,7 @@ public function testRunCommandWithoutConfigurationShouldShowErrorMessage(): void $output = $this->commandTester->getDisplay(); // Assert - $this->assertSame(1, $this->commandTester->getStatusCode()); + $this->assertSame(2, $this->commandTester->getStatusCode()); $this->assertStringContainsString('The required -b option is missing.', $output); } diff --git a/tests/integration/Command/TransferGeneratorCommandTest.php b/tests/integration/Command/TransferGeneratorCommandTest.php index ab21f75c..379b4678 100644 --- a/tests/integration/Command/TransferGeneratorCommandTest.php +++ b/tests/integration/Command/TransferGeneratorCommandTest.php @@ -37,7 +37,7 @@ public function testRunCommandWithoutConfigurationShouldShowErrorMessage(): void $output = $this->commandTester->getDisplay(); // Assert - $this->assertSame(1, $this->commandTester->getStatusCode()); + $this->assertSame(2, $this->commandTester->getStatusCode()); $this->assertStringContainsString('The required -c option is missing.', $output); } From 9316a46f71db6e02fa7f05b85f908d8656f55ba2 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 20:45:33 +0100 Subject: [PATCH 08/56] Added environment variable to invalidate transfer cache --- .../transfer-generator.transfer.yml | 4 ++++ docker-compose.yml | 1 + src/Generated/ConfigContentTransfer.php | 22 ++++++++++++++----- src/Generated/transfer-object.list.csv | 4 ++-- .../Environment/Enum/EnvironmentEnum.php | 8 ++++++- src/Shared/Environment/EnvironmentReader.php | 9 ++++++++ .../EnvironmentReaderInterface.php | 2 ++ .../Config/Config/Config.php | 5 +++++ .../Config/Config/ConfigInterface.php | 5 +++++ .../Config/Config/ConfigProxy.php | 5 +++++ .../Config/Enum/ConfigKeyEnum.php | 3 ++- .../Parser/Builder/ConfigContentBuilder.php | 3 ++- .../Builder/ConfigContentBuilderInterface.php | 2 +- .../Parser/Filter/ConfigNormalizerTrait.php | 2 +- .../Generator/Reader/TransferHashReader.php | 5 +++-- .../TransferGeneratorBulkCommandTest.php | 8 +++++++ 16 files changed, 74 insertions(+), 14 deletions(-) diff --git a/config/definition/transfer-generator.transfer.yml b/config/definition/transfer-generator.transfer.yml index 9c27f52a..308563a1 100644 --- a/config/definition/transfer-generator.transfer.yml +++ b/config/definition/transfer-generator.transfer.yml @@ -28,6 +28,10 @@ ConfigContent: type: string required: protected: + isCacheEnabled: + type: bool + required: + protected: Definition: fileName: diff --git a/docker-compose.yml b/docker-compose.yml index e9a244cb..79b8e65a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: environment: XDEBUG_MODE: ${XDEBUG_MODE:-off} PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT: ${PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT:-/home/transfer/transfer-object} + PICAMATOR_TRANSFER_OBJECT_IS_CACHE_ENABLED: ${PICAMATOR_TRANSFER_OBJECT_IS_CACHE_ENABLED:-true} build: context: docker/php args: diff --git a/src/Generated/ConfigContentTransfer.php b/src/Generated/ConfigContentTransfer.php index 4cd8c18e..cbcd2bb3 100644 --- a/src/Generated/ConfigContentTransfer.php +++ b/src/Generated/ConfigContentTransfer.php @@ -17,11 +17,12 @@ */ final class ConfigContentTransfer extends AbstractTransfer { - protected const int META_DATA_SIZE = 6; + protected const int META_DATA_SIZE = 7; protected const array META_DATA = [ self::DEFINITION_PATH_PROP => self::DEFINITION_PATH_INDEX, self::HASH_FILE_NAME_PROP => self::HASH_FILE_NAME_INDEX, + self::IS_CACHE_ENABLED_PROP => self::IS_CACHE_ENABLED_INDEX, self::RELATIVE_DEFINITION_PATH_PROP => self::RELATIVE_DEFINITION_PATH_INDEX, self::TRANSFER_NAMESPACE_PROP => self::TRANSFER_NAMESPACE_INDEX, self::TRANSFER_PATH_PROP => self::TRANSFER_PATH_INDEX, @@ -50,9 +51,20 @@ final class ConfigContentTransfer extends AbstractTransfer } } + // isCacheEnabled + public const string IS_CACHE_ENABLED_PROP = 'isCacheEnabled'; + private const int IS_CACHE_ENABLED_INDEX = 2; + + public protected(set) bool $isCacheEnabled { + get => $this->getData(self::IS_CACHE_ENABLED_INDEX); + set { + $this->setData(self::IS_CACHE_ENABLED_INDEX, $value); + } + } + // relativeDefinitionPath public const string RELATIVE_DEFINITION_PATH_PROP = 'relativeDefinitionPath'; - private const int RELATIVE_DEFINITION_PATH_INDEX = 2; + private const int RELATIVE_DEFINITION_PATH_INDEX = 3; public string $relativeDefinitionPath { get => $this->getData(self::RELATIVE_DEFINITION_PATH_INDEX); @@ -63,7 +75,7 @@ final class ConfigContentTransfer extends AbstractTransfer // transferNamespace public const string TRANSFER_NAMESPACE_PROP = 'transferNamespace'; - private const int TRANSFER_NAMESPACE_INDEX = 3; + private const int TRANSFER_NAMESPACE_INDEX = 4; public string $transferNamespace { get => $this->getData(self::TRANSFER_NAMESPACE_INDEX); @@ -74,7 +86,7 @@ final class ConfigContentTransfer extends AbstractTransfer // transferPath public const string TRANSFER_PATH_PROP = 'transferPath'; - private const int TRANSFER_PATH_INDEX = 4; + private const int TRANSFER_PATH_INDEX = 5; public string $transferPath { get => $this->getData(self::TRANSFER_PATH_INDEX); @@ -85,7 +97,7 @@ final class ConfigContentTransfer extends AbstractTransfer // uuid public const string UUID_PROP = 'uuid'; - private const int UUID_INDEX = 5; + private const int UUID_INDEX = 6; public protected(set) string $uuid { get => $this->getData(self::UUID_INDEX); diff --git a/src/Generated/transfer-object.list.csv b/src/Generated/transfer-object.list.csv index 8810caec..a2a6d839 100644 --- a/src/Generated/transfer-object.list.csv +++ b/src/Generated/transfer-object.list.csv @@ -1,5 +1,5 @@ ConfigTransfer,fa113db0d8da9557305bc223b5058f1159f5265e13ee6f3d3f241ce6dea211cf -ConfigContentTransfer,90d55471965eb3de22fb9cc4b74b80b8435d9d96281617729c900e44cd828613 +ConfigContentTransfer,ef96669b4877eeb3a554581b986d1ca200d4f8605d9e9022d568db613b3549b4 DefinitionTransfer,2f3aee2d9280769303b79b3188a48409b9c515e61e7318408239c12e2a18bbb4 DefinitionContentTransfer,f9091ac813bf472677ee58d39fcced7c192ac19300059e9788fd79b8e3f93077 DefinitionPropertyTransfer,df7f49080dcca259103f571b2c656c416aec7616769c54ed024980349762e7ac @@ -18,4 +18,4 @@ DefinitionGeneratorTransfer,b54540b65d2766439be5cf0a7566580b78e1241134289c28ba30 DefinitionGeneratorContentTransfer,4ef671b1a35f0cde54246d2cf8e7b0feaa3faa25352e44abd3d504a6d6d48e17 DefinitionBuilderTransfer,d5899c829de6422e636803b720f68fb7dc79165cf13e4611b7d33951bc7e0a94 DefinitionFilesystemTransfer,6726bd810cc3c76c8c2f5791f883b27bab9ac4aebcf4c7d094f814527442f0dd -TemplateTransfer,5f95485e1bf24759188a0fc318f8bc8aa24c7b35cc4d47d21e39baf7eed01179 +TemplateTransfer,5f95485e1bf24759188a0fc318f8bc8aa24c7b35cc4d47d21e39baf7eed01179 \ No newline at end of file diff --git a/src/Shared/Environment/Enum/EnvironmentEnum.php b/src/Shared/Environment/Enum/EnvironmentEnum.php index 716b8e44..9344d58c 100644 --- a/src/Shared/Environment/Enum/EnvironmentEnum.php +++ b/src/Shared/Environment/Enum/EnvironmentEnum.php @@ -8,6 +8,11 @@ enum EnvironmentEnum: string { + /** + * @api + */ + private const string ENV_PREFIX = 'PICAMATOR_TRANSFER_OBJECT_'; + /** * @api */ @@ -27,12 +32,13 @@ enum EnvironmentEnum: string /** * @api */ - private const string ENV_PREFIX = 'PICAMATOR_TRANSFER_OBJECT_'; + case IS_CACHE_ENABLED = self::ENV_PREFIX . 'IS_CACHE_ENABLED'; private const array DEFAULT_VALUES = [ self::PROJECT_ROOT->name => '', self::PROJECT_ROOT_ALIAS->name => '', self::MAX_FILE_SIZE_MB->name => '10', + self::IS_CACHE_ENABLED->name => '1', ]; public function getDefault(): string diff --git a/src/Shared/Environment/EnvironmentReader.php b/src/Shared/Environment/EnvironmentReader.php index fdf9fd1e..335b23ed 100644 --- a/src/Shared/Environment/EnvironmentReader.php +++ b/src/Shared/Environment/EnvironmentReader.php @@ -42,6 +42,15 @@ public function getMaxFileSizeBytes(): int return $this->getMaxFileSizeMegabytes() * 1_000_000; } + public function getIsCacheEnabled(): bool + { + $invalidateCache = $this->getEnvironment(EnvironmentEnum::IS_CACHE_ENABLED); + + return $invalidateCache === '1' + || $invalidateCache === 'true' + || $invalidateCache === 'TRUE'; + } + private function getEnvironment(EnvironmentEnum $environment): string { $value = $this->getenv($environment->value); diff --git a/src/Shared/Environment/EnvironmentReaderInterface.php b/src/Shared/Environment/EnvironmentReaderInterface.php index 264179e5..8da45cfe 100644 --- a/src/Shared/Environment/EnvironmentReaderInterface.php +++ b/src/Shared/Environment/EnvironmentReaderInterface.php @@ -11,4 +11,6 @@ public function getProjectRoot(): string; public function getMaxFileSizeMegabytes(): int; public function getMaxFileSizeBytes(): int; + + public function getIsCacheEnabled(): bool; } diff --git a/src/TransferGenerator/Config/Config/Config.php b/src/TransferGenerator/Config/Config/Config.php index f697279c..23a84ae2 100644 --- a/src/TransferGenerator/Config/Config/Config.php +++ b/src/TransferGenerator/Config/Config/Config.php @@ -42,4 +42,9 @@ public function getHashFileName(): string { return $this->configTransfer->hashFileName; } + + public function getIsCacheEnabled(): bool + { + return $this->configTransfer->isCacheEnabled; + } } diff --git a/src/TransferGenerator/Config/Config/ConfigInterface.php b/src/TransferGenerator/Config/Config/ConfigInterface.php index 446c47bd..0514d57f 100644 --- a/src/TransferGenerator/Config/Config/ConfigInterface.php +++ b/src/TransferGenerator/Config/Config/ConfigInterface.php @@ -35,4 +35,9 @@ public function getUuid(): string; * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ public function getHashFileName(): string; + + /** + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + */ + public function getIsCacheEnabled(): bool; } diff --git a/src/TransferGenerator/Config/Config/ConfigProxy.php b/src/TransferGenerator/Config/Config/ConfigProxy.php index 8029d17a..083066bf 100644 --- a/src/TransferGenerator/Config/Config/ConfigProxy.php +++ b/src/TransferGenerator/Config/Config/ConfigProxy.php @@ -40,6 +40,11 @@ public function getHashFileName(): string return $this->getConfig()->getHashFileName(); } + public function getIsCacheEnabled(): bool + { + return $this->getConfig()->getIsCacheEnabled(); + } + public static function loadConfig(ConfigInterface $config): void { self::$config = $config; diff --git a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php index ac80bf80..2d37240b 100644 --- a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php +++ b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php @@ -13,12 +13,13 @@ enum ConfigKeyEnum: string case DEFINITION_PATH = ConfigContentTransfer::DEFINITION_PATH_PROP; /** - * @return array + * @return array */ public static function getDefaultConfig(): array { $defaultContent[ConfigContentTransfer::UUID_PROP] = ''; $defaultContent[ConfigContentTransfer::HASH_FILE_NAME_PROP] = ''; + $defaultContent[ConfigContentTransfer::IS_CACHE_ENABLED_PROP] = true; foreach (self::cases() as $keyEnum) { $defaultContent[$keyEnum->value] = ''; diff --git a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php index d1a67801..62950eb2 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php @@ -21,6 +21,7 @@ public function createContentTransfer(array $configData): ConfigContentTransfer { $configData[ConfigContentTransfer::UUID_PROP] = $this->getUuid(); $configData[ConfigContentTransfer::HASH_FILE_NAME_PROP] = static::HASH_FILE_NAME; + $configData[ConfigContentTransfer::IS_CACHE_ENABLED_PROP] = $this->environmentReader->getIsCacheEnabled(); $contentTransfer = new ConfigContentTransfer($configData); @@ -31,7 +32,7 @@ public function createContentTransfer(array $configData): ConfigContentTransfer private function getUuid(): string { - return uniqid('', true); + return uniqid(more_entropy: true); } private function parseContentPath(ConfigContentTransfer $contentTransfer): void diff --git a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilderInterface.php b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilderInterface.php index c09e108e..6c162c22 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilderInterface.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilderInterface.php @@ -9,7 +9,7 @@ interface ConfigContentBuilderInterface { /** - * @param array $configData + * @param array $configData */ public function createContentTransfer(array $configData): ConfigContentTransfer; } diff --git a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php index 0b06ea81..c413fe70 100644 --- a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php +++ b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php @@ -11,7 +11,7 @@ trait ConfigNormalizerTrait private const string CONFIG_SECTION_KEY = 'generator'; /** - * @return array + * @return array */ final protected function normalizeConfig(mixed $configData): array { diff --git a/src/TransferGenerator/Generator/Reader/TransferHashReader.php b/src/TransferGenerator/Generator/Reader/TransferHashReader.php index 763855aa..347dbeb9 100644 --- a/src/TransferGenerator/Generator/Reader/TransferHashReader.php +++ b/src/TransferGenerator/Generator/Reader/TransferHashReader.php @@ -29,8 +29,9 @@ public function readHashFile(): TransferHashTransfer private function reloadHashFileCache(): void { - $filePath = $this->getFilePath(); - $hashes = $this->fileReader->readFile($filePath); + $hashes = $this->config->getIsCacheEnabled() + ? $this->fileReader->readFile($this->getFilePath()) + : []; $this->hashTransfer = new TransferHashTransfer([ TransferHashTransfer::HASHES_PROP => $hashes, diff --git a/tests/integration/Command/TransferGeneratorBulkCommandTest.php b/tests/integration/Command/TransferGeneratorBulkCommandTest.php index 24c76dda..36cf6e31 100644 --- a/tests/integration/Command/TransferGeneratorBulkCommandTest.php +++ b/tests/integration/Command/TransferGeneratorBulkCommandTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Picamator\Tests\Integration\TransferObject\Helper\FailedFiberTrait; use Picamator\TransferObject\Command\TransferGeneratorBulkCommand; +use Picamator\TransferObject\Shared\Environment\Enum\EnvironmentEnum; use Picamator\TransferObject\TransferGenerator\TransferGeneratorFacadeInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -24,8 +25,15 @@ final class TransferGeneratorBulkCommandTest extends TestCase private const string ERROR_CONFIG_LIST_PATH = '/tests/integration/Command/data/config/error/config.list.txt'; + private const string IS_CACHE_ENABLED = EnvironmentEnum::IS_CACHE_ENABLED->value; + private CommandTester $commandTester; + public static function setUpBeforeClass(): void + { + putenv(self::IS_CACHE_ENABLED . '=0'); + } + protected function setUp(): void { $command = new TransferGeneratorBulkCommand(); From 37d5905b9a8134dc017cbd9f39228b9f8e7d7ac8 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 22:07:26 +0100 Subject: [PATCH 09/56] Added new configuration field: hashFileName to allow several configurations save transfer object in the same directory --- schema/config.schema.json | 4 +++ src/Shared/Environment/EnvironmentReader.php | 8 ++--- .../Config/ConfigFactory.php | 1 + .../Config/Enum/ConfigKeyEnum.php | 16 +++++++-- .../Parser/Builder/ConfigContentBuilder.php | 12 ------- .../Config/Parser/ConfigParser.php | 23 ++++++++++-- .../Parser/Filter/ConfigNormalizerTrait.php | 20 +++++++++++ .../Success/CommandFirstTransfer.php | 2 +- .../Success/CommandSecondTransfer.php | 2 +- .../Success/CommandThirdTransfer.php | 36 +++++++++++++++++++ .../Success/transfer-object.first.list.csv | 2 ++ .../Success/transfer-object.list.csv | 2 -- .../Success/transfer-object.second.list.csv | 1 + .../TransferGeneratorBulkCommandTest.php | 2 +- .../Command/TransferGeneratorCommandTest.php | 5 +-- .../data/config/success/config.list.txt | 3 +- .../command.first.transfer.yml | 0 .../command.second.transfer.yml | 0 .../command.third.transfer.yml | 4 +++ ....config.yml => generator.first.config.yml} | 5 +-- .../success/generator.second.config.yml | 6 ++++ 21 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 tests/integration/Command/Generated/Success/CommandThirdTransfer.php create mode 100644 tests/integration/Command/Generated/Success/transfer-object.first.list.csv delete mode 100644 tests/integration/Command/Generated/Success/transfer-object.list.csv create mode 100644 tests/integration/Command/Generated/Success/transfer-object.second.list.csv rename tests/integration/Command/data/config/success/{definition => definition-first}/command.first.transfer.yml (100%) rename tests/integration/Command/data/config/success/{definition => definition-first}/command.second.transfer.yml (100%) create mode 100644 tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml rename tests/integration/Command/data/config/success/{generator.config.yml => generator.first.config.yml} (63%) create mode 100644 tests/integration/Command/data/config/success/generator.second.config.yml diff --git a/schema/config.schema.json b/schema/config.schema.json index 9c2afbfd..719f2d3a 100644 --- a/schema/config.schema.json +++ b/schema/config.schema.json @@ -18,6 +18,10 @@ "definitionPath": { "type": "string", "description": "Path to the Definition Files." + }, + "hashFileName": { + "type": "string", + "description": "File name where Transfer Object content hashes are saved for change detection. Optional. Default: transfer-object.list.csv" } }, "required": ["transferNamespace", "transferPath", "definitionPath"], diff --git a/src/Shared/Environment/EnvironmentReader.php b/src/Shared/Environment/EnvironmentReader.php index 335b23ed..4f614e7a 100644 --- a/src/Shared/Environment/EnvironmentReader.php +++ b/src/Shared/Environment/EnvironmentReader.php @@ -44,11 +44,11 @@ public function getMaxFileSizeBytes(): int public function getIsCacheEnabled(): bool { - $invalidateCache = $this->getEnvironment(EnvironmentEnum::IS_CACHE_ENABLED); + $isCacheEnabled = $this->getEnvironment(EnvironmentEnum::IS_CACHE_ENABLED); - return $invalidateCache === '1' - || $invalidateCache === 'true' - || $invalidateCache === 'TRUE'; + return $isCacheEnabled === '1' + || $isCacheEnabled === 'true' + || $isCacheEnabled === 'TRUE'; } private function getEnvironment(EnvironmentEnum $environment): string diff --git a/src/TransferGenerator/Config/ConfigFactory.php b/src/TransferGenerator/Config/ConfigFactory.php index 530fb2d3..ffd75f30 100644 --- a/src/TransferGenerator/Config/ConfigFactory.php +++ b/src/TransferGenerator/Config/ConfigFactory.php @@ -123,6 +123,7 @@ className: ConfigParser::class, $ghost->__construct( $this->createYmlParser(), $this->createConfigContentBuilder(), + $this->createEnvironmentReader(), ); } ); diff --git a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php index 2d37240b..c6ae7616 100644 --- a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php +++ b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php @@ -11,6 +11,14 @@ enum ConfigKeyEnum: string case TRANSFER_NAMESPACE = ConfigContentTransfer::TRANSFER_NAMESPACE_PROP; case TRANSFER_PATH = ConfigContentTransfer::TRANSFER_PATH_PROP; case DEFINITION_PATH = ConfigContentTransfer::DEFINITION_PATH_PROP; + case HASH_FILE_NAME = ConfigContentTransfer::HASH_FILE_NAME_PROP; + + private const array DEFAULT_VALUES = [ + self::TRANSFER_NAMESPACE->name => '', + self::TRANSFER_PATH->name => '', + self::DEFINITION_PATH->name => '', + self::HASH_FILE_NAME->name => 'transfer-object.list.csv', + ]; /** * @return array @@ -18,13 +26,17 @@ enum ConfigKeyEnum: string public static function getDefaultConfig(): array { $defaultContent[ConfigContentTransfer::UUID_PROP] = ''; - $defaultContent[ConfigContentTransfer::HASH_FILE_NAME_PROP] = ''; $defaultContent[ConfigContentTransfer::IS_CACHE_ENABLED_PROP] = true; foreach (self::cases() as $keyEnum) { - $defaultContent[$keyEnum->value] = ''; + $defaultContent[$keyEnum->value] = self::DEFAULT_VALUES[$keyEnum->name]; } return $defaultContent; } + + public function getDefaultValue(): string + { + return self::DEFAULT_VALUES[$this->name]; + } } diff --git a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php index 62950eb2..2a01e7b3 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php @@ -11,30 +11,18 @@ { protected const string PLACEHOLDER = '${PROJECT_ROOT}'; - protected const string HASH_FILE_NAME = 'transfer-object.list.csv'; - public function __construct(private EnvironmentReaderInterface $environmentReader) { } public function createContentTransfer(array $configData): ConfigContentTransfer { - $configData[ConfigContentTransfer::UUID_PROP] = $this->getUuid(); - $configData[ConfigContentTransfer::HASH_FILE_NAME_PROP] = static::HASH_FILE_NAME; - $configData[ConfigContentTransfer::IS_CACHE_ENABLED_PROP] = $this->environmentReader->getIsCacheEnabled(); - $contentTransfer = new ConfigContentTransfer($configData); - $this->parseContentPath($contentTransfer); return $contentTransfer; } - private function getUuid(): string - { - return uniqid(more_entropy: true); - } - private function parseContentPath(ConfigContentTransfer $contentTransfer): void { $relativeDefinitionPath = $this->filterPath($contentTransfer->definitionPath); diff --git a/src/TransferGenerator/Config/Parser/ConfigParser.php b/src/TransferGenerator/Config/Parser/ConfigParser.php index 83c4c6fa..5ff2e388 100644 --- a/src/TransferGenerator/Config/Parser/ConfigParser.php +++ b/src/TransferGenerator/Config/Parser/ConfigParser.php @@ -6,6 +6,7 @@ use Picamator\TransferObject\Dependency\YmlParser\YmlParserInterface; use Picamator\TransferObject\Generated\ConfigContentTransfer; +use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; use Picamator\TransferObject\TransferGenerator\Config\Parser\Builder\ConfigContentBuilderInterface; use Picamator\TransferObject\TransferGenerator\Config\Parser\Filter\ConfigNormalizerTrait; @@ -16,14 +17,30 @@ public function __construct( private YmlParserInterface $parser, private ConfigContentBuilderInterface $builder, + private EnvironmentReaderInterface $environmentReader, ) { } public function parseConfig(string $configPath): ConfigContentTransfer { - $configData = $this->parser->parseFile($configPath); - $normalizedConfigData = $this->normalizeConfig($configData); + $configData = $this->parser->parseFile($configPath) + |> $this->normalizeConfig(...) + |> $this->normalizeHashFileName(...) + |> $this->expandConfig(...); - return $this->builder->createContentTransfer($normalizedConfigData); + return $this->builder->createContentTransfer($configData); + } + + /** + * @param array $configData + * + * @return array + */ + private function expandConfig(array $configData): array + { + $configData[ConfigContentTransfer::UUID_PROP] = uniqid(more_entropy: true); + $configData[ConfigContentTransfer::IS_CACHE_ENABLED_PROP] = $this->environmentReader->getIsCacheEnabled(); + + return $configData; } } diff --git a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php index c413fe70..a9ed3a5e 100644 --- a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php +++ b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php @@ -33,4 +33,24 @@ final protected function normalizeConfig(mixed $configData): array return $filteredData; } + + /** + * @param array $configData + * + * @return array + */ + final protected function normalizeHashFileName(array $configData): array + { + $configKey = ConfigKeyEnum::HASH_FILE_NAME; + + /** @var string $hashFileName */ + $hashFileName = $configData[$configKey->value] ?: $configKey->getDefaultValue(); + + /** @var string $hashFileName */ + $hashFileName = pathinfo($hashFileName, flags: PATHINFO_BASENAME); + + $configData[$configKey->value] = $hashFileName; + + return $configData; + } } diff --git a/tests/integration/Command/Generated/Success/CommandFirstTransfer.php b/tests/integration/Command/Generated/Success/CommandFirstTransfer.php index e751728a..bd0f70da 100644 --- a/tests/integration/Command/Generated/Success/CommandFirstTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandFirstTransfer.php @@ -13,7 +13,7 @@ * * Note: Do not manually edit this file, as changes will be overwritten. * - * @see /tests/integration/Command/data/config/success/definition/command.first.transfer.yml Definition file path. + * @see /tests/integration/Command/data/config/success/definition-first/command.first.transfer.yml Definition file path. */ final class CommandFirstTransfer extends AbstractTransfer { diff --git a/tests/integration/Command/Generated/Success/CommandSecondTransfer.php b/tests/integration/Command/Generated/Success/CommandSecondTransfer.php index 24c1d03f..16647884 100644 --- a/tests/integration/Command/Generated/Success/CommandSecondTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandSecondTransfer.php @@ -13,7 +13,7 @@ * * Note: Do not manually edit this file, as changes will be overwritten. * - * @see /tests/integration/Command/data/config/success/definition/command.second.transfer.yml Definition file path. + * @see /tests/integration/Command/data/config/success/definition-first/command.second.transfer.yml Definition file path. */ final class CommandSecondTransfer extends AbstractTransfer { diff --git a/tests/integration/Command/Generated/Success/CommandThirdTransfer.php b/tests/integration/Command/Generated/Success/CommandThirdTransfer.php new file mode 100644 index 00000000..6c6cbba5 --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandThirdTransfer.php @@ -0,0 +1,36 @@ + self::RUN_INDEX, + ]; + + // run + public const string RUN_PROP = 'run'; + private const int RUN_INDEX = 0; + + public ?true $run { + get => $this->getData(self::RUN_INDEX); + set { + $this->setData(self::RUN_INDEX, $value); + } + } +} diff --git a/tests/integration/Command/Generated/Success/transfer-object.first.list.csv b/tests/integration/Command/Generated/Success/transfer-object.first.list.csv new file mode 100644 index 00000000..fdd6feec --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.first.list.csv @@ -0,0 +1,2 @@ +CommandSecondTransfer,b2b9189a22542814f4d696dac08a33668c10a2140fa266e5b42f86b27502ce13 +CommandFirstTransfer,e70cbc8584c84e982c1d2b03d84bcfbc6a55743c33fa617e7af38fd99297cfe3 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.list.csv b/tests/integration/Command/Generated/Success/transfer-object.list.csv deleted file mode 100644 index a0d1ffdb..00000000 --- a/tests/integration/Command/Generated/Success/transfer-object.list.csv +++ /dev/null @@ -1,2 +0,0 @@ -CommandSecondTransfer,74cefcbf8a44fa6b6981c11e6b8c8dec8b2da152755d6c70bafd38f1eb5c847c -CommandFirstTransfer,08c25060e2d59248791d1f727d85bd2a6a6a6186289430c6421cc96db090b1c7 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.second.list.csv b/tests/integration/Command/Generated/Success/transfer-object.second.list.csv new file mode 100644 index 00000000..c4931c58 --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.second.list.csv @@ -0,0 +1 @@ +CommandThirdTransfer,2a18df50cc97bbbf28047407610584ce97fd61939bbb713e5536994b12f523f6 \ No newline at end of file diff --git a/tests/integration/Command/TransferGeneratorBulkCommandTest.php b/tests/integration/Command/TransferGeneratorBulkCommandTest.php index 36cf6e31..a44fe393 100644 --- a/tests/integration/Command/TransferGeneratorBulkCommandTest.php +++ b/tests/integration/Command/TransferGeneratorBulkCommandTest.php @@ -74,7 +74,7 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): $output = $this->commandTester->getDisplay(); // Assert - $this->commandTester->assertCommandIsSuccessful(); + $this->commandTester->assertCommandIsSuccessful($output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); } diff --git a/tests/integration/Command/TransferGeneratorCommandTest.php b/tests/integration/Command/TransferGeneratorCommandTest.php index 379b4678..ce6247af 100644 --- a/tests/integration/Command/TransferGeneratorCommandTest.php +++ b/tests/integration/Command/TransferGeneratorCommandTest.php @@ -18,7 +18,8 @@ final class TransferGeneratorCommandTest extends TestCase { use FailedFiberTrait; - private const string SUCCESS_CONFIG_PATH = '/tests/integration/Command/data/config/success/generator.config.yml'; + private const string SUCCESS_CONFIG_PATH + = '/tests/integration/Command/data/config/success/generator.first.config.yml'; private const string ERROR_CONFIG_PATH = '/tests/integration/Command/data/config/error/generator.config.yml'; private CommandTester $commandTester; @@ -64,7 +65,7 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): $output = $this->commandTester->getDisplay(); // Assert - $this->commandTester->assertCommandIsSuccessful(); + $this->commandTester->assertCommandIsSuccessful($output); $this->assertStringContainsString('command.first.transfer.yml: CommandFirstTransfer', $output); $this->assertStringContainsString('command.second.transfer.yml: CommandSecondTransfer', $output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); diff --git a/tests/integration/Command/data/config/success/config.list.txt b/tests/integration/Command/data/config/success/config.list.txt index 5922206c..e19fca3c 100644 --- a/tests/integration/Command/data/config/success/config.list.txt +++ b/tests/integration/Command/data/config/success/config.list.txt @@ -1 +1,2 @@ -tests/integration/Command/data/config/success/generator.config.yml +tests/integration/Command/data/config/success/generator.first.config.yml +tests/integration/Command/data/config/success/generator.second.config.yml diff --git a/tests/integration/Command/data/config/success/definition/command.first.transfer.yml b/tests/integration/Command/data/config/success/definition-first/command.first.transfer.yml similarity index 100% rename from tests/integration/Command/data/config/success/definition/command.first.transfer.yml rename to tests/integration/Command/data/config/success/definition-first/command.first.transfer.yml diff --git a/tests/integration/Command/data/config/success/definition/command.second.transfer.yml b/tests/integration/Command/data/config/success/definition-first/command.second.transfer.yml similarity index 100% rename from tests/integration/Command/data/config/success/definition/command.second.transfer.yml rename to tests/integration/Command/data/config/success/definition-first/command.second.transfer.yml diff --git a/tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml b/tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml new file mode 100644 index 00000000..0e8e7a13 --- /dev/null +++ b/tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +CommandThird: + run: + type: true diff --git a/tests/integration/Command/data/config/success/generator.config.yml b/tests/integration/Command/data/config/success/generator.first.config.yml similarity index 63% rename from tests/integration/Command/data/config/success/generator.config.yml rename to tests/integration/Command/data/config/success/generator.first.config.yml index fd752acf..0e5c8952 100644 --- a/tests/integration/Command/data/config/success/generator.config.yml +++ b/tests/integration/Command/data/config/success/generator.first.config.yml @@ -1,5 +1,6 @@ -# $schema: ./../../../../../../schema/config.schema.json +# $schema: ./../../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" - definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-first" + hashFileName: "transfer-object.first.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.second.config.yml b/tests/integration/Command/data/config/success/generator.second.config.yml new file mode 100644 index 00000000..8dce0f1d --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.second.config.yml @@ -0,0 +1,6 @@ +# $schema: ./../../../../../../../../schema/config.schema.json +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" + transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-second" + hashFileName: "transfer-object.second.list.csv" From d10ff137292763425c2f6b01aaeb726be0d4a643 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 22:08:58 +0100 Subject: [PATCH 10/56] Changed TransferGeneratorCommandTest code style --- tests/integration/Command/TransferGeneratorCommandTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/Command/TransferGeneratorCommandTest.php b/tests/integration/Command/TransferGeneratorCommandTest.php index ce6247af..1505f5f0 100644 --- a/tests/integration/Command/TransferGeneratorCommandTest.php +++ b/tests/integration/Command/TransferGeneratorCommandTest.php @@ -20,6 +20,7 @@ final class TransferGeneratorCommandTest extends TestCase private const string SUCCESS_CONFIG_PATH = '/tests/integration/Command/data/config/success/generator.first.config.yml'; + private const string ERROR_CONFIG_PATH = '/tests/integration/Command/data/config/error/generator.config.yml'; private CommandTester $commandTester; From b3825fe427b65cfc9cbe3523c062fb6cf335e30f Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 22:18:18 +0100 Subject: [PATCH 11/56] Changed normalizeHashFileName --- .../Config/Parser/Filter/ConfigNormalizerTrait.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php index a9ed3a5e..8ccaa81c 100644 --- a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php +++ b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php @@ -44,7 +44,8 @@ final protected function normalizeHashFileName(array $configData): array $configKey = ConfigKeyEnum::HASH_FILE_NAME; /** @var string $hashFileName */ - $hashFileName = $configData[$configKey->value] ?: $configKey->getDefaultValue(); + $hashFileName = $configData[$configKey->value] ?? null; + $hashFileName = $hashFileName ?: $configKey->getDefaultValue(); /** @var string $hashFileName */ $hashFileName = pathinfo($hashFileName, flags: PATHINFO_BASENAME); From c3bfa2336ed5a12eaac0fca90197b158ee123fa1 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 22:51:02 +0100 Subject: [PATCH 12/56] Refactored ConfigNormalizerTrait, added hashFileName validator, actualized config schema with pattern --- schema/config.schema.json | 3 +- .../Config/ConfigFactory.php | 7 ++++ .../Config/Enum/ConfigKeyEnum.php | 5 --- .../Config/Parser/ConfigParser.php | 1 - .../Parser/Filter/ConfigNormalizerTrait.php | 37 ++++++------------- .../Content/HashFileNameContentValidator.php | 36 ++++++++++++++++++ 6 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php diff --git a/schema/config.schema.json b/schema/config.schema.json index 719f2d3a..20c7299b 100644 --- a/schema/config.schema.json +++ b/schema/config.schema.json @@ -21,7 +21,8 @@ }, "hashFileName": { "type": "string", - "description": "File name where Transfer Object content hashes are saved for change detection. Optional. Default: transfer-object.list.csv" + "description": "File name where Transfer Object content hashes are saved for change detection. Optional. Default: transfer-object.list.csv", + "pattern": "^[a-zA-Z0-9._-]+\\.csv$" } }, "required": ["transferNamespace", "transferPath", "definitionPath"], diff --git a/src/TransferGenerator/Config/ConfigFactory.php b/src/TransferGenerator/Config/ConfigFactory.php index ffd75f30..592d0f39 100644 --- a/src/TransferGenerator/Config/ConfigFactory.php +++ b/src/TransferGenerator/Config/ConfigFactory.php @@ -24,6 +24,7 @@ use Picamator\TransferObject\TransferGenerator\Config\Validator\ConfigValidatorInterface; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\ContentValidatorInterface; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\DefinitionPathContentValidator; +use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\HashFileNameContentValidator; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\RequiredContentValidator; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\TransferNamespaceContentValidator; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\TransferPathContentValidator; @@ -86,9 +87,15 @@ protected function createConfigContentValidators(): ArrayObject $this->createDefinitionPathConfigContentValidator(), $this->createTransferPathConfigContentValidator(), $this->createTransferNamespaceConfigContentValidator(), + $this->createHashFileNameContentValidator(), ]); } + protected function createHashFileNameContentValidator(): ContentValidatorInterface + { + return new HashFileNameContentValidator(); + } + protected function createTransferNamespaceConfigContentValidator(): ContentValidatorInterface { return new TransferNamespaceContentValidator(); diff --git a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php index c6ae7616..ca57cc44 100644 --- a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php +++ b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php @@ -34,9 +34,4 @@ public static function getDefaultConfig(): array return $defaultContent; } - - public function getDefaultValue(): string - { - return self::DEFAULT_VALUES[$this->name]; - } } diff --git a/src/TransferGenerator/Config/Parser/ConfigParser.php b/src/TransferGenerator/Config/Parser/ConfigParser.php index 5ff2e388..df7ba029 100644 --- a/src/TransferGenerator/Config/Parser/ConfigParser.php +++ b/src/TransferGenerator/Config/Parser/ConfigParser.php @@ -25,7 +25,6 @@ public function parseConfig(string $configPath): ConfigContentTransfer { $configData = $this->parser->parseFile($configPath) |> $this->normalizeConfig(...) - |> $this->normalizeHashFileName(...) |> $this->expandConfig(...); return $this->builder->createContentTransfer($configData); diff --git a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php index 8ccaa81c..3eef5192 100644 --- a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php +++ b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php @@ -15,43 +15,30 @@ trait ConfigNormalizerTrait */ final protected function normalizeConfig(mixed $configData): array { - $defaultConfig = ConfigKeyEnum::getDefaultConfig(); if (!is_array($configData)) { - return $defaultConfig; + return ConfigKeyEnum::getDefaultConfig(); } $sectionData = $configData[self::CONFIG_SECTION_KEY] ?? null; - if (!is_array($sectionData)) { - return $defaultConfig; - } - - $filteredData = []; - foreach ($defaultConfig as $key => $defaultValue) { - $sectionValue = $sectionData[$key] ?? null; - $filteredData[$key] = is_string($sectionValue) ? $sectionValue : $defaultValue; - } - return $filteredData; + return is_array($sectionData) + ? $this->filterSectionData($sectionData) + : ConfigKeyEnum::getDefaultConfig(); } /** - * @param array $configData + * @param array $sectionData * * @return array */ - final protected function normalizeHashFileName(array $configData): array + private function filterSectionData(array $sectionData): array { - $configKey = ConfigKeyEnum::HASH_FILE_NAME; - - /** @var string $hashFileName */ - $hashFileName = $configData[$configKey->value] ?? null; - $hashFileName = $hashFileName ?: $configKey->getDefaultValue(); - - /** @var string $hashFileName */ - $hashFileName = pathinfo($hashFileName, flags: PATHINFO_BASENAME); - - $configData[$configKey->value] = $hashFileName; + $filteredData = []; + foreach (ConfigKeyEnum::getDefaultConfig() as $key => $defaultValue) { + $sectionValue = $sectionData[$key] ?? null; + $filteredData[$key] = is_string($sectionValue) ? $sectionValue : $defaultValue; + } - return $configData; + return $filteredData; } } diff --git a/src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php b/src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php new file mode 100644 index 00000000..7e6e8b6f --- /dev/null +++ b/src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php @@ -0,0 +1,36 @@ +hashFileName; + if (preg_match(self::HASH_FILE_NAME_REGEX, $hashFileName) === 1) { + return null; + } + + $errorMessage = $this->getErrorMessage($hashFileName); + + return $this->createErrorMessageTransfer($errorMessage); + } + + private function getErrorMessage(string $hashFileName): string + { + return sprintf(self::ERROR_MESSAGE_TEMPLATE, $hashFileName); + } +} From c6f147454dddfafe1eef9c97bf8a4dbf1c561a09 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 27 Feb 2026 23:05:09 +0100 Subject: [PATCH 13/56] Fixed/added configuration schema path --- .../integration/Command/data/config/error/generator.config.yml | 1 + .../Command/data/config/success/generator.first.config.yml | 2 +- .../Command/data/config/success/generator.second.config.yml | 2 +- .../data/config/error/duplicate-transfer/generator.config.yml | 1 + .../error/empty-definition-directory/generator.config.yml | 1 + .../data/config/error/empty-definition/generator.config.yml | 1 + .../config/error/empty-property-definition/generator.config.yml | 1 + .../config/error/invalid-attribute-name/generator.config.yml | 1 + .../config/error/invalid-attribute-target/generator.config.yml | 1 + .../data/config/error/invalid-attribute/generator.config.yml | 1 + .../data/config/error/invalid-class-name/generator.config.yml | 1 + .../config/error/invalid-collection-type/generator.config.yml | 1 + .../config/error/invalid-date-time-type/generator.config.yml | 1 + .../data/config/error/invalid-enum-type/generator.config.yml | 1 + .../config/error/invalid-property-name/generator.config.yml | 1 + .../config/error/invalid-transfer-type/generator.config.yml | 1 + .../config/error/invalid-type-definition/generator.config.yml | 1 + .../invalid-type-namespace-with-alias/generator.config.yml | 1 + .../config/error/invalid-type-namespace/generator.config.yml | 1 + .../data/config/error/invalid-yml-format/generator.config.yml | 1 + .../data/config/error/missed-type/generator.config.yml | 1 + .../config/error/reserved-property-name/generator.config.yml | 1 + .../data/config/error/unsupported-type/generator.config.yml | 1 + .../TransferGenerator/data/config/success/generator.config.yml | 1 + 24 files changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/integration/Command/data/config/error/generator.config.yml b/tests/integration/Command/data/config/error/generator.config.yml index 3960d26d..79402f81 100644 --- a/tests/integration/Command/data/config/error/generator.config.yml +++ b/tests/integration/Command/data/config/error/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Error" diff --git a/tests/integration/Command/data/config/success/generator.first.config.yml b/tests/integration/Command/data/config/success/generator.first.config.yml index 0e5c8952..394c1573 100644 --- a/tests/integration/Command/data/config/success/generator.first.config.yml +++ b/tests/integration/Command/data/config/success/generator.first.config.yml @@ -1,4 +1,4 @@ -# $schema: ./../../../../../../../../schema/config.schema.json +# $schema: ./../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" diff --git a/tests/integration/Command/data/config/success/generator.second.config.yml b/tests/integration/Command/data/config/success/generator.second.config.yml index 8dce0f1d..1af45549 100644 --- a/tests/integration/Command/data/config/success/generator.second.config.yml +++ b/tests/integration/Command/data/config/success/generator.second.config.yml @@ -1,4 +1,4 @@ -# $schema: ./../../../../../../../../schema/config.schema.json +# $schema: ./../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" diff --git a/tests/integration/TransferGenerator/data/config/error/duplicate-transfer/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/duplicate-transfer/generator.config.yml index 4a1f5288..390ab71d 100644 --- a/tests/integration/TransferGenerator/data/config/error/duplicate-transfer/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/duplicate-transfer/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/empty-definition-directory/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/empty-definition-directory/generator.config.yml index 0d564b2b..cbe8bd42 100644 --- a/tests/integration/TransferGenerator/data/config/error/empty-definition-directory/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/empty-definition-directory/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/empty-definition/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/empty-definition/generator.config.yml index 72d7ceb7..ae3fa987 100644 --- a/tests/integration/TransferGenerator/data/config/error/empty-definition/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/empty-definition/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/empty-property-definition/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/empty-property-definition/generator.config.yml index 4ad06877..4bc3b2e6 100644 --- a/tests/integration/TransferGenerator/data/config/error/empty-property-definition/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/empty-property-definition/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-attribute-name/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-attribute-name/generator.config.yml index bdafa633..7ad5c3d0 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-attribute-name/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-attribute-name/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-attribute-target/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-attribute-target/generator.config.yml index 2e43ef7f..d6aac5e7 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-attribute-target/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-attribute-target/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-attribute/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-attribute/generator.config.yml index 7a4ed954..c9a28c36 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-attribute/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-attribute/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-class-name/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-class-name/generator.config.yml index 59e89ea6..da83ed6d 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-class-name/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-class-name/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-collection-type/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-collection-type/generator.config.yml index 7b413b06..7b3f8651 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-collection-type/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-collection-type/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-date-time-type/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-date-time-type/generator.config.yml index d49e223e..cc0f7c26 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-date-time-type/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-date-time-type/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-enum-type/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-enum-type/generator.config.yml index c26446b5..f96a1508 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-enum-type/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-enum-type/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-property-name/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-property-name/generator.config.yml index 171a2fe1..846dc81e 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-property-name/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-property-name/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-transfer-type/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-transfer-type/generator.config.yml index 5bd3f921..4c319ec7 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-transfer-type/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-transfer-type/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-type-definition/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-type-definition/generator.config.yml index db837f8a..ed5ae66e 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-type-definition/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-type-definition/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/generator.config.yml index f7777e58..1fc72c7f 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace-with-alias/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/generator.config.yml index c5dddb1d..39a8d416 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-type-namespace/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-yml-format/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-yml-format/generator.config.yml index d9dbeb82..984e84d4 100644 --- a/tests/integration/TransferGenerator/data/config/error/invalid-yml-format/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/invalid-yml-format/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/missed-type/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/missed-type/generator.config.yml index 1183fceb..b58d99ff 100644 --- a/tests/integration/TransferGenerator/data/config/error/missed-type/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/missed-type/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/reserved-property-name/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/reserved-property-name/generator.config.yml index 6407663a..2888ae7b 100644 --- a/tests/integration/TransferGenerator/data/config/error/reserved-property-name/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/reserved-property-name/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/error/unsupported-type/generator.config.yml b/tests/integration/TransferGenerator/data/config/error/unsupported-type/generator.config.yml index af78d2e5..7bf5b548 100644 --- a/tests/integration/TransferGenerator/data/config/error/unsupported-type/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/error/unsupported-type/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" diff --git a/tests/integration/TransferGenerator/data/config/success/generator.config.yml b/tests/integration/TransferGenerator/data/config/success/generator.config.yml index e18f747b..3b37f8bd 100644 --- a/tests/integration/TransferGenerator/data/config/success/generator.config.yml +++ b/tests/integration/TransferGenerator/data/config/success/generator.config.yml @@ -1,3 +1,4 @@ +# $schema: ./../../../../../../schema/config.schema.json generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Success" From fb18d353ac3880f0d8077ae29c52805adfe3c0eb Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sat, 28 Feb 2026 22:39:36 +0100 Subject: [PATCH 14/56] Removed project root overwrite from docker compose, removed cache veriable from docker compose --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 79b8e65a..b1f82383 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,8 +5,7 @@ services: - "host.docker.internal:host-gateway" environment: XDEBUG_MODE: ${XDEBUG_MODE:-off} - PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT: ${PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT:-/home/transfer/transfer-object} - PICAMATOR_TRANSFER_OBJECT_IS_CACHE_ENABLED: ${PICAMATOR_TRANSFER_OBJECT_IS_CACHE_ENABLED:-true} + PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT: /home/transfer/transfer-object build: context: docker/php args: From c2f2b175e9882659c0d62e8d61cf548e8de522e9 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sat, 28 Feb 2026 22:41:18 +0100 Subject: [PATCH 15/56] Removed HashFileWriterTest test --- tests/unit/Shared/Hash/HashFileWriterTest.php | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 tests/unit/Shared/Hash/HashFileWriterTest.php diff --git a/tests/unit/Shared/Hash/HashFileWriterTest.php b/tests/unit/Shared/Hash/HashFileWriterTest.php deleted file mode 100644 index baf7ba6c..00000000 --- a/tests/unit/Shared/Hash/HashFileWriterTest.php +++ /dev/null @@ -1,51 +0,0 @@ -filesystemMock = $this->createMock(FilesystemInterface::class); - - $this->writer = new HashFileWriter($this->filesystemMock); - } - - #[TestDox('Write hash file data')] - public function testWriteFile(): void - { - // Arrange - $path = '/some/path'; - $data = new ArrayObject([ - 'CustomerTransfer' => 'some-hash', - ]); - - $expected = 'CustomerTransfer,some-hash'; - - // Expect - $this->filesystemMock - ->expects($this->once()) - ->method('dumpFile') - ->with($path, $expected) - ->seal(); - - // Act - $this->writer->writeFile($path, $data); - } -} From f7aced16e586b01e648b8a6c7d92f2ddc47fc324 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sat, 28 Feb 2026 23:33:52 +0100 Subject: [PATCH 16/56] Optimize command functional tests aplying separate configurations, fixed turn on and off cache, fixed failed transfer deletion on disabled cache --- .../Generator/Generator/GeneratorFactory.php | 1 + .../Generator/Reader/TransferHashReader.php | 5 +- .../Generator/Writer/TransferWriter.php | 6 +- ...heDisabledTransferGeneratorCommandTest.php | 67 +++++++++++++++++++ ...nsfer.php => CommandBulkFirstTransfer.php} | 4 +- .../Success/CommandBulkSecondTransfer.php | 36 ++++++++++ ...nsfer.php => CommandBulkThirdTransfer.php} | 4 +- .../CommandCacheDisabledFirstTransfer.php | 36 ++++++++++ .../Success/CommandFirstTransfer.php | 2 +- .../transfer-object.bulk.first.list.csv | 2 + .../transfer-object.bulk.second.list.csv | 1 + .../transfer-object.cache.disabled.list.csv | 1 + .../Success/transfer-object.first.list.csv | 2 - .../Success/transfer-object.list.csv | 1 + .../Success/transfer-object.second.list.csv | 1 - .../TransferGeneratorBulkCommandTest.php | 20 +++--- .../Command/TransferGeneratorCommandTest.php | 5 +- .../data/config/success/config.list.txt | 4 +- .../command.first.transfer.yml | 4 ++ .../command.second.transfer.yml | 2 +- .../command.third.transfer.yml | 2 +- .../command.transfer.yml | 4 ++ .../command.transfer.yml} | 0 ...ig.yml => generator.bulk.first.config.yml} | 4 +- .../success/generator.bulk.second.config.yml | 6 ++ .../generator.cache.disabled.config.yml | 6 ++ ....first.config.yml => generator.config.yml} | 4 +- tests/integration/Helper/EnvironmentTrait.php | 22 ++++++ 28 files changed, 220 insertions(+), 32 deletions(-) create mode 100644 tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php rename tests/integration/Command/Generated/Success/{CommandSecondTransfer.php => CommandBulkFirstTransfer.php} (87%) create mode 100644 tests/integration/Command/Generated/Success/CommandBulkSecondTransfer.php rename tests/integration/Command/Generated/Success/{CommandThirdTransfer.php => CommandBulkThirdTransfer.php} (87%) create mode 100644 tests/integration/Command/Generated/Success/CommandCacheDisabledFirstTransfer.php create mode 100644 tests/integration/Command/Generated/Success/transfer-object.bulk.first.list.csv create mode 100644 tests/integration/Command/Generated/Success/transfer-object.bulk.second.list.csv create mode 100644 tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv delete mode 100644 tests/integration/Command/Generated/Success/transfer-object.first.list.csv create mode 100644 tests/integration/Command/Generated/Success/transfer-object.list.csv delete mode 100644 tests/integration/Command/Generated/Success/transfer-object.second.list.csv create mode 100644 tests/integration/Command/data/config/success/definition-bulk-first/command.first.transfer.yml rename tests/integration/Command/data/config/success/{definition-first => definition-bulk-first}/command.second.transfer.yml (81%) rename tests/integration/Command/data/config/success/{definition-second => definition-bulk-second}/command.third.transfer.yml (82%) create mode 100644 tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml rename tests/integration/Command/data/config/success/{definition-first/command.first.transfer.yml => definition/command.transfer.yml} (100%) rename tests/integration/Command/data/config/success/{generator.second.config.yml => generator.bulk.first.config.yml} (76%) create mode 100644 tests/integration/Command/data/config/success/generator.bulk.second.config.yml create mode 100644 tests/integration/Command/data/config/success/generator.cache.disabled.config.yml rename tests/integration/Command/data/config/success/{generator.first.config.yml => generator.config.yml} (78%) create mode 100644 tests/integration/Helper/EnvironmentTrait.php diff --git a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php index 3bd5797c..2a0b10df 100644 --- a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php +++ b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php @@ -139,6 +139,7 @@ protected function createTransferWriter(): TransferWriterInterface factory: fn(): TransferWriterInterface => new TransferWriter( $this->createTransferHashReader(), $this->createGeneratorFilesystem(), + $this->getConfig(), ), ); } diff --git a/src/TransferGenerator/Generator/Reader/TransferHashReader.php b/src/TransferGenerator/Generator/Reader/TransferHashReader.php index 347dbeb9..763855aa 100644 --- a/src/TransferGenerator/Generator/Reader/TransferHashReader.php +++ b/src/TransferGenerator/Generator/Reader/TransferHashReader.php @@ -29,9 +29,8 @@ public function readHashFile(): TransferHashTransfer private function reloadHashFileCache(): void { - $hashes = $this->config->getIsCacheEnabled() - ? $this->fileReader->readFile($this->getFilePath()) - : []; + $filePath = $this->getFilePath(); + $hashes = $this->fileReader->readFile($filePath); $this->hashTransfer = new TransferHashTransfer([ TransferHashTransfer::HASHES_PROP => $hashes, diff --git a/src/TransferGenerator/Generator/Writer/TransferWriter.php b/src/TransferGenerator/Generator/Writer/TransferWriter.php index 6ea42b12..9b71fe64 100644 --- a/src/TransferGenerator/Generator/Writer/TransferWriter.php +++ b/src/TransferGenerator/Generator/Writer/TransferWriter.php @@ -6,6 +6,7 @@ use Picamator\TransferObject\Generated\TransferGeneratorContentTransfer; use Picamator\TransferObject\Generated\TransferHashTransfer; +use Picamator\TransferObject\TransferGenerator\Config\Config\ConfigInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Reader\TransferHashReaderInterface; @@ -14,6 +15,7 @@ public function __construct( private TransferHashReaderInterface $hashReader, private GeneratorFilesystemInterface $filesystem, + private readonly ConfigInterface $config, ) { } @@ -22,7 +24,7 @@ public function writeFile(TransferGeneratorContentTransfer $contentTransfer): vo $hashTransfer = $this->hashReader->readHashFile(); $hashTransfer->actualHashes[$contentTransfer->className] = $contentTransfer->hash; - if (!$this->isFileChanged($contentTransfer, $hashTransfer)) { + if ($this->config->getIsCacheEnabled() && !$this->isFileModified($contentTransfer, $hashTransfer)) { return; } @@ -31,7 +33,7 @@ public function writeFile(TransferGeneratorContentTransfer $contentTransfer): vo $this->filesystem->writeTempFile($contentTransfer); } - private function isFileChanged( + private function isFileModified( TransferGeneratorContentTransfer $contentTransfer, TransferHashTransfer $hashTransfer, ): bool { diff --git a/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php new file mode 100644 index 00000000..9d829656 --- /dev/null +++ b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php @@ -0,0 +1,67 @@ +commandTester = new CommandTester($command); + } + + #[TestDox('Run command with valid configuration should show success message')] + public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): void + { + // Act + $this->commandTester->execute( + ['-c' => self::SUCCESS_CONFIG_PATH], + ['verbosity' => OutputInterface::VERBOSITY_VERBOSE] + ); + $output = $this->commandTester->getDisplay(); + + // Assert + $this->commandTester->assertCommandIsSuccessful($output); + $this->assertStringContainsString('command.transfer.yml: CommandCacheDisabledFirst', $output); + $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); + + foreach (self::SUCCESS_GENERATED_FILES as $file) { + $this->assertFileExists(__DIR__ . '/' . $file); + } + } +} diff --git a/tests/integration/Command/Generated/Success/CommandSecondTransfer.php b/tests/integration/Command/Generated/Success/CommandBulkFirstTransfer.php similarity index 87% rename from tests/integration/Command/Generated/Success/CommandSecondTransfer.php rename to tests/integration/Command/Generated/Success/CommandBulkFirstTransfer.php index 16647884..27a4e7c1 100644 --- a/tests/integration/Command/Generated/Success/CommandSecondTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandBulkFirstTransfer.php @@ -13,9 +13,9 @@ * * Note: Do not manually edit this file, as changes will be overwritten. * - * @see /tests/integration/Command/data/config/success/definition-first/command.second.transfer.yml Definition file path. + * @see /tests/integration/Command/data/config/success/definition-bulk-first/command.first.transfer.yml Definition file path. */ -final class CommandSecondTransfer extends AbstractTransfer +final class CommandBulkFirstTransfer extends AbstractTransfer { protected const int META_DATA_SIZE = 1; diff --git a/tests/integration/Command/Generated/Success/CommandBulkSecondTransfer.php b/tests/integration/Command/Generated/Success/CommandBulkSecondTransfer.php new file mode 100644 index 00000000..c17f95f8 --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandBulkSecondTransfer.php @@ -0,0 +1,36 @@ + self::RUN_INDEX, + ]; + + // run + public const string RUN_PROP = 'run'; + private const int RUN_INDEX = 0; + + public ?true $run { + get => $this->getData(self::RUN_INDEX); + set { + $this->setData(self::RUN_INDEX, $value); + } + } +} diff --git a/tests/integration/Command/Generated/Success/CommandThirdTransfer.php b/tests/integration/Command/Generated/Success/CommandBulkThirdTransfer.php similarity index 87% rename from tests/integration/Command/Generated/Success/CommandThirdTransfer.php rename to tests/integration/Command/Generated/Success/CommandBulkThirdTransfer.php index 6c6cbba5..f4e9df19 100644 --- a/tests/integration/Command/Generated/Success/CommandThirdTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandBulkThirdTransfer.php @@ -13,9 +13,9 @@ * * Note: Do not manually edit this file, as changes will be overwritten. * - * @see /tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml Definition file path. + * @see /tests/integration/Command/data/config/success/definition-bulk-second/command.third.transfer.yml Definition file path. */ -final class CommandThirdTransfer extends AbstractTransfer +final class CommandBulkThirdTransfer extends AbstractTransfer { protected const int META_DATA_SIZE = 1; diff --git a/tests/integration/Command/Generated/Success/CommandCacheDisabledFirstTransfer.php b/tests/integration/Command/Generated/Success/CommandCacheDisabledFirstTransfer.php new file mode 100644 index 00000000..2f303dcf --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandCacheDisabledFirstTransfer.php @@ -0,0 +1,36 @@ + self::RUN_INDEX, + ]; + + // run + public const string RUN_PROP = 'run'; + private const int RUN_INDEX = 0; + + public ?true $run { + get => $this->getData(self::RUN_INDEX); + set { + $this->setData(self::RUN_INDEX, $value); + } + } +} diff --git a/tests/integration/Command/Generated/Success/CommandFirstTransfer.php b/tests/integration/Command/Generated/Success/CommandFirstTransfer.php index bd0f70da..e475c5e1 100644 --- a/tests/integration/Command/Generated/Success/CommandFirstTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandFirstTransfer.php @@ -13,7 +13,7 @@ * * Note: Do not manually edit this file, as changes will be overwritten. * - * @see /tests/integration/Command/data/config/success/definition-first/command.first.transfer.yml Definition file path. + * @see /tests/integration/Command/data/config/success/definition/command.transfer.yml Definition file path. */ final class CommandFirstTransfer extends AbstractTransfer { diff --git a/tests/integration/Command/Generated/Success/transfer-object.bulk.first.list.csv b/tests/integration/Command/Generated/Success/transfer-object.bulk.first.list.csv new file mode 100644 index 00000000..a810a888 --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.bulk.first.list.csv @@ -0,0 +1,2 @@ +CommandBulkSecondTransfer,548b828c0a3df2efb3ebd46982e1fd982d25025c8b773c3581fed9dd8273e41c +CommandBulkFirstTransfer,e14f23be273fde5da310757845eb9cdcc3b78c00f730b289bdf72f9e51d45833 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.bulk.second.list.csv b/tests/integration/Command/Generated/Success/transfer-object.bulk.second.list.csv new file mode 100644 index 00000000..6fc7bfd8 --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.bulk.second.list.csv @@ -0,0 +1 @@ +CommandBulkThirdTransfer,d310c23a73f84fb0b25a56e2da7c9b06aa358efe47ed9665e2c147b4186325d5 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv b/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv new file mode 100644 index 00000000..9fd18695 --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv @@ -0,0 +1 @@ +CommandCacheDisabledFirstTransfer,fcd98ca6044b14ef0a11dfd4190fe7eb35659aca6205a5cf68136c56735e1469 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.first.list.csv b/tests/integration/Command/Generated/Success/transfer-object.first.list.csv deleted file mode 100644 index fdd6feec..00000000 --- a/tests/integration/Command/Generated/Success/transfer-object.first.list.csv +++ /dev/null @@ -1,2 +0,0 @@ -CommandSecondTransfer,b2b9189a22542814f4d696dac08a33668c10a2140fa266e5b42f86b27502ce13 -CommandFirstTransfer,e70cbc8584c84e982c1d2b03d84bcfbc6a55743c33fa617e7af38fd99297cfe3 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.list.csv b/tests/integration/Command/Generated/Success/transfer-object.list.csv new file mode 100644 index 00000000..9fcbac28 --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.list.csv @@ -0,0 +1 @@ +CommandFirstTransfer,f231b26aaeee6d207b07f4b16d0333e8f8b2c2ca527fde60197e5d44214bb654 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.second.list.csv b/tests/integration/Command/Generated/Success/transfer-object.second.list.csv deleted file mode 100644 index c4931c58..00000000 --- a/tests/integration/Command/Generated/Success/transfer-object.second.list.csv +++ /dev/null @@ -1 +0,0 @@ -CommandThirdTransfer,2a18df50cc97bbbf28047407610584ce97fd61939bbb713e5536994b12f523f6 \ No newline at end of file diff --git a/tests/integration/Command/TransferGeneratorBulkCommandTest.php b/tests/integration/Command/TransferGeneratorBulkCommandTest.php index a44fe393..165a32e3 100644 --- a/tests/integration/Command/TransferGeneratorBulkCommandTest.php +++ b/tests/integration/Command/TransferGeneratorBulkCommandTest.php @@ -9,7 +9,6 @@ use PHPUnit\Framework\TestCase; use Picamator\Tests\Integration\TransferObject\Helper\FailedFiberTrait; use Picamator\TransferObject\Command\TransferGeneratorBulkCommand; -use Picamator\TransferObject\Shared\Environment\Enum\EnvironmentEnum; use Picamator\TransferObject\TransferGenerator\TransferGeneratorFacadeInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -20,20 +19,21 @@ final class TransferGeneratorBulkCommandTest extends TestCase private const string SUCCESS_CONFIG_LIST_PATH = '/tests/integration/Command/data/config/success/config.list.txt'; + private const array SUCCESS_GENERATED_FILES = [ + 'Generated/Success/CommandBulkFirstTransfer.php', + 'Generated/Success/CommandBulkSecondTransfer.php', + 'Generated/Success/CommandBulkThirdTransfer.php', + 'Generated/Success/transfer-object.bulk.first.list.csv', + 'Generated/Success/transfer-object.bulk.second.list.csv', + ]; + private const string ERROR_CONFIG_EMPTY_LIST_PATH = '/tests/integration/Command/data/config/error/config.empty.list.txt'; private const string ERROR_CONFIG_LIST_PATH = '/tests/integration/Command/data/config/error/config.list.txt'; - private const string IS_CACHE_ENABLED = EnvironmentEnum::IS_CACHE_ENABLED->value; - private CommandTester $commandTester; - public static function setUpBeforeClass(): void - { - putenv(self::IS_CACHE_ENABLED . '=0'); - } - protected function setUp(): void { $command = new TransferGeneratorBulkCommand(); @@ -76,6 +76,10 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): // Assert $this->commandTester->assertCommandIsSuccessful($output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); + + foreach (self::SUCCESS_GENERATED_FILES as $file) { + $this->assertFileExists(__DIR__ . '/' . $file); + } } #[TestDox('Run command with empty configuration should show error message')] diff --git a/tests/integration/Command/TransferGeneratorCommandTest.php b/tests/integration/Command/TransferGeneratorCommandTest.php index 1505f5f0..141e2d97 100644 --- a/tests/integration/Command/TransferGeneratorCommandTest.php +++ b/tests/integration/Command/TransferGeneratorCommandTest.php @@ -19,7 +19,7 @@ final class TransferGeneratorCommandTest extends TestCase use FailedFiberTrait; private const string SUCCESS_CONFIG_PATH - = '/tests/integration/Command/data/config/success/generator.first.config.yml'; + = '/tests/integration/Command/data/config/success/generator.config.yml'; private const string ERROR_CONFIG_PATH = '/tests/integration/Command/data/config/error/generator.config.yml'; @@ -67,8 +67,7 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): // Assert $this->commandTester->assertCommandIsSuccessful($output); - $this->assertStringContainsString('command.first.transfer.yml: CommandFirstTransfer', $output); - $this->assertStringContainsString('command.second.transfer.yml: CommandSecondTransfer', $output); + $this->assertStringContainsString('command.transfer.yml: CommandFirstTransfer', $output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); } diff --git a/tests/integration/Command/data/config/success/config.list.txt b/tests/integration/Command/data/config/success/config.list.txt index e19fca3c..24813562 100644 --- a/tests/integration/Command/data/config/success/config.list.txt +++ b/tests/integration/Command/data/config/success/config.list.txt @@ -1,2 +1,2 @@ -tests/integration/Command/data/config/success/generator.first.config.yml -tests/integration/Command/data/config/success/generator.second.config.yml +tests/integration/Command/data/config/success/generator.bulk.first.config.yml +tests/integration/Command/data/config/success/generator.bulk.second.config.yml diff --git a/tests/integration/Command/data/config/success/definition-bulk-first/command.first.transfer.yml b/tests/integration/Command/data/config/success/definition-bulk-first/command.first.transfer.yml new file mode 100644 index 00000000..29381a9e --- /dev/null +++ b/tests/integration/Command/data/config/success/definition-bulk-first/command.first.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +CommandBulkFirst: + run: + type: true diff --git a/tests/integration/Command/data/config/success/definition-first/command.second.transfer.yml b/tests/integration/Command/data/config/success/definition-bulk-first/command.second.transfer.yml similarity index 81% rename from tests/integration/Command/data/config/success/definition-first/command.second.transfer.yml rename to tests/integration/Command/data/config/success/definition-bulk-first/command.second.transfer.yml index 9e67b7b9..9a8f6249 100644 --- a/tests/integration/Command/data/config/success/definition-first/command.second.transfer.yml +++ b/tests/integration/Command/data/config/success/definition-bulk-first/command.second.transfer.yml @@ -1,4 +1,4 @@ # $schema: ./../../../../../../../schema/definition.schema.json -CommandSecond: +CommandBulkSecond: run: type: true diff --git a/tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml b/tests/integration/Command/data/config/success/definition-bulk-second/command.third.transfer.yml similarity index 82% rename from tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml rename to tests/integration/Command/data/config/success/definition-bulk-second/command.third.transfer.yml index 0e8e7a13..3ee7e8cf 100644 --- a/tests/integration/Command/data/config/success/definition-second/command.third.transfer.yml +++ b/tests/integration/Command/data/config/success/definition-bulk-second/command.third.transfer.yml @@ -1,4 +1,4 @@ # $schema: ./../../../../../../../schema/definition.schema.json -CommandThird: +CommandBulkThird: run: type: true diff --git a/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml b/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml new file mode 100644 index 00000000..17b47389 --- /dev/null +++ b/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +CommandCacheDisabledFirst: + run: + type: true diff --git a/tests/integration/Command/data/config/success/definition-first/command.first.transfer.yml b/tests/integration/Command/data/config/success/definition/command.transfer.yml similarity index 100% rename from tests/integration/Command/data/config/success/definition-first/command.first.transfer.yml rename to tests/integration/Command/data/config/success/definition/command.transfer.yml diff --git a/tests/integration/Command/data/config/success/generator.second.config.yml b/tests/integration/Command/data/config/success/generator.bulk.first.config.yml similarity index 76% rename from tests/integration/Command/data/config/success/generator.second.config.yml rename to tests/integration/Command/data/config/success/generator.bulk.first.config.yml index 1af45549..1dc9e10f 100644 --- a/tests/integration/Command/data/config/success/generator.second.config.yml +++ b/tests/integration/Command/data/config/success/generator.bulk.first.config.yml @@ -2,5 +2,5 @@ generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" - definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-second" - hashFileName: "transfer-object.second.list.csv" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-bulk-first" + hashFileName: "transfer-object.bulk.first.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.bulk.second.config.yml b/tests/integration/Command/data/config/success/generator.bulk.second.config.yml new file mode 100644 index 00000000..c09b7dbe --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.bulk.second.config.yml @@ -0,0 +1,6 @@ +# $schema: ./../../../../../../schema/config.schema.json +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" + transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-bulk-second" + hashFileName: "transfer-object.bulk.second.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml b/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml new file mode 100644 index 00000000..5cc3d1b2 --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml @@ -0,0 +1,6 @@ +# $schema: ./../../../../../../schema/config.schema.json +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" + transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-cache-disabled" + hashFileName: "transfer-object.cache.disabled.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.first.config.yml b/tests/integration/Command/data/config/success/generator.config.yml similarity index 78% rename from tests/integration/Command/data/config/success/generator.first.config.yml rename to tests/integration/Command/data/config/success/generator.config.yml index 394c1573..50cc1c8b 100644 --- a/tests/integration/Command/data/config/success/generator.first.config.yml +++ b/tests/integration/Command/data/config/success/generator.config.yml @@ -2,5 +2,5 @@ generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" - definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-first" - hashFileName: "transfer-object.first.list.csv" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition" + hashFileName: "transfer-object.list.csv" diff --git a/tests/integration/Helper/EnvironmentTrait.php b/tests/integration/Helper/EnvironmentTrait.php new file mode 100644 index 00000000..ddeaad0e --- /dev/null +++ b/tests/integration/Helper/EnvironmentTrait.php @@ -0,0 +1,22 @@ +value; + + final protected static function turnOffCache(): void + { + putenv(self::IS_CACHE_ENABLED . '=0'); + } + + final protected static function turnOnCache(): void + { + putenv(self::IS_CACHE_ENABLED . '=1'); + } +} From a29c044291ff8899369ce7212197718874af039f Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 10:57:00 +0100 Subject: [PATCH 17/56] Added file modified assert for CacheDisabledTransferGeneratorCommandTest --- ...heDisabledTransferGeneratorCommandTest.php | 18 ++++-- .../TransferGeneratorBulkCommandTest.php | 17 +++-- tests/integration/Helper/FileTrait.php | 64 +++++++++++++++++++ 3 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 tests/integration/Helper/FileTrait.php diff --git a/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php index 9d829656..d2fb7802 100644 --- a/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php +++ b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Picamator\Tests\Integration\TransferObject\Helper\EnvironmentTrait; use Picamator\Tests\Integration\TransferObject\Helper\FailedFiberTrait; +use Picamator\Tests\Integration\TransferObject\Helper\FileTrait; use Picamator\TransferObject\Command\TransferGeneratorCommand; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -18,13 +19,14 @@ final class CacheDisabledTransferGeneratorCommandTest extends TestCase { use FailedFiberTrait; use EnvironmentTrait; + use FileTrait; private const string SUCCESS_CONFIG_PATH = '/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml'; private const array SUCCESS_GENERATED_FILES = [ - 'Generated/Success/CommandCacheDisabledFirstTransfer.php', - 'Generated/Success/transfer-object.cache.disabled.list.csv', + __DIR__ . '/Generated/Success/CommandCacheDisabledFirstTransfer.php', + __DIR__ . '/Generated/Success/transfer-object.cache.disabled.list.csv', ]; private CommandTester $commandTester; @@ -45,9 +47,12 @@ protected function setUp(): void $this->commandTester = new CommandTester($command); } - #[TestDox('Run command with valid configuration should show success message')] + #[TestDox('Run command with disabled cache should show always regenerate transfers')] public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): void { + // Arrange + $modifiedTimesBefore = $this->getModifiedTimes(self::SUCCESS_GENERATED_FILES); + // Act $this->commandTester->execute( ['-c' => self::SUCCESS_CONFIG_PATH], @@ -55,13 +60,14 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): ); $output = $this->commandTester->getDisplay(); + $modifiedTimesAfter = $this->getModifiedTimes(self::SUCCESS_GENERATED_FILES); + // Assert $this->commandTester->assertCommandIsSuccessful($output); $this->assertStringContainsString('command.transfer.yml: CommandCacheDisabledFirst', $output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); - foreach (self::SUCCESS_GENERATED_FILES as $file) { - $this->assertFileExists(__DIR__ . '/' . $file); - } + $this->assertFilesExist(self::SUCCESS_GENERATED_FILES); + $this->assertNotSameModifiedTimes($modifiedTimesBefore, $modifiedTimesAfter); } } diff --git a/tests/integration/Command/TransferGeneratorBulkCommandTest.php b/tests/integration/Command/TransferGeneratorBulkCommandTest.php index 165a32e3..81ab59d7 100644 --- a/tests/integration/Command/TransferGeneratorBulkCommandTest.php +++ b/tests/integration/Command/TransferGeneratorBulkCommandTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; use Picamator\Tests\Integration\TransferObject\Helper\FailedFiberTrait; +use Picamator\Tests\Integration\TransferObject\Helper\FileTrait; use Picamator\TransferObject\Command\TransferGeneratorBulkCommand; use Picamator\TransferObject\TransferGenerator\TransferGeneratorFacadeInterface; use Symfony\Component\Console\Tester\CommandTester; @@ -16,15 +17,16 @@ final class TransferGeneratorBulkCommandTest extends TestCase { use FailedFiberTrait; + use FileTrait; private const string SUCCESS_CONFIG_LIST_PATH = '/tests/integration/Command/data/config/success/config.list.txt'; private const array SUCCESS_GENERATED_FILES = [ - 'Generated/Success/CommandBulkFirstTransfer.php', - 'Generated/Success/CommandBulkSecondTransfer.php', - 'Generated/Success/CommandBulkThirdTransfer.php', - 'Generated/Success/transfer-object.bulk.first.list.csv', - 'Generated/Success/transfer-object.bulk.second.list.csv', + __DIR__ . '/Generated/Success/CommandBulkFirstTransfer.php', + __DIR__ . '/Generated/Success/CommandBulkSecondTransfer.php', + __DIR__ . '/Generated/Success/CommandBulkThirdTransfer.php', + __DIR__ . '/Generated/Success/transfer-object.bulk.first.list.csv', + __DIR__ . '/Generated/Success/transfer-object.bulk.second.list.csv', ]; private const string ERROR_CONFIG_EMPTY_LIST_PATH @@ -76,10 +78,7 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): // Assert $this->commandTester->assertCommandIsSuccessful($output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); - - foreach (self::SUCCESS_GENERATED_FILES as $file) { - $this->assertFileExists(__DIR__ . '/' . $file); - } + $this->assertFilesExist(self::SUCCESS_GENERATED_FILES); } #[TestDox('Run command with empty configuration should show error message')] diff --git a/tests/integration/Helper/FileTrait.php b/tests/integration/Helper/FileTrait.php new file mode 100644 index 00000000..2576509f --- /dev/null +++ b/tests/integration/Helper/FileTrait.php @@ -0,0 +1,64 @@ + $paths + */ + final protected function assertFilesExist(array $paths): void + { + foreach ($paths as $file) { + $this->assertFileExists($file); + } + } + + /** + * @param array $paths + * + * @return array + */ + final protected function getModifiedTimes(array $paths): array + { + $modificationTimes = []; + foreach ($paths as $path) { + $modificationTime = filemtime($path); + + $this->assertNotFalse( + $modificationTime, + sprintf('Failed to get file "%s" modified date.', $path), + ); + + $modificationTimes[$path] = $modificationTime; + } + + return $modificationTimes; + } + + /** + * @param array $modifiedTimesBefore + * @param array $modifiedTimesAfter + */ + final protected function assertNotSameModifiedTimes(array $modifiedTimesBefore, array $modifiedTimesAfter): void + { + $this->assertSameSize( + $modifiedTimesBefore, + $modifiedTimesAfter, + 'Modified times should be the same size.', + ); + + foreach ($modifiedTimesBefore as $path => $modifiedTimeBefore) { + $this->assertNotSame( + $modifiedTimeBefore, + $modifiedTimesAfter[$path], + sprintf('Modified file time "%s" should be different.', $path), + ); + } + } +} From 3bd5e78875cfc97cc272c74ba7dda00b3aa4d106 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 17:16:08 +0100 Subject: [PATCH 18/56] Covered transfer generator command with the test case to rotate file with modified shash and delete the obsolite transfer --- src/Shared/Environment/EnvironmentReader.php | 4 +- .../Generator/Writer/TransferWriter.php | 2 +- ...heDisabledTransferGeneratorCommandTest.php | 20 +++-- .../FileHashTransferGeneratorCommandTest.php | 81 +++++++++++++++++++ ...r.php => CommandCacheDisabledTransfer.php} | 2 +- .../Success/CommandFileHashTransfer.php | 36 +++++++++ ...dFirstTransfer.php => CommandTransfer.php} | 2 +- .../transfer-object.cache.disabled.list.csv | 2 +- .../transfer-object.file.hash.list.csv | 1 + .../Success/transfer-object.list.csv | 2 +- .../Command/TransferGeneratorCommandTest.php | 2 +- .../command.transfer.yml | 2 +- .../definition-file-hash/command.transfer.yml | 4 + .../success/definition/command.transfer.yml | 2 +- .../success/generator.file.hash.config.yml | 6 ++ tests/integration/Helper/FileTrait.php | 52 +++++++++--- var/config/config.list.txt | 4 + 17 files changed, 191 insertions(+), 33 deletions(-) create mode 100644 tests/integration/Command/FileHashTransferGeneratorCommandTest.php rename tests/integration/Command/Generated/Success/{CommandCacheDisabledFirstTransfer.php => CommandCacheDisabledTransfer.php} (93%) create mode 100644 tests/integration/Command/Generated/Success/CommandFileHashTransfer.php rename tests/integration/Command/Generated/Success/{CommandFirstTransfer.php => CommandTransfer.php} (94%) create mode 100644 tests/integration/Command/Generated/Success/transfer-object.file.hash.list.csv create mode 100644 tests/integration/Command/data/config/success/definition-file-hash/command.transfer.yml create mode 100644 tests/integration/Command/data/config/success/generator.file.hash.config.yml diff --git a/src/Shared/Environment/EnvironmentReader.php b/src/Shared/Environment/EnvironmentReader.php index 4f614e7a..66834822 100644 --- a/src/Shared/Environment/EnvironmentReader.php +++ b/src/Shared/Environment/EnvironmentReader.php @@ -24,7 +24,7 @@ public function getProjectRoot(): string public function getMaxFileSizeMegabytes(): int { - $environment = EnvironmentEnum::MAX_FILE_SIZE_MB; + $environment = @EnvironmentEnum::MAX_FILE_SIZE_MB; $maxFileSize = (int)$this->getEnvironment($environment); if ($maxFileSize === 0) { @@ -44,7 +44,7 @@ public function getMaxFileSizeBytes(): int public function getIsCacheEnabled(): bool { - $isCacheEnabled = $this->getEnvironment(EnvironmentEnum::IS_CACHE_ENABLED); + $isCacheEnabled = $this->getEnvironment(@EnvironmentEnum::IS_CACHE_ENABLED); return $isCacheEnabled === '1' || $isCacheEnabled === 'true' diff --git a/src/TransferGenerator/Generator/Writer/TransferWriter.php b/src/TransferGenerator/Generator/Writer/TransferWriter.php index 9b71fe64..488f637c 100644 --- a/src/TransferGenerator/Generator/Writer/TransferWriter.php +++ b/src/TransferGenerator/Generator/Writer/TransferWriter.php @@ -15,7 +15,7 @@ public function __construct( private TransferHashReaderInterface $hashReader, private GeneratorFilesystemInterface $filesystem, - private readonly ConfigInterface $config, + private ConfigInterface $config, ) { } diff --git a/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php index d2fb7802..003617e0 100644 --- a/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php +++ b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; use Picamator\Tests\Integration\TransferObject\Helper\EnvironmentTrait; -use Picamator\Tests\Integration\TransferObject\Helper\FailedFiberTrait; use Picamator\Tests\Integration\TransferObject\Helper\FileTrait; use Picamator\TransferObject\Command\TransferGeneratorCommand; use Symfony\Component\Console\Output\OutputInterface; @@ -17,15 +16,14 @@ #[Group('command')] final class CacheDisabledTransferGeneratorCommandTest extends TestCase { - use FailedFiberTrait; use EnvironmentTrait; use FileTrait; - private const string SUCCESS_CONFIG_PATH + private const string CONFIG_PATH = '/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml'; - private const array SUCCESS_GENERATED_FILES = [ - __DIR__ . '/Generated/Success/CommandCacheDisabledFirstTransfer.php', + private const array GENERATED_FILES = [ + __DIR__ . '/Generated/Success/CommandCacheDisabledTransfer.php', __DIR__ . '/Generated/Success/transfer-object.cache.disabled.list.csv', ]; @@ -48,26 +46,26 @@ protected function setUp(): void } #[TestDox('Run command with disabled cache should show always regenerate transfers')] - public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): void + public function testRunCommandWithDisabledCacheShouldAlwaysRegenerateTransfers(): void { // Arrange - $modifiedTimesBefore = $this->getModifiedTimes(self::SUCCESS_GENERATED_FILES); + $modifiedTimesBefore = $this->getModifiedTimes(self::GENERATED_FILES); // Act $this->commandTester->execute( - ['-c' => self::SUCCESS_CONFIG_PATH], + ['-c' => self::CONFIG_PATH], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE] ); $output = $this->commandTester->getDisplay(); - $modifiedTimesAfter = $this->getModifiedTimes(self::SUCCESS_GENERATED_FILES); + $modifiedTimesAfter = $this->getModifiedTimes(self::GENERATED_FILES); // Assert $this->commandTester->assertCommandIsSuccessful($output); - $this->assertStringContainsString('command.transfer.yml: CommandCacheDisabledFirst', $output); + $this->assertStringContainsString('command.transfer.yml: CommandCacheDisabled', $output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); - $this->assertFilesExist(self::SUCCESS_GENERATED_FILES); + $this->assertFilesExist(self::GENERATED_FILES); $this->assertNotSameModifiedTimes($modifiedTimesBefore, $modifiedTimesAfter); } } diff --git a/tests/integration/Command/FileHashTransferGeneratorCommandTest.php b/tests/integration/Command/FileHashTransferGeneratorCommandTest.php new file mode 100644 index 00000000..fa954483 --- /dev/null +++ b/tests/integration/Command/FileHashTransferGeneratorCommandTest.php @@ -0,0 +1,81 @@ +commandTester = new CommandTester($command); + } + + #[TestDox('Generate transfer objects where the file hash was changed and one to delete should rotate the files')] + public function testRunCommandWithHashFileChangeIncludeFileToDeleteShouldRotateTheFiles(): void + { + // Arrange + $this->saveFileContent(self::HASH_FILE_PATH, self::HASH_FILE_CONTENT); + $this->createEmptyFile(self::TRANSFER_TO_DELETE_PATH); + + $transferModifiedTimeBefore = $this->getModifiedTime(self::TRANSFER_TO_UPDATE_PATH); + + // Act + $this->commandTester->execute( + ['-c' => self::CONFIG_PATH], + ['verbosity' => OutputInterface::VERBOSITY_VERBOSE] + ); + $output = $this->commandTester->getDisplay(); + + $transferModifiedTimeAfter = $this->getModifiedTime(self::TRANSFER_TO_UPDATE_PATH); + + // Assert + $this->commandTester->assertCommandIsSuccessful($output); + $this->assertStringContainsString('command.transfer.yml: CommandFileHashTransfer', $output); + $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); + + $this->assertFilesExist(self::SUCCESS_GENERATED_FILES); + $this->assertNotSameModifiedTime($transferModifiedTimeBefore, $transferModifiedTimeAfter); + $this->assertFileDoesNotExist( + self::TRANSFER_TO_DELETE_PATH, + 'The file should be deleted', + ); + } +} diff --git a/tests/integration/Command/Generated/Success/CommandCacheDisabledFirstTransfer.php b/tests/integration/Command/Generated/Success/CommandCacheDisabledTransfer.php similarity index 93% rename from tests/integration/Command/Generated/Success/CommandCacheDisabledFirstTransfer.php rename to tests/integration/Command/Generated/Success/CommandCacheDisabledTransfer.php index 2f303dcf..6ed73aaf 100644 --- a/tests/integration/Command/Generated/Success/CommandCacheDisabledFirstTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandCacheDisabledTransfer.php @@ -15,7 +15,7 @@ * * @see /tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml Definition file path. */ -final class CommandCacheDisabledFirstTransfer extends AbstractTransfer +final class CommandCacheDisabledTransfer extends AbstractTransfer { protected const int META_DATA_SIZE = 1; diff --git a/tests/integration/Command/Generated/Success/CommandFileHashTransfer.php b/tests/integration/Command/Generated/Success/CommandFileHashTransfer.php new file mode 100644 index 00000000..9971ca06 --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandFileHashTransfer.php @@ -0,0 +1,36 @@ + self::RUN_INDEX, + ]; + + // run + public const string RUN_PROP = 'run'; + private const int RUN_INDEX = 0; + + public ?true $run { + get => $this->getData(self::RUN_INDEX); + set { + $this->setData(self::RUN_INDEX, $value); + } + } +} diff --git a/tests/integration/Command/Generated/Success/CommandFirstTransfer.php b/tests/integration/Command/Generated/Success/CommandTransfer.php similarity index 94% rename from tests/integration/Command/Generated/Success/CommandFirstTransfer.php rename to tests/integration/Command/Generated/Success/CommandTransfer.php index e475c5e1..4638e665 100644 --- a/tests/integration/Command/Generated/Success/CommandFirstTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandTransfer.php @@ -15,7 +15,7 @@ * * @see /tests/integration/Command/data/config/success/definition/command.transfer.yml Definition file path. */ -final class CommandFirstTransfer extends AbstractTransfer +final class CommandTransfer extends AbstractTransfer { protected const int META_DATA_SIZE = 1; diff --git a/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv b/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv index 9fd18695..5fe0a8c1 100644 --- a/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv +++ b/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv @@ -1 +1 @@ -CommandCacheDisabledFirstTransfer,fcd98ca6044b14ef0a11dfd4190fe7eb35659aca6205a5cf68136c56735e1469 \ No newline at end of file +CommandCacheDisabledTransfer,83617e2ec05bf88b6601a41991a0e0a947453b72887f74bb9de25cfbee45ec06 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.file.hash.list.csv b/tests/integration/Command/Generated/Success/transfer-object.file.hash.list.csv new file mode 100644 index 00000000..9978c599 --- /dev/null +++ b/tests/integration/Command/Generated/Success/transfer-object.file.hash.list.csv @@ -0,0 +1 @@ +CommandFileHashTransfer,d2a2912c58b8a385fc3ff1a357d8276e7efba9ecc6395d9e89423346c489fc31 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/transfer-object.list.csv b/tests/integration/Command/Generated/Success/transfer-object.list.csv index 9fcbac28..74278e8e 100644 --- a/tests/integration/Command/Generated/Success/transfer-object.list.csv +++ b/tests/integration/Command/Generated/Success/transfer-object.list.csv @@ -1 +1 @@ -CommandFirstTransfer,f231b26aaeee6d207b07f4b16d0333e8f8b2c2ca527fde60197e5d44214bb654 \ No newline at end of file +CommandTransfer,0410ef0d7c05257ea2df77174ba68c94186f78d99d0d65a7d62b78b5b4797e42 \ No newline at end of file diff --git a/tests/integration/Command/TransferGeneratorCommandTest.php b/tests/integration/Command/TransferGeneratorCommandTest.php index 141e2d97..3f5520bf 100644 --- a/tests/integration/Command/TransferGeneratorCommandTest.php +++ b/tests/integration/Command/TransferGeneratorCommandTest.php @@ -67,7 +67,7 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): // Assert $this->commandTester->assertCommandIsSuccessful($output); - $this->assertStringContainsString('command.transfer.yml: CommandFirstTransfer', $output); + $this->assertStringContainsString('command.transfer.yml: CommandTransfer', $output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); } diff --git a/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml b/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml index 17b47389..87429c32 100644 --- a/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml +++ b/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml @@ -1,4 +1,4 @@ # $schema: ./../../../../../../../schema/definition.schema.json -CommandCacheDisabledFirst: +CommandCacheDisabled: run: type: true diff --git a/tests/integration/Command/data/config/success/definition-file-hash/command.transfer.yml b/tests/integration/Command/data/config/success/definition-file-hash/command.transfer.yml new file mode 100644 index 00000000..ad245440 --- /dev/null +++ b/tests/integration/Command/data/config/success/definition-file-hash/command.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +CommandFileHash: + run: + type: true diff --git a/tests/integration/Command/data/config/success/definition/command.transfer.yml b/tests/integration/Command/data/config/success/definition/command.transfer.yml index 4c2cf5f4..656e48bd 100644 --- a/tests/integration/Command/data/config/success/definition/command.transfer.yml +++ b/tests/integration/Command/data/config/success/definition/command.transfer.yml @@ -1,4 +1,4 @@ # $schema: ./../../../../../../../schema/definition.schema.json -CommandFirst: +Command: run: type: true diff --git a/tests/integration/Command/data/config/success/generator.file.hash.config.yml b/tests/integration/Command/data/config/success/generator.file.hash.config.yml new file mode 100644 index 00000000..d3067f5e --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.file.hash.config.yml @@ -0,0 +1,6 @@ +# $schema: ./../../../../../../schema/config.schema.json +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" + transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-file-hash" + hashFileName: "transfer-object.file.hash.list.csv" diff --git a/tests/integration/Helper/FileTrait.php b/tests/integration/Helper/FileTrait.php index 2576509f..54f807fa 100644 --- a/tests/integration/Helper/FileTrait.php +++ b/tests/integration/Helper/FileTrait.php @@ -28,19 +28,24 @@ final protected function getModifiedTimes(array $paths): array { $modificationTimes = []; foreach ($paths as $path) { - $modificationTime = filemtime($path); - - $this->assertNotFalse( - $modificationTime, - sprintf('Failed to get file "%s" modified date.', $path), - ); - - $modificationTimes[$path] = $modificationTime; + $modificationTimes[$path] = $this->getModifiedTime($path); } return $modificationTimes; } + final protected function getModifiedTime(string $path): int + { + $modificationTime = filemtime($path); + + $this->assertNotFalse( + $modificationTime, + sprintf('Failed to get file "%s" modified date.', $path), + ); + + return $modificationTime; + } + /** * @param array $modifiedTimesBefore * @param array $modifiedTimesAfter @@ -54,11 +59,34 @@ final protected function assertNotSameModifiedTimes(array $modifiedTimesBefore, ); foreach ($modifiedTimesBefore as $path => $modifiedTimeBefore) { - $this->assertNotSame( - $modifiedTimeBefore, - $modifiedTimesAfter[$path], - sprintf('Modified file time "%s" should be different.', $path), + $this->assertArrayHasKey( + $path, + $modifiedTimesAfter, + sprintf('Modified Time Before "%s" should exist.', $path), ); + + $this->assertNotSameModifiedTime($modifiedTimeBefore, $modifiedTimesAfter[$path]); } } + + final protected function assertNotSameModifiedTime(int $modifiedTimeBefore, int $modifiedTimeAfter): void + { + $this->assertNotSame( + $modifiedTimeBefore, + $modifiedTimeAfter, + 'Modified file time should be different.', + ); + } + + final protected function saveFileContent(string $path, string $content): void + { + $result = file_put_contents($path, $content); + $this->assertNotFalse($result, sprintf('Failed to save content to "%s".', $path)); + } + + final protected function createEmptyFile(string $path): void + { + $result = touch($path); + $this->assertNotFalse($result, sprintf('Failed to create file "%s".', $path)); + } } diff --git a/var/config/config.list.txt b/var/config/config.list.txt index dd24e672..5cda6395 100644 --- a/var/config/config.list.txt +++ b/var/config/config.list.txt @@ -4,6 +4,10 @@ ./tests/integration/Transfer/data/config/bcmath/generator.config.yml ./tests/integration/TransferGenerator/data/config/success/generator.config.yml ./tests/integration/Command/data/config/success/generator.config.yml +./tests/integration/Command/data/config/success/generator.bulk.first.config.yml +./tests/integration/Command/data/config/success/generator.bulk.second.config.yml +./tests/integration/Command/data/config/success/generator.cache.disabled.config.yml +./tests/integration/Command/data/config/success/generator.file.hash.config.yml ./tests/integration/DefinitionGenerator/data/config/open-weather/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/google-shopping-content/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/frankfurter-dev-v1/generator.config.yml From 52b85500085d452dcd293e7c689d72c6ecfd9007 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 17:25:21 +0100 Subject: [PATCH 19/56] Covered config hash file name by tests --- .../TransferGenerator/Config/Loader/ConfigLoaderTest.php | 2 ++ .../data/config/error/invalid-hash-file-name.config.yml | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml diff --git a/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php b/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php index eaa7b604..6f41a358 100644 --- a/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php +++ b/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php @@ -60,5 +60,7 @@ public static function invalidConfigDataProvider(): Generator yield 'invalid definition root key' => ['invalid-definition-root-key.config.yml']; yield 'transfer path is not local' => ['transfer-path-is-not-local.config.yml']; + + yield 'invalid hash file name' => ['invalid-hash-file-name.config.yml']; } } diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml new file mode 100644 index 00000000..79f91930 --- /dev/null +++ b/tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml @@ -0,0 +1,5 @@ +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" + transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" + definitionPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/data/config/error/missed-type/definition" + hashFileName: "invalid-hash-file-name.txt" From 352b5374aa4dacea7601641268c2b277ace86fb3 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 17:41:31 +0100 Subject: [PATCH 20/56] Improved TransferGeneratorCommandTest code coverage --- ...dTransfer.php => CommandFirstTransfer.php} | 4 +-- .../Success/CommandSecondTransfer.php | 36 +++++++++++++++++++ .../Success/transfer-object.list.csv | 3 +- .../Command/TransferGeneratorCommandTest.php | 3 +- ...ransfer.yml => command.first.transfer.yml} | 2 +- .../definition/command.second.transfer.yml | 4 +++ 6 files changed, 47 insertions(+), 5 deletions(-) rename tests/integration/Command/Generated/Success/{CommandTransfer.php => CommandFirstTransfer.php} (89%) create mode 100644 tests/integration/Command/Generated/Success/CommandSecondTransfer.php rename tests/integration/Command/data/config/success/definition/{command.transfer.yml => command.first.transfer.yml} (86%) create mode 100644 tests/integration/Command/data/config/success/definition/command.second.transfer.yml diff --git a/tests/integration/Command/Generated/Success/CommandTransfer.php b/tests/integration/Command/Generated/Success/CommandFirstTransfer.php similarity index 89% rename from tests/integration/Command/Generated/Success/CommandTransfer.php rename to tests/integration/Command/Generated/Success/CommandFirstTransfer.php index 4638e665..e751728a 100644 --- a/tests/integration/Command/Generated/Success/CommandTransfer.php +++ b/tests/integration/Command/Generated/Success/CommandFirstTransfer.php @@ -13,9 +13,9 @@ * * Note: Do not manually edit this file, as changes will be overwritten. * - * @see /tests/integration/Command/data/config/success/definition/command.transfer.yml Definition file path. + * @see /tests/integration/Command/data/config/success/definition/command.first.transfer.yml Definition file path. */ -final class CommandTransfer extends AbstractTransfer +final class CommandFirstTransfer extends AbstractTransfer { protected const int META_DATA_SIZE = 1; diff --git a/tests/integration/Command/Generated/Success/CommandSecondTransfer.php b/tests/integration/Command/Generated/Success/CommandSecondTransfer.php new file mode 100644 index 00000000..24c1d03f --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandSecondTransfer.php @@ -0,0 +1,36 @@ + self::RUN_INDEX, + ]; + + // run + public const string RUN_PROP = 'run'; + private const int RUN_INDEX = 0; + + public ?true $run { + get => $this->getData(self::RUN_INDEX); + set { + $this->setData(self::RUN_INDEX, $value); + } + } +} diff --git a/tests/integration/Command/Generated/Success/transfer-object.list.csv b/tests/integration/Command/Generated/Success/transfer-object.list.csv index 74278e8e..a0d1ffdb 100644 --- a/tests/integration/Command/Generated/Success/transfer-object.list.csv +++ b/tests/integration/Command/Generated/Success/transfer-object.list.csv @@ -1 +1,2 @@ -CommandTransfer,0410ef0d7c05257ea2df77174ba68c94186f78d99d0d65a7d62b78b5b4797e42 \ No newline at end of file +CommandSecondTransfer,74cefcbf8a44fa6b6981c11e6b8c8dec8b2da152755d6c70bafd38f1eb5c847c +CommandFirstTransfer,08c25060e2d59248791d1f727d85bd2a6a6a6186289430c6421cc96db090b1c7 \ No newline at end of file diff --git a/tests/integration/Command/TransferGeneratorCommandTest.php b/tests/integration/Command/TransferGeneratorCommandTest.php index 3f5520bf..3f153922 100644 --- a/tests/integration/Command/TransferGeneratorCommandTest.php +++ b/tests/integration/Command/TransferGeneratorCommandTest.php @@ -67,7 +67,8 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): // Assert $this->commandTester->assertCommandIsSuccessful($output); - $this->assertStringContainsString('command.transfer.yml: CommandTransfer', $output); + $this->assertStringContainsString('command.first.transfer.yml: CommandFirstTransfer', $output); + $this->assertStringContainsString('command.second.transfer.yml: CommandSecondTransfer', $output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); } diff --git a/tests/integration/Command/data/config/success/definition/command.transfer.yml b/tests/integration/Command/data/config/success/definition/command.first.transfer.yml similarity index 86% rename from tests/integration/Command/data/config/success/definition/command.transfer.yml rename to tests/integration/Command/data/config/success/definition/command.first.transfer.yml index 656e48bd..4c2cf5f4 100644 --- a/tests/integration/Command/data/config/success/definition/command.transfer.yml +++ b/tests/integration/Command/data/config/success/definition/command.first.transfer.yml @@ -1,4 +1,4 @@ # $schema: ./../../../../../../../schema/definition.schema.json -Command: +CommandFirst: run: type: true diff --git a/tests/integration/Command/data/config/success/definition/command.second.transfer.yml b/tests/integration/Command/data/config/success/definition/command.second.transfer.yml new file mode 100644 index 00000000..9e67b7b9 --- /dev/null +++ b/tests/integration/Command/data/config/success/definition/command.second.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +CommandSecond: + run: + type: true From 7bbdf4af0c5ad1cad360883ced778de6bf6afc8b Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 17:45:16 +0100 Subject: [PATCH 21/56] Removed copy method from Filesystem --- .../Filesystem/FilesystemBridge.php | 19 ------------------- .../Filesystem/FilesystemInterface.php | 5 ----- 2 files changed, 24 deletions(-) diff --git a/src/Dependency/Filesystem/FilesystemBridge.php b/src/Dependency/Filesystem/FilesystemBridge.php index 4cbced9b..f570ae13 100644 --- a/src/Dependency/Filesystem/FilesystemBridge.php +++ b/src/Dependency/Filesystem/FilesystemBridge.php @@ -15,25 +15,6 @@ public function __construct( ) { } - public function copy(string $originFile, string $targetFile): void - { - try { - $this->filesystem->copy($originFile, $targetFile); - // @codeCoverageIgnoreStart - } catch (Throwable $e) { - throw new FilesystemException( - sprintf( - 'Failed to copy "%s" to "%s". Error: "%s".', - $originFile, - $targetFile, - $e->getMessage(), - ), - previous: $e, - ); - } - // @codeCoverageIgnoreEnd - } - public function rename(string $origin, string $target, bool $overwrite = false): void { try { diff --git a/src/Dependency/Filesystem/FilesystemInterface.php b/src/Dependency/Filesystem/FilesystemInterface.php index f51a939a..163def15 100644 --- a/src/Dependency/Filesystem/FilesystemInterface.php +++ b/src/Dependency/Filesystem/FilesystemInterface.php @@ -6,11 +6,6 @@ interface FilesystemInterface { - /** - * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - */ - public function copy(string $originFile, string $targetFile): void; - /** * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException */ From 47479731ff4b5c66b24bf3f94b725686fcaac471 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 17:46:28 +0100 Subject: [PATCH 22/56] Removed unused method from FinderInterface --- src/Dependency/Finder/FinderBridge.php | 30 ----------------------- src/Dependency/Finder/FinderInterface.php | 11 --------- 2 files changed, 41 deletions(-) diff --git a/src/Dependency/Finder/FinderBridge.php b/src/Dependency/Finder/FinderBridge.php index 5d9fcc11..21df178b 100644 --- a/src/Dependency/Finder/FinderBridge.php +++ b/src/Dependency/Finder/FinderBridge.php @@ -44,36 +44,6 @@ public function findFilesInDirectory( // @codeCoverageIgnoreEnd } - public function findFilesInDirectoryExclude( - string $filePattern, - string $dirName, - string $exclude, - ): IteratorAggregate&Countable { - try { - $finder = Finder::create() - ->files() - ->name($filePattern) - ->depth(0) - ->in($dirName) - ->exclude($exclude); - - return $this->getFinderBridge($finder); - // @codeCoverageIgnoreStart - } catch (Throwable $e) { - throw new FinderException( - sprintf( - 'Failed to find files "%s" in directory "%s" excluding "%s". Error: "%s".', - $filePattern, - $dirName, - $exclude, - $e->getMessage(), - ), - previous: $e, - ); - } - // @codeCoverageIgnoreEnd - } - /** * @return Countable&IteratorAggregate */ diff --git a/src/Dependency/Finder/FinderInterface.php b/src/Dependency/Finder/FinderInterface.php index 64c585cc..c23a18b7 100644 --- a/src/Dependency/Finder/FinderInterface.php +++ b/src/Dependency/Finder/FinderInterface.php @@ -19,15 +19,4 @@ public function findFilesInDirectory( string $dirName, ?string $maxFileSize = null, ): IteratorAggregate&Countable; - - /** - * @throws \Picamator\TransferObject\Dependency\Exception\FinderException - * - * @return Countable&IteratorAggregate - */ - public function findFilesInDirectoryExclude( - string $filePattern, - string $dirName, - string $exclude, - ): IteratorAggregate&Countable; } From e9936f747e527db9cd118737094ca931c03e26c2 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 18:12:16 +0100 Subject: [PATCH 23/56] Improved ConfigReader test coverage --- .../Config/Reader/ConfigReaderTest.php | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tests/unit/TransferGenerator/Config/Reader/ConfigReaderTest.php diff --git a/tests/unit/TransferGenerator/Config/Reader/ConfigReaderTest.php b/tests/unit/TransferGenerator/Config/Reader/ConfigReaderTest.php new file mode 100644 index 00000000..54348c45 --- /dev/null +++ b/tests/unit/TransferGenerator/Config/Reader/ConfigReaderTest.php @@ -0,0 +1,128 @@ +validatorMock = $this->createMock(ConfigValidatorInterface::class); + $this->parserMock = $this->createMock(ConfigParserInterface::class); + $this->builderMock = $this->createMock(ConfigBuilderInterface::class); + + $this->configReader = new ConfigReader( + $this->validatorMock, + $this->parserMock, + $this->builderMock, + ); + } + + #[TestDox('Validate file throws exception should return false')] + public function testValidateFileThrowsExceptionShouldReturnFalse(): void + { + // Arrange + $configPath = 'some-config-path.config.yml'; + $expectedConfigTransfer = $this->createInvalidConfigTransfer(); + + // Expect + $this->validatorMock->expects($this->once()) + ->method('validateFile') + ->with($configPath) + ->willThrowException(new FilesystemException()); + + $this->validatorMock->expects($this->never()) + ->method('validateContent') + ->with($configPath) + ->seal(); + + $this->parserMock->expects($this->never()) + ->method('parseConfig') + ->seal(); + + $this->builderMock->expects($this->once()) + ->method('createErrorConfigTransfer') + ->willReturn($expectedConfigTransfer) + ->seal(); + + // Act + $actual = $this->configReader->getConfig($configPath); + + // Assert + $this->assertFalse($actual->validator->isValid); + } + + #[TestDox('Validate content throws exception should return false')] + public function testValidateContentThrowsExceptionShouldReturnFalse(): void + { + // Arrange + $configPath = 'some-config-path.config.yml'; + $expectedConfigTransfer = $this->createInvalidConfigTransfer(); + + $contentTransfer = new ConfigContentTransfer(); + + $fileValidatorTransfer = new ValidatorTransfer([ + ValidatorTransfer::IS_VALID_PROP => true, + ]); + + // Expect + $this->validatorMock->expects($this->once()) + ->method('validateFile') + ->with($configPath) + ->willReturn($fileValidatorTransfer); + + $this->validatorMock->expects($this->once()) + ->method('validateContent') + ->willThrowException(new FilesystemException()) + ->seal(); + + $this->parserMock->expects($this->once()) + ->method('parseConfig') + ->willReturn($contentTransfer) + ->seal(); + + $this->builderMock->expects($this->once()) + ->method('createErrorConfigTransfer') + ->willReturn($expectedConfigTransfer) + ->seal(); + + // Act + $actual = $this->configReader->getConfig($configPath); + + // Assert + $this->assertFalse($actual->validator->isValid); + } + + private function createInvalidConfigTransfer(): ConfigTransfer + { + return new ConfigTransfer([ + ConfigTransfer::VALIDATOR_PROP => [ + ValidatorTransfer::IS_VALID_PROP => false, + ], + ]); + } +} From dfc183753176ba8d52004602c9356579bf61e30f Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 18:30:47 +0100 Subject: [PATCH 24/56] Improved AttributesPropertyExpander test coverage --- .../AttributesPropertyExpanderTest.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/unit/TransferGenerator/Definition/Parser/Expander/AttributesPropertyExpanderTest.php diff --git a/tests/unit/TransferGenerator/Definition/Parser/Expander/AttributesPropertyExpanderTest.php b/tests/unit/TransferGenerator/Definition/Parser/Expander/AttributesPropertyExpanderTest.php new file mode 100644 index 00000000..d9125972 --- /dev/null +++ b/tests/unit/TransferGenerator/Definition/Parser/Expander/AttributesPropertyExpanderTest.php @@ -0,0 +1,51 @@ +namespaceBuilderMock = $this->createMock(NamespaceBuilderInterface::class); + + $this->expander = new AttributesPropertyExpander($this->namespaceBuilderMock); + } + + #[TestDox('Attribute regex failed to parse should skip attribute')] + public function testAttributeRegexFailedToParseShouldSkipAttribute(): void + { + // Arrange + $propertyType = [ + 'attributes' => [''], + ]; + + $propertyTransfer = new DefinitionPropertyTransfer(); + + // Expect + $this->namespaceBuilderMock->expects($this->never()) + ->method('createNamespaceTransfer') + ->seal(); + + // Act + $this->expander->expandPropertyTransfer($propertyType, $propertyTransfer); + + // Assert + $this->assertEmpty($propertyTransfer->attributes); + } +} From 9863547779011c565113f0e6c037f7b3abab025f Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 18:52:15 +0100 Subject: [PATCH 25/56] Improved PropertyNormalizerTrait test coverage --- .../Error/AddressStatisticsTransfer.php | 26 +-------- .../Generated/Error/transfer-object.list.csv | 1 + .../Filter/PropertyNormalizerInterface.php | 13 +++++ .../Parser/Filter/PropertyNormalizerTest.php | 56 +++++++++++++++++++ 4 files changed, 73 insertions(+), 23 deletions(-) create mode 100644 tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv create mode 100644 tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerInterface.php create mode 100644 tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerTest.php diff --git a/tests/integration/TransferGenerator/Generated/Error/AddressStatisticsTransfer.php b/tests/integration/TransferGenerator/Generated/Error/AddressStatisticsTransfer.php index 98e746f4..a387b184 100644 --- a/tests/integration/TransferGenerator/Generated/Error/AddressStatisticsTransfer.php +++ b/tests/integration/TransferGenerator/Generated/Error/AddressStatisticsTransfer.php @@ -5,7 +5,6 @@ namespace Picamator\Tests\Integration\TransferObject\TransferGenerator\Generated\Error; use Picamator\TransferObject\Transfer\AbstractTransfer; -use Picamator\TransferObject\Transfer\Attribute\Transformer\TransferTransformerAttribute; /** * Specification: @@ -14,43 +13,24 @@ * * Note: Do not manually edit this file, as changes will be overwritten. * - * @see /tests/integration/TransferGenerator/data/config/error/unsupported-type/definition/address-statistics.transfer.yml Definition file path. + * @see /tests/integration/TransferGenerator/data/config/error/missed-type/definition/address-statistics.transfer.yml Definition file path. */ final class AddressStatisticsTransfer extends AbstractTransfer { - protected const int META_DATA_SIZE = 2; + protected const int META_DATA_SIZE = 1; protected const array META_DATA = [ self::ADDRESS_BOOK_UUID_PROP => self::ADDRESS_BOOK_UUID_INDEX, - self::ADDRESS_UUID_PROP => self::ADDRESS_UUID_INDEX, - ]; - - protected const array META_TRANSFORMERS = [ - self::ADDRESS_BOOK_UUID_PROP => 'ADDRESS_BOOK_UUID_PROP', - self::ADDRESS_UUID_PROP => 'ADDRESS_UUID_PROP', ]; // addressBookUuid - #[TransferTransformerAttribute(objectTransfer::class)] public const string ADDRESS_BOOK_UUID_PROP = 'addressBookUuid'; private const int ADDRESS_BOOK_UUID_INDEX = 0; - public ?objectTransfer $addressBookUuid { + public ?string $addressBookUuid { get => $this->getData(self::ADDRESS_BOOK_UUID_INDEX); set { $this->setData(self::ADDRESS_BOOK_UUID_INDEX, $value); } } - - // addressUuid - #[TransferTransformerAttribute(stringTransfer::class)] - public const string ADDRESS_UUID_PROP = 'addressUuid'; - private const int ADDRESS_UUID_INDEX = 1; - - public ?stringTransfer $addressUuid { - get => $this->getData(self::ADDRESS_UUID_INDEX); - set { - $this->setData(self::ADDRESS_UUID_INDEX, $value); - } - } } diff --git a/tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv b/tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv new file mode 100644 index 00000000..a6114a2b --- /dev/null +++ b/tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv @@ -0,0 +1 @@ +AddressStatisticsTransfer,e318618bcdc43dcd88217d18fb05f1fd0707d6d3364b264bb577298a9939ff75 \ No newline at end of file diff --git a/tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerInterface.php b/tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerInterface.php new file mode 100644 index 00000000..08b128c3 --- /dev/null +++ b/tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerInterface.php @@ -0,0 +1,13 @@ +> + */ + public function normalizeProperties(mixed $properties): array; +} diff --git a/tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerTest.php b/tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerTest.php new file mode 100644 index 00000000..318a8e40 --- /dev/null +++ b/tests/unit/TransferGenerator/Definition/Parser/Filter/PropertyNormalizerTest.php @@ -0,0 +1,56 @@ +propertyNormalizer = new class () implements PropertyNormalizerInterface { + use PropertyNormalizerTrait { + normalizeProperties as public; + } + }; + } + + public function testPropertyKeyIsIntegerShouldBeSkipped(): void + { + // Arrange + $properties = [ + 'someProperty' => [ + 0 => 'test', + ], + ]; + + // Act + $actual = $this->propertyNormalizer->normalizeProperties($properties); + + // Assert + $this->assertEmpty($actual['someProperty']); + } + + public function testUnknowPropertyKeyShouldBeSkipped(): void + { + // Arrange + $properties = [ + 'someProperty' => [ + 'unknownKey' => 'test', + ], + ]; + + // Act + $actual = $this->propertyNormalizer->normalizeProperties($properties); + + // Assert + $this->assertEmpty($actual['someProperty']); + } +} From 3a8c7614aab1342313aca97ae0f303cc0e11def9 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 19:11:35 +0100 Subject: [PATCH 26/56] Moved Definition Generator tests from main readme to the test readme --- README.md | 25 ------------------- .../DefinitionGenerator/data/README.md | 20 +++++++++++++-- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 73d991c4..d6c6b7dd 100644 --- a/README.md +++ b/README.md @@ -109,31 +109,6 @@ Examples * [Transfer Generator](/examples/try-transfer-generator.php) * [Advanced Transfer Generator](/examples/try-advanced-transfer-generator.php) -Usage Tests ------------ - -Definition Files and Transfer Object generators have been tested against the following APIs: - -* [NASA Open Api](https://api.nasa.gov/neo/rest/v1/neo/2465633?api_key=DEMO_KEY) -* [OpenWeather](https://openweathermap.org/current?collection=current_forecast#example_JSON) -* [Google Content API for Shopping](https://developers.google.com/shopping-content/guides/products/products-api?hl=en) -* [Frankfurter - open-source currency data API](https://api.frankfurter.dev/v1/latest) -* [Tagesschau API](https://tagesschau.api.bund.dev) -* [Statistisches Bundesamt (Destatis)](https://www-genesis.destatis.de/genesisWS/swagger-ui/index.html#/find/findPost) -* [Wero - Digital Payment Wallet](https://developerhub.ppro.com/global-api/docs/wero) - -### Scenario - -1. Rest API response is used as a blueprint to generate Definition Files -2. Transfer Objects are generated based on Definition Files -3. Transfer Object instance is created with the API response -4. Transfer Object is converted back to the array -5. The converted array is compared with the API response - -For all APIs, data are ✅ matched. - -For detailed information, please check [DefinitionGeneratorFacadeTest](/tests/integration/DefinitionGenerator/DefinitionGeneratorFacadeTest.php). - Documentation ------------- diff --git a/tests/integration/DefinitionGenerator/data/README.md b/tests/integration/DefinitionGenerator/data/README.md index fef3b8d6..98bbd0e4 100644 --- a/tests/integration/DefinitionGenerator/data/README.md +++ b/tests/integration/DefinitionGenerator/data/README.md @@ -1,5 +1,10 @@ -API Response Reference -====================== +Test Cases +=========== + +Data Provider +------------- + +Definition Files and Transfer Object generators have been tested against the following APIs: | File | Source | |---------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------| @@ -10,3 +15,14 @@ API Response Reference | [tagesschau-api-bund-dev.json](/tests/integration/DefinitionGenerator/data/api-response/tagesschau-api-bund-dev-v2.json) | [Tagesschau API](https://tagesschau.api.bund.dev) | | [genesis-destatis-find.json](/tests/integration/DefinitionGenerator/data/api-response/genesis-destatis-find.json) | [Statistisches Bundesamt (Destatis)](https://www-genesis.destatis.de/genesisWS/swagger-ui/index.html#/find/findPost) | | [wero-payment-charges-v1.json](/tests/integration/DefinitionGenerator/data/api-response/wero-payment-charges-v1.json) | [Wero - Digital Payment Wallet](https://developerhub.ppro.com/global-api/docs/wero) | + +Scenario +-------- + +1. Rest API response is used as a blueprint to generate Definition Files +2. Transfer Objects are generated based on Definition Files +3. Transfer Object instance is created with the API response +4. Transfer Object is converted back to the array +5. The converted array is compared with the API response + +For all APIs, data are ✅ matched. From 01e2766ff8f1816866dcb5f75874c398fd31c6f9 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 19:14:11 +0100 Subject: [PATCH 27/56] Actualized definition generator integration test readme --- tests/integration/DefinitionGenerator/data/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/DefinitionGenerator/data/README.md b/tests/integration/DefinitionGenerator/data/README.md index 98bbd0e4..53810a5e 100644 --- a/tests/integration/DefinitionGenerator/data/README.md +++ b/tests/integration/DefinitionGenerator/data/README.md @@ -1,10 +1,10 @@ -Test Cases -=========== +Definition Generator Integration Tests +====================================== Data Provider ------------- -Definition Files and Transfer Object generators have been tested against the following APIs: +Definition Generator has been tested against the following APIs: | File | Source | |---------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------| From 04c1b3145082bad84cb6236efc069ee4f125421c Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 19:18:48 +0100 Subject: [PATCH 28/56] Actualized readme key features --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d6c6b7dd..f1822dd8 100644 --- a/README.md +++ b/README.md @@ -60,14 +60,14 @@ Key Features **Symfony Compatibility:** * Provides Symfony console command: - * [TransferGeneratorCommand](/src/Command/TransferGeneratorCommand.php) - * [TransferGeneratorBulkCommand](/src/Command/TransferGeneratorBulkCommand.php) - * [DefinitionGeneratorCommand](/src/Command/DefinitionGeneratorCommand.php) + * [TransferGeneratorCommand](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate) + * [TransferGeneratorBulkCommand](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) + * [DefinitionGeneratorCommand](https://github.com/picamator/transfer-object/wiki/Console-Commands#definition-generate) * Includes Symfony services: - * [TransferGeneratorFacade](/src/TransferGenerator/TransferGeneratorFacade.php) - * [DefinitionGeneratorFacade](/src/DefinitionGenerator/DefinitionGeneratorFacade.php) + * [TransferGeneratorFacade](https://github.com/picamator/transfer-object/wiki/Facade-Interfaces#transfer-object-generator) + * [DefinitionGeneratorFacade](https://github.com/picamator/transfer-object/wiki/Facade-Interfaces#definition-generator) * Enables automatic Symfony request query data mapping - * Supports [Symfony validator](https://github.com/symfony/validator) attributes + * Supports [Symfony validator](https://github.com/picamator/transfer-object/wiki/Definition-File#attributes) attributes **Transfer Object:** From a983c2d66106974d1a78ba456d3179f1e776c367 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 20:40:24 +0100 Subject: [PATCH 29/56] Extracted examples from the main README to the examples readme, added more details on example readme --- README.md | 10 +---- examples/README.md | 64 +++++++++++++++++++++++++++ examples/data/product.json | 47 ++++++++++++++++++++ examples/try-definition-generator.php | 45 +++---------------- 4 files changed, 118 insertions(+), 48 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/data/product.json diff --git a/README.md b/README.md index f1822dd8..fa01bb3a 100644 --- a/README.md +++ b/README.md @@ -102,17 +102,11 @@ $ composer require picamator/transfer-object | 4.0.0 | 8.4 | 7.3 | | 5.0.0 | 8.5 | 8.0 | -Examples ---------- - -* [Definition Generator](/examples/try-definition-generator.php) -* [Transfer Generator](/examples/try-transfer-generator.php) -* [Advanced Transfer Generator](/examples/try-advanced-transfer-generator.php) - Documentation ------------- -For more details, please visit [Project's Wiki](https://github.com/picamator/transfer-object/wiki). +* [Examples](examples) +* [Project's Wiki](https://github.com/picamator/transfer-object/wiki) Publications ------------ diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..e4292c06 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,64 @@ +Examples +======== + +To run examples, please install Docker SDK. + +```console +$ docker/sdk install +``` +For more details, please visit [Development Environment](https://github.com/picamator/transfer-object/wiki/Development-Environment). + +Transfer Object Generator +------------------------- + +The following command will generate the transfer objects: + +```console +$ docker/sdk to-generate examples/config/transfer-generator/generator.config.yml +``` + +Alternatively, transfer objects can be generated using the `TransferGeneratorFacade`, +see [try-transfer-generator.php](try-transfer-generator.php), and [try-advanced-transfer-generator.php](try-advanced-transfer-generator.php). + +The following commands will run the samples: + +```console +$ docker/sdk cli examples/try-transfer-generator.php +``` + +and + +```console +$ docker/sdk cli examples/try-advanced-transfer-generator.php +``` + +Definition Generator +--------------------- + +To generate the definition files, based on [product.json](data/product.json), +the following command can be used: + +```console +$ docker/sdk df-generate +``` + +Please answer the input questions as: + +1. Definition directory path: `examples/config/definition-generator/definition` +2. Transfer Object class name: `Product` +3. JSON local path or url: `examples/data/product.json` + +Then the transfer objects are generated by running: + +```console +$ docker/sdk to-generate examples/config/definition-generator/generator.config.yml +``` + +Alternatively, definition files can be generated using the `DefinitionGeneratorFacade`, +see [try-definition-generator.php](try-definition-generator.php). + +The following commands will run the sample: + +```console +$ docker/sdk cli examples/try-definition-generator.php +``` diff --git a/examples/data/product.json b/examples/data/product.json new file mode 100644 index 00000000..9c512adb --- /dev/null +++ b/examples/data/product.json @@ -0,0 +1,47 @@ +{ + "sku": "T-123", + "name": "Tomato", + "price": 12.99, + "currency": "EUR", + "stock": 100, + "isDiscounted": false, + "deliveryOptions": [ + { + "name": "express" + }, + { + "name": "standard" + } + ], + "details": { + "description": "Local farm Bio Tomato.", + "isRegional": true + }, + "stores": [ + "DE", + "AT" + ], + "labels": { + "sale": "Sale" + }, + "availabilities": { + "2024-12-25": { + "total": 100, + "buffer": 5 + }, + "2024-12-26": { + "total": 200, + "buffer": 10 + } + }, + "measurementUnit": { + "palette": { + "type": "p", + "items": 1000 + }, + "box": { + "type": "b", + "items": 10 + } + } +} diff --git a/examples/try-definition-generator.php b/examples/try-definition-generator.php index e4b31aef..3687de77 100644 --- a/examples/try-definition-generator.php +++ b/examples/try-definition-generator.php @@ -16,46 +16,11 @@ ======================================================= STORY; -$productData = [ - 'sku' => 'T-123', - 'name' => 'Tomato', - 'price' => 12.99, - 'currency' => 'EUR', - 'stock' => 100, - 'isDiscounted' => false, - 'deliveryOptions' => [ - ['name' => 'express'], - ['name' => 'standard'], - ], - 'details' => [ - 'description' => 'Local farm Bio Tomato.', - 'isRegional' => true, - ], - 'stores' => ['DE', 'AT'], - 'labels' => [ - 'sale' => 'Sale', - ], - 'availabilities' => [ - '2024-12-25' => [ - 'total' => 100, - 'buffer' => 5, - ], - '2024-12-26' => [ - 'total' => 200, - 'buffer' => 10, - ], - ], - 'measurementUnit' => [ - 'palette' => [ - 'type' => 'p', - 'items' => 1_000, - ], - 'box' => [ - 'type' => 'b', - 'items' => 10, - ], - ], -]; + +/** @var string $product */ +$product = file_get_contents(__DIR__ . '/data/product.json'); +/** @var array $productData */ +$productData = json_decode($product, associative: true, flags: JSON_THROW_ON_ERROR); echo <<<'STORY' ======================================================= From 7c83c5fb202d6ff114e54afc750ed9628ef4a3f5 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 20:42:07 +0100 Subject: [PATCH 30/56] Actualized link to examples --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa01bb3a..dd2975a6 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ $ composer require picamator/transfer-object Documentation ------------- -* [Examples](examples) +* [Examples](examples#readme) * [Project's Wiki](https://github.com/picamator/transfer-object/wiki) Publications From a25fa77ce33731b2b614fc49c0dccb9aabd282e0 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 20:43:53 +0100 Subject: [PATCH 31/56] Actualized link to examples (reverted last commit) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd2975a6..fa01bb3a 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ $ composer require picamator/transfer-object Documentation ------------- -* [Examples](examples#readme) +* [Examples](examples) * [Project's Wiki](https://github.com/picamator/transfer-object/wiki) Publications From b4bca0604816d624befedab90094f81766457786 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sun, 1 Mar 2026 20:46:25 +0100 Subject: [PATCH 32/56] Actualyzed examples readme --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index e4292c06..ab699de4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,7 +48,7 @@ Please answer the input questions as: 2. Transfer Object class name: `Product` 3. JSON local path or url: `examples/data/product.json` -Then the transfer objects are generated by running: +The following commands will generate transfer objects: ```console $ docker/sdk to-generate examples/config/definition-generator/generator.config.yml From b66dd516b94e4ee0d855da565ba0f4dc0ea813cf Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 08:51:20 +0100 Subject: [PATCH 33/56] Updated example readme --- examples/README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/README.md b/examples/README.md index ab699de4..930744c3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,16 +11,18 @@ For more details, please visit [Development Environment](https://github.com/pica Transfer Object Generator ------------------------- -The following command will generate the transfer objects: +The following command generates the transfer objects: ```console $ docker/sdk to-generate examples/config/transfer-generator/generator.config.yml ``` -Alternatively, transfer objects can be generated using the `TransferGeneratorFacade`, -see [try-transfer-generator.php](try-transfer-generator.php), and [try-advanced-transfer-generator.php](try-advanced-transfer-generator.php). +Alternatively, transfer objects can be generated using the `TransferGeneratorFacade`: -The following commands will run the samples: +* [try-transfer-generator.php](try-transfer-generator.php) +* [try-advanced-transfer-generator.php](try-advanced-transfer-generator.php). + +The following commands run the samples: ```console $ docker/sdk cli examples/try-transfer-generator.php @@ -48,16 +50,17 @@ Please answer the input questions as: 2. Transfer Object class name: `Product` 3. JSON local path or url: `examples/data/product.json` -The following commands will generate transfer objects: +The following command generates transfer objects: ```console $ docker/sdk to-generate examples/config/definition-generator/generator.config.yml ``` -Alternatively, definition files can be generated using the `DefinitionGeneratorFacade`, -see [try-definition-generator.php](try-definition-generator.php). +Alternatively, definition files can be generated using the `DefinitionGeneratorFacade`: + +* [try-definition-generator.php](try-definition-generator.php). -The following commands will run the sample: +The following command runs the sample: ```console $ docker/sdk cli examples/try-definition-generator.php From 10fe47e1a3c11d3748593f906235a254ea433185 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 08:53:29 +0100 Subject: [PATCH 34/56] Removed semicolumn on the Definition Generator command questions --- src/Command/DefinitionGeneratorCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Command/DefinitionGeneratorCommand.php b/src/Command/DefinitionGeneratorCommand.php index e82de384..bdc68445 100644 --- a/src/Command/DefinitionGeneratorCommand.php +++ b/src/Command/DefinitionGeneratorCommand.php @@ -36,9 +36,9 @@ { use InputNormalizerTrait; - private const string QUESTION_DEFINITION_PATH = 'Definition directory path: '; - private const string QUESTION_CLASS_NAME = 'Transfer Object class name: '; - private const string QUESTION_JSON_PATH = 'JSON local path or url: '; + private const string QUESTION_DEFINITION_PATH = 'Definition directory path'; + private const string QUESTION_CLASS_NAME = 'Transfer Object class name'; + private const string QUESTION_JSON_PATH = 'JSON local path or url'; private const string START_SECTION_NAME = 'Generating Transfer Object Definitions ✨'; From 76e3d1793bca66e2ca1c44f00d35e37130db4f62 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 09:56:56 +0100 Subject: [PATCH 35/56] Generated hash names dinamically to avoid possible file name collision, removed hash name from configuration --- ...6b2628f97dda4dfab2a1ed0.transfer.hash.csv} | 0 ...26b99acdb29c8d744f65294.transfer.hash.csv} | 0 ...d852431397f3f96a71446de.transfer.hash.csv} | 0 schema/config.schema.json | 5 --- ...481220b288a359e8ba84d39.transfer.hash.csv} | 0 .../Config/ConfigFactory.php | 7 ---- .../Config/Enum/ConfigKeyEnum.php | 11 ++---- .../Config/Parser/ConfigParser.php | 4 +++ .../Content/HashFileNameContentValidator.php | 36 ------------------- ...heDisabledTransferGeneratorCommandTest.php | 2 +- .../FileHashTransferGeneratorCommandTest.php | 3 +- ...936ab8d1d3345d16a865d7c.transfer.hash.csv} | 0 ...cad6f58298fb442f506dfed.transfer.hash.csv} | 0 ...2cf9c812c7950fa44cf8ecf.transfer.hash.csv} | 0 ...c1e5b63e91f543eb7ccfedb.transfer.hash.csv} | 0 ...46704689cc0cdf2f0b1b639.transfer.hash.csv} | 0 .../TransferGeneratorBulkCommandTest.php | 4 +-- .../success/generator.bulk.first.config.yml | 1 - .../success/generator.bulk.second.config.yml | 1 - .../generator.cache.disabled.config.yml | 1 - .../data/config/success/generator.config.yml | 1 - .../success/generator.file.hash.config.yml | 1 - ...9314fb7ccdbc975025dc9c9.transfer.hash.csv} | 0 ...74b343fa08bcbe78029391b.transfer.hash.csv} | 0 ...4fcbc8b9d2e405457646511.transfer.hash.csv} | 0 ...273a49e044c7ba6adc25262.transfer.hash.csv} | 0 ...237c6a7a37ed524b4ddd154.transfer.hash.csv} | 0 ...bc62803fcc81ff92c1b2c31.transfer.hash.csv} | 0 ...2690c3fd858b4160efea711.transfer.hash.csv} | 0 ...c3d5176d2bdc9b518cfc96d.transfer.hash.csv} | 0 ...bcfdd64902b54ffb12b66a7.transfer.hash.csv} | 0 .../Config/Loader/ConfigLoaderTest.php | 2 -- .../Generated/Error/transfer-object.list.csv | 1 - ...debc9c19ee65b5ab2cbd6be.transfer.hash.csv} | 0 .../error/invalid-hash-file-name.config.yml | 5 --- var/config/config.list.txt | 4 +++ 36 files changed, 15 insertions(+), 74 deletions(-) rename examples/Generated/AdvancedTransferGenerator/{transfer-object.list.csv => 7509ef4008791f2426b2628f97dda4dfab2a1ed0.transfer.hash.csv} (100%) rename examples/Generated/DefinitionGenerator/{transfer-object.list.csv => e6032560f45b3a6f826b99acdb29c8d744f65294.transfer.hash.csv} (100%) rename examples/Generated/TransferGenerator/{transfer-object.list.csv => 16cc72947d7980764d852431397f3f96a71446de.transfer.hash.csv} (100%) rename src/Generated/{transfer-object.list.csv => d0671a0a246503701481220b288a359e8ba84d39.transfer.hash.csv} (100%) delete mode 100644 src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php rename tests/integration/Command/Generated/Success/{transfer-object.bulk.first.list.csv => 0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv} (100%) rename tests/integration/Command/Generated/Success/{transfer-object.file.hash.list.csv => 11f6d3fa94ad43441cad6f58298fb442f506dfed.transfer.hash.csv} (100%) rename tests/integration/Command/Generated/Success/{transfer-object.cache.disabled.list.csv => 58208de788342fbf62cf9c812c7950fa44cf8ecf.transfer.hash.csv} (100%) rename tests/integration/Command/Generated/Success/{transfer-object.bulk.second.list.csv => 660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv} (100%) rename tests/integration/Command/Generated/Success/{transfer-object.list.csv => 6e2abc8ac5faed56c46704689cc0cdf2f0b1b639.transfer.hash.csv} (100%) rename tests/integration/DefinitionGenerator/Generated/Destatis/{transfer-object.list.csv => 85d80569af63ae1bb9314fb7ccdbc975025dc9c9.transfer.hash.csv} (100%) rename tests/integration/DefinitionGenerator/Generated/Frankfurter/{transfer-object.list.csv => bee179e5ccc082b4074b343fa08bcbe78029391b.transfer.hash.csv} (100%) rename tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/{transfer-object.list.csv => 7548834bb63ce3afa4fcbc8b9d2e405457646511.transfer.hash.csv} (100%) rename tests/integration/DefinitionGenerator/Generated/NasaNeo/{transfer-object.list.csv => 47cb0e86ae1de4df5273a49e044c7ba6adc25262.transfer.hash.csv} (100%) rename tests/integration/DefinitionGenerator/Generated/OpenWeather/{transfer-object.list.csv => 8fa882f29e7d077bb237c6a7a37ed524b4ddd154.transfer.hash.csv} (100%) rename tests/integration/DefinitionGenerator/Generated/Tagesschau/{transfer-object.list.csv => 703741f07ffb6bfd3bc62803fcc81ff92c1b2c31.transfer.hash.csv} (100%) rename tests/integration/DefinitionGenerator/Generated/Wero/{transfer-object.list.csv => 42dec6482416321802690c3fd858b4160efea711.transfer.hash.csv} (100%) rename tests/integration/Transfer/Generated/{transfer-object.list.csv => 8b96a0de99f620e0bc3d5176d2bdc9b518cfc96d.transfer.hash.csv} (100%) rename tests/integration/Transfer/Generated/BcMath/{transfer-object.list.csv => 935ae0ad4ba6c99bbbcfdd64902b54ffb12b66a7.transfer.hash.csv} (100%) delete mode 100644 tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv rename tests/integration/TransferGenerator/Generated/Success/{transfer-object.list.csv => 264b6745de577b923debc9c19ee65b5ab2cbd6be.transfer.hash.csv} (100%) delete mode 100644 tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml diff --git a/examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv b/examples/Generated/AdvancedTransferGenerator/7509ef4008791f2426b2628f97dda4dfab2a1ed0.transfer.hash.csv similarity index 100% rename from examples/Generated/AdvancedTransferGenerator/transfer-object.list.csv rename to examples/Generated/AdvancedTransferGenerator/7509ef4008791f2426b2628f97dda4dfab2a1ed0.transfer.hash.csv diff --git a/examples/Generated/DefinitionGenerator/transfer-object.list.csv b/examples/Generated/DefinitionGenerator/e6032560f45b3a6f826b99acdb29c8d744f65294.transfer.hash.csv similarity index 100% rename from examples/Generated/DefinitionGenerator/transfer-object.list.csv rename to examples/Generated/DefinitionGenerator/e6032560f45b3a6f826b99acdb29c8d744f65294.transfer.hash.csv diff --git a/examples/Generated/TransferGenerator/transfer-object.list.csv b/examples/Generated/TransferGenerator/16cc72947d7980764d852431397f3f96a71446de.transfer.hash.csv similarity index 100% rename from examples/Generated/TransferGenerator/transfer-object.list.csv rename to examples/Generated/TransferGenerator/16cc72947d7980764d852431397f3f96a71446de.transfer.hash.csv diff --git a/schema/config.schema.json b/schema/config.schema.json index 20c7299b..9c2afbfd 100644 --- a/schema/config.schema.json +++ b/schema/config.schema.json @@ -18,11 +18,6 @@ "definitionPath": { "type": "string", "description": "Path to the Definition Files." - }, - "hashFileName": { - "type": "string", - "description": "File name where Transfer Object content hashes are saved for change detection. Optional. Default: transfer-object.list.csv", - "pattern": "^[a-zA-Z0-9._-]+\\.csv$" } }, "required": ["transferNamespace", "transferPath", "definitionPath"], diff --git a/src/Generated/transfer-object.list.csv b/src/Generated/d0671a0a246503701481220b288a359e8ba84d39.transfer.hash.csv similarity index 100% rename from src/Generated/transfer-object.list.csv rename to src/Generated/d0671a0a246503701481220b288a359e8ba84d39.transfer.hash.csv diff --git a/src/TransferGenerator/Config/ConfigFactory.php b/src/TransferGenerator/Config/ConfigFactory.php index 592d0f39..ffd75f30 100644 --- a/src/TransferGenerator/Config/ConfigFactory.php +++ b/src/TransferGenerator/Config/ConfigFactory.php @@ -24,7 +24,6 @@ use Picamator\TransferObject\TransferGenerator\Config\Validator\ConfigValidatorInterface; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\ContentValidatorInterface; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\DefinitionPathContentValidator; -use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\HashFileNameContentValidator; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\RequiredContentValidator; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\TransferNamespaceContentValidator; use Picamator\TransferObject\TransferGenerator\Config\Validator\Content\TransferPathContentValidator; @@ -87,15 +86,9 @@ protected function createConfigContentValidators(): ArrayObject $this->createDefinitionPathConfigContentValidator(), $this->createTransferPathConfigContentValidator(), $this->createTransferNamespaceConfigContentValidator(), - $this->createHashFileNameContentValidator(), ]); } - protected function createHashFileNameContentValidator(): ContentValidatorInterface - { - return new HashFileNameContentValidator(); - } - protected function createTransferNamespaceConfigContentValidator(): ContentValidatorInterface { return new TransferNamespaceContentValidator(); diff --git a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php index ca57cc44..2d37240b 100644 --- a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php +++ b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php @@ -11,14 +11,6 @@ enum ConfigKeyEnum: string case TRANSFER_NAMESPACE = ConfigContentTransfer::TRANSFER_NAMESPACE_PROP; case TRANSFER_PATH = ConfigContentTransfer::TRANSFER_PATH_PROP; case DEFINITION_PATH = ConfigContentTransfer::DEFINITION_PATH_PROP; - case HASH_FILE_NAME = ConfigContentTransfer::HASH_FILE_NAME_PROP; - - private const array DEFAULT_VALUES = [ - self::TRANSFER_NAMESPACE->name => '', - self::TRANSFER_PATH->name => '', - self::DEFINITION_PATH->name => '', - self::HASH_FILE_NAME->name => 'transfer-object.list.csv', - ]; /** * @return array @@ -26,10 +18,11 @@ enum ConfigKeyEnum: string public static function getDefaultConfig(): array { $defaultContent[ConfigContentTransfer::UUID_PROP] = ''; + $defaultContent[ConfigContentTransfer::HASH_FILE_NAME_PROP] = ''; $defaultContent[ConfigContentTransfer::IS_CACHE_ENABLED_PROP] = true; foreach (self::cases() as $keyEnum) { - $defaultContent[$keyEnum->value] = self::DEFAULT_VALUES[$keyEnum->name]; + $defaultContent[$keyEnum->value] = ''; } return $defaultContent; diff --git a/src/TransferGenerator/Config/Parser/ConfigParser.php b/src/TransferGenerator/Config/Parser/ConfigParser.php index df7ba029..15e0d404 100644 --- a/src/TransferGenerator/Config/Parser/ConfigParser.php +++ b/src/TransferGenerator/Config/Parser/ConfigParser.php @@ -38,6 +38,10 @@ public function parseConfig(string $configPath): ConfigContentTransfer private function expandConfig(array $configData): array { $configData[ConfigContentTransfer::UUID_PROP] = uniqid(more_entropy: true); + + $definitionPath = $configData[ConfigContentTransfer::DEFINITION_PATH_PROP] ?? ''; + $configData[ConfigContentTransfer::HASH_FILE_NAME_PROP] = sha1((string)$definitionPath) . '.transfer.hash.csv'; + $configData[ConfigContentTransfer::IS_CACHE_ENABLED_PROP] = $this->environmentReader->getIsCacheEnabled(); return $configData; diff --git a/src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php b/src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php deleted file mode 100644 index 7e6e8b6f..00000000 --- a/src/TransferGenerator/Config/Validator/Content/HashFileNameContentValidator.php +++ /dev/null @@ -1,36 +0,0 @@ -hashFileName; - if (preg_match(self::HASH_FILE_NAME_REGEX, $hashFileName) === 1) { - return null; - } - - $errorMessage = $this->getErrorMessage($hashFileName); - - return $this->createErrorMessageTransfer($errorMessage); - } - - private function getErrorMessage(string $hashFileName): string - { - return sprintf(self::ERROR_MESSAGE_TEMPLATE, $hashFileName); - } -} diff --git a/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php index 003617e0..cd43a8d8 100644 --- a/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php +++ b/tests/integration/Command/CacheDisabledTransferGeneratorCommandTest.php @@ -24,7 +24,7 @@ final class CacheDisabledTransferGeneratorCommandTest extends TestCase private const array GENERATED_FILES = [ __DIR__ . '/Generated/Success/CommandCacheDisabledTransfer.php', - __DIR__ . '/Generated/Success/transfer-object.cache.disabled.list.csv', + __DIR__ . '/Generated/Success/58208de788342fbf62cf9c812c7950fa44cf8ecf.transfer.hash.csv', ]; private CommandTester $commandTester; diff --git a/tests/integration/Command/FileHashTransferGeneratorCommandTest.php b/tests/integration/Command/FileHashTransferGeneratorCommandTest.php index fa954483..3513b984 100644 --- a/tests/integration/Command/FileHashTransferGeneratorCommandTest.php +++ b/tests/integration/Command/FileHashTransferGeneratorCommandTest.php @@ -22,7 +22,8 @@ final class FileHashTransferGeneratorCommandTest extends TestCase private const string CONFIG_PATH = '/tests/integration/Command/data/config/success/generator.file.hash.config.yml'; - private const string HASH_FILE_PATH = __DIR__ . '/Generated/Success/transfer-object.file.hash.list.csv'; + private const string HASH_FILE_PATH = + __DIR__ . '/Generated/Success/11f6d3fa94ad43441cad6f58298fb442f506dfed.transfer.hash.csv'; private const string HASH_FILE_CONTENT = <<<'CONTENT' CommandFileHashTransfer,some-hash-1 diff --git a/tests/integration/Command/Generated/Success/transfer-object.bulk.first.list.csv b/tests/integration/Command/Generated/Success/0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv similarity index 100% rename from tests/integration/Command/Generated/Success/transfer-object.bulk.first.list.csv rename to tests/integration/Command/Generated/Success/0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv diff --git a/tests/integration/Command/Generated/Success/transfer-object.file.hash.list.csv b/tests/integration/Command/Generated/Success/11f6d3fa94ad43441cad6f58298fb442f506dfed.transfer.hash.csv similarity index 100% rename from tests/integration/Command/Generated/Success/transfer-object.file.hash.list.csv rename to tests/integration/Command/Generated/Success/11f6d3fa94ad43441cad6f58298fb442f506dfed.transfer.hash.csv diff --git a/tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv b/tests/integration/Command/Generated/Success/58208de788342fbf62cf9c812c7950fa44cf8ecf.transfer.hash.csv similarity index 100% rename from tests/integration/Command/Generated/Success/transfer-object.cache.disabled.list.csv rename to tests/integration/Command/Generated/Success/58208de788342fbf62cf9c812c7950fa44cf8ecf.transfer.hash.csv diff --git a/tests/integration/Command/Generated/Success/transfer-object.bulk.second.list.csv b/tests/integration/Command/Generated/Success/660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv similarity index 100% rename from tests/integration/Command/Generated/Success/transfer-object.bulk.second.list.csv rename to tests/integration/Command/Generated/Success/660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv diff --git a/tests/integration/Command/Generated/Success/transfer-object.list.csv b/tests/integration/Command/Generated/Success/6e2abc8ac5faed56c46704689cc0cdf2f0b1b639.transfer.hash.csv similarity index 100% rename from tests/integration/Command/Generated/Success/transfer-object.list.csv rename to tests/integration/Command/Generated/Success/6e2abc8ac5faed56c46704689cc0cdf2f0b1b639.transfer.hash.csv diff --git a/tests/integration/Command/TransferGeneratorBulkCommandTest.php b/tests/integration/Command/TransferGeneratorBulkCommandTest.php index 81ab59d7..7a312b38 100644 --- a/tests/integration/Command/TransferGeneratorBulkCommandTest.php +++ b/tests/integration/Command/TransferGeneratorBulkCommandTest.php @@ -25,8 +25,8 @@ final class TransferGeneratorBulkCommandTest extends TestCase __DIR__ . '/Generated/Success/CommandBulkFirstTransfer.php', __DIR__ . '/Generated/Success/CommandBulkSecondTransfer.php', __DIR__ . '/Generated/Success/CommandBulkThirdTransfer.php', - __DIR__ . '/Generated/Success/transfer-object.bulk.first.list.csv', - __DIR__ . '/Generated/Success/transfer-object.bulk.second.list.csv', + __DIR__ . '/Generated/Success/0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv', + __DIR__ . '/Generated/Success/660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv', ]; private const string ERROR_CONFIG_EMPTY_LIST_PATH diff --git a/tests/integration/Command/data/config/success/generator.bulk.first.config.yml b/tests/integration/Command/data/config/success/generator.bulk.first.config.yml index 1dc9e10f..22e6a9f3 100644 --- a/tests/integration/Command/data/config/success/generator.bulk.first.config.yml +++ b/tests/integration/Command/data/config/success/generator.bulk.first.config.yml @@ -3,4 +3,3 @@ generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-bulk-first" - hashFileName: "transfer-object.bulk.first.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.bulk.second.config.yml b/tests/integration/Command/data/config/success/generator.bulk.second.config.yml index c09b7dbe..425e15e6 100644 --- a/tests/integration/Command/data/config/success/generator.bulk.second.config.yml +++ b/tests/integration/Command/data/config/success/generator.bulk.second.config.yml @@ -3,4 +3,3 @@ generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-bulk-second" - hashFileName: "transfer-object.bulk.second.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml b/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml index 5cc3d1b2..afbd31c2 100644 --- a/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml +++ b/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml @@ -3,4 +3,3 @@ generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-cache-disabled" - hashFileName: "transfer-object.cache.disabled.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.config.yml b/tests/integration/Command/data/config/success/generator.config.yml index 50cc1c8b..fd752acf 100644 --- a/tests/integration/Command/data/config/success/generator.config.yml +++ b/tests/integration/Command/data/config/success/generator.config.yml @@ -3,4 +3,3 @@ generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition" - hashFileName: "transfer-object.list.csv" diff --git a/tests/integration/Command/data/config/success/generator.file.hash.config.yml b/tests/integration/Command/data/config/success/generator.file.hash.config.yml index d3067f5e..ec3fbe6d 100644 --- a/tests/integration/Command/data/config/success/generator.file.hash.config.yml +++ b/tests/integration/Command/data/config/success/generator.file.hash.config.yml @@ -3,4 +3,3 @@ generator: transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Success" transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Success" definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/success/definition-file-hash" - hashFileName: "transfer-object.file.hash.list.csv" diff --git a/tests/integration/DefinitionGenerator/Generated/Destatis/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Destatis/85d80569af63ae1bb9314fb7ccdbc975025dc9c9.transfer.hash.csv similarity index 100% rename from tests/integration/DefinitionGenerator/Generated/Destatis/transfer-object.list.csv rename to tests/integration/DefinitionGenerator/Generated/Destatis/85d80569af63ae1bb9314fb7ccdbc975025dc9c9.transfer.hash.csv diff --git a/tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Frankfurter/bee179e5ccc082b4074b343fa08bcbe78029391b.transfer.hash.csv similarity index 100% rename from tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer-object.list.csv rename to tests/integration/DefinitionGenerator/Generated/Frankfurter/bee179e5ccc082b4074b343fa08bcbe78029391b.transfer.hash.csv diff --git a/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/7548834bb63ce3afa4fcbc8b9d2e405457646511.transfer.hash.csv similarity index 100% rename from tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer-object.list.csv rename to tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/7548834bb63ce3afa4fcbc8b9d2e405457646511.transfer.hash.csv diff --git a/tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/NasaNeo/47cb0e86ae1de4df5273a49e044c7ba6adc25262.transfer.hash.csv similarity index 100% rename from tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer-object.list.csv rename to tests/integration/DefinitionGenerator/Generated/NasaNeo/47cb0e86ae1de4df5273a49e044c7ba6adc25262.transfer.hash.csv diff --git a/tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/OpenWeather/8fa882f29e7d077bb237c6a7a37ed524b4ddd154.transfer.hash.csv similarity index 100% rename from tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer-object.list.csv rename to tests/integration/DefinitionGenerator/Generated/OpenWeather/8fa882f29e7d077bb237c6a7a37ed524b4ddd154.transfer.hash.csv diff --git a/tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Tagesschau/703741f07ffb6bfd3bc62803fcc81ff92c1b2c31.transfer.hash.csv similarity index 100% rename from tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer-object.list.csv rename to tests/integration/DefinitionGenerator/Generated/Tagesschau/703741f07ffb6bfd3bc62803fcc81ff92c1b2c31.transfer.hash.csv diff --git a/tests/integration/DefinitionGenerator/Generated/Wero/transfer-object.list.csv b/tests/integration/DefinitionGenerator/Generated/Wero/42dec6482416321802690c3fd858b4160efea711.transfer.hash.csv similarity index 100% rename from tests/integration/DefinitionGenerator/Generated/Wero/transfer-object.list.csv rename to tests/integration/DefinitionGenerator/Generated/Wero/42dec6482416321802690c3fd858b4160efea711.transfer.hash.csv diff --git a/tests/integration/Transfer/Generated/transfer-object.list.csv b/tests/integration/Transfer/Generated/8b96a0de99f620e0bc3d5176d2bdc9b518cfc96d.transfer.hash.csv similarity index 100% rename from tests/integration/Transfer/Generated/transfer-object.list.csv rename to tests/integration/Transfer/Generated/8b96a0de99f620e0bc3d5176d2bdc9b518cfc96d.transfer.hash.csv diff --git a/tests/integration/Transfer/Generated/BcMath/transfer-object.list.csv b/tests/integration/Transfer/Generated/BcMath/935ae0ad4ba6c99bbbcfdd64902b54ffb12b66a7.transfer.hash.csv similarity index 100% rename from tests/integration/Transfer/Generated/BcMath/transfer-object.list.csv rename to tests/integration/Transfer/Generated/BcMath/935ae0ad4ba6c99bbbcfdd64902b54ffb12b66a7.transfer.hash.csv diff --git a/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php b/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php index 6f41a358..eaa7b604 100644 --- a/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php +++ b/tests/integration/TransferGenerator/Config/Loader/ConfigLoaderTest.php @@ -60,7 +60,5 @@ public static function invalidConfigDataProvider(): Generator yield 'invalid definition root key' => ['invalid-definition-root-key.config.yml']; yield 'transfer path is not local' => ['transfer-path-is-not-local.config.yml']; - - yield 'invalid hash file name' => ['invalid-hash-file-name.config.yml']; } } diff --git a/tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv b/tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv deleted file mode 100644 index a6114a2b..00000000 --- a/tests/integration/TransferGenerator/Generated/Error/transfer-object.list.csv +++ /dev/null @@ -1 +0,0 @@ -AddressStatisticsTransfer,e318618bcdc43dcd88217d18fb05f1fd0707d6d3364b264bb577298a9939ff75 \ No newline at end of file diff --git a/tests/integration/TransferGenerator/Generated/Success/transfer-object.list.csv b/tests/integration/TransferGenerator/Generated/Success/264b6745de577b923debc9c19ee65b5ab2cbd6be.transfer.hash.csv similarity index 100% rename from tests/integration/TransferGenerator/Generated/Success/transfer-object.list.csv rename to tests/integration/TransferGenerator/Generated/Success/264b6745de577b923debc9c19ee65b5ab2cbd6be.transfer.hash.csv diff --git a/tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml b/tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml deleted file mode 100644 index 79f91930..00000000 --- a/tests/integration/TransferGenerator/data/config/error/invalid-hash-file-name.config.yml +++ /dev/null @@ -1,5 +0,0 @@ -generator: - transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\TransferGenerator\\Generated\\Error" - transferPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/Generated/Error" - definitionPath: "${PROJECT_ROOT}/tests/integration/TransferGenerator/data/config/error/missed-type/definition" - hashFileName: "invalid-hash-file-name.txt" diff --git a/var/config/config.list.txt b/var/config/config.list.txt index 5cda6395..0cd4054e 100644 --- a/var/config/config.list.txt +++ b/var/config/config.list.txt @@ -3,16 +3,20 @@ ./tests/integration/Transfer/data/config/generator.config.yml ./tests/integration/Transfer/data/config/bcmath/generator.config.yml ./tests/integration/TransferGenerator/data/config/success/generator.config.yml + ./tests/integration/Command/data/config/success/generator.config.yml ./tests/integration/Command/data/config/success/generator.bulk.first.config.yml ./tests/integration/Command/data/config/success/generator.bulk.second.config.yml ./tests/integration/Command/data/config/success/generator.cache.disabled.config.yml ./tests/integration/Command/data/config/success/generator.file.hash.config.yml + +./tests/integration/DefinitionGenerator/data/config/genesis-destatis-find/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/open-weather/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/google-shopping-content/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/frankfurter-dev-v1/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/tagesschau-api-bund-dev-v2/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/nasa-neo-rest-v1-neo-2465633/generator.config.yml +./tests/integration/DefinitionGenerator/data/config/wero-payment-charges-v1/generator.config.yml ./examples/config/transfer-generator/generator.config.yml ./examples/config/advanced-transfer-generator/generator.config.yml From 21087771eabc6fb5a91f854162ab1aca2e133933 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 10:09:34 +0100 Subject: [PATCH 36/56] Used separate temporary directory per configuration and per process --- .../Generator/Filesystem/FilesystemTrait.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php index 6d736373..c1efb0b9 100644 --- a/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php +++ b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php @@ -29,7 +29,12 @@ final protected function getTransferPath(?string $filename = null): string */ final protected function getTemporaryPath(?string $filename = null): string { - $path = $this->config->getTransferPath() . DIRECTORY_SEPARATOR . self::TEMPORARY_DIR; + $path = $this->config->getTransferPath() + . DIRECTORY_SEPARATOR + . self::TEMPORARY_DIR + . DIRECTORY_SEPARATOR + . $this->config->getUuid(); + if ($filename !== null) { $path .= DIRECTORY_SEPARATOR . $filename; } From 6715f7f4b3b33144c4e8dc30ac9216eb94ccfa76 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 10:15:08 +0100 Subject: [PATCH 37/56] Wrapped post process to run on finally --- .../Workflow/TransferGeneratorWorkflow.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php b/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php index d619f3ea..b02ccee1 100644 --- a/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php +++ b/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php @@ -25,14 +25,19 @@ public function generateTransfers(string $configPath): Generator return false; } - $transferGenerator = $this->processTransfers(); + try { + $transferGenerator = $this->processTransfers(); - yield from $transferGenerator; + yield from $transferGenerator; - /** @var bool $isSuccessful */ - $isSuccessful = $transferGenerator->getReturn(); + /** @var bool $isSuccessful */ + $isSuccessful = $transferGenerator->getReturn(); + } finally { + $isSuccessful ??= false; + $transferGenerator = $this->postProcessTransfers($isSuccessful); + } - yield $this->postProcessTransfers($isSuccessful); + yield $transferGenerator; return $isSuccessful; } From e8ae42ea2027256f3597237fd9a4183193ca24d9 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 13:12:16 +0100 Subject: [PATCH 38/56] Implemented transfer object generator locker --- .../AdvancedTransferGenerator/transfer.lock | 0 .../DefinitionGenerator/transfer.lock | 0 .../Generated/TransferGenerator/transfer.lock | 0 src/Generated/transfer.lock | 0 src/Shared/Exception/FileLockerException.php | 11 +++ src/Shared/Locker/FileLocker.php | 72 +++++++++++++++++++ src/Shared/Locker/FileLockerInterface.php | 15 ++++ src/Shared/SharedFactoryTrait.php | 10 +++ .../Generator/Generator/GeneratorFactory.php | 15 ++++ .../Processor/Command/PostProcessCommand.php | 12 ++-- .../Processor/Command/PreProcessCommand.php | 3 + .../Generator/Writer/TransferLocker.php | 35 +++++++++ .../Writer/TransferLockerInterface.php | 12 ++++ .../Command/Generated/Error/transfer.lock | 0 .../Command/Generated/Success/transfer.lock | 0 .../Generated/Destatis/transfer.lock | 0 .../Generated/Frankfurter/transfer.lock | 0 .../GoogleShoppingContent/transfer.lock | 0 .../Generated/NasaNeo/transfer.lock | 0 .../Generated/OpenWeather/transfer.lock | 0 .../Generated/Tagesschau/transfer.lock | 0 .../Generated/Wero/transfer.lock | 0 .../Transfer/Generated/BcMath/transfer.lock | 0 .../Transfer/Generated/transfer.lock | 0 .../Generated/Error/transfer.lock | 0 .../Generated/Success/transfer.lock | 0 .../Command/PostProcessCommandTest.php | 7 ++ .../Command/PreProcessCommandTest.php | 7 ++ 28 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 examples/Generated/AdvancedTransferGenerator/transfer.lock create mode 100644 examples/Generated/DefinitionGenerator/transfer.lock create mode 100644 examples/Generated/TransferGenerator/transfer.lock create mode 100644 src/Generated/transfer.lock create mode 100644 src/Shared/Exception/FileLockerException.php create mode 100644 src/Shared/Locker/FileLocker.php create mode 100644 src/Shared/Locker/FileLockerInterface.php create mode 100644 src/TransferGenerator/Generator/Writer/TransferLocker.php create mode 100644 src/TransferGenerator/Generator/Writer/TransferLockerInterface.php create mode 100644 tests/integration/Command/Generated/Error/transfer.lock create mode 100644 tests/integration/Command/Generated/Success/transfer.lock create mode 100644 tests/integration/DefinitionGenerator/Generated/Destatis/transfer.lock create mode 100644 tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer.lock create mode 100644 tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer.lock create mode 100644 tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer.lock create mode 100644 tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer.lock create mode 100644 tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer.lock create mode 100644 tests/integration/DefinitionGenerator/Generated/Wero/transfer.lock create mode 100644 tests/integration/Transfer/Generated/BcMath/transfer.lock create mode 100644 tests/integration/Transfer/Generated/transfer.lock create mode 100644 tests/integration/TransferGenerator/Generated/Error/transfer.lock create mode 100644 tests/integration/TransferGenerator/Generated/Success/transfer.lock diff --git a/examples/Generated/AdvancedTransferGenerator/transfer.lock b/examples/Generated/AdvancedTransferGenerator/transfer.lock new file mode 100644 index 00000000..e69de29b diff --git a/examples/Generated/DefinitionGenerator/transfer.lock b/examples/Generated/DefinitionGenerator/transfer.lock new file mode 100644 index 00000000..e69de29b diff --git a/examples/Generated/TransferGenerator/transfer.lock b/examples/Generated/TransferGenerator/transfer.lock new file mode 100644 index 00000000..e69de29b diff --git a/src/Generated/transfer.lock b/src/Generated/transfer.lock new file mode 100644 index 00000000..e69de29b diff --git a/src/Shared/Exception/FileLockerException.php b/src/Shared/Exception/FileLockerException.php new file mode 100644 index 00000000..c99df3a6 --- /dev/null +++ b/src/Shared/Exception/FileLockerException.php @@ -0,0 +1,11 @@ +fopen($filename); + if ($lockFile === false) { + throw new FileLockerException( + sprintf('Failed to open file "%s".', $filename), + ); + } + + if ($this->flock($lockFile, LOCK_EX) === false) { + $this->fclose($lockFile); + + throw new FileLockerException( + sprintf('Failed to acquire lock for file "%s".', $filename), + ); + } + + $this->lockFile = $lockFile; + } + + public function releaseLock(): void + { + if (!is_resource($this->lockFile)) { + return; + } + + $this->flock($this->lockFile, LOCK_UN); + $this->fclose($this->lockFile); + + $this->lockFile = null; + } + + /** + * @param resource $lockFile + */ + protected function fclose($lockFile): bool + { + return fclose($lockFile); + } + + /** + * @param resource $lockFile + * @param int<0, 7> $operation + */ + protected function flock($lockFile, int $operation): bool + { + return flock($lockFile, $operation); + } + + /** + * @return false|resource + */ + protected function fopen(string $filename) + { + return fopen($filename, 'c'); + } +} diff --git a/src/Shared/Locker/FileLockerInterface.php b/src/Shared/Locker/FileLockerInterface.php new file mode 100644 index 00000000..4ea27eef --- /dev/null +++ b/src/Shared/Locker/FileLockerInterface.php @@ -0,0 +1,15 @@ + new HashFileWriter($this->createFilesystem()), ); } + + final protected function createFileLocker(): FileLockerInterface + { + return $this->getCached( + key: 'shared:FileLocker', + factory: fn(): FileLockerInterface => new FileLocker(), + ); + } } diff --git a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php index 2a0b10df..e15674d3 100644 --- a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php +++ b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php @@ -30,6 +30,8 @@ use Picamator\TransferObject\TransferGenerator\Generator\Reader\TransferHashReaderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Render\RenderFactory; use Picamator\TransferObject\TransferGenerator\Generator\Render\TemplateRenderInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLocker; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotator; use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferWriter; @@ -55,6 +57,7 @@ protected function createPostProcessCommand(): PostProcessCommandInterface $this->createTransferGeneratorBuilder(), $this->createGeneratorFilesystem(), $this->createTransferRotator(), + $this->createTransferLocker(), ); } @@ -81,6 +84,18 @@ protected function createPreProcessCommand(): PreProcessCommandInterface $this->createConfigLoader(), $this->createTransferGeneratorBuilder(), $this->createGeneratorFilesystem(), + $this->createTransferLocker(), + ); + } + + protected function createTransferLocker(): TransferLockerInterface + { + return $this->getCached( + key: 'transfer-generator:TransferLocker', + factory: fn(): TransferLockerInterface => new TransferLocker( + $this->createFileLocker(), + $this->getConfig(), + ), ); } diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php index b8d6fa37..bebaabfc 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php @@ -8,6 +8,7 @@ use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; use Throwable; @@ -17,16 +18,19 @@ public function __construct( private TransferGeneratorBuilderInterface $builder, private GeneratorFilesystemInterface $filesystem, private TransferRotatorInterface $transferRotator, + private TransferLockerInterface $transferLocker, ) { } public function postProcess(bool $isSuccessful): TransferGeneratorTransfer { - if ($isSuccessful) { - return $this->postProcessSuccess(); - } + $generatorTransfer = $isSuccessful + ? $this->postProcessSuccess() + : $this->postProcessError(); + + $this->transferLocker->releaseLock(); - return $this->postProcessError(); + return $generatorTransfer; } private function postProcessSuccess(): TransferGeneratorTransfer diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php index 3052c234..30357214 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php @@ -9,6 +9,7 @@ use Picamator\TransferObject\TransferGenerator\Config\Loader\ConfigLoaderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; readonly class PreProcessCommand implements PreProcessCommandInterface { @@ -16,6 +17,7 @@ public function __construct( private ConfigLoaderInterface $configLoader, private TransferGeneratorBuilderInterface $builder, private GeneratorFilesystemInterface $filesystem, + private TransferLockerInterface $transferLocker, ) { } @@ -38,6 +40,7 @@ private function createTempDir(): ?TransferGeneratorTransfer { try { $this->filesystem->createTempDir(); + $this->transferLocker->acquireLock(); } catch (FilesystemException $e) { return $this->builder->createErrorGeneratorTransfer($e->getMessage()); } diff --git a/src/TransferGenerator/Generator/Writer/TransferLocker.php b/src/TransferGenerator/Generator/Writer/TransferLocker.php new file mode 100644 index 00000000..f0161c46 --- /dev/null +++ b/src/TransferGenerator/Generator/Writer/TransferLocker.php @@ -0,0 +1,35 @@ +getLockFilePath(); + $this->fileLocker->acquireLock($lockFilePath); + } + + public function releaseLock(): void + { + $this->fileLocker->releaseLock(); + } + + private function getLockFilePath(): string + { + return $this->config->getTransferPath() . DIRECTORY_SEPARATOR . self::LOCK_FILE_NAME; + } +} diff --git a/src/TransferGenerator/Generator/Writer/TransferLockerInterface.php b/src/TransferGenerator/Generator/Writer/TransferLockerInterface.php new file mode 100644 index 00000000..adc69a8e --- /dev/null +++ b/src/TransferGenerator/Generator/Writer/TransferLockerInterface.php @@ -0,0 +1,12 @@ +filesystemStub = $this->createStub(GeneratorFilesystemInterface::class); $this->transferRotatorMock = $this->createMock(TransferRotatorInterface::class); + $transferLockerStub = $this->createStub(TransferLockerInterface::class); + $transferLockerStub + ->method('releaseLock') + ->seal(); + $this->command = new PostProcessCommand( $builder, $this->filesystemStub, $this->transferRotatorMock, + $transferLockerStub, ); } diff --git a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php index 64e9c7f5..ec1e15b5 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php @@ -16,6 +16,7 @@ use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilder; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PreProcessCommand; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PreProcessCommandInterface; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; #[Group('transfer-generator')] final class PreProcessCommandTest extends TestCase @@ -34,10 +35,16 @@ protected function setUp(): void $this->filesystemStub = $this->createStub(GeneratorFilesystemInterface::class); + $transferLockerStub = $this->createStub(TransferLockerInterface::class); + $transferLockerStub + ->method('acquireLock') + ->seal(); + $this->command = new PreProcessCommand( $this->configLoaderStub, $builder, $this->filesystemStub, + $transferLockerStub, ); } From cc1086642f8716a342ccc4c133ef7785460f0b28 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 13:16:16 +0100 Subject: [PATCH 39/56] Actualized config list for transfer object bulk commmand --- var/config/config.list.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/var/config/config.list.txt b/var/config/config.list.txt index 0cd4054e..9394bcd8 100644 --- a/var/config/config.list.txt +++ b/var/config/config.list.txt @@ -4,19 +4,19 @@ ./tests/integration/Transfer/data/config/bcmath/generator.config.yml ./tests/integration/TransferGenerator/data/config/success/generator.config.yml -./tests/integration/Command/data/config/success/generator.config.yml -./tests/integration/Command/data/config/success/generator.bulk.first.config.yml ./tests/integration/Command/data/config/success/generator.bulk.second.config.yml ./tests/integration/Command/data/config/success/generator.cache.disabled.config.yml +./tests/integration/Command/data/config/success/generator.config.yml ./tests/integration/Command/data/config/success/generator.file.hash.config.yml +./tests/integration/Command/data/config/success/generator.bulk.first.config.yml ./tests/integration/DefinitionGenerator/data/config/genesis-destatis-find/generator.config.yml +./tests/integration/DefinitionGenerator/data/config/wero-payment-charges-v1/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/open-weather/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/google-shopping-content/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/frankfurter-dev-v1/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/tagesschau-api-bund-dev-v2/generator.config.yml ./tests/integration/DefinitionGenerator/data/config/nasa-neo-rest-v1-neo-2465633/generator.config.yml -./tests/integration/DefinitionGenerator/data/config/wero-payment-charges-v1/generator.config.yml ./examples/config/transfer-generator/generator.config.yml ./examples/config/advanced-transfer-generator/generator.config.yml From 0026a6ec58cbf42d34102211bbf3b2b067e23a22 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 3 Mar 2026 14:54:00 +0100 Subject: [PATCH 40/56] Cover file locker with tests --- tests/unit/Shared/Locker/FileLockerTest.php | 96 +++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tests/unit/Shared/Locker/FileLockerTest.php diff --git a/tests/unit/Shared/Locker/FileLockerTest.php b/tests/unit/Shared/Locker/FileLockerTest.php new file mode 100644 index 00000000..115ff0d0 --- /dev/null +++ b/tests/unit/Shared/Locker/FileLockerTest.php @@ -0,0 +1,96 @@ +fileLockerMock = $this->getMockBuilder(FileLocker::class) + ->onlyMethods([ + 'fclose', + 'flock', + 'fopen', + ]) + ->getMock(); + } + + #[TestDox('Failed to open the lock should throw exception')] + public function testFailedToOpenTheLockShouldThrowException(): void + { + // Arrange + $lockFile = 'some.lock'; + + // Expect + $this->fileLockerMock->expects($this->once()) + ->method('fopen') + ->with($lockFile) + ->willReturn(false) + ->seal(); + + $this->expectException(FileLockerException::class); + + // Act + $this->fileLockerMock->acquireLock($lockFile); + } + + #[TestDox('Failed to lock should throw exception')] + public function testFailedToLockShouldThrowException(): void + { + // Arrange + $lockFile = 'some.lock'; + $file = self::openFile(); + + // Expect + $this->fileLockerMock->expects($this->once()) + ->method('fopen') + ->with($lockFile) + ->willReturn($file); + + $this->fileLockerMock->expects($this->once()) + ->method('flock') + ->with($this->isResource(), LOCK_EX) + ->willReturn(false); + + $this->fileLockerMock->expects($this->once()) + ->method('fclose') + ->with($this->isResource()) + ->willReturn(true) + ->seal(); + + $this->expectException(FileLockerException::class); + + // Act + $this->fileLockerMock->acquireLock($lockFile); + } + + #[TestDox('Release empty lock should early return')] + public function testReleaseEmptyLockShouldEarlyReturn(): void + { + // expect + $this->fileLockerMock->expects($this->never()) + ->method('flock'); + + $this->fileLockerMock->expects($this->never()) + ->method('fclose') + ->seal(); + + // Act + $this->fileLockerMock->releaseLock(); + } +} From 2ecceb73879e1389b2ecba9ec8a14c313b0b5c6c Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Thu, 5 Mar 2026 10:32:35 +0100 Subject: [PATCH 41/56] Refactored file rotate flow to lock the transfer destination directory only when there are something to write or delete --- .../Generator/Filesystem/FilesystemTrait.php | 3 -- .../Filesystem/GeneratorFilesystem.php | 15 +------ .../GeneratorFilesystemInterface.php | 14 ++++--- .../Generator/Filesystem/HashFilesystem.php | 20 ++++----- .../Filesystem/HashFilesystemInterface.php | 7 +++- .../Generator/Generator/GeneratorFactory.php | 3 +- .../Processor/Command/PostProcessCommand.php | 8 +--- .../Processor/Command/PreProcessCommand.php | 3 -- .../Generator/Writer/TransferRotator.php | 42 +++++++++++++++---- .../Command/PostProcessCommandTest.php | 7 ---- .../Command/PreProcessCommandTest.php | 7 ---- 11 files changed, 59 insertions(+), 70 deletions(-) diff --git a/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php index c1efb0b9..065c22e6 100644 --- a/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php +++ b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php @@ -4,9 +4,6 @@ namespace Picamator\TransferObject\TransferGenerator\Generator\Filesystem; -/** - * @property \Picamator\TransferObject\TransferGenerator\Config\Config\ConfigInterface $config - */ trait FilesystemTrait { private const string TEMPORARY_DIR = '_tmp'; diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php index b31c77cb..6c385cae 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php @@ -55,21 +55,10 @@ public function writeTempFile(TransferGeneratorContentTransfer $contentTransfer) $this->filesystem->dumpFile($filePath, $contentTransfer->content); } - public function rotateFiles(ArrayObject $toCopyClassNames, ArrayObject $toDeleteClassNames): void - { - if ($toCopyClassNames->count() > 0) { - $this->renameTempFiles($toCopyClassNames); - } - - if ($toDeleteClassNames->count() > 0) { - $this->deleteFiles($toDeleteClassNames); - } - } - /** * @param \ArrayObject $classNames */ - private function deleteFiles(ArrayObject $classNames): void + public function deleteFiles(ArrayObject $classNames): void { $fileNames = $this->getFileNames($classNames); foreach ($fileNames as $fileName) { @@ -81,7 +70,7 @@ private function deleteFiles(ArrayObject $classNames): void /** * @param \ArrayObject $classNames */ - private function renameTempFiles(ArrayObject $classNames): void + public function renameTempFiles(ArrayObject $classNames): void { $fileNames = $this->getFileNames($classNames); foreach ($fileNames as $fileName) { diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php index 7871ffc3..f7961ab3 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php @@ -21,16 +21,20 @@ public function deleteTempDir(): void; /** * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException */ public function writeTempFile(TransferGeneratorContentTransfer $contentTransfer): void; /** - * @param \ArrayObject $toCopyClassNames - * @param \ArrayObject $toDeleteClassNames + * @param \ArrayObject $classNames * * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ - public function rotateFiles(ArrayObject $toCopyClassNames, ArrayObject $toDeleteClassNames): void; + public function renameTempFiles(ArrayObject $classNames): void; + + /** + * @param \ArrayObject $classNames + * + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + public function deleteFiles(ArrayObject $classNames): void; } diff --git a/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php b/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php index 0ab525f0..a287e687 100644 --- a/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php @@ -20,13 +20,16 @@ public function __construct( ) { } - public function rotateHashFile(ArrayObject $hashes): void + /** + * @param \ArrayObject $hashes + */ + public function writeHashTmpFile(ArrayObject $hashes): void { - $this->writeHashTmpFile($hashes); - $this->renameHashTmpFile(); + $filePath = $this->getTemporaryPath($this->config->getHashFileName()); + $this->fileWriter->writeFile($filePath, $hashes); } - private function renameHashTmpFile(): void + public function renameHashTmpFile(): void { $fileName = $this->config->getHashFileName(); @@ -35,13 +38,4 @@ private function renameHashTmpFile(): void $this->filesystem->rename($originalFile, $targetFile, overwrite: true); } - - /** - * @param \ArrayObject $hashes - */ - private function writeHashTmpFile(ArrayObject $hashes): void - { - $filePath = $this->getTemporaryPath($this->config->getHashFileName()); - $this->fileWriter->writeFile($filePath, $hashes); - } } diff --git a/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php index 7ef1f680..27d01d34 100644 --- a/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php +++ b/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php @@ -13,5 +13,10 @@ interface HashFilesystemInterface * * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException */ - public function rotateHashFile(ArrayObject $hashes): void; + public function writeHashTmpFile(ArrayObject $hashes): void; + + /** + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + public function renameHashTmpFile(): void; } diff --git a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php index e15674d3..45246195 100644 --- a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php +++ b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php @@ -57,7 +57,6 @@ protected function createPostProcessCommand(): PostProcessCommandInterface $this->createTransferGeneratorBuilder(), $this->createGeneratorFilesystem(), $this->createTransferRotator(), - $this->createTransferLocker(), ); } @@ -84,7 +83,6 @@ protected function createPreProcessCommand(): PreProcessCommandInterface $this->createConfigLoader(), $this->createTransferGeneratorBuilder(), $this->createGeneratorFilesystem(), - $this->createTransferLocker(), ); } @@ -123,6 +121,7 @@ protected function createTransferRotator(): TransferRotatorInterface $this->createTransferHashReader(), $this->createHashFilesystem(), $this->createGeneratorFilesystem(), + $this->createTransferLocker(), ), ); } diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php index bebaabfc..d492ab83 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php @@ -8,7 +8,6 @@ use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; -use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; use Throwable; @@ -18,19 +17,14 @@ public function __construct( private TransferGeneratorBuilderInterface $builder, private GeneratorFilesystemInterface $filesystem, private TransferRotatorInterface $transferRotator, - private TransferLockerInterface $transferLocker, ) { } public function postProcess(bool $isSuccessful): TransferGeneratorTransfer { - $generatorTransfer = $isSuccessful + return $isSuccessful ? $this->postProcessSuccess() : $this->postProcessError(); - - $this->transferLocker->releaseLock(); - - return $generatorTransfer; } private function postProcessSuccess(): TransferGeneratorTransfer diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php index 30357214..3052c234 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommand.php @@ -9,7 +9,6 @@ use Picamator\TransferObject\TransferGenerator\Config\Loader\ConfigLoaderInterface; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; -use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; readonly class PreProcessCommand implements PreProcessCommandInterface { @@ -17,7 +16,6 @@ public function __construct( private ConfigLoaderInterface $configLoader, private TransferGeneratorBuilderInterface $builder, private GeneratorFilesystemInterface $filesystem, - private TransferLockerInterface $transferLocker, ) { } @@ -40,7 +38,6 @@ private function createTempDir(): ?TransferGeneratorTransfer { try { $this->filesystem->createTempDir(); - $this->transferLocker->acquireLock(); } catch (FilesystemException $e) { return $this->builder->createErrorGeneratorTransfer($e->getMessage()); } diff --git a/src/TransferGenerator/Generator/Writer/TransferRotator.php b/src/TransferGenerator/Generator/Writer/TransferRotator.php index 03037036..634c3060 100644 --- a/src/TransferGenerator/Generator/Writer/TransferRotator.php +++ b/src/TransferGenerator/Generator/Writer/TransferRotator.php @@ -16,26 +16,52 @@ public function __construct( private TransferHashReaderInterface $hashReader, private HashFilesystemInterface $hashFilesystem, private GeneratorFilesystemInterface $filesystem, + private TransferLockerInterface $transferLocker, ) { } public function rotateFiles(): void { $hashTransfer = $this->hashReader->readHashFile(); - $toDeleteClassNames = $this->getToDeleteClassNames($hashTransfer); + $toDelete = $this->getToDelete($hashTransfer); - if ($toDeleteClassNames->count() === 0 && $hashTransfer->toCopyClassNames->count() === 0) { - $this->filesystem->deleteTempDir(); + $isToDelete = $toDelete->count() > 0; + $isToCopy = $hashTransfer->toCopyClassNames->count() > 0; + + if (!$isToDelete && !$isToCopy) { + return; + } + + $this->transferLocker->acquireLock(); + + try { + if ($isToDelete) { + $this->filesystem->deleteFiles($toDelete); + } + + if ($isToCopy) { + $this->filesystem->renameTempFiles($hashTransfer->toCopyClassNames); + } + + $this->rotateHashFile($hashTransfer->actualHashes); + } finally { + $this->transferLocker->releaseLock(); } + } - $this->filesystem->rotateFiles($hashTransfer->toCopyClassNames, $toDeleteClassNames); - $this->hashFilesystem->rotateHashFile($hashTransfer->actualHashes); + /** + * @param ArrayObject $actualHashes + */ + private function rotateHashFile(ArrayObject $actualHashes): void + { + $this->hashFilesystem->writeHashTmpFile($actualHashes); + $this->hashFilesystem->renameHashTmpFile(); } /** * @return \ArrayObject */ - private function getToDeleteClassNames(TransferHashTransfer $hashTransfer): ArrayObject + private function getToDelete(TransferHashTransfer $hashTransfer): ArrayObject { /** @var \ArrayObject $classesNames */ $classesNames = new ArrayObject(); @@ -49,9 +75,7 @@ private function getToDeleteClassNames(TransferHashTransfer $hashTransfer): Arra continue; } - /** @var string $className */ - $className = pathinfo($className, flags: PATHINFO_BASENAME); - $classesNames[] = $className; + $classesNames[] = basename($className); } return $classesNames; diff --git a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php index 892681be..3869804c 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php @@ -14,7 +14,6 @@ use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilder; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PostProcessCommand; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PostProcessCommandInterface; -use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferRotatorInterface; #[Group('transfer-generator')] @@ -33,16 +32,10 @@ protected function setUp(): void $this->filesystemStub = $this->createStub(GeneratorFilesystemInterface::class); $this->transferRotatorMock = $this->createMock(TransferRotatorInterface::class); - $transferLockerStub = $this->createStub(TransferLockerInterface::class); - $transferLockerStub - ->method('releaseLock') - ->seal(); - $this->command = new PostProcessCommand( $builder, $this->filesystemStub, $this->transferRotatorMock, - $transferLockerStub, ); } diff --git a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php index ec1e15b5..64e9c7f5 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PreProcessCommandTest.php @@ -16,7 +16,6 @@ use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilder; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PreProcessCommand; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command\PreProcessCommandInterface; -use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferLockerInterface; #[Group('transfer-generator')] final class PreProcessCommandTest extends TestCase @@ -35,16 +34,10 @@ protected function setUp(): void $this->filesystemStub = $this->createStub(GeneratorFilesystemInterface::class); - $transferLockerStub = $this->createStub(TransferLockerInterface::class); - $transferLockerStub - ->method('acquireLock') - ->seal(); - $this->command = new PreProcessCommand( $this->configLoaderStub, $builder, $this->filesystemStub, - $transferLockerStub, ); } From 0081e65f455805e21fe3b34b76a9db41a85e5507 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Thu, 5 Mar 2026 10:40:41 +0100 Subject: [PATCH 42/56] Removed transfer.lock, added transfer.lock to gitignore --- .gitignore | 3 +++ examples/Generated/AdvancedTransferGenerator/transfer.lock | 0 examples/Generated/DefinitionGenerator/transfer.lock | 0 examples/Generated/TransferGenerator/transfer.lock | 0 src/Generated/transfer.lock | 0 tests/integration/Command/Generated/Error/transfer.lock | 0 tests/integration/Command/Generated/Success/transfer.lock | 0 .../DefinitionGenerator/Generated/Destatis/transfer.lock | 0 .../DefinitionGenerator/Generated/Frankfurter/transfer.lock | 0 .../Generated/GoogleShoppingContent/transfer.lock | 0 .../DefinitionGenerator/Generated/NasaNeo/transfer.lock | 0 .../DefinitionGenerator/Generated/OpenWeather/transfer.lock | 0 .../DefinitionGenerator/Generated/Tagesschau/transfer.lock | 0 .../DefinitionGenerator/Generated/Wero/transfer.lock | 0 tests/integration/Transfer/Generated/BcMath/transfer.lock | 0 tests/integration/Transfer/Generated/transfer.lock | 0 .../TransferGenerator/Generated/Error/transfer.lock | 0 .../TransferGenerator/Generated/Success/transfer.lock | 0 18 files changed, 3 insertions(+) delete mode 100644 examples/Generated/AdvancedTransferGenerator/transfer.lock delete mode 100644 examples/Generated/DefinitionGenerator/transfer.lock delete mode 100644 examples/Generated/TransferGenerator/transfer.lock delete mode 100644 src/Generated/transfer.lock delete mode 100644 tests/integration/Command/Generated/Error/transfer.lock delete mode 100644 tests/integration/Command/Generated/Success/transfer.lock delete mode 100644 tests/integration/DefinitionGenerator/Generated/Destatis/transfer.lock delete mode 100644 tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer.lock delete mode 100644 tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer.lock delete mode 100644 tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer.lock delete mode 100644 tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer.lock delete mode 100644 tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer.lock delete mode 100644 tests/integration/DefinitionGenerator/Generated/Wero/transfer.lock delete mode 100644 tests/integration/Transfer/Generated/BcMath/transfer.lock delete mode 100644 tests/integration/Transfer/Generated/transfer.lock delete mode 100644 tests/integration/TransferGenerator/Generated/Error/transfer.lock delete mode 100644 tests/integration/TransferGenerator/Generated/Success/transfer.lock diff --git a/.gitignore b/.gitignore index 2ca51287..2d3462a4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,8 +33,11 @@ var/cache/* # project src/**/_tmp +src/**/transfer.lock examples/**/_tmp +examples/**/transfer.lock tests/**/_tmp +tests/**/transfer.lock # environment .env diff --git a/examples/Generated/AdvancedTransferGenerator/transfer.lock b/examples/Generated/AdvancedTransferGenerator/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/Generated/DefinitionGenerator/transfer.lock b/examples/Generated/DefinitionGenerator/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/Generated/TransferGenerator/transfer.lock b/examples/Generated/TransferGenerator/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Generated/transfer.lock b/src/Generated/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/Command/Generated/Error/transfer.lock b/tests/integration/Command/Generated/Error/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/Command/Generated/Success/transfer.lock b/tests/integration/Command/Generated/Success/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/DefinitionGenerator/Generated/Destatis/transfer.lock b/tests/integration/DefinitionGenerator/Generated/Destatis/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer.lock b/tests/integration/DefinitionGenerator/Generated/Frankfurter/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer.lock b/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer.lock b/tests/integration/DefinitionGenerator/Generated/NasaNeo/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer.lock b/tests/integration/DefinitionGenerator/Generated/OpenWeather/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer.lock b/tests/integration/DefinitionGenerator/Generated/Tagesschau/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/DefinitionGenerator/Generated/Wero/transfer.lock b/tests/integration/DefinitionGenerator/Generated/Wero/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/Transfer/Generated/BcMath/transfer.lock b/tests/integration/Transfer/Generated/BcMath/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/Transfer/Generated/transfer.lock b/tests/integration/Transfer/Generated/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/TransferGenerator/Generated/Error/transfer.lock b/tests/integration/TransferGenerator/Generated/Error/transfer.lock deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/TransferGenerator/Generated/Success/transfer.lock b/tests/integration/TransferGenerator/Generated/Success/transfer.lock deleted file mode 100644 index e69de29b..00000000 From d3091131dba251e42ac2ba32885777add40caa89 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Thu, 5 Mar 2026 21:12:29 +0100 Subject: [PATCH 43/56] Fixed issue to have temporary directory leftovers, simplified deletion temporary directory --- .../Filesystem/GeneratorFilesystem.php | 12 +++++------ .../Processor/Command/PostProcessCommand.php | 7 +------ .../Generator/TransferGeneratorService.php | 13 ++++-------- .../Workflow/TransferGeneratorWorkflow.php | 7 ++++--- .../Command/data/config/error/config.list.txt | 2 +- .../definition-bulk/command.transfer.yml | 2 ++ .../config/error/generator.bulk.config.yml | 5 +++++ .../Command/PostProcessCommandTest.php | 21 ------------------- 8 files changed, 23 insertions(+), 46 deletions(-) create mode 100644 tests/integration/Command/data/config/error/definition-bulk/command.transfer.yml create mode 100644 tests/integration/Command/data/config/error/generator.bulk.config.yml diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php index 6c385cae..421253e3 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php @@ -24,10 +24,9 @@ public function __construct( public function createTempDir(): void { - $temporaryPath = $this->getTemporaryPath(); + $tempPath = $this->getTemporaryPath(); - $this->deleteTempDir(); - $this->filesystem->mkdir($temporaryPath); + $this->filesystem->mkdir($tempPath); } /** @@ -35,9 +34,10 @@ public function createTempDir(): void */ public function deleteTempDir(): void { - $temporaryPath = $this->getTemporaryPath(); - if ($this->filesystem->exists($temporaryPath)) { - $this->filesystem->remove($temporaryPath); + $tempPath = $this->getTemporaryPath(); + + if ($this->filesystem->exists($tempPath)) { + $this->filesystem->remove($tempPath); } } diff --git a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php index d492ab83..1865b6d1 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php @@ -4,7 +4,6 @@ namespace Picamator\TransferObject\TransferGenerator\Generator\Generator\Processor\Command; -use Picamator\TransferObject\Dependency\Exception\FilesystemException; use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Generator\Filesystem\GeneratorFilesystemInterface; use Picamator\TransferObject\TransferGenerator\Generator\Generator\Builder\TransferGeneratorBuilderInterface; @@ -43,11 +42,7 @@ private function postProcessSuccess(): TransferGeneratorTransfer private function postProcessError(): TransferGeneratorTransfer { - try { - $this->filesystem->deleteTempDir(); - } catch (FilesystemException $e) { - return $this->builder->createErrorGeneratorTransfer($e->getMessage()); - } + $this->filesystem->deleteTempDir(); return $this->builder->createSuccessGeneratorTransfer(); } diff --git a/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php b/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php index cfe64874..4f90674d 100644 --- a/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php +++ b/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php @@ -20,9 +20,6 @@ public function __construct( ) { } - /** - * @throws \Picamator\TransferObject\Shared\Exception\TransferExceptionInterface - */ public function generateTransfersOrFail(string $configPath): int { $count = 0; @@ -34,16 +31,14 @@ public function generateTransfersOrFail(string $configPath): int continue; } - $this->throwError($generatorTransfer); + $exception = $this->getException($generatorTransfer); + $generator->throw($exception); } return $count; } - /** - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException - */ - private function throwError(TransferGeneratorTransfer $generatorTransfer): never + private function getException(TransferGeneratorTransfer $generatorTransfer): TransferGeneratorException { $messageParts[] = self::ERROR_MESSAGE; if ($generatorTransfer->className !== null) { @@ -63,6 +58,6 @@ private function throwError(TransferGeneratorTransfer $generatorTransfer): never $message = implode(PHP_EOL, $messageParts); - throw new TransferGeneratorException($message); + return new TransferGeneratorException($message); } } diff --git a/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php b/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php index b02ccee1..8e8f7deb 100644 --- a/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php +++ b/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php @@ -17,11 +17,13 @@ public function __construct( public function generateTransfers(string $configPath): Generator { - $generatorTransfer = $this->processConfig($configPath); + $generatorTransfer = $this->preProcess($configPath); yield $generatorTransfer; if (!$this->isValidTransfer($generatorTransfer)) { + $this->postProcessTransfers(isSuccessful: false); + return false; } @@ -55,8 +57,7 @@ private function processTransfers(): Generator return $this->processor->process(); } - - private function processConfig(string $configPath): TransferGeneratorTransfer + private function preProcess(string $configPath): TransferGeneratorTransfer { return $this->processor->preProcess($configPath); } diff --git a/tests/integration/Command/data/config/error/config.list.txt b/tests/integration/Command/data/config/error/config.list.txt index 635fd0c3..9ccb5b7a 100644 --- a/tests/integration/Command/data/config/error/config.list.txt +++ b/tests/integration/Command/data/config/error/config.list.txt @@ -1 +1 @@ -tests/integration/Command/data/config/error/generator.config.yml +tests/integration/Command/data/config/error/generator.bulk.config.yml diff --git a/tests/integration/Command/data/config/error/definition-bulk/command.transfer.yml b/tests/integration/Command/data/config/error/definition-bulk/command.transfer.yml new file mode 100644 index 00000000..ac89a79d --- /dev/null +++ b/tests/integration/Command/data/config/error/definition-bulk/command.transfer.yml @@ -0,0 +1,2 @@ +CommandBulk: + run: diff --git a/tests/integration/Command/data/config/error/generator.bulk.config.yml b/tests/integration/Command/data/config/error/generator.bulk.config.yml new file mode 100644 index 00000000..a09da1a7 --- /dev/null +++ b/tests/integration/Command/data/config/error/generator.bulk.config.yml @@ -0,0 +1,5 @@ +# $schema: ./../../../../../../schema/config.schema.json +generator: + transferNamespace: "Picamator\\Tests\\Integration\\TransferObject\\Command\\Generated\\Error" + transferPath: "${PROJECT_ROOT}/tests/integration/Command/Generated/Error" + definitionPath: "${PROJECT_ROOT}/tests/integration/Command/data/config/error/definition-bulk" diff --git a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php index 3869804c..298496ae 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php @@ -59,25 +59,4 @@ public function testFilesystemExceptionShouldBeHandledOnPostProcessSuccess(): vo // Assert $this->assertFalse($actual->validator->isValid); } - - #[TestDox('Filesystem exception should be handled on PostProcessError')] - public function testFilesystemExceptionShouldBeHandledOnPostProcessError(): void - { - // Arrange - $this->filesystemStub - ->method('deleteTempDir') - ->willThrowException(new FilesystemException()) - ->seal(); - - // Expect - $this->transferRotatorMock->expects($this->never()) - ->method('rotateFiles') - ->seal(); - - // Act - $actual = $this->command->postProcess(false); - - // Assert - $this->assertFalse($actual->validator->isValid); - } } From 677bef85cd8d5b5b2beb02943ba05e756a730937 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 09:12:58 +0100 Subject: [PATCH 44/56] Simplified transfer generation workflow - executing post command --- .../Generator/Generator/TransferGeneratorService.php | 9 ++++----- .../Generator/Workflow/TransferGeneratorWorkflow.php | 2 -- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php b/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php index 4f90674d..56787d46 100644 --- a/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php +++ b/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php @@ -31,14 +31,15 @@ public function generateTransfersOrFail(string $configPath): int continue; } - $exception = $this->getException($generatorTransfer); + $errorMessage = $this->getErrorMessage($generatorTransfer); + $exception = new TransferGeneratorException($errorMessage); $generator->throw($exception); } return $count; } - private function getException(TransferGeneratorTransfer $generatorTransfer): TransferGeneratorException + private function getErrorMessage(TransferGeneratorTransfer $generatorTransfer): string { $messageParts[] = self::ERROR_MESSAGE; if ($generatorTransfer->className !== null) { @@ -56,8 +57,6 @@ private function getException(TransferGeneratorTransfer $generatorTransfer): Tra $messageParts[] = $message->errorMessage; } - $message = implode(PHP_EOL, $messageParts); - - return new TransferGeneratorException($message); + return implode(PHP_EOL, $messageParts); } } diff --git a/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php b/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php index 8e8f7deb..5925a8fd 100644 --- a/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php +++ b/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php @@ -22,8 +22,6 @@ public function generateTransfers(string $configPath): Generator yield $generatorTransfer; if (!$this->isValidTransfer($generatorTransfer)) { - $this->postProcessTransfers(isSuccessful: false); - return false; } From 8e9075ef17ab968135604392070dcd96a14ea2c4 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 10:11:16 +0100 Subject: [PATCH 45/56] Actualized AGENTS.md with hash and lock files details --- AGENTS.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 77692075..ec9f1163 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,8 +55,18 @@ Directory Structure * should not contain any custom-written code * each transfer object generator run overwrites all the files in the directory * can be used across modules -- `src/Generated/_tmp`: temporary directory including newly generated transfer objects before they are finally moved to the `src/Generated` +- `src/Generated/_tmp`: temporary directory to hold the transfer object generator's process directories +- `src/Generated/_tmp/{uuid}`: transfer object generator's process directory named by UUID. +The directory is created before the process starts and holds new transfer objects. + * only when the process is finished successfully, the transfer objects are moved to the `Generated` directory. + * each process directory is deleted after the process is finished * in case of an unexpected error, the directory might not be deleted. +- `src/Generated/_tmp/{uuid}/{hash}.transfer.hash.csv`: hash file each line of which contains comma separated: + * transfer object class name + * transfer object content hash +- `src/Generated/{hash}.transfer.hash.csv`: hash file from previous transfer object generation run. +It is used to check for transfer object content changes as well as if some transfer objects should be deleted. +- `src/Generated/transfer.lock`: lock file used to prevent multiple processes to write to the `Generated` directory at the same time. - `src/Shared`: contains code shared across modules * can be used across modules - `src/Transfer`: transfer object module From bb987c206af669c3dc28119c027a58e72d91ba39 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 10:48:53 +0100 Subject: [PATCH 46/56] Actualized readme --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fa01bb3a..08cbbdd9 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,10 @@ Key Features Installation ------------ -Composer installation: +**Composer installation:** + +The Transfer Object Generator is available on [Packagist](https://packagist.org/packages/picamator/transfer-object) +and can be installed using [Composer](https://getcomposer.org/): ```console $ composer require picamator/transfer-object @@ -102,11 +105,53 @@ $ composer require picamator/transfer-object | 4.0.0 | 8.4 | 7.3 | | 5.0.0 | 8.5 | 8.0 | +**Directory Structure:** + +After installation, for the small project size, the following directory structure is recommended: + +* `src/Generated`: directory to keep all project's Transfer Objects +* `src/config/generator.config.yml`: generator's [configuration file](https://github.com/picamator/transfer-object/wiki/Console-Commands#configuration) +* `src/config/definition/*.transfer.yml`: [definition files](https://github.com/picamator/transfer-object/wiki/Definition-File), +where each of them groups transfer objects definitions by business domain. + +With this setup, `generator.config.yml` looks like this: + +```yml +# $schema: ./../vendor/picamator/transfer-object/schema/config.schema.json +generator: + transferNamespace: "YourVendorNamespace\\YourProjectNamespace\\Generated" + transferPath: "${PROJECT_ROOT}/src/Generated" + definitionPath: "${PROJECT_ROOT}/config/definition" +``` + +Where `YourVendorNamespace\\YourProjectNamespace` should be replaced with your vendor's and project's namespace. + +Additionally `.gitignore` should contain: + +```shell +src/Generated/_tmp +src/Generated/transfer.lock +``` + +Finally, the Transfer Objects are generated by running the following command: + +```console +$ ./vendor/bin/transfer-generate -c config/generator.config.yml +``` + +> [!TIP] +> For large projects, each module can keep its own Transfer Object Generator configuration. +> It is still possible to keep all transfer objects in one directory, +> with the warning notice as some transfer objects might be overwritten by another module. + +> [!TIP] +> To generate transfer objects in the multi-configuration setup, the [bulk command](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) should be used. + Documentation ------------- -* [Examples](examples) * [Project's Wiki](https://github.com/picamator/transfer-object/wiki) +* [Examples](examples) Publications ------------ From 3bb9821a59b9ac21029446bf9d6d6a221d7ae87c Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 11:03:20 +0100 Subject: [PATCH 47/56] Fixed README and AGENTS typos --- AGENTS.md | 24 ++++++++++++------------ README.md | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ec9f1163..23698f3e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,20 +53,19 @@ Directory Structure - `src/Dependency`: wrapper over third-party dependencies - `src/Generated`: directory where generated transfer objects are saved * should not contain any custom-written code - * each transfer object generator run overwrites all the files in the directory - * can be used across modules + * can be used across modules like `src/ModuleOne/Generated`, `src/ModuleTwo/Generated`, etc. - `src/Generated/_tmp`: temporary directory to hold the transfer object generator's process directories - `src/Generated/_tmp/{uuid}`: transfer object generator's process directory named by UUID. The directory is created before the process starts and holds new transfer objects. * only when the process is finished successfully, the transfer objects are moved to the `Generated` directory. * each process directory is deleted after the process is finished * in case of an unexpected error, the directory might not be deleted. -- `src/Generated/_tmp/{uuid}/{hash}.transfer.hash.csv`: hash file each line of which contains comma separated: +- `src/Generated/_tmp/{uuid}/{hash}.transfer.hash.csv`: hash file, each line of which contains comma-separated: * transfer object class name * transfer object content hash - `src/Generated/{hash}.transfer.hash.csv`: hash file from previous transfer object generation run. It is used to check for transfer object content changes as well as if some transfer objects should be deleted. -- `src/Generated/transfer.lock`: lock file used to prevent multiple processes to write to the `Generated` directory at the same time. +- `src/Generated/transfer.lock`: lock file used to prevent multiple processes from writing to the `Generated` directory at the same time. - `src/Shared`: contains code shared across modules * can be used across modules - `src/Transfer`: transfer object module @@ -75,7 +74,7 @@ It is used to check for transfer object content changes as well as if some trans ### Technical -- `.github`: GitHub CI actions, template and README.md images +- `.github`: GitHub CI actions, template, and README.md images - `.xdebug`: Xdebug configuration for [Native Path Mapping](https://xdebug.org/funding/001-native-path-mapping) - `docker`: [dockerized development environment](https://github.com/picamator/transfer-object/wiki/Development-Environment) configuration with shell helper commands @@ -99,7 +98,7 @@ Code Style - classes should be `readonly` when possible - classes should use Constructor Property Promotion - class properties should have `private` visibility unless one is a transfer object, or it is necessary for inheritance -- class method's and property's names should be similar across modules +- class methods and property names should be similar across modules * **expander** classes should have `public` methods prefixed by `expand` * **parser** classes should have `public` methods prefixed by `parse` * **builder** classes should have `public` methods prefixed by `create` @@ -187,12 +186,12 @@ docker/sdk cli ./examples/try-transfer-generator.php How to Generate Internal Transfer Objects ----------------------------------------- -All project transfer objects (generator's, examples, tests) can be generated with the following command: +All project transfer objects (generators, examples, tests) can be generated with the following command: ```console docker/sdk to-generate-bulk ``` -To generate only generator's transfer objects, please run the following command: +To generate only the generator's transfer objects, please run the following command: ```console docker/sdk to-generate ``` @@ -200,7 +199,8 @@ docker/sdk to-generate How to Generate Transfer Objects By Configuration File ------------------------------------------------------ -Transfer objects can be generated by a configuration file path, relative from the project's root, by running the following command: +Transfer objects can be generated by a configuration file path, +relative to the project's root, by running the following command: ```console docker/sdk to-generate [path-to-configuration-file] ``` @@ -238,7 +238,7 @@ docker/sdk phpunit 'Picamator\\Tests\\Unit\\TransferObject\\Command\\Helper\\Inp How to Run PHPStan ------------------ -For all project's files, PHPStan can be run with the following command: +For all project files, PHPStan can be run with the following command: ```console docker/sdk phpstan ``` @@ -251,7 +251,7 @@ docker/sdk phpstan How to Run PHP CodeSniffer -------------------------- -For all project's files, PHP CodeSniffer can be run with the following command: +For all project files, PHP CodeSniffer can be run with the following command: ```console docker/sdk phpcs ``` @@ -264,7 +264,7 @@ docker/sdk phpcs How to Run PHP Code Beautifier and Fixer ---------------------------------------- -For all project's files, PHP Code Beautifier and Fixer can be run with the following command: +For all project files, PHP Code Beautifier and Fixer can be run with the following command: ```console docker/sdk phpcbf ``` diff --git a/README.md b/README.md index 08cbbdd9..24ec5229 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ $ composer require picamator/transfer-object After installation, for the small project size, the following directory structure is recommended: -* `src/Generated`: directory to keep all project's Transfer Objects +* `src/Generated`: directory to keep all the project's Transfer Objects * `src/config/generator.config.yml`: generator's [configuration file](https://github.com/picamator/transfer-object/wiki/Console-Commands#configuration) * `src/config/definition/*.transfer.yml`: [definition files](https://github.com/picamator/transfer-object/wiki/Definition-File), where each of them groups transfer objects definitions by business domain. @@ -126,7 +126,7 @@ generator: Where `YourVendorNamespace\\YourProjectNamespace` should be replaced with your vendor's and project's namespace. -Additionally `.gitignore` should contain: +Additionally, `.gitignore` should contain: ```shell src/Generated/_tmp @@ -141,8 +141,8 @@ $ ./vendor/bin/transfer-generate -c config/generator.config.yml > [!TIP] > For large projects, each module can keep its own Transfer Object Generator configuration. -> It is still possible to keep all transfer objects in one directory, -> with the warning notice as some transfer objects might be overwritten by another module. +> It is still possible to keep all transfer objects in one directory +> please be sure the transfer object names are unique. > [!TIP] > To generate transfer objects in the multi-configuration setup, the [bulk command](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) should be used. From 8cf7d3e60ee86f081944042e5a8882d965e1379c Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 11:35:23 +0100 Subject: [PATCH 48/56] Actualized readme --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 24ec5229..7dd4700d 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ $ composer require picamator/transfer-object **Directory Structure:** -After installation, for the small project size, the following directory structure is recommended: +After installation, for the small project, the following directory structure is recommended: * `src/Generated`: directory to keep all the project's Transfer Objects * `src/config/generator.config.yml`: generator's [configuration file](https://github.com/picamator/transfer-object/wiki/Console-Commands#configuration) @@ -129,6 +129,7 @@ Where `YourVendorNamespace\\YourProjectNamespace` should be replaced with your v Additionally, `.gitignore` should contain: ```shell +# Transfer Objects src/Generated/_tmp src/Generated/transfer.lock ``` @@ -140,12 +141,13 @@ $ ./vendor/bin/transfer-generate -c config/generator.config.yml ``` > [!TIP] -> For large projects, each module can keep its own Transfer Object Generator configuration. +> For large projects, each module can have its own generator configuration. > It is still possible to keep all transfer objects in one directory > please be sure the transfer object names are unique. > [!TIP] -> To generate transfer objects in the multi-configuration setup, the [bulk command](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) should be used. +> To generate transfer objects in the multi-configuration setup, +> the [bulk command](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) should be used. Documentation ------------- From 1ebf9d57e521d211ec4f90390aeeca25d30e7538 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 11:42:04 +0100 Subject: [PATCH 49/56] Actualized readme --- README.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7dd4700d..3515cce6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Transfer Object Generator ========================== -Would you like to build Symfony-compatible Transfer Objects? +Would you like to build Symfony-compatible transfer objects? You're in the right place! 🎉 @@ -78,7 +78,7 @@ Key Features * `IteratorAggregate` * `JsonSerializable` * `Countable` -* Handles embedded and collection Transfer Objects. +* Handles embedded and collection transfer objects. * Works with PHP primitive data types. * Extends compatibility to advanced types: * `BackedEnum` @@ -86,7 +86,7 @@ Key Features * `DateTimeImmutable` * `BcMath\Number` * Supports asymmetric property visibility. -* Integrates with external Transfer Objects. +* Integrates with external transfer objects. Installation ------------ @@ -107,14 +107,14 @@ $ composer require picamator/transfer-object **Directory Structure:** -After installation, for the small project, the following directory structure is recommended: +After installation, the following directory structure is recommended: -* `src/Generated`: directory to keep all the project's Transfer Objects +* `src/Generated`: directory to keep all the project's transfer objects * `src/config/generator.config.yml`: generator's [configuration file](https://github.com/picamator/transfer-object/wiki/Console-Commands#configuration) * `src/config/definition/*.transfer.yml`: [definition files](https://github.com/picamator/transfer-object/wiki/Definition-File), where each of them groups transfer objects definitions by business domain. -With this setup, `generator.config.yml` looks like this: +With this setup, `generator.config.yml` looks like: ```yml # $schema: ./../vendor/picamator/transfer-object/schema/config.schema.json @@ -124,17 +124,18 @@ generator: definitionPath: "${PROJECT_ROOT}/config/definition" ``` -Where `YourVendorNamespace\\YourProjectNamespace` should be replaced with your vendor's and project's namespace. +Where `YourVendorNamespace\\YourProjectNamespace` should be replaced with +your vendor and project namespace. Additionally, `.gitignore` should contain: ```shell -# Transfer Objects +# transfer objects src/Generated/_tmp src/Generated/transfer.lock ``` -Finally, the Transfer Objects are generated by running the following command: +Finally, the transfer objects are generated by running command: ```console $ ./vendor/bin/transfer-generate -c config/generator.config.yml @@ -142,10 +143,6 @@ $ ./vendor/bin/transfer-generate -c config/generator.config.yml > [!TIP] > For large projects, each module can have its own generator configuration. -> It is still possible to keep all transfer objects in one directory -> please be sure the transfer object names are unique. - -> [!TIP] > To generate transfer objects in the multi-configuration setup, > the [bulk command](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) should be used. From c73e3aee246bf5638540a9dccd6c988aad642131 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 11:46:23 +0100 Subject: [PATCH 50/56] Actualized readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3515cce6..7e666bbc 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ src/Generated/_tmp src/Generated/transfer.lock ``` -Finally, the transfer objects are generated by running command: +Then, running command generates transfer objects: ```console $ ./vendor/bin/transfer-generate -c config/generator.config.yml @@ -143,8 +143,8 @@ $ ./vendor/bin/transfer-generate -c config/generator.config.yml > [!TIP] > For large projects, each module can have its own generator configuration. -> To generate transfer objects in the multi-configuration setup, -> the [bulk command](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) should be used. +> Please use [bulk command](https://github.com/picamator/transfer-object/wiki/Console-Commands#transfer-generate-bulk) +> to generate transfer objects for the multi-configuration setup. Documentation ------------- From f2331d577d4ae2cc2637c7af5a20fbdb3f21069c Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 12:02:29 +0100 Subject: [PATCH 51/56] Actualized readme and agents --- AGENTS.md | 16 ++++++---------- README.md | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 23698f3e..ac9de781 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,19 +1,15 @@ Purpose ------- -This file is the project index for "agents": - -- the modules that generate or use PHP transfer objects -- the IDE plugins that help to develop the project -- the IDE plugins that help to integrate PHP transfer objects into the application - +This file is for AI Agents. It is intentionally short and only contains agent-specific facts and a concise inventory. + Full how-to and contribution guides are in the canonical destinations: -- README.md -- CONTRIBUTING.md -- CODE_OF_CONDUCT.md -- SECURITY.md +- [README.md](README.md) +- [CONTRIBUTING.md](CONTRIBUTING.md) +- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) +- [SECURITY.md](SECURITY.md) - [WIKI](https://github.com/picamator/transfer-object/wiki) Installation diff --git a/README.md b/README.md index 7e666bbc..8fcac6ac 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Then, running [console command](https://github.com/picamator/transfer-object/wik $ ./vendor/bin/transfer-generate [-c|--configuration CONFIGURATION] ``` -Builds the Transfer Object: +Builds the transfer object: ```php $customerTransfer = new CustomerTransfer(); From c04fa5ace2209271c3ced9442f8c01252a3d2363 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 13:38:40 +0100 Subject: [PATCH 52/56] Actualized agents --- AGENTS.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ac9de781..37c8950c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,11 +25,9 @@ Directory Structure ### Console commands - `bin`: project's console commands: - * `transfer-generate`: generate transfer objects from configuration files - * `transfer-generate-bulk`: generate transfer objects by the list of configuration files - * `definition-generate`: generate definition files - -> Installing the project by composer, the console commands are available in the vendor's bin directory. + * `transfer-generate`: generate transfer objects from a single configuration file + * `transfer-generate-bulk`: generate transfer objects from a list of configuration files + * `definition-generate`: generate definition files from JSON blueprints ### Config @@ -201,6 +199,14 @@ relative to the project's root, by running the following command: docker/sdk to-generate [path-to-configuration-file] ``` +How to Generate Definition Files +-------------------------------- + +To generate definition files from JSON blueprints, please run the following command: +```console +docker/sdk df-generate +``` + How to Run PHPUnit Tests ------------------------ From f09edbee71e6cf23f87cd18062c6e46fe76fae7c Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 14:02:10 +0100 Subject: [PATCH 53/56] Actualized readme files --- AGENTS.md | 22 +++++++++++++++++++ README.md | 6 ++--- docker/README.md | 19 +++++++++++++++- .../DefinitionGenerator/data/README.md | 14 ++++++------ 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 37c8950c..6d48cf8e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,6 +142,7 @@ How To Install Project ---------------------- The project is installed by running the following command: + ```console docker/sdk install ``` @@ -150,16 +151,19 @@ How To Build/Start/Stop Docker Environment ------------------------------------------- Docker Environment is built by running the following command: + ```console docker/sdk build ``` Docker Environment is started by running the following command: + ```console docker/sdk start ``` Docker Environment is stopped by running the following command: + ```console docker/sdk stop ``` @@ -168,11 +172,13 @@ How to Run PHP Script --------------------- The PHP script runs by command: + ```console docker/sdk cli [path-to-script] ``` For instance, the `./examples/try-transfer-generator.php`: + ```console docker/sdk cli ./examples/try-transfer-generator.php ``` @@ -181,11 +187,13 @@ How to Generate Internal Transfer Objects ----------------------------------------- All project transfer objects (generators, examples, tests) can be generated with the following command: + ```console docker/sdk to-generate-bulk ``` To generate only the generator's transfer objects, please run the following command: + ```console docker/sdk to-generate ``` @@ -195,6 +203,7 @@ How to Generate Transfer Objects By Configuration File Transfer objects can be generated by a configuration file path, relative to the project's root, by running the following command: + ```console docker/sdk to-generate [path-to-configuration-file] ``` @@ -203,6 +212,7 @@ How to Generate Definition Files -------------------------------- To generate definition files from JSON blueprints, please run the following command: + ```console docker/sdk df-generate ``` @@ -213,6 +223,7 @@ How to Run PHPUnit Tests ### How to Run All Tests All tests can be run with the following command: + ```console docker/sdk phpunit ``` @@ -220,6 +231,7 @@ docker/sdk phpunit ### How to Run Test Group A test group can be run with the following command: + ```console docker/sdk phpunit-group ``` @@ -227,12 +239,14 @@ docker/sdk phpunit-group ### How to Run Test Case A test case can be run with the following command: + ```console docker/sdk phpunit '' ``` For instance, the test case `Picamator\Tests\Unit\TransferObject\Command\Helper\InputNormalizerTest` can be run with the following command: + ```console docker/sdk phpunit 'Picamator\\Tests\\Unit\\TransferObject\\Command\\Helper\\InputNormalizerTest' ``` @@ -241,11 +255,13 @@ How to Run PHPStan ------------------ For all project files, PHPStan can be run with the following command: + ```console docker/sdk phpstan ``` For the specific file: + ```console docker/sdk phpstan ``` @@ -254,11 +270,13 @@ How to Run PHP CodeSniffer -------------------------- For all project files, PHP CodeSniffer can be run with the following command: + ```console docker/sdk phpcs ``` For the specific file: + ```console docker/sdk phpcs ``` @@ -267,11 +285,13 @@ How to Run PHP Code Beautifier and Fixer ---------------------------------------- For all project files, PHP Code Beautifier and Fixer can be run with the following command: + ```console docker/sdk phpcbf ``` For the specific file: + ```console docker/sdk phpcbf ``` @@ -280,11 +300,13 @@ How to Run Composer ------------------- Composer can be run with the following command: + ```console docker/sdk composer ``` The command supports multiple arguments, for example: + ```console docker/sdk composer install ``` diff --git a/README.md b/README.md index 8fcac6ac..b80aa826 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,9 @@ $ composer require picamator/transfer-object ``` | Version | PHP | Symfony | -|-------|-----|---------| -| 4.0.0 | 8.4 | 7.3 | -| 5.0.0 | 8.5 | 8.0 | +|---------|-----|---------| +| ≤ 4.0.0 | 8.4 | 7.3 | +| ≥ 5.0.0 | 8.5 | 8.0 | **Directory Structure:** diff --git a/docker/README.md b/docker/README.md index 9e0bffac..ab39530b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,4 +1,21 @@ Docker SDK ========== -For detailed information, please follow [Development Environment](https://github.com/picamator/transfer-object/wiki/Development-Environment). +Docker SDK provides the project's [Development Environment](https://github.com/picamator/transfer-object/wiki/Development-Environment). + +All developer operations are managed via the `docker/sdk` script. + +Quick Start +----------- + +To install the project's development environment, run: + +```console +$ docker/sdk install +``` + +For more commands, run: + +```console +$ docker/sdk +``` diff --git a/tests/integration/DefinitionGenerator/data/README.md b/tests/integration/DefinitionGenerator/data/README.md index 53810a5e..9638da66 100644 --- a/tests/integration/DefinitionGenerator/data/README.md +++ b/tests/integration/DefinitionGenerator/data/README.md @@ -6,15 +6,15 @@ Data Provider Definition Generator has been tested against the following APIs: -| File | Source | -|---------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------| -| [nasa-neo-rest-v1-neo-2465633.json](/tests/integration/DefinitionGenerator/data/api-response/nasa-neo-rest-v1-neo-2465633.json) | [NASA Open Api](https://api.nasa.gov/neo/rest/v1/neo/2465633?api_key=DEMO_KEY) | -| [open-weather.json](/tests/integration/DefinitionGenerator/data/api-response/open-weather.json) | [OpenWeather](https://openweathermap.org/current?collection=current_forecast#example_JSON) | +| File | Source | +|---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| [nasa-neo-rest-v1-neo-2465633.json](/tests/integration/DefinitionGenerator/data/api-response/nasa-neo-rest-v1-neo-2465633.json) | [NASA Open Api](https://api.nasa.gov/neo/rest/v1/neo/2465633?api_key=DEMO_KEY) | +| [open-weather.json](/tests/integration/DefinitionGenerator/data/api-response/open-weather.json) | [OpenWeather](https://openweathermap.org/current?collection=current_forecast#example_JSON) | | [google-shopping-content.json](/tests/integration/DefinitionGenerator/data/api-response/google-shopping-content.json) | [Google Content API for Shopping](https://developers.google.com/shopping-content/guides/products/products-api?hl=en) | -| [frankfurter-dev.json](/tests/integration/DefinitionGenerator/data/api-response/frankfurter-dev-v1.json) | [Frankfurter - open-source currency data API](https://api.frankfurter.dev/v1/latest) | -| [tagesschau-api-bund-dev.json](/tests/integration/DefinitionGenerator/data/api-response/tagesschau-api-bund-dev-v2.json) | [Tagesschau API](https://tagesschau.api.bund.dev) | +| [frankfurter-dev-v1.json](/tests/integration/DefinitionGenerator/data/api-response/frankfurter-dev-v1.json) | [Frankfurter – open-source currency data API](https://api.frankfurter.dev/v1/latest) | +| [tagesschau-api-bund-dev.json](/tests/integration/DefinitionGenerator/data/api-response/tagesschau-api-bund-dev-v2.json) | [Tagesschau API](https://tagesschau.api.bund.dev) | | [genesis-destatis-find.json](/tests/integration/DefinitionGenerator/data/api-response/genesis-destatis-find.json) | [Statistisches Bundesamt (Destatis)](https://www-genesis.destatis.de/genesisWS/swagger-ui/index.html#/find/findPost) | -| [wero-payment-charges-v1.json](/tests/integration/DefinitionGenerator/data/api-response/wero-payment-charges-v1.json) | [Wero - Digital Payment Wallet](https://developerhub.ppro.com/global-api/docs/wero) | +| [wero-payment-charges-v1.json](/tests/integration/DefinitionGenerator/data/api-response/wero-payment-charges-v1.json) | [Wero - Digital Payment Wallet](https://developerhub.ppro.com/global-api/docs/wero) | Scenario -------- From 3b0f106dc2b3dd89ca8ec9fa979e477b3b9a9506 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Fri, 6 Mar 2026 23:20:48 +0100 Subject: [PATCH 54/56] Actualized readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b80aa826..8524aa4d 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ Documentation * [Project's Wiki](https://github.com/picamator/transfer-object/wiki) * [Examples](examples) +* [Integration Test Scenario](tests/integration/DefinitionGenerator/data/README.md) Publications ------------ From 341b36fb8716aa424b9b3d4b16b1f7fb33af39db Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sat, 7 Mar 2026 17:26:31 +0100 Subject: [PATCH 55/56] Upgraded composer dependencies --- composer.lock | 99 ++++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/composer.lock b/composer.lock index 6e2ae33d..b1605c14 100644 --- a/composer.lock +++ b/composer.lock @@ -61,16 +61,16 @@ }, { "name": "symfony/console", - "version": "v8.0.4", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b" + "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b", - "reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b", + "url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a", + "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a", "shasum": "" }, "require": { @@ -127,7 +127,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.0.4" + "source": "https://github.com/symfony/console/tree/v8.0.7" }, "funding": [ { @@ -147,7 +147,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T13:06:50+00:00" + "time": "2026-03-06T14:06:22+00:00" }, { "name": "symfony/deprecation-contracts", @@ -218,16 +218,16 @@ }, { "name": "symfony/filesystem", - "version": "v8.0.1", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d" + "reference": "7bf9162d7a0dff98d079b72948508fa48018a770" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770", + "reference": "7bf9162d7a0dff98d079b72948508fa48018a770", "shasum": "" }, "require": { @@ -264,7 +264,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.1" + "source": "https://github.com/symfony/filesystem/tree/v8.0.6" }, "funding": [ { @@ -284,20 +284,20 @@ "type": "tidelift" } ], - "time": "2025-12-01T09:13:36+00:00" + "time": "2026-02-25T16:59:43+00:00" }, { "name": "symfony/finder", - "version": "v8.0.5", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0" + "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0", - "reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0", + "url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c", + "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c", "shasum": "" }, "require": { @@ -332,7 +332,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v8.0.5" + "source": "https://github.com/symfony/finder/tree/v8.0.6" }, "funding": [ { @@ -352,7 +352,7 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:08:38+00:00" + "time": "2026-01-29T09:41:02+00:00" }, { "name": "symfony/polyfill-ctype", @@ -778,16 +778,16 @@ }, { "name": "symfony/string", - "version": "v8.0.4", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "758b372d6882506821ed666032e43020c4f57194" + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", - "reference": "758b372d6882506821ed666032e43020c4f57194", + "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4", "shasum": "" }, "require": { @@ -844,7 +844,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.4" + "source": "https://github.com/symfony/string/tree/v8.0.6" }, "funding": [ { @@ -864,20 +864,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:37:40+00:00" + "time": "2026-02-09T10:14:57+00:00" }, { "name": "symfony/yaml", - "version": "v8.0.1", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "7a1a90ba1df6e821a6b53c4cabdc32a56cabfb14" + "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/7a1a90ba1df6e821a6b53c4cabdc32a56cabfb14", - "reference": "7a1a90ba1df6e821a6b53c4cabdc32a56cabfb14", + "url": "https://api.github.com/repos/symfony/yaml/zipball/5f006c50a981e1630bbb70ad409c5d85f9a716e0", + "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0", "shasum": "" }, "require": { @@ -919,7 +919,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v8.0.1" + "source": "https://github.com/symfony/yaml/tree/v8.0.6" }, "funding": [ { @@ -939,22 +939,22 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:17:06+00:00" + "time": "2026-02-09T10:14:57+00:00" } ], "packages-dev": [ { "name": "captainhook/captainhook", - "version": "5.28.3", + "version": "5.28.5", "source": { "type": "git", "url": "https://github.com/captainhook-git/captainhook.git", - "reference": "5d35b249f3843ef36ead119f4347e649278ad6d8" + "reference": "2a7316bf4ba4c3b11b3544c063788622d3520ee1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/captainhook-git/captainhook/zipball/5d35b249f3843ef36ead119f4347e649278ad6d8", - "reference": "5d35b249f3843ef36ead119f4347e649278ad6d8", + "url": "https://api.github.com/repos/captainhook-git/captainhook/zipball/2a7316bf4ba4c3b11b3544c063788622d3520ee1", + "reference": "2a7316bf4ba4c3b11b3544c063788622d3520ee1", "shasum": "" }, "require": { @@ -1017,7 +1017,7 @@ ], "support": { "issues": "https://github.com/captainhook-git/captainhook/issues", - "source": "https://github.com/captainhook-git/captainhook/tree/5.28.3" + "source": "https://github.com/captainhook-git/captainhook/tree/5.28.5" }, "funding": [ { @@ -1025,7 +1025,7 @@ "type": "github" } ], - "time": "2026-02-16T14:08:58+00:00" + "time": "2026-02-28T08:59:22+00:00" }, { "name": "captainhook/secrets", @@ -3544,16 +3544,16 @@ }, { "name": "symfony/validator", - "version": "v8.0.5", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "ba171e89ee2d01c24c1d8201d59ec595ef4adba1" + "reference": "04f7111e6f246d8211081fdc76e34b1298a9fc27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/ba171e89ee2d01c24c1d8201d59ec595ef4adba1", - "reference": "ba171e89ee2d01c24c1d8201d59ec595ef4adba1", + "url": "https://api.github.com/repos/symfony/validator/zipball/04f7111e6f246d8211081fdc76e34b1298a9fc27", + "reference": "04f7111e6f246d8211081fdc76e34b1298a9fc27", "shasum": "" }, "require": { @@ -3564,7 +3564,8 @@ }, "conflict": { "doctrine/lexer": "<1.1", - "symfony/doctrine-bridge": "<7.4" + "symfony/doctrine-bridge": "<7.4", + "symfony/expression-language": "<7.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", @@ -3614,7 +3615,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v8.0.5" + "source": "https://github.com/symfony/validator/tree/v8.0.7" }, "funding": [ { @@ -3634,20 +3635,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T09:06:10+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/var-dumper", - "version": "v8.0.4", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "326e0406fc315eca57ef5740fa4a280b7a068c82" + "reference": "2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/326e0406fc315eca57ef5740fa4a280b7a068c82", - "reference": "326e0406fc315eca57ef5740fa4a280b7a068c82", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209", + "reference": "2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209", "shasum": "" }, "require": { @@ -3701,7 +3702,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v8.0.4" + "source": "https://github.com/symfony/var-dumper/tree/v8.0.6" }, "funding": [ { @@ -3721,7 +3722,7 @@ "type": "tidelift" } ], - "time": "2026-01-01T23:07:29+00:00" + "time": "2026-02-15T10:53:29+00:00" }, { "name": "theseer/tokenizer", From c14bdf0aeba3dd1b5c64d90193c04becb16937d6 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Sat, 7 Mar 2026 17:35:29 +0100 Subject: [PATCH 56/56] Actualized readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8524aa4d..38df5c8c 100644 --- a/README.md +++ b/README.md @@ -109,10 +109,10 @@ $ composer require picamator/transfer-object After installation, the following directory structure is recommended: -* `src/Generated`: directory to keep all the project's transfer objects +* `src/Generated`: transfer objects directory * `src/config/generator.config.yml`: generator's [configuration file](https://github.com/picamator/transfer-object/wiki/Console-Commands#configuration) -* `src/config/definition/*.transfer.yml`: [definition files](https://github.com/picamator/transfer-object/wiki/Definition-File), -where each of them groups transfer objects definitions by business domain. +* `src/config/definition/*.transfer.yml`: transfer objects [definition files](https://github.com/picamator/transfer-object/wiki/Definition-File), +where each of them groups transfer objects definitions by business domain, e.g. `payment.transfer.yml`, `sales.transfer.yml`, etc. With this setup, `generator.config.yml` looks like: