From 61df801271537f5600452b7bdd9710fd347cd375 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Mon, 23 Feb 2026 12:57:20 +0100 Subject: [PATCH 1/6] Added @phpstan-consistent-constructor on AbstractTransfer --- src/Transfer/AbstractTransfer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Transfer/AbstractTransfer.php b/src/Transfer/AbstractTransfer.php index 1b21b514..02cd5e7e 100644 --- a/src/Transfer/AbstractTransfer.php +++ b/src/Transfer/AbstractTransfer.php @@ -10,6 +10,8 @@ /** * @api + * + * @phpstan-consistent-constructor */ abstract class AbstractTransfer implements TransferInterface { From c2d15e8a65a7a0872df5e4dce4b76e798900653c Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Mon, 23 Feb 2026 17:48:14 +0100 Subject: [PATCH 2/6] Extracted project root environment logic to separate class in shared, refactored related classes --- .../Environment/Enum/EnvironmentEnum.php | 19 ++++ src/Shared/Environment/EnvironmentReader.php | 45 ++++++++ .../EnvironmentReaderInterface.php | 10 ++ src/Shared/SharedFactoryTrait.php | 10 ++ .../Config/ConfigFactory.php | 10 +- .../Environment/ConfigEnvironmentRender.php | 74 ------------- .../Parser/Builder/ConfigContentBuilder.php | 10 +- .../Parser/Render/ProjectRootRender.php | 30 ++++++ .../Render/ProjectRootRenderInterface.php} | 4 +- .../Environment/EnvironmentReaderTest.php | 100 +++++++++++++++++ .../ConfigEnvironmentRenderTest.php | 101 ------------------ 11 files changed, 224 insertions(+), 189 deletions(-) create mode 100644 src/Shared/Environment/Enum/EnvironmentEnum.php create mode 100644 src/Shared/Environment/EnvironmentReader.php create mode 100644 src/Shared/Environment/EnvironmentReaderInterface.php delete mode 100644 src/TransferGenerator/Config/Environment/ConfigEnvironmentRender.php create mode 100644 src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php rename src/TransferGenerator/Config/{Environment/ConfigEnvironmentRenderInterface.php => Parser/Render/ProjectRootRenderInterface.php} (59%) create mode 100644 tests/unit/Shared/Environment/EnvironmentReaderTest.php delete mode 100644 tests/unit/TransferGenerator/Config/Environment/ConfigEnvironmentRenderTest.php diff --git a/src/Shared/Environment/Enum/EnvironmentEnum.php b/src/Shared/Environment/Enum/EnvironmentEnum.php new file mode 100644 index 00000000..b5714bf5 --- /dev/null +++ b/src/Shared/Environment/Enum/EnvironmentEnum.php @@ -0,0 +1,19 @@ +name => '', + ]; + + public function getDefault(): string + { + return self::DEFAULT_VALUES[$this->name]; + } +} diff --git a/src/Shared/Environment/EnvironmentReader.php b/src/Shared/Environment/EnvironmentReader.php new file mode 100644 index 00000000..67231080 --- /dev/null +++ b/src/Shared/Environment/EnvironmentReader.php @@ -0,0 +1,45 @@ +getEnvironment(EnvironmentEnum::PROJECT_ROOT); + if ($projectRoot === '') { + return $this->getcwd() ?: ''; + } + + $projectRoot = trim($projectRoot); + + return rtrim($projectRoot, '\/'); + } + + private function getEnvironment(EnvironmentEnum $environment): string + { + $value = $this->getenv($environment->value); + if (is_string($value)) { + return $value; + } + + return $environment->getDefault(); + } + + /** + * @return array|false|string + */ + protected function getenv(string $name): array|false|string + { + return getenv($name); + } + + protected function getcwd(): false|string + { + return getcwd(); + } +} diff --git a/src/Shared/Environment/EnvironmentReaderInterface.php b/src/Shared/Environment/EnvironmentReaderInterface.php new file mode 100644 index 00000000..1e576e92 --- /dev/null +++ b/src/Shared/Environment/EnvironmentReaderInterface.php @@ -0,0 +1,10 @@ + new FileReader(), ); } + + final protected function createEnvironmentReader(): EnvironmentReaderInterface + { + return $this->getCached( + key: 'shared:EnvironmentReader', + factory: fn(): EnvironmentReaderInterface => new EnvironmentReader(), + ); + } } diff --git a/src/TransferGenerator/Config/ConfigFactory.php b/src/TransferGenerator/Config/ConfigFactory.php index 421e55b1..5e7e6eb4 100644 --- a/src/TransferGenerator/Config/ConfigFactory.php +++ b/src/TransferGenerator/Config/ConfigFactory.php @@ -6,8 +6,6 @@ use ArrayObject; use Picamator\TransferObject\Shared\SharedFactoryTrait; -use Picamator\TransferObject\TransferGenerator\Config\Environment\ConfigEnvironmentRender; -use Picamator\TransferObject\TransferGenerator\Config\Environment\ConfigEnvironmentRenderInterface; use Picamator\TransferObject\TransferGenerator\Config\Loader\ConfigLoader; use Picamator\TransferObject\TransferGenerator\Config\Loader\ConfigLoaderInterface; use Picamator\TransferObject\TransferGenerator\Config\Parser\Builder\ConfigBuilder; @@ -16,6 +14,8 @@ use Picamator\TransferObject\TransferGenerator\Config\Parser\Builder\ConfigContentBuilderInterface; use Picamator\TransferObject\TransferGenerator\Config\Parser\ConfigParser; use Picamator\TransferObject\TransferGenerator\Config\Parser\ConfigParserInterface; +use Picamator\TransferObject\TransferGenerator\Config\Parser\Render\ProjectRootRender; +use Picamator\TransferObject\TransferGenerator\Config\Parser\Render\ProjectRootRenderInterface; use Picamator\TransferObject\TransferGenerator\Config\Reader\ConfigReader; use Picamator\TransferObject\TransferGenerator\Config\Reader\ConfigReaderInterface; use Picamator\TransferObject\TransferGenerator\Config\Validator\BulkContentValidator; @@ -134,11 +134,11 @@ className: ConfigParser::class, protected function createConfigContentBuilder(): ConfigContentBuilderInterface { - return new ConfigContentBuilder($this->createConfigEnvironmentRender()); + return new ConfigContentBuilder($this->createProjectRootRender()); } - protected function createConfigEnvironmentRender(): ConfigEnvironmentRenderInterface + protected function createProjectRootRender(): ProjectRootRenderInterface { - return new ConfigEnvironmentRender(); + return new ProjectRootRender($this->createEnvironmentReader()); } } diff --git a/src/TransferGenerator/Config/Environment/ConfigEnvironmentRender.php b/src/TransferGenerator/Config/Environment/ConfigEnvironmentRender.php deleted file mode 100644 index 7cf6ed53..00000000 --- a/src/TransferGenerator/Config/Environment/ConfigEnvironmentRender.php +++ /dev/null @@ -1,74 +0,0 @@ -getProjectRoot(); - $path = $this->rtrimPath($path); - - return str_replace(static::PLACEHOLDER, $projectRoot, $path); - } - - public function renderRelativeProjectRoot(string $path): string - { - $path = $this->rtrimPath($path); - - return str_replace(static::PLACEHOLDER, '', $path); - } - - private function getProjectRoot(): string - { - if (isset($this->projectRootCache)) { - return $this->projectRootCache; - } - - $projectRoot = $this->getEnvironment() ?: $this->getWorkingDir(); - - return $this->projectRootCache = $projectRoot; - } - - private function getWorkingDir(): string - { - return $this->getcwd() ?: ''; - } - - protected function getcwd(): false|string - { - return getcwd(); - } - - private function getEnvironment(): string - { - $envValue = $this->getenv(static::ENVIRONMENT_KEY); - if (!is_string($envValue)) { - return ''; - } - - return $envValue - |> trim(...) - |> $this->rtrimPath(...); - } - - /** - * @return array|false|string - */ - protected function getenv(string $name): array|false|string - { - return getenv($name); - } - - private function rtrimPath(string $path): string - { - return rtrim($path, '\/'); - } -} diff --git a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php index 55013403..daafb531 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php @@ -5,16 +5,12 @@ namespace Picamator\TransferObject\TransferGenerator\Config\Parser\Builder; use Picamator\TransferObject\Generated\ConfigContentTransfer; -use Picamator\TransferObject\TransferGenerator\Config\Environment\ConfigEnvironmentRenderInterface; -use Picamator\TransferObject\TransferGenerator\Config\Parser\Filter\ConfigNormalizerTrait; +use Picamator\TransferObject\TransferGenerator\Config\Parser\Render\ProjectRootRenderInterface; readonly class ConfigContentBuilder implements ConfigContentBuilderInterface { - use ConfigNormalizerTrait; - - public function __construct( - private ConfigEnvironmentRenderInterface $environmentRender, - ) { + public function __construct(private ProjectRootRenderInterface $environmentRender) + { } public function createContentTransfer(array $configData): ConfigContentTransfer diff --git a/src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php b/src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php new file mode 100644 index 00000000..02b25205 --- /dev/null +++ b/src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php @@ -0,0 +1,30 @@ +environmentReader->getProjectRoot(); + + return str_replace(static::PLACEHOLDER, $projectRoot, $path); + } + + public function renderRelativeProjectRoot(string $path): string + { + $path = rtrim($path, '\/'); + + return str_replace(static::PLACEHOLDER, '', $path); + } +} diff --git a/src/TransferGenerator/Config/Environment/ConfigEnvironmentRenderInterface.php b/src/TransferGenerator/Config/Parser/Render/ProjectRootRenderInterface.php similarity index 59% rename from src/TransferGenerator/Config/Environment/ConfigEnvironmentRenderInterface.php rename to src/TransferGenerator/Config/Parser/Render/ProjectRootRenderInterface.php index 6366ee43..13daedee 100644 --- a/src/TransferGenerator/Config/Environment/ConfigEnvironmentRenderInterface.php +++ b/src/TransferGenerator/Config/Parser/Render/ProjectRootRenderInterface.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Picamator\TransferObject\TransferGenerator\Config\Environment; +namespace Picamator\TransferObject\TransferGenerator\Config\Parser\Render; -interface ConfigEnvironmentRenderInterface +interface ProjectRootRenderInterface { public function renderProjectRoot(string $path): string; diff --git a/tests/unit/Shared/Environment/EnvironmentReaderTest.php b/tests/unit/Shared/Environment/EnvironmentReaderTest.php new file mode 100644 index 00000000..58f092b3 --- /dev/null +++ b/tests/unit/Shared/Environment/EnvironmentReaderTest.php @@ -0,0 +1,100 @@ +readerMock = $this->getMockBuilder(EnvironmentReader::class) + ->onlyMethods([ + 'getenv', + 'getcwd', + ]) + ->getMock(); + } + + #[TestDox('Environment variable project root "$projectRoot" is set with "$expected"')] + #[TestWith(['/home/my-user', '/home/my-user'])] + #[TestWith([' /home/my-user/ ', '/home/my-user'])] + public function testProjectRootVariableIsSet(string $projectRoot, string $expected): void + { + // Expect + $this->readerMock->expects($this->once()) + ->method('getenv') + ->willReturn($projectRoot); + + $this->readerMock->expects($this->never()) + ->method('getcwd') + ->seal(); + + // Act + $actual = $this->readerMock->getProjectRoot(); + + // Assert + $this->assertSame($expected, $actual); + } + + #[TestDox('Environment variable project root is not set should fallback to current working directory')] + public function testProjectRootVariableIsNotSetShouldFallbackToCurrentWorkingDirectory(): void + { + // Arrange + $projectRoot = false; + $workingDirectory = '/home/work-dir/my-user'; + + // Expect + $this->readerMock->expects($this->once()) + ->method('getenv') + ->willReturn($projectRoot); + + $this->readerMock->expects($this->once()) + ->method('getcwd') + ->willReturn($workingDirectory) + ->seal(); + + // Act + $actual = $this->readerMock->getProjectRoot(); + + // Assert + $this->assertSame($workingDirectory, $actual); + } + + #[TestDox('Environment variable project root is not set and current working directory is failed')] + public function testProjectRootVariableIsNotSetAndWorkingDirectoryIsFailed(): void + { + // Arrange + $projectRoot = false; + $workingDirectory = false; + + $expected = ''; + + // Expect + $this->readerMock->expects($this->once()) + ->method('getenv') + ->willReturn($projectRoot); + + $this->readerMock->expects($this->once()) + ->method('getcwd') + ->willReturn($workingDirectory) + ->seal(); + + // Act + $actual = $this->readerMock->getProjectRoot(); + + // Assert + $this->assertSame($expected, $actual); + } +} diff --git a/tests/unit/TransferGenerator/Config/Environment/ConfigEnvironmentRenderTest.php b/tests/unit/TransferGenerator/Config/Environment/ConfigEnvironmentRenderTest.php deleted file mode 100644 index 5128840a..00000000 --- a/tests/unit/TransferGenerator/Config/Environment/ConfigEnvironmentRenderTest.php +++ /dev/null @@ -1,101 +0,0 @@ -renderMock = $this->getMockBuilder(ConfigEnvironmentRender::class) - ->onlyMethods([ - 'getenv', - 'getcwd', - ]) - ->getMock(); - } - - #[TestDox('Environment variable working directory is set')] - public function testEnvironmentVariableIsSet(): void - { - // Arrange - $configPath = '${PROJECT_ROOT}/some-config-path.yml'; - $envProjectRoot = '/home/my-user'; - $expected = '/home/my-user/some-config-path.yml'; - - // Expect - $this->renderMock->expects($this->once()) - ->method('getenv') - ->willReturn($envProjectRoot); - - $this->renderMock->expects($this->never()) - ->method('getcwd') - ->seal(); - - // Act - $this->renderMock->renderProjectRoot($configPath); - $actual = $this->renderMock->renderProjectRoot($configPath); // duplicate run to test internal cache - - // Assert - $this->assertSame($expected, $actual); - } - - #[TestDox('Environment variable working directory is not set should use fallback')] - public function testEnvironmentVariableIsNotSetShouldFallbackToWorkingDirectory(): void - { - // Arrange - $configPath = '${PROJECT_ROOT}/some-config-path.yml'; - $workingDirectory = '/home/my-user'; - $expected = '/home/my-user/some-config-path.yml'; - - // Expect - $this->renderMock->expects($this->once()) - ->method('getenv') - ->willReturn(false); - - $this->renderMock->expects($this->once()) - ->method('getcwd') - ->willReturn($workingDirectory) - ->seal(); - - // Act - $actual = $this->renderMock->renderProjectRoot($configPath); - - // Assert - $this->assertSame($expected, $actual); - } - - #[TestDox('Environment variable working directory is not set and failed to get working directory')] - public function testEnvironmentVariableIsNotSetAndWorkingDirectoryIsFailedShouldFallbackToEmpty(): void - { - // Arrange - $configPath = '${PROJECT_ROOT}/some-config-path.yml'; - $expected = '/some-config-path.yml'; - - // Expect - $this->renderMock->expects($this->once()) - ->method('getenv') - ->willReturn(false); - - $this->renderMock->expects($this->once()) - ->method('getcwd') - ->willReturn(false) - ->seal(); - - // Act - $actual = $this->renderMock->renderProjectRoot($configPath); - - // Assert - $this->assertSame($expected, $actual); - } -} From 15ec321343b28c51f128b77654c0ca0d2035f083 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Mon, 23 Feb 2026 18:15:12 +0100 Subject: [PATCH 3/6] Refactored ConfigContentBuilder --- src/Shared/Environment/EnvironmentReader.php | 10 ++++-- .../Config/ConfigFactory.php | 9 +---- .../Parser/Builder/ConfigContentBuilder.php | 33 ++++++++++++------- .../Parser/Render/ProjectRootRender.php | 30 ----------------- .../Render/ProjectRootRenderInterface.php | 12 ------- 5 files changed, 29 insertions(+), 65 deletions(-) delete mode 100644 src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php delete mode 100644 src/TransferGenerator/Config/Parser/Render/ProjectRootRenderInterface.php diff --git a/src/Shared/Environment/EnvironmentReader.php b/src/Shared/Environment/EnvironmentReader.php index 67231080..723f23f4 100644 --- a/src/Shared/Environment/EnvironmentReader.php +++ b/src/Shared/Environment/EnvironmentReader.php @@ -15,9 +15,8 @@ public function getProjectRoot(): string return $this->getcwd() ?: ''; } - $projectRoot = trim($projectRoot); - - return rtrim($projectRoot, '\/'); + return trim($projectRoot) + |> $this->filterPath(...); } private function getEnvironment(EnvironmentEnum $environment): string @@ -30,6 +29,11 @@ private function getEnvironment(EnvironmentEnum $environment): string return $environment->getDefault(); } + private function filterPath(string $path): string + { + return rtrim($path, '\/'); + } + /** * @return array|false|string */ diff --git a/src/TransferGenerator/Config/ConfigFactory.php b/src/TransferGenerator/Config/ConfigFactory.php index 5e7e6eb4..530fb2d3 100644 --- a/src/TransferGenerator/Config/ConfigFactory.php +++ b/src/TransferGenerator/Config/ConfigFactory.php @@ -14,8 +14,6 @@ use Picamator\TransferObject\TransferGenerator\Config\Parser\Builder\ConfigContentBuilderInterface; use Picamator\TransferObject\TransferGenerator\Config\Parser\ConfigParser; use Picamator\TransferObject\TransferGenerator\Config\Parser\ConfigParserInterface; -use Picamator\TransferObject\TransferGenerator\Config\Parser\Render\ProjectRootRender; -use Picamator\TransferObject\TransferGenerator\Config\Parser\Render\ProjectRootRenderInterface; use Picamator\TransferObject\TransferGenerator\Config\Reader\ConfigReader; use Picamator\TransferObject\TransferGenerator\Config\Reader\ConfigReaderInterface; use Picamator\TransferObject\TransferGenerator\Config\Validator\BulkContentValidator; @@ -134,11 +132,6 @@ className: ConfigParser::class, protected function createConfigContentBuilder(): ConfigContentBuilderInterface { - return new ConfigContentBuilder($this->createProjectRootRender()); - } - - protected function createProjectRootRender(): ProjectRootRenderInterface - { - return new ProjectRootRender($this->createEnvironmentReader()); + return new ConfigContentBuilder($this->createEnvironmentReader()); } } diff --git a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php index daafb531..298fd341 100644 --- a/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php +++ b/src/TransferGenerator/Config/Parser/Builder/ConfigContentBuilder.php @@ -5,11 +5,13 @@ namespace Picamator\TransferObject\TransferGenerator\Config\Parser\Builder; use Picamator\TransferObject\Generated\ConfigContentTransfer; -use Picamator\TransferObject\TransferGenerator\Config\Parser\Render\ProjectRootRenderInterface; +use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; readonly class ConfigContentBuilder implements ConfigContentBuilderInterface { - public function __construct(private ProjectRootRenderInterface $environmentRender) + protected const string PLACEHOLDER = '${PROJECT_ROOT}'; + + public function __construct(private EnvironmentReaderInterface $environmentReader) { } @@ -17,20 +19,27 @@ public function createContentTransfer(array $configData): ConfigContentTransfer { $contentTransfer = new ConfigContentTransfer($configData); - return $this->renderPathKeys($contentTransfer); - } + $relativeDefinitionPath = $this->filterPath($contentTransfer->definitionPath); + $contentTransfer->relativeDefinitionPath = $this->replacePlaceholder($relativeDefinitionPath, ''); - private function renderPathKeys(ConfigContentTransfer $contentTransfer): ConfigContentTransfer - { - $contentTransfer->relativeDefinitionPath = - $this->environmentRender->renderRelativeProjectRoot($contentTransfer->definitionPath); + $projectRoot = $this->environmentReader->getProjectRoot(); - $contentTransfer->definitionPath = - $this->environmentRender->renderProjectRoot($contentTransfer->definitionPath); + $definitionPath = $this->replacePlaceholder($contentTransfer->definitionPath, $projectRoot); + $contentTransfer->definitionPath = $definitionPath; - $contentTransfer->transferPath = - $this->environmentRender->renderProjectRoot($contentTransfer->transferPath); + $transferPath = $this->replacePlaceholder($contentTransfer->transferPath, $projectRoot); + $contentTransfer->transferPath = $transferPath; return $contentTransfer; } + + private function filterPath(string $path): string + { + return rtrim($path, '\/'); + } + + private function replacePlaceholder(string $path, string $replace): string + { + return str_replace(static::PLACEHOLDER, $replace, $path); + } } diff --git a/src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php b/src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php deleted file mode 100644 index 02b25205..00000000 --- a/src/TransferGenerator/Config/Parser/Render/ProjectRootRender.php +++ /dev/null @@ -1,30 +0,0 @@ -environmentReader->getProjectRoot(); - - return str_replace(static::PLACEHOLDER, $projectRoot, $path); - } - - public function renderRelativeProjectRoot(string $path): string - { - $path = rtrim($path, '\/'); - - return str_replace(static::PLACEHOLDER, '', $path); - } -} diff --git a/src/TransferGenerator/Config/Parser/Render/ProjectRootRenderInterface.php b/src/TransferGenerator/Config/Parser/Render/ProjectRootRenderInterface.php deleted file mode 100644 index 13daedee..00000000 --- a/src/TransferGenerator/Config/Parser/Render/ProjectRootRenderInterface.php +++ /dev/null @@ -1,12 +0,0 @@ - Date: Mon, 23 Feb 2026 18:24:28 +0100 Subject: [PATCH 4/6] Renamed environmet variable PROJECT_ROOT to PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT --- .github/workflows/ci.yml | 4 ++-- docker-compose.yml | 2 +- src/Shared/Environment/Enum/EnvironmentEnum.php | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b319eea2..9c85cf28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -158,10 +158,10 @@ jobs: - name: Generate Transfer Objects env: - PROJECT_ROOT: ${{ github.workspace }} + PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT: ${{ github.workspace }} run: composer transfer-generate -- -c ./config/generator.config.yml - name: Run PHPUnit env: - PROJECT_ROOT: ${{ github.workspace }} + PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT: ${{ github.workspace }} run: composer phpunit diff --git a/docker-compose.yml b/docker-compose.yml index d5cd9840..e9a244cb 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} - PROJECT_ROOT: /home/transfer/transfer-object + PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT: ${PICAMATOR_TRANSFER_OBJECT_PROJECT_ROOT:-/home/transfer/transfer-object} build: context: docker/php args: diff --git a/src/Shared/Environment/Enum/EnvironmentEnum.php b/src/Shared/Environment/Enum/EnvironmentEnum.php index b5714bf5..ec1c00c6 100644 --- a/src/Shared/Environment/Enum/EnvironmentEnum.php +++ b/src/Shared/Environment/Enum/EnvironmentEnum.php @@ -6,7 +6,9 @@ enum EnvironmentEnum: string { - case PROJECT_ROOT = 'PROJECT_ROOT'; + case PROJECT_ROOT = self::ENV_PREFIX . 'PROJECT_ROOT'; + + private const string ENV_PREFIX = 'PICAMATOR_TRANSFER_OBJECT_'; private const array DEFAULT_VALUES = [ self::PROJECT_ROOT->name => '', From 4f4b5d3f7a3f7d054331a73714fec0bcc1d264f5 Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Mon, 23 Feb 2026 19:15:24 +0100 Subject: [PATCH 5/6] Reverted PROJECT_ROOT environment as depricated, introduced new environment variable for max file size --- .../Environment/Enum/EnvironmentEnum.php | 21 +++++++++ src/Shared/Environment/EnvironmentReader.php | 19 ++++---- .../EnvironmentReaderInterface.php | 2 + src/Shared/SharedFactoryTrait.php | 2 +- src/Shared/Validator/FileSizeValidator.php | 18 ++++++-- .../Definition/DefinitionFactory.php | 1 + .../Filesystem/DefinitionFinder.php | 8 ++-- .../Environment/EnvironmentReaderTest.php | 46 ++++++++++++++----- .../Validator/FileSizeValidatorTest.php | 16 +++++-- 9 files changed, 101 insertions(+), 32 deletions(-) diff --git a/src/Shared/Environment/Enum/EnvironmentEnum.php b/src/Shared/Environment/Enum/EnvironmentEnum.php index ec1c00c6..716b8e44 100644 --- a/src/Shared/Environment/Enum/EnvironmentEnum.php +++ b/src/Shared/Environment/Enum/EnvironmentEnum.php @@ -4,14 +4,35 @@ namespace Picamator\TransferObject\Shared\Environment\Enum; +use Deprecated; + enum EnvironmentEnum: string { + /** + * @api + */ + #[Deprecated(message: 'Please use PROJECT_ROOT instead.', since: '5.5.0')] + case PROJECT_ROOT_ALIAS = 'PROJECT_ROOT'; + + /** + * @api + */ case PROJECT_ROOT = self::ENV_PREFIX . 'PROJECT_ROOT'; + /** + * @api + */ + case MAX_FILE_SIZE_MB = self::ENV_PREFIX . 'MAX_FILE_SIZE_MB'; + + /** + * @api + */ private const string ENV_PREFIX = 'PICAMATOR_TRANSFER_OBJECT_'; private const array DEFAULT_VALUES = [ self::PROJECT_ROOT->name => '', + self::PROJECT_ROOT_ALIAS->name => '', + self::MAX_FILE_SIZE_MB->name => '10', ]; public function getDefault(): string diff --git a/src/Shared/Environment/EnvironmentReader.php b/src/Shared/Environment/EnvironmentReader.php index 723f23f4..b9039a46 100644 --- a/src/Shared/Environment/EnvironmentReader.php +++ b/src/Shared/Environment/EnvironmentReader.php @@ -10,13 +10,21 @@ { public function getProjectRoot(): string { - $projectRoot = $this->getEnvironment(EnvironmentEnum::PROJECT_ROOT); + $projectRoot = $this->getEnvironment(EnvironmentEnum::PROJECT_ROOT) + ?: $this->getEnvironment(EnvironmentEnum::PROJECT_ROOT_ALIAS); + if ($projectRoot === '') { return $this->getcwd() ?: ''; } - return trim($projectRoot) - |> $this->filterPath(...); + $projectRoot = trim($projectRoot); + + return rtrim($projectRoot, '\/'); + } + + public function getMaxFileSizeMegabytes(): string + { + return $this->getEnvironment(EnvironmentEnum::MAX_FILE_SIZE_MB); } private function getEnvironment(EnvironmentEnum $environment): string @@ -29,11 +37,6 @@ private function getEnvironment(EnvironmentEnum $environment): string return $environment->getDefault(); } - private function filterPath(string $path): string - { - return rtrim($path, '\/'); - } - /** * @return array|false|string */ diff --git a/src/Shared/Environment/EnvironmentReaderInterface.php b/src/Shared/Environment/EnvironmentReaderInterface.php index 1e576e92..aac44eb2 100644 --- a/src/Shared/Environment/EnvironmentReaderInterface.php +++ b/src/Shared/Environment/EnvironmentReaderInterface.php @@ -7,4 +7,6 @@ interface EnvironmentReaderInterface { public function getProjectRoot(): string; + + public function getMaxFileSizeMegabytes(): string; } diff --git a/src/Shared/SharedFactoryTrait.php b/src/Shared/SharedFactoryTrait.php index 7fc3fc3d..245e7a11 100644 --- a/src/Shared/SharedFactoryTrait.php +++ b/src/Shared/SharedFactoryTrait.php @@ -77,7 +77,7 @@ final protected function createFileSizeValidator(): FileSizeValidatorInterface { return $this->getCached( key: 'shared:FileSizeValidator', - factory: fn(): FileSizeValidatorInterface => new FileSizeValidator(), + factory: fn(): FileSizeValidatorInterface => new FileSizeValidator($this->createEnvironmentReader()), ); } diff --git a/src/Shared/Validator/FileSizeValidator.php b/src/Shared/Validator/FileSizeValidator.php index 4001eca4..9fe2e232 100644 --- a/src/Shared/Validator/FileSizeValidator.php +++ b/src/Shared/Validator/FileSizeValidator.php @@ -5,6 +5,7 @@ namespace Picamator\TransferObject\Shared\Validator; use Picamator\TransferObject\Generated\ValidatorMessageTransfer; +use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; readonly class FileSizeValidator implements FileSizeValidatorInterface { @@ -15,7 +16,11 @@ File "%s" ("%d" bytes) exceeds the maximum allowed size of "%d" bytes. Please split the file into smaller parts. TEMPLATE; - private const int MAX_FILE_SIZE_BYTES = 10_000_000; + private const int MAX_FILE_SIZE_BYTE_MULTIPLIER = 1_000_000; + + public function __construct(private EnvironmentReaderInterface $environmentReader) + { + } public function validate(string $path): ?ValidatorMessageTransfer { @@ -26,12 +31,14 @@ public function validate(string $path): ?ValidatorMessageTransfer return $this->createErrorMessageTransfer($errorMessage); } - if ($fileSize > self::MAX_FILE_SIZE_BYTES) { + $maxFileSizeBytes = $this->getMaxFileSizeBytes(); + + if ($fileSize > $maxFileSizeBytes) { $errorMessage = sprintf( self::MAX_SIZE_ERROR_MESSAGE_TEMPLATE, $path, $fileSize, - self::MAX_FILE_SIZE_BYTES + $maxFileSizeBytes, ); return $this->createErrorMessageTransfer($errorMessage); @@ -40,6 +47,11 @@ public function validate(string $path): ?ValidatorMessageTransfer return null; } + private function getMaxFileSizeBytes(): int + { + return (int)$this->environmentReader->getMaxFileSizeMegabytes() * self::MAX_FILE_SIZE_BYTE_MULTIPLIER; + } + protected function filesize(string $path): int|false { return @filesize($path); diff --git a/src/TransferGenerator/Definition/DefinitionFactory.php b/src/TransferGenerator/Definition/DefinitionFactory.php index d39f2f66..99d4e5b7 100644 --- a/src/TransferGenerator/Definition/DefinitionFactory.php +++ b/src/TransferGenerator/Definition/DefinitionFactory.php @@ -48,6 +48,7 @@ protected function createDefinitionFinder(): DefinitionFinderInterface { return new DefinitionFinder( $this->createFinder(), + $this->createEnvironmentReader(), $this->getConfig(), ); } diff --git a/src/TransferGenerator/Definition/Filesystem/DefinitionFinder.php b/src/TransferGenerator/Definition/Filesystem/DefinitionFinder.php index f54d0e9a..e2d07230 100644 --- a/src/TransferGenerator/Definition/Filesystem/DefinitionFinder.php +++ b/src/TransferGenerator/Definition/Filesystem/DefinitionFinder.php @@ -8,19 +8,19 @@ use Generator; use IteratorAggregate; use Picamator\TransferObject\Dependency\Finder\FinderInterface; +use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; use Picamator\TransferObject\TransferGenerator\Config\Config\ConfigInterface; use Picamator\TransferObject\TransferGenerator\Exception\TransferGeneratorDefinitionException; readonly class DefinitionFinder implements DefinitionFinderInterface { - private const string MAX_FILE_SIZE = '10M'; - private const string FILE_NAME_PATTERN = '*.transfer.yml'; private const string DEFINITIONS_NOT_FOUND_ERROR_MESSAGE = 'Missing Transfer Object definitions.'; public function __construct( private FinderInterface $finder, + private EnvironmentReaderInterface $environmentReader, private ConfigInterface $config, ) { } @@ -44,10 +44,12 @@ public function getDefinitionFiles(): Generator */ private function findDefinitionFiles(): IteratorAggregate&Countable { + $maxFileSize = $this->environmentReader->getMaxFileSizeMegabytes() . 'M'; + $definitionFinder = $this->finder->findFilesInDirectory( filePattern: self::FILE_NAME_PATTERN, dirName: $this->config->getDefinitionPath(), - maxFileSize: self::MAX_FILE_SIZE, + maxFileSize: $maxFileSize, ); $fileCount = $definitionFinder->count(); diff --git a/tests/unit/Shared/Environment/EnvironmentReaderTest.php b/tests/unit/Shared/Environment/EnvironmentReaderTest.php index 58f092b3..9e2ab81c 100644 --- a/tests/unit/Shared/Environment/EnvironmentReaderTest.php +++ b/tests/unit/Shared/Environment/EnvironmentReaderTest.php @@ -5,14 +5,17 @@ namespace Picamator\Tests\Unit\TransferObject\Shared\Environment; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Picamator\TransferObject\Shared\Environment\Enum\EnvironmentEnum; use Picamator\TransferObject\Shared\Environment\EnvironmentReader; use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; #[Group('shared')] +#[IgnoreDeprecations] class EnvironmentReaderTest extends TestCase { private EnvironmentReaderInterface&MockObject $readerMock; @@ -35,6 +38,7 @@ public function testProjectRootVariableIsSet(string $projectRoot, string $expect // Expect $this->readerMock->expects($this->once()) ->method('getenv') + ->with(EnvironmentEnum::PROJECT_ROOT->value) ->willReturn($projectRoot); $this->readerMock->expects($this->never()) @@ -48,17 +52,38 @@ public function testProjectRootVariableIsSet(string $projectRoot, string $expect $this->assertSame($expected, $actual); } - #[TestDox('Environment variable project root is not set should fallback to current working directory')] - public function testProjectRootVariableIsNotSetShouldFallbackToCurrentWorkingDirectory(): void + #[TestDox('Environment variable project root is not set should fallback to Alias')] + public function testProjectRootVariableIsNotSetShouldFallbackToAlias(): void + { + // Arrange + $projectRoot = '/home/my-user'; + + // Expect + $this->readerMock->expects($this->exactly(2)) + ->method('getenv') + ->willReturnOnConsecutiveCalls(false, $projectRoot); + + $this->readerMock->expects($this->never()) + ->method('getcwd') + ->seal(); + + // Act + $actual = $this->readerMock->getProjectRoot(); + + // Assert + $this->assertSame($projectRoot, $actual); + } + + #[TestDox('Environment variable project root and Alias are not set should fallback to current working directory')] + public function testProjectRootVariableAndAliasAreNotSetShouldFallbackToCurrentWorkingDirectory(): void { // Arrange - $projectRoot = false; $workingDirectory = '/home/work-dir/my-user'; // Expect - $this->readerMock->expects($this->once()) + $this->readerMock->expects($this->exactly(2)) ->method('getenv') - ->willReturn($projectRoot); + ->willReturn(false); $this->readerMock->expects($this->once()) ->method('getcwd') @@ -72,23 +97,20 @@ public function testProjectRootVariableIsNotSetShouldFallbackToCurrentWorkingDir $this->assertSame($workingDirectory, $actual); } - #[TestDox('Environment variable project root is not set and current working directory is failed')] + #[TestDox('Environment variable project root and alias are not set and current working directory is failed')] public function testProjectRootVariableIsNotSetAndWorkingDirectoryIsFailed(): void { // Arrange - $projectRoot = false; - $workingDirectory = false; - $expected = ''; // Expect - $this->readerMock->expects($this->once()) + $this->readerMock->expects($this->exactly(2)) ->method('getenv') - ->willReturn($projectRoot); + ->willReturn(false); $this->readerMock->expects($this->once()) ->method('getcwd') - ->willReturn($workingDirectory) + ->willReturn(false) ->seal(); // Act diff --git a/tests/unit/Shared/Validator/FileSizeValidatorTest.php b/tests/unit/Shared/Validator/FileSizeValidatorTest.php index c3d364f4..01a8f114 100644 --- a/tests/unit/Shared/Validator/FileSizeValidatorTest.php +++ b/tests/unit/Shared/Validator/FileSizeValidatorTest.php @@ -7,20 +7,25 @@ use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Picamator\TransferObject\Shared\Environment\EnvironmentReaderInterface; use Picamator\TransferObject\Shared\Validator\FileSizeValidator; final class FileSizeValidatorTest extends TestCase { - /** - * @see \Picamator\TransferObject\Shared\Validator\FileSizeValidator::MAX_FILE_SIZE_BYTES - */ - private const int MAX_FILE_SIZE_BYTES = 10_000_000; + private const string MAX_FILE_SIZE_MEGABYTES = '1'; private FileSizeValidator&MockObject $validatorMock; protected function setUp(): void { + $environmentReaderStub = $this->createStub(EnvironmentReaderInterface::class); + $environmentReaderStub + ->method('getMaxFileSizeMegabytes') + ->willReturn(self::MAX_FILE_SIZE_MEGABYTES) + ->seal(); + $this->validatorMock = $this->getMockBuilder(FileSizeValidator::class) + ->setConstructorArgs([$environmentReaderStub]) ->onlyMethods(['filesize']) ->getMock(); } @@ -51,12 +56,13 @@ public function testMaxFileSizeExceeded(): void { // Arrange $path = '/some-path.txt'; + $fileSize = (int)self::MAX_FILE_SIZE_MEGABYTES * 1_000_000 + 10; // Expect $this->validatorMock->expects($this->once()) ->method('filesize') ->with($path) - ->willReturn(self::MAX_FILE_SIZE_BYTES + 10) + ->willReturn($fileSize) ->seal(); // Act From cb3f19fb9f0613c773c4b19647dab62c1063b70e Mon Sep 17 00:00:00 2001 From: Sergii Pryz Date: Tue, 24 Feb 2026 18:53:21 +0100 Subject: [PATCH 6/6] Marked test as final --- tests/unit/Shared/Environment/EnvironmentReaderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/Shared/Environment/EnvironmentReaderTest.php b/tests/unit/Shared/Environment/EnvironmentReaderTest.php index 9e2ab81c..e036b09c 100644 --- a/tests/unit/Shared/Environment/EnvironmentReaderTest.php +++ b/tests/unit/Shared/Environment/EnvironmentReaderTest.php @@ -16,7 +16,7 @@ #[Group('shared')] #[IgnoreDeprecations] -class EnvironmentReaderTest extends TestCase +final class EnvironmentReaderTest extends TestCase { private EnvironmentReaderInterface&MockObject $readerMock;