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/AGENTS.md b/AGENTS.md index 77692075..6d48cf8e 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 @@ -29,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 @@ -53,10 +47,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 -- `src/Generated/_tmp`: temporary directory including newly generated transfer objects before they are finally moved to the `src/Generated` + * 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: + * 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 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 @@ -65,7 +68,7 @@ Directory Structure ### 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 @@ -89,7 +92,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` @@ -139,6 +142,7 @@ How To Install Project ---------------------- The project is installed by running the following command: + ```console docker/sdk install ``` @@ -147,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 ``` @@ -165,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 ``` @@ -177,12 +186,14 @@ 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 ``` @@ -190,17 +201,29 @@ 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] ``` +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 ------------------------ ### How to Run All Tests All tests can be run with the following command: + ```console docker/sdk phpunit ``` @@ -208,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 ``` @@ -215,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' ``` @@ -228,12 +254,14 @@ 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 ``` For the specific file: + ```console docker/sdk phpstan ``` @@ -241,12 +269,14 @@ 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 ``` For the specific file: + ```console docker/sdk phpcs ``` @@ -254,12 +284,14 @@ 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 ``` For the specific file: + ```console docker/sdk phpcbf ``` @@ -268,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 73d991c4..38df5c8c 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! 🎉 @@ -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(); @@ -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:** @@ -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,58 +86,72 @@ Key Features * `DateTimeImmutable` * `BcMath\Number` * Supports asymmetric property visibility. -* Integrates with external Transfer Objects. +* Integrates with external transfer objects. 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 ``` | 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:** -Examples ---------- +After installation, the following directory structure is recommended: -* [Definition Generator](/examples/try-definition-generator.php) -* [Transfer Generator](/examples/try-transfer-generator.php) -* [Advanced Transfer Generator](/examples/try-advanced-transfer-generator.php) +* `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`: 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. -Usage Tests ------------ +With this setup, `generator.config.yml` looks like: + +```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" +``` -Definition Files and Transfer Object generators have been tested against the following APIs: +Where `YourVendorNamespace\\YourProjectNamespace` should be replaced with +your vendor and project namespace. -* [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) +Additionally, `.gitignore` should contain: -### Scenario +```shell +# transfer objects +src/Generated/_tmp +src/Generated/transfer.lock +``` -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 +Then, running command generates transfer objects: -For all APIs, data are ✅ matched. +```console +$ ./vendor/bin/transfer-generate -c config/generator.config.yml +``` -For detailed information, please check [DefinitionGeneratorFacadeTest](/tests/integration/DefinitionGenerator/DefinitionGeneratorFacadeTest.php). +> [!TIP] +> For large projects, each module can have its own generator configuration. +> 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 ------------- -For more details, please visit [Project's Wiki](https://github.com/picamator/transfer-object/wiki). +* [Project's Wiki](https://github.com/picamator/transfer-object/wiki) +* [Examples](examples) +* [Integration Test Scenario](tests/integration/DefinitionGenerator/data/README.md) Publications ------------ 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", diff --git a/config/definition/transfer-generator.transfer.yml b/config/definition/transfer-generator.transfer.yml index 81597837..308563a1 100644 --- a/config/definition/transfer-generator.transfer.yml +++ b/config/definition/transfer-generator.transfer.yml @@ -20,6 +20,18 @@ ConfigContent: relativeDefinitionPath: type: string required: + hashFileName: + type: string + required: + protected: + uuid: + type: string + required: + protected: + isCacheEnabled: + type: bool + required: + protected: Definition: fileName: @@ -116,6 +128,23 @@ TransferGeneratorContent: type: string required: protected: + hash: + type: string + required: + protected: + +TransferHash: + hashes: + type: ArrayObject + protected: + configUuid: + type: string + required: + protected: + actualHashes: + type: ArrayObject + toCopyClassNames: + type: ArrayObject TransferGeneratorBulk: progress: diff --git a/docker-compose.yml b/docker-compose.yml index e9a244cb..b1f82383 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +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_PROJECT_ROOT: /home/transfer/transfer-object build: context: docker/php args: 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/examples/Generated/AdvancedTransferGenerator/7509ef4008791f2426b2628f97dda4dfab2a1ed0.transfer.hash.csv b/examples/Generated/AdvancedTransferGenerator/7509ef4008791f2426b2628f97dda4dfab2a1ed0.transfer.hash.csv new file mode 100644 index 00000000..7a663fe2 --- /dev/null +++ b/examples/Generated/AdvancedTransferGenerator/7509ef4008791f2426b2628f97dda4dfab2a1ed0.transfer.hash.csv @@ -0,0 +1 @@ +AdvancedCustomerTransfer,e95f378cb17b10a5f5bacc253d684f2e406526cb9179da626f2ae589770b7351 \ No newline at end of file diff --git a/examples/Generated/DefinitionGenerator/e6032560f45b3a6f826b99acdb29c8d744f65294.transfer.hash.csv b/examples/Generated/DefinitionGenerator/e6032560f45b3a6f826b99acdb29c8d744f65294.transfer.hash.csv new file mode 100644 index 00000000..461b2e0a --- /dev/null +++ b/examples/Generated/DefinitionGenerator/e6032560f45b3a6f826b99acdb29c8d744f65294.transfer.hash.csv @@ -0,0 +1,8 @@ +ProductTransfer,4b971a742f409a8f34474cc398ae3a421a170da2af27fadb758419f55d67558c +DeliveryOptionsTransfer,ae3c027988c0aff9cd9ca4a4c75806c5570cccea47f11b740bf66b8587e686b2 +DetailsTransfer,a71bdbbd8934a1a4bbc2f6b87e582e45e299c3beebea7f015e7479262e9b5dfb +LabelsTransfer,d4b058fdeb18278361c47cb129e9f0d194dce6be97d8a10672dd987f7b026091 +AvailabilitiesTransfer,18871357c1130789f629ec1e8d836042f5c3f02d88c90c997a25a743253bfc64 +MeasurementUnitTransfer,4da9dffe333a556436bb6dfd4c94a01e19ebe00c935e5773a9b32011a72a61a9 +PaletteTransfer,080031bc3844e2505f97fba584a2652e5f9719e2c730e00ebacffb0b17673ddc +BoxTransfer,119ddf5e9e460935f949eb86d047e91142b6e5b58c3862e4e6ede2f5cf7f61d6 \ No newline at end of file diff --git a/examples/Generated/TransferGenerator/16cc72947d7980764d852431397f3f96a71446de.transfer.hash.csv b/examples/Generated/TransferGenerator/16cc72947d7980764d852431397f3f96a71446de.transfer.hash.csv new file mode 100644 index 00000000..86fb5ac4 --- /dev/null +++ b/examples/Generated/TransferGenerator/16cc72947d7980764d852431397f3f96a71446de.transfer.hash.csv @@ -0,0 +1,4 @@ +CredentialsTransfer,4c9e3a5af952bb41f1d90f55262d56f9eebb5fe7a296fd8b9b00f74269bb3fa5 +CustomerTransfer,e7fa9a90f41ef578b42d4f8c42d07202f62edb7da389adf72c523c798b00252c +AgentTransfer,88d58cf9383287603ea138ae39629eda250f33209381fa8232597456922b7389 +MerchantTransfer,715ed4d116f7ad2d92398aed0baf2031b4ee391663df9ba7a2e1bd9350f0b4e4 \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..930744c3 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,67 @@ +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 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`: + +* [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 +``` + +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` + +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`: + +* [try-definition-generator.php](try-definition-generator.php). + +The following command runs 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' ======================================================= 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 ✨'; 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/src/Dependency/Filesystem/FilesystemBridge.php b/src/Dependency/Filesystem/FilesystemBridge.php index 549e61ba..f570ae13 100644 --- a/src/Dependency/Filesystem/FilesystemBridge.php +++ b/src/Dependency/Filesystem/FilesystemBridge.php @@ -15,17 +15,17 @@ public function __construct( ) { } - public function copy(string $originFile, string $targetFile): void + public function rename(string $origin, string $target, bool $overwrite = false): void { try { - $this->filesystem->copy($originFile, $targetFile); + $this->filesystem->rename($origin, $target, $overwrite); // @codeCoverageIgnoreStart } catch (Throwable $e) { throw new FilesystemException( sprintf( - 'Failed to copy "%s" to "%s". Error: "%s".', - $originFile, - $targetFile, + 'Failed to rename "%s" to "%s". Error: "%s".', + $origin, + $target, $e->getMessage(), ), previous: $e, diff --git a/src/Dependency/Filesystem/FilesystemInterface.php b/src/Dependency/Filesystem/FilesystemInterface.php index 8a19a4d9..163def15 100644 --- a/src/Dependency/Filesystem/FilesystemInterface.php +++ b/src/Dependency/Filesystem/FilesystemInterface.php @@ -9,7 +9,7 @@ interface FilesystemInterface /** * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException */ - public function copy(string $originFile, string $targetFile): void; + public function rename(string $origin, string $target, bool $overwrite = false): void; /** * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException 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; } diff --git a/src/Generated/ConfigContentTransfer.php b/src/Generated/ConfigContentTransfer.php index 19bbe22d..cbcd2bb3 100644 --- a/src/Generated/ConfigContentTransfer.php +++ b/src/Generated/ConfigContentTransfer.php @@ -17,13 +17,16 @@ */ final class ConfigContentTransfer extends AbstractTransfer { - protected const int META_DATA_SIZE = 4; + 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, + self::UUID_PROP => self::UUID_INDEX, ]; // definitionPath @@ -37,9 +40,31 @@ 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); + } + } + + // 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 = 1; + private const int RELATIVE_DEFINITION_PATH_INDEX = 3; public string $relativeDefinitionPath { get => $this->getData(self::RELATIVE_DEFINITION_PATH_INDEX); @@ -50,7 +75,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 = 4; public string $transferNamespace { get => $this->getData(self::TRANSFER_NAMESPACE_INDEX); @@ -61,7 +86,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 = 5; public string $transferPath { get => $this->getData(self::TRANSFER_PATH_INDEX); @@ -69,4 +94,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 = 6; + + 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/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/d0671a0a246503701481220b288a359e8ba84d39.transfer.hash.csv b/src/Generated/d0671a0a246503701481220b288a359e8ba84d39.transfer.hash.csv new file mode 100644 index 00000000..a2a6d839 --- /dev/null +++ b/src/Generated/d0671a0a246503701481220b288a359e8ba84d39.transfer.hash.csv @@ -0,0 +1,21 @@ +ConfigTransfer,fa113db0d8da9557305bc223b5058f1159f5265e13ee6f3d3f241ce6dea211cf +ConfigContentTransfer,ef96669b4877eeb3a554581b986d1ca200d4f8605d9e9022d568db613b3549b4 +DefinitionTransfer,2f3aee2d9280769303b79b3188a48409b9c515e61e7318408239c12e2a18bbb4 +DefinitionContentTransfer,f9091ac813bf472677ee58d39fcced7c192ac19300059e9788fd79b8e3f93077 +DefinitionPropertyTransfer,df7f49080dcca259103f571b2c656c416aec7616769c54ed024980349762e7ac +DefinitionAttributeTransfer,dd8933e2d0cb072b0fed8e8a25a333f4f0a0d0915ed873864ca1879651beb089 +DefinitionBuiltInTypeTransfer,e39bce896d4c7087e0b5b83f5fe8899413c313b855b94dd287bdc101b960cafd +DefinitionEmbeddedTypeTransfer,7c77e18e2db40b7fec2113771872ebd56b299dbd2976358af857e0ed1cd7d5f7 +DefinitionNamespaceTransfer,4169380f244fb5fb139f4174bc913c9975bd27c6d0b304a50694c1566817a1cf +TransferGeneratorTransfer,6b2f8deecd6f95a38d66dd4ededed882b156c2f99e235deeaf87a4e9c1a8d703 +TransferGeneratorContentTransfer,51bd33f51f7d22da97b2c940d573acba8e7afa47e84c9f5552aeae05c9e1f537 +TransferHashTransfer,9f6c2ed84c3db81d394dee94af03388c31483eef6fa7d367fdc5487a6d88e40b +TransferGeneratorBulkTransfer,a0f33353da9725bb6e38e306c9ad8f7454aaa648d2821130406de43b8632ad67 +ValidatorTransfer,ec88c3635d5173ead19a291b626dadf3f675200314147938a2bffdb1b7df1b78 +ValidatorMessageTransfer,e579192f13bf4cdc9fab844dbefaf8a6a8246ad05f893d9723328870ffc974c3 +FileReaderProgressTransfer,7ec9bb1a892f0594535cca1709515f465aadb15ea2dac393256ae75a201db404 +DefinitionGeneratorTransfer,b54540b65d2766439be5cf0a7566580b78e1241134289c28ba30a7f56be0d8f5 +DefinitionGeneratorContentTransfer,4ef671b1a35f0cde54246d2cf8e7b0feaa3faa25352e44abd3d504a6d6d48e17 +DefinitionBuilderTransfer,d5899c829de6422e636803b720f68fb7dc79165cf13e4611b7d33951bc7e0a94 +DefinitionFilesystemTransfer,6726bd810cc3c76c8c2f5791f883b27bab9ac4aebcf4c7d094f814527442f0dd +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..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) { @@ -42,6 +42,15 @@ public function getMaxFileSizeBytes(): int return $this->getMaxFileSizeMegabytes() * 1_000_000; } + public function getIsCacheEnabled(): bool + { + $isCacheEnabled = $this->getEnvironment(@EnvironmentEnum::IS_CACHE_ENABLED); + + return $isCacheEnabled === '1' + || $isCacheEnabled === 'true' + || $isCacheEnabled === '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/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 @@ +fileExists($path)) { + return []; + } + + $content = []; + foreach ($this->fileReader->readFile($path) as $line) { + $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); + $hash = substr($line, $separatorPos + 1); + + 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..69adae7b --- /dev/null +++ b/src/Shared/Hash/HashFileWriter.php @@ -0,0 +1,39 @@ +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 .= << $data + * + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + public function writeFile(string $filename, ArrayObject $data): void; +} diff --git a/src/Shared/Locker/FileLocker.php b/src/Shared/Locker/FileLocker.php new file mode 100644 index 00000000..ecba4223 --- /dev/null +++ b/src/Shared/Locker/FileLocker.php @@ -0,0 +1,72 @@ +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 EnvironmentReader(), ); } + + final protected function createHashFileReader(): HashFileReaderInterface + { + return $this->getCached( + key: 'shared:HashFileReader', + factory: fn(): HashFileReaderInterface => new HashFileReader($this->createFileReader()), + ); + } + + final protected function createHashFileWriter(): HashFileWriterInterface + { + return $this->getCached( + key: 'shared:HashFileWriter', + factory: fn(): HashFileWriterInterface => 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/Config/Config/Config.php b/src/TransferGenerator/Config/Config/Config.php index bf4e61fa..23a84ae2 100644 --- a/src/TransferGenerator/Config/Config/Config.php +++ b/src/TransferGenerator/Config/Config/Config.php @@ -32,4 +32,19 @@ public function getRelativeDefinitionPath(): string { return $this->configTransfer->relativeDefinitionPath; } + + public function getUuid(): string + { + return $this->configTransfer->uuid; + } + + 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 8b6ae9db..0514d57f 100644 --- a/src/TransferGenerator/Config/Config/ConfigInterface.php +++ b/src/TransferGenerator/Config/Config/ConfigInterface.php @@ -25,4 +25,19 @@ public function getDefinitionPath(): string; * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ public function getRelativeDefinitionPath(): string; + + /** + * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + */ + 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 feed87b6..083066bf 100644 --- a/src/TransferGenerator/Config/Config/ConfigProxy.php +++ b/src/TransferGenerator/Config/Config/ConfigProxy.php @@ -30,6 +30,21 @@ public function getRelativeDefinitionPath(): string return $this->getConfig()->getRelativeDefinitionPath(); } + public function getUuid(): string + { + return $this->getConfig()->getUuid(); + } + + 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/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 266d1deb..2d37240b 100644 --- a/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php +++ b/src/TransferGenerator/Config/Enum/ConfigKeyEnum.php @@ -13,11 +13,14 @@ enum ConfigKeyEnum: string case DEFINITION_PATH = ConfigContentTransfer::DEFINITION_PATH_PROP; /** - * @return array + * @return array */ public static function getDefaultConfig(): array { - $defaultContent = []; + $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 298fd341..2a01e7b3 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php @@ -18,7 +18,13 @@ public function __construct(private EnvironmentReaderInterface $environmentReade public function createContentTransfer(array $configData): ConfigContentTransfer { $contentTransfer = new ConfigContentTransfer($configData); + $this->parseContentPath($contentTransfer); + return $contentTransfer; + } + + private function parseContentPath(ConfigContentTransfer $contentTransfer): void + { $relativeDefinitionPath = $this->filterPath($contentTransfer->definitionPath); $contentTransfer->relativeDefinitionPath = $this->replacePlaceholder($relativeDefinitionPath, ''); @@ -29,8 +35,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/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/ConfigParser.php b/src/TransferGenerator/Config/Parser/ConfigParser.php index 83c4c6fa..15e0d404 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,33 @@ 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->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); + + $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/Parser/Filter/ConfigNormalizerTrait.php b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php index 0b06ea81..3eef5192 100644 --- a/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php +++ b/src/TransferGenerator/Config/Parser/Filter/ConfigNormalizerTrait.php @@ -11,22 +11,30 @@ trait ConfigNormalizerTrait private const string CONFIG_SECTION_KEY = 'generator'; /** - * @return array + * @return array */ 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; - } + return is_array($sectionData) + ? $this->filterSectionData($sectionData) + : ConfigKeyEnum::getDefaultConfig(); + } + + /** + * @param array $sectionData + * + * @return array + */ + private function filterSectionData(array $sectionData): array + { $filteredData = []; - foreach ($defaultConfig as $key => $defaultValue) { + foreach (ConfigKeyEnum::getDefaultConfig() as $key => $defaultValue) { $sectionValue = $sectionData[$key] ?? null; $filteredData[$key] = is_string($sectionValue) ? $sectionValue : $defaultValue; } diff --git a/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php new file mode 100644 index 00000000..065c22e6 --- /dev/null +++ b/src/TransferGenerator/Generator/Filesystem/FilesystemTrait.php @@ -0,0 +1,41 @@ +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 + . DIRECTORY_SEPARATOR + . $this->config->getUuid(); + + 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..421253e3 100644 --- a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php +++ b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystem.php @@ -4,32 +4,29 @@ 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; 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 = '.php'; public function __construct( private FilesystemInterface $filesystem, - private FinderInterface $finder, private ConfigInterface $config, ) { } public function createTempDir(): void { - $temporaryPath = $this->getTemporaryPath(); + $tempPath = $this->getTemporaryPath(); - $this->deleteTempDir(); - $this->filesystem->mkdir($temporaryPath); + $this->filesystem->mkdir($tempPath); } /** @@ -37,25 +34,17 @@ public function createTempDir(): void */ public function deleteTempDir(): void { - $temporaryPath = $this->getTemporaryPath(); - if ($this->filesystem->exists($temporaryPath)) { - $this->filesystem->remove($temporaryPath); - } - } + $tempPath = $this->getTemporaryPath(); - public function rotateTempDir(): void - { - $this->deleteOldFiles(); - $this->copyTempFiles(); - $this->deleteTempDir(); + if ($this->filesystem->exists($tempPath)) { + $this->filesystem->remove($tempPath); + } } - public function writeFile(TransferGeneratorContentTransfer $contentTransfer): void + public function writeTempFile(TransferGeneratorContentTransfer $contentTransfer): void { - $filePath = $this->getTemporaryPath() - . DIRECTORY_SEPARATOR - . $contentTransfer->className - . self::FILE_EXTENSION; + $fileName = $this->getFileName($contentTransfer->className); + $filePath = $this->getTemporaryPath($fileName); if ($this->filesystem->exists($filePath)) { throw new TransferGeneratorException( @@ -67,45 +56,48 @@ public function writeFile(TransferGeneratorContentTransfer $contentTransfer): vo } /** - * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\Dependency\Exception\FinderException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + * @param \ArrayObject $classNames */ - private function deleteOldFiles(): void + public function deleteFiles(ArrayObject $classNames): void { - $finder = $this->finder->findFilesInDirectoryExclude( - filePattern: self::FILE_NAME_PATTERN, - dirName: $this->config->getTransferPath(), - exclude: self::TEMPORARY_DIR, - ); - - $this->filesystem->remove($finder); + $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 + public function renameTempFiles(ArrayObject $classNames): void { - $finder = $this->finder->findFilesInDirectory( - filePattern: self::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->rename($originalFile, $targetFile, overwrite: true); } } /** - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException + * @param \ArrayObject $classNames + * + * @return array */ - private function getTemporaryPath(): string + private function getFileNames(ArrayObject $classNames): array + { + $fileNames = []; + foreach ($classNames as $className) { + $fileNames[] = $this->getFileName($className); + } + + return $fileNames; + } + + private function getFileName(string $className): string { - return $this->config->getTransferPath() . DIRECTORY_SEPARATOR . self::TEMPORARY_DIR; + return $className . self::TRANSFER_FILE_EXTENSION; } } diff --git a/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/GeneratorFilesystemInterface.php index e88efc71..f7961ab3 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 @@ -20,14 +21,20 @@ public function deleteTempDir(): void; /** * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\Dependency\Exception\FinderException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorConfigNotFoundException */ - public function rotateTempDir(): void; + public function writeTempFile(TransferGeneratorContentTransfer $contentTransfer): void; /** + * @param \ArrayObject $classNames + * * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException */ - public function writeFile(TransferGeneratorContentTransfer $contentTransfer): 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 new file mode 100644 index 00000000..a287e687 --- /dev/null +++ b/src/TransferGenerator/Generator/Filesystem/HashFilesystem.php @@ -0,0 +1,41 @@ + $hashes + */ + public function writeHashTmpFile(ArrayObject $hashes): void + { + $filePath = $this->getTemporaryPath($this->config->getHashFileName()); + $this->fileWriter->writeFile($filePath, $hashes); + } + + public function renameHashTmpFile(): void + { + $fileName = $this->config->getHashFileName(); + + $originalFile = $this->getTemporaryPath($fileName); + $targetFile = $this->getTransferPath($fileName); + + $this->filesystem->rename($originalFile, $targetFile, overwrite: true); + } +} diff --git a/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php b/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php new file mode 100644 index 00000000..27d01d34 --- /dev/null +++ b/src/TransferGenerator/Generator/Filesystem/HashFilesystemInterface.php @@ -0,0 +1,22 @@ + $hashes + * + * @throws \Picamator\TransferObject\Dependency\Exception\FilesystemException + */ + 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 f5f71eec..45246195 100644 --- a/src/TransferGenerator/Generator/Generator/GeneratorFactory.php +++ b/src/TransferGenerator/Generator/Generator/GeneratorFactory.php @@ -12,6 +12,8 @@ use Picamator\TransferObject\TransferGenerator\Definition\Reader\DefinitionReaderInterface; 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; @@ -24,8 +26,16 @@ 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\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; +use Picamator\TransferObject\TransferGenerator\Generator\Writer\TransferWriterInterface; class GeneratorFactory { @@ -46,6 +56,7 @@ protected function createPostProcessCommand(): PostProcessCommandInterface return new PostProcessCommand( $this->createTransferGeneratorBuilder(), $this->createGeneratorFilesystem(), + $this->createTransferRotator(), ); } @@ -62,7 +73,7 @@ protected function createProcessCommand(): ProcessCommandInterface return new ProcessCommand( $this->createTransferGeneratorBuilder(), $this->createTemplateRender(), - $this->createGeneratorFilesystem(), + $this->createTransferWriter(), ); } @@ -75,6 +86,17 @@ protected function createPreProcessCommand(): PreProcessCommandInterface ); } + protected function createTransferLocker(): TransferLockerInterface + { + return $this->getCached( + key: 'transfer-generator:TransferLocker', + factory: fn(): TransferLockerInterface => new TransferLocker( + $this->createFileLocker(), + $this->getConfig(), + ), + ); + } + protected function createGeneratorFilesystem(): GeneratorFilesystemInterface { /** @var GeneratorFilesystemInterface $generatorFilesystem */ @@ -83,7 +105,6 @@ className: GeneratorFilesystem::class, initializer: function (GeneratorFilesystem $ghost): void { $ghost->__construct( $this->createFilesystem(), - $this->createFinder(), $this->getConfig(), ); } @@ -92,6 +113,19 @@ 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(), + $this->createTransferLocker(), + ), + ); + } + protected function createTransferGeneratorBuilder(): TransferGeneratorBuilderInterface { return $this->getCached( @@ -100,6 +134,41 @@ protected function createTransferGeneratorBuilder(): TransferGeneratorBuilderInt ); } + 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(), + $this->getConfig(), + ), + ); + } + + protected function createTransferHashReader(): TransferHashReaderInterface + { + return $this->getCached( + key: 'transfer-generator:TransferHashReader', + factory: fn(): TransferHashReaderInterface => new TransferHashReader( + $this->createHashFileReader(), + $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..1865b6d1 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommand.php @@ -4,48 +4,45 @@ 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 { public function __construct( private TransferGeneratorBuilderInterface $builder, private GeneratorFilesystemInterface $filesystem, + private TransferRotatorInterface $transferRotator, ) { } public function postProcess(bool $isSuccessful): TransferGeneratorTransfer { - if ($isSuccessful) { - return $this->postProcessSuccess(); - } - - return $this->postProcessError(); + return $isSuccessful + ? $this->postProcessSuccess() + : $this->postProcessError(); } private function postProcessSuccess(): TransferGeneratorTransfer { try { - $this->filesystem->rotateTempDir(); - } catch (FilesystemException | FinderException | TransferGeneratorConfigNotFoundException $e) { - return $this->builder->createErrorGeneratorTransfer($e->getMessage()); + $this->transferRotator->rotateFiles(); + $generatorTransfer = $this->builder->createSuccessGeneratorTransfer(); + } catch (Throwable $e) { + $generatorTransfer = $this->builder->createErrorGeneratorTransfer($e->getMessage()); + } finally { + $this->filesystem->deleteTempDir(); } - return $this->builder->createSuccessGeneratorTransfer(); + return $generatorTransfer; } 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/Processor/Command/ProcessCommand.php b/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php index c9557ad8..a201c881 100644 --- a/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php +++ b/src/TransferGenerator/Generator/Generator/Processor/Command/ProcessCommand.php @@ -9,16 +9,16 @@ use Picamator\TransferObject\Generated\TransferGeneratorContentTransfer; use Picamator\TransferObject\Generated\TransferGeneratorTransfer; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException; -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 GeneratorFilesystemInterface $filesystem, + private TransferWriterInterface $transferWriter, ) { } @@ -45,7 +45,7 @@ private function generateTransfer(DefinitionTransfer $definitionTransfer): Trans private function saveContent(TransferGeneratorContentTransfer $contentTransfer): void { - $this->filesystem->writeFile($contentTransfer); + $this->transferWriter->writeFile($contentTransfer); } private function renderContent(DefinitionTransfer $definitionTransfer): TransferGeneratorContentTransfer diff --git a/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php b/src/TransferGenerator/Generator/Generator/TransferGeneratorService.php index cfe64874..56787d46 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,15 @@ public function generateTransfersOrFail(string $configPath): int continue; } - $this->throwError($generatorTransfer); + $errorMessage = $this->getErrorMessage($generatorTransfer); + $exception = new TransferGeneratorException($errorMessage); + $generator->throw($exception); } return $count; } - /** - * @throws \Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorException - */ - private function throwError(TransferGeneratorTransfer $generatorTransfer): never + private function getErrorMessage(TransferGeneratorTransfer $generatorTransfer): string { $messageParts[] = self::ERROR_MESSAGE; if ($generatorTransfer->className !== null) { @@ -61,8 +57,6 @@ private function throwError(TransferGeneratorTransfer $generatorTransfer): never $messageParts[] = $message->errorMessage; } - $message = implode(PHP_EOL, $messageParts); - - throw 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 d619f3ea..5925a8fd 100644 --- a/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php +++ b/src/TransferGenerator/Generator/Generator/Workflow/TransferGeneratorWorkflow.php @@ -17,7 +17,7 @@ public function __construct( public function generateTransfers(string $configPath): Generator { - $generatorTransfer = $this->processConfig($configPath); + $generatorTransfer = $this->preProcess($configPath); yield $generatorTransfer; @@ -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; } @@ -50,8 +55,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/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 @@ +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/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 @@ +hashReader->readHashFile(); + $toDelete = $this->getToDelete($hashTransfer); + + $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(); + } + } + + /** + * @param ArrayObject $actualHashes + */ + private function rotateHashFile(ArrayObject $actualHashes): void + { + $this->hashFilesystem->writeHashTmpFile($actualHashes); + $this->hashFilesystem->renameHashTmpFile(); + } + + /** + * @return \ArrayObject + */ + private function getToDelete(TransferHashTransfer $hashTransfer): ArrayObject + { + /** @var \ArrayObject $classesNames */ + $classesNames = new ArrayObject(); + $hashesIterator = $hashTransfer->hashes->getIterator(); + + while ($hashesIterator->valid()) { + $className = $hashesIterator->key(); + $hashesIterator->next(); + + if (isset($hashTransfer->actualHashes[$className])) { + continue; + } + + $classesNames[] = basename($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..e150e0e9 --- /dev/null +++ b/src/TransferGenerator/Generator/Writer/TransferRotatorInterface.php @@ -0,0 +1,14 @@ +hashReader->readHashFile(); + $hashTransfer->actualHashes[$contentTransfer->className] = $contentTransfer->hash; + + if ($this->config->getIsCacheEnabled() && !$this->isFileModified($contentTransfer, $hashTransfer)) { + return; + } + + $hashTransfer->toCopyClassNames[] = $contentTransfer->className; + + $this->filesystem->writeTempFile($contentTransfer); + } + + private function isFileModified( + TransferGeneratorContentTransfer $contentTransfer, + TransferHashTransfer $hashTransfer, + ): bool { + $previousHash = $hashTransfer->hashes[$contentTransfer->className] ?? null; + + return $previousHash === null + || !hash_equals(known_string: $contentTransfer->hash, user_string: $previousHash); + } +} 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 @@ +commandTester = new CommandTester($command); + } + + #[TestDox('Run command with disabled cache should show always regenerate transfers')] + public function testRunCommandWithDisabledCacheShouldAlwaysRegenerateTransfers(): void + { + // Arrange + $modifiedTimesBefore = $this->getModifiedTimes(self::GENERATED_FILES); + + // Act + $this->commandTester->execute( + ['-c' => self::CONFIG_PATH], + ['verbosity' => OutputInterface::VERBOSITY_VERBOSE] + ); + $output = $this->commandTester->getDisplay(); + + $modifiedTimesAfter = $this->getModifiedTimes(self::GENERATED_FILES); + + // Assert + $this->commandTester->assertCommandIsSuccessful($output); + $this->assertStringContainsString('command.transfer.yml: CommandCacheDisabled', $output); + $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); + + $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..3513b984 --- /dev/null +++ b/tests/integration/Command/FileHashTransferGeneratorCommandTest.php @@ -0,0 +1,82 @@ +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/0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv b/tests/integration/Command/Generated/Success/0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv new file mode 100644 index 00000000..a810a888 --- /dev/null +++ b/tests/integration/Command/Generated/Success/0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv @@ -0,0 +1,2 @@ +CommandBulkSecondTransfer,548b828c0a3df2efb3ebd46982e1fd982d25025c8b773c3581fed9dd8273e41c +CommandBulkFirstTransfer,e14f23be273fde5da310757845eb9cdcc3b78c00f730b289bdf72f9e51d45833 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/11f6d3fa94ad43441cad6f58298fb442f506dfed.transfer.hash.csv b/tests/integration/Command/Generated/Success/11f6d3fa94ad43441cad6f58298fb442f506dfed.transfer.hash.csv new file mode 100644 index 00000000..9978c599 --- /dev/null +++ b/tests/integration/Command/Generated/Success/11f6d3fa94ad43441cad6f58298fb442f506dfed.transfer.hash.csv @@ -0,0 +1 @@ +CommandFileHashTransfer,d2a2912c58b8a385fc3ff1a357d8276e7efba9ecc6395d9e89423346c489fc31 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/58208de788342fbf62cf9c812c7950fa44cf8ecf.transfer.hash.csv b/tests/integration/Command/Generated/Success/58208de788342fbf62cf9c812c7950fa44cf8ecf.transfer.hash.csv new file mode 100644 index 00000000..5fe0a8c1 --- /dev/null +++ b/tests/integration/Command/Generated/Success/58208de788342fbf62cf9c812c7950fa44cf8ecf.transfer.hash.csv @@ -0,0 +1 @@ +CommandCacheDisabledTransfer,83617e2ec05bf88b6601a41991a0e0a947453b72887f74bb9de25cfbee45ec06 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv b/tests/integration/Command/Generated/Success/660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv new file mode 100644 index 00000000..6fc7bfd8 --- /dev/null +++ b/tests/integration/Command/Generated/Success/660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv @@ -0,0 +1 @@ +CommandBulkThirdTransfer,d310c23a73f84fb0b25a56e2da7c9b06aa358efe47ed9665e2c147b4186325d5 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/6e2abc8ac5faed56c46704689cc0cdf2f0b1b639.transfer.hash.csv b/tests/integration/Command/Generated/Success/6e2abc8ac5faed56c46704689cc0cdf2f0b1b639.transfer.hash.csv new file mode 100644 index 00000000..a0d1ffdb --- /dev/null +++ b/tests/integration/Command/Generated/Success/6e2abc8ac5faed56c46704689cc0cdf2f0b1b639.transfer.hash.csv @@ -0,0 +1,2 @@ +CommandSecondTransfer,74cefcbf8a44fa6b6981c11e6b8c8dec8b2da152755d6c70bafd38f1eb5c847c +CommandFirstTransfer,08c25060e2d59248791d1f727d85bd2a6a6a6186289430c6421cc96db090b1c7 \ No newline at end of file diff --git a/tests/integration/Command/Generated/Success/CommandBulkFirstTransfer.php b/tests/integration/Command/Generated/Success/CommandBulkFirstTransfer.php new file mode 100644 index 00000000..27a4e7c1 --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandBulkFirstTransfer.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/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/CommandBulkThirdTransfer.php b/tests/integration/Command/Generated/Success/CommandBulkThirdTransfer.php new file mode 100644 index 00000000..f4e9df19 --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandBulkThirdTransfer.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/CommandCacheDisabledTransfer.php b/tests/integration/Command/Generated/Success/CommandCacheDisabledTransfer.php new file mode 100644 index 00000000..6ed73aaf --- /dev/null +++ b/tests/integration/Command/Generated/Success/CommandCacheDisabledTransfer.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/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/TransferGeneratorBulkCommandTest.php b/tests/integration/Command/TransferGeneratorBulkCommandTest.php index 547e8a47..7a312b38 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,9 +17,18 @@ 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 = [ + __DIR__ . '/Generated/Success/CommandBulkFirstTransfer.php', + __DIR__ . '/Generated/Success/CommandBulkSecondTransfer.php', + __DIR__ . '/Generated/Success/CommandBulkThirdTransfer.php', + __DIR__ . '/Generated/Success/0522db555dcdc49fa936ab8d1d3345d16a865d7c.transfer.hash.csv', + __DIR__ . '/Generated/Success/660b282118a23a83dc1e5b63e91f543eb7ccfedb.transfer.hash.csv', + ]; + private const string ERROR_CONFIG_EMPTY_LIST_PATH = '/tests/integration/Command/data/config/error/config.empty.list.txt'; @@ -40,7 +50,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); } @@ -66,8 +76,9 @@ public function testRunCommandWithValidConfigurationShouldShowSuccessMessage(): $output = $this->commandTester->getDisplay(); // Assert - $this->commandTester->assertCommandIsSuccessful(); + $this->commandTester->assertCommandIsSuccessful($output); $this->assertStringContainsString('All Transfer Objects were generated successfully!', $output); + $this->assertFilesExist(self::SUCCESS_GENERATED_FILES); } #[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 ab21f75c..3f153922 100644 --- a/tests/integration/Command/TransferGeneratorCommandTest.php +++ b/tests/integration/Command/TransferGeneratorCommandTest.php @@ -18,7 +18,9 @@ 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.config.yml'; + private const string ERROR_CONFIG_PATH = '/tests/integration/Command/data/config/error/generator.config.yml'; private CommandTester $commandTester; @@ -37,7 +39,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); } @@ -64,7 +66,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/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/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/config.list.txt b/tests/integration/Command/data/config/success/config.list.txt index 5922206c..24813562 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.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-bulk-first/command.second.transfer.yml b/tests/integration/Command/data/config/success/definition-bulk-first/command.second.transfer.yml new file mode 100644 index 00000000..9a8f6249 --- /dev/null +++ b/tests/integration/Command/data/config/success/definition-bulk-first/command.second.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +CommandBulkSecond: + run: + type: true diff --git a/tests/integration/Command/data/config/success/definition-bulk-second/command.third.transfer.yml b/tests/integration/Command/data/config/success/definition-bulk-second/command.third.transfer.yml new file mode 100644 index 00000000..3ee7e8cf --- /dev/null +++ b/tests/integration/Command/data/config/success/definition-bulk-second/command.third.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +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..87429c32 --- /dev/null +++ b/tests/integration/Command/data/config/success/definition-cache-disabled/command.transfer.yml @@ -0,0 +1,4 @@ +# $schema: ./../../../../../../../schema/definition.schema.json +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/generator.bulk.first.config.yml b/tests/integration/Command/data/config/success/generator.bulk.first.config.yml new file mode 100644 index 00000000..22e6a9f3 --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.bulk.first.config.yml @@ -0,0 +1,5 @@ +# $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-first" 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..425e15e6 --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.bulk.second.config.yml @@ -0,0 +1,5 @@ +# $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" 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..afbd31c2 --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.cache.disabled.config.yml @@ -0,0 +1,5 @@ +# $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" 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..ec3fbe6d --- /dev/null +++ b/tests/integration/Command/data/config/success/generator.file.hash.config.yml @@ -0,0 +1,5 @@ +# $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" diff --git a/tests/integration/DefinitionGenerator/Generated/Destatis/85d80569af63ae1bb9314fb7ccdbc975025dc9c9.transfer.hash.csv b/tests/integration/DefinitionGenerator/Generated/Destatis/85d80569af63ae1bb9314fb7ccdbc975025dc9c9.transfer.hash.csv new file mode 100644 index 00000000..b9dd7f6a --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Destatis/85d80569af63ae1bb9314fb7ccdbc975025dc9c9.transfer.hash.csv @@ -0,0 +1,7 @@ +DestatisTransfer,c3cd5e792d4fa83a91e9ce9f67c9e63576cc157f87492eae1553363d8aa8bee7 +IdentTransfer,d34e5a4643a1c396df5e9ebaca2b98b14df78a72c6aea16fe04cc37dd20c76c6 +StatusTransfer,30aee249decbf14511b71f79970a944faffa8acb803e90bf2b242ac57462da2a +ParameterTransfer,e6f42137bb0fea339a4df47b54a20179d160d938378338f9044dac488378722f +StatisticsTransfer,31fdb8a195ecabf52428713a2a861758bcb048e65ff1deaafd118470a82626cf +TablesTransfer,26b53d666cd7129ac38fb6b8f13459b9e08fd38b52783d6aadf59751a549a364 +VariablesTransfer,e3fa5bc5233220c3df3eea08215b3c11298f9102abc168b8517f640fd74563da \ No newline at end of file diff --git a/tests/integration/DefinitionGenerator/Generated/Frankfurter/bee179e5ccc082b4074b343fa08bcbe78029391b.transfer.hash.csv b/tests/integration/DefinitionGenerator/Generated/Frankfurter/bee179e5ccc082b4074b343fa08bcbe78029391b.transfer.hash.csv new file mode 100644 index 00000000..1b464fc8 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Frankfurter/bee179e5ccc082b4074b343fa08bcbe78029391b.transfer.hash.csv @@ -0,0 +1,2 @@ +ExchangeRateTransfer,11dd4fb491f625dbcf4ca057fd0497d70062f91bc085655bc4bcb8c6cdd08f23 +RatesTransfer,eef143d206a3955d653ea38e40742a8a45b0e9b169fad9b46d3ed00450c918fb \ No newline at end of file diff --git a/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/7548834bb63ce3afa4fcbc8b9d2e405457646511.transfer.hash.csv b/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/7548834bb63ce3afa4fcbc8b9d2e405457646511.transfer.hash.csv new file mode 100644 index 00000000..090d242e --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/GoogleShoppingContent/7548834bb63ce3afa4fcbc8b9d2e405457646511.transfer.hash.csv @@ -0,0 +1,2 @@ +ProductTransfer,16260d759f0a7e1d30d3eac313430d750a8fec967937d2eced1b87fa86cfd575 +PriceTransfer,0d567e2578ccfb9b5712a516436b2b42e4d486cace8815b04185f2851c8f9e24 \ No newline at end of file diff --git a/tests/integration/DefinitionGenerator/Generated/NasaNeo/47cb0e86ae1de4df5273a49e044c7ba6adc25262.transfer.hash.csv b/tests/integration/DefinitionGenerator/Generated/NasaNeo/47cb0e86ae1de4df5273a49e044c7ba6adc25262.transfer.hash.csv new file mode 100644 index 00000000..38917183 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/NasaNeo/47cb0e86ae1de4df5273a49e044c7ba6adc25262.transfer.hash.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 \ No newline at end of file diff --git a/tests/integration/DefinitionGenerator/Generated/OpenWeather/8fa882f29e7d077bb237c6a7a37ed524b4ddd154.transfer.hash.csv b/tests/integration/DefinitionGenerator/Generated/OpenWeather/8fa882f29e7d077bb237c6a7a37ed524b4ddd154.transfer.hash.csv new file mode 100644 index 00000000..08a55369 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/OpenWeather/8fa882f29e7d077bb237c6a7a37ed524b4ddd154.transfer.hash.csv @@ -0,0 +1,7 @@ +ForecastTransfer,d932242b15fc3d15dad99bea0aa40648ccd76b4c96535dd5700df7f7830bcdc4 +CoordTransfer,747a9b38bb0290a2db5bbfe37d7036a800fa2db2ca053f6353703aa3848bead9 +WeatherTransfer,c20a83c5338f8de3cd2cb15e6450abefa476f79424caeab206fe72a04c2418ac +MainTransfer,3e8798af1fd0a2686fa8fe665caeed58ef5e5ff4986f4852a2e79fa20316768c +WindTransfer,f247ce8a520e7a21a50e3e353c506a7b5af82a91762ea6d02246bf0f00e655ee +CloudsTransfer,e7af5ba3aca69a69bf521a01aa4ffb0491c7513e2745aaa7e8ded14bac7ab030 +SysTransfer,4410b52347e3e8847850c79088b8a8b5d68ae404027e95a4d888f322898d2834 \ No newline at end of file diff --git a/tests/integration/DefinitionGenerator/Generated/Tagesschau/703741f07ffb6bfd3bc62803fcc81ff92c1b2c31.transfer.hash.csv b/tests/integration/DefinitionGenerator/Generated/Tagesschau/703741f07ffb6bfd3bc62803fcc81ff92c1b2c31.transfer.hash.csv new file mode 100644 index 00000000..54215e16 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Tagesschau/703741f07ffb6bfd3bc62803fcc81ff92c1b2c31.transfer.hash.csv @@ -0,0 +1,7 @@ +ArdNewsTransfer,362bf99002cdc2149f0ecabf287dced5fa4b4e84d56a71a107facd63ab98cefd +NewsTransfer,5d91a9b4fdb54d2cb8871fc5b5799fb6cf9919183b9f88183c83bfd17638ce76 +TeaserImageTransfer,ca3dc39cdd919011864d212d5daa674f3e93672cb0f2faa04a11939b389a6d0f +TagsTransfer,9d581cd4cff899645e12436a3053d2daba1e5c19752faec930e363d3d3871927 +TrackingTransfer,7b9258e1369e675b03a1b1f46b295299637ba8999da1c2e5637b3894264897d8 +BrandingImageTransfer,50138d66b1aaba6857130974daffeedd326a7aea96336cfddef9b65487eb6527 +ImageVariantsTransfer,2ddddd5dc57c167681f04b1703a5e2d9d5efd4afcf54188ee38ca3055b63d064 \ No newline at end of file diff --git a/tests/integration/DefinitionGenerator/Generated/Wero/42dec6482416321802690c3fd858b4160efea711.transfer.hash.csv b/tests/integration/DefinitionGenerator/Generated/Wero/42dec6482416321802690c3fd858b4160efea711.transfer.hash.csv new file mode 100644 index 00000000..6d0275d3 --- /dev/null +++ b/tests/integration/DefinitionGenerator/Generated/Wero/42dec6482416321802690c3fd858b4160efea711.transfer.hash.csv @@ -0,0 +1,5 @@ +PaymentChargesTransfer,4083b1be36b95116290ae78c951aef9a7bd0fb07bd0b47a34cc54942e8708f12 +AmountTransfer,2711d68a6899a374b653ad85ec39c80d9327513a461bedc50aa6aa2607c6853c +ConsumerTransfer,32e5293f1c8bf58256f9f7698923f2ca6e0b5b6d5a51b463c1b95e0a3086fb7b +AuthenticationSettingsTransfer,afeaf7f28dc031f535e8b2a3a204b2cfcb1d48999966802bd4a7781de2bd569e +SettingsTransfer,0e6f04b58eefaf95723cafa1becb35ea6a448571d99e97f0e3a21fd50558bf82 \ No newline at end of file diff --git a/tests/integration/DefinitionGenerator/data/README.md b/tests/integration/DefinitionGenerator/data/README.md index fef3b8d6..9638da66 100644 --- a/tests/integration/DefinitionGenerator/data/README.md +++ b/tests/integration/DefinitionGenerator/data/README.md @@ -1,12 +1,28 @@ -API Response Reference -====================== +Definition Generator Integration Tests +====================================== -| 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) | +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) | | [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 +-------- + +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. 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'); + } +} diff --git a/tests/integration/Helper/FileTrait.php b/tests/integration/Helper/FileTrait.php new file mode 100644 index 00000000..54f807fa --- /dev/null +++ b/tests/integration/Helper/FileTrait.php @@ -0,0 +1,92 @@ + $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) { + $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 + */ + 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->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/tests/integration/Transfer/Generated/8b96a0de99f620e0bc3d5176d2bdc9b518cfc96d.transfer.hash.csv b/tests/integration/Transfer/Generated/8b96a0de99f620e0bc3d5176d2bdc9b518cfc96d.transfer.hash.csv new file mode 100644 index 00000000..df92bf98 --- /dev/null +++ b/tests/integration/Transfer/Generated/8b96a0de99f620e0bc3d5176d2bdc9b518cfc96d.transfer.hash.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 \ No newline at end of file diff --git a/tests/integration/Transfer/Generated/BcMath/935ae0ad4ba6c99bbbcfdd64902b54ffb12b66a7.transfer.hash.csv b/tests/integration/Transfer/Generated/BcMath/935ae0ad4ba6c99bbbcfdd64902b54ffb12b66a7.transfer.hash.csv new file mode 100644 index 00000000..465e37a2 --- /dev/null +++ b/tests/integration/Transfer/Generated/BcMath/935ae0ad4ba6c99bbbcfdd64902b54ffb12b66a7.transfer.hash.csv @@ -0,0 +1 @@ +BcMathNumberTransfer,08592943e5993454c0e18fae20d4808423e9e764d8593f5f95637d733ade4d83 \ No newline at end of file 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/Success/264b6745de577b923debc9c19ee65b5ab2cbd6be.transfer.hash.csv b/tests/integration/TransferGenerator/Generated/Success/264b6745de577b923debc9c19ee65b5ab2cbd6be.transfer.hash.csv new file mode 100644 index 00000000..4f1b8e7d --- /dev/null +++ b/tests/integration/TransferGenerator/Generated/Success/264b6745de577b923debc9c19ee65b5ab2cbd6be.transfer.hash.csv @@ -0,0 +1,4 @@ +AddressTransfer,1119ef41f62193712329476757d26b0419cacc74f578c12196a8e5fb674d6ed1 +CountryTransfer,6e9bc668f5e502a80c3b741e548b65728c84435a421105d6a4998d39fbd9ff42 +AddressStatisticsTransfer,067635074458269d46b8c6b612c977b33754365ba771b0aa84f1a895192a0db4 +AddressBookTransfer,ac3f65a9f5bf54d514c2d5b03792f2d923f9d1f2fd82c38aa4645c6d0736e499 \ No newline at end of file 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" diff --git a/tests/unit/Shared/Hash/HashFileReaderTest.php b/tests/unit/Shared/Hash/HashFileReaderTest.php new file mode 100644 index 00000000..3e7b0c7c --- /dev/null +++ b/tests/unit/Shared/Hash/HashFileReaderTest.php @@ -0,0 +1,97 @@ +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/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(); + } +} 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, + ], + ]); + } +} 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); + } +} 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']); + } +} diff --git a/tests/unit/TransferGenerator/Generator/Filesystem/GeneratorFilesystemTest.php b/tests/unit/TransferGenerator/Generator/Filesystem/GeneratorFilesystemTest.php index 9e30ba7d..218946ac 100644 --- a/tests/unit/TransferGenerator/Generator/Filesystem/GeneratorFilesystemTest.php +++ b/tests/unit/TransferGenerator/Generator/Filesystem/GeneratorFilesystemTest.php @@ -9,7 +9,6 @@ use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; 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; @@ -29,13 +28,10 @@ protected function setUp(): void { $this->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 b950909d..298496ae 100644 --- a/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php +++ b/tests/unit/TransferGenerator/Generator/Generator/Processor/Command/PostProcessCommandTest.php @@ -6,6 +6,7 @@ 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; @@ -13,6 +14,7 @@ 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 @@ -21,15 +23,19 @@ final class PostProcessCommandTest extends TestCase private GeneratorFilesystemInterface&Stub $filesystemStub; + private TransferRotatorInterface&MockObject $transferRotatorMock; + protected function setUp(): void { $builder = new TransferGeneratorBuilder(); $this->filesystemStub = $this->createStub(GeneratorFilesystemInterface::class); + $this->transferRotatorMock = $this->createMock(TransferRotatorInterface::class); $this->command = new PostProcessCommand( $builder, $this->filesystemStub, + $this->transferRotatorMock, ); } @@ -38,28 +44,17 @@ public function testFilesystemExceptionShouldBeHandledOnPostProcessSuccess(): vo { // Arrange $this->filesystemStub - ->method('rotateTempDir') - ->willThrowException(new FilesystemException()) + ->method('deleteTempDir') ->seal(); - // Act - $actual = $this->command->postProcess(true); - - // Assert - $this->assertFalse($actual->validator->isValid); - } - - #[TestDox('Filesystem exception should be handled on PostProcessError')] - public function testFilesystemExceptionShouldBeHandledOnPostProcessError(): void - { - // Arrange - $this->filesystemStub - ->method('deleteTempDir') + // Expect + $this->transferRotatorMock->expects($this->once()) + ->method('rotateFiles') ->willThrowException(new FilesystemException()) ->seal(); // Act - $actual = $this->command->postProcess(false); + $actual = $this->command->postProcess(true); // Assert $this->assertFalse($actual->validator->isValid); diff --git a/var/config/config.list.txt b/var/config/config.list.txt index dd24e672..9394bcd8 100644 --- a/var/config/config.list.txt +++ b/var/config/config.list.txt @@ -3,7 +3,15 @@ ./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.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