├── .github ├── actions │ └── composer-cache │ │ └── action.yml └── workflows │ └── test_extension.yml ├── .gitignore ├── .php-version ├── LICENSE.txt ├── README.md ├── component ├── README.md └── app │ └── etc │ └── cli_arg_auto_proxy │ └── di.xml ├── composer.json ├── images └── logo.png ├── lib ├── Enum │ └── ProxyClassEntityInterfaceEnum.php ├── Exception │ └── ClassIsNotEligibleForProxyException.php ├── Mapper │ └── ProxiedConstructArgsToDiConfigMapper.php ├── Plugin │ └── Dom │ │ └── EnrichCliConfigWithProxyPlugin.php ├── Preference │ └── Framework │ │ └── ObjectManager │ │ └── Config │ │ └── Reader │ │ └── Dom │ │ └── Interceptor.php ├── README.md ├── Service │ ├── EnrichCliConfigWithProxyService.php │ └── GetProxiedConstructArgsConfigService.php ├── Test │ └── Unit │ │ ├── Map │ │ └── ProxiedConstructArgsToDiConfigMapperTest.php │ │ ├── Plugin │ │ └── Dom │ │ │ └── EnrichCliConfigWithProxyPluginTest.php │ │ ├── Preference │ │ └── Framework │ │ │ └── ObjectManager │ │ │ └── Config │ │ │ └── Reader │ │ │ └── Dom │ │ │ └── InterceptorTest.php │ │ └── Service │ │ ├── EnrichCliConfigWithProxyServiceTest.php │ │ └── GetProxiedConstructArgsConfigServiceTest.php └── Validator │ └── IsClassEligibleForProxyValidator.php ├── phpstan.neon.dist └── phpunit.xml /.github/actions/composer-cache/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache Composer packages' 2 | runs: 3 | using: 'composite' 4 | steps: 5 | - id: composer-cache 6 | uses: actions/cache@v3 7 | with: 8 | path: vendor 9 | key: ${{ inputs.runner-os }}-php-${{ hashFiles('**/composer.lock') }} 10 | restore-keys: | 11 | ${{ inputs.runner-os }}-php- -------------------------------------------------------------------------------- /.github/workflows/test_extension.yml: -------------------------------------------------------------------------------- 1 | name: Test Extension 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | validate-composer: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Validate composer.json and composer.lock 21 | run: composer validate 22 | 23 | build: 24 | 25 | runs-on: ubuntu-latest 26 | 27 | needs: validate-composer 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | - uses: ./.github/actions/composer-cache 32 | 33 | - name: Install dependencies 34 | run: composer install --prefer-dist --no-progress 35 | 36 | PHP-Compatibility: 37 | runs-on: ubuntu-latest 38 | 39 | needs: build 40 | 41 | steps: 42 | - uses: actions/checkout@v3 43 | - uses: ./.github/actions/composer-cache 44 | 45 | - name: PHP 7.4 compatibility 46 | run: composer sniffer:php7.4 47 | 48 | - name: PHP 8.0 compatibility 49 | run: composer sniffer:php8.0 50 | 51 | - name: PHP 8.1 compatibility 52 | run: composer sniffer:php8.1 53 | 54 | - name: PHP 8.2 compatibility 55 | run: composer sniffer:php8.2 56 | 57 | - name: PHP 8.3 compatibility 58 | run: composer sniffer:php8.3 59 | 60 | Static-tests: 61 | runs-on: ubuntu-latest 62 | 63 | needs: build 64 | 65 | steps: 66 | - uses: actions/checkout@v3 67 | - uses: ./.github/actions/composer-cache 68 | 69 | - name: phpstan 70 | run: composer phpstan 71 | 72 | PHP-Unit: 73 | runs-on: ubuntu-latest 74 | 75 | needs: build 76 | 77 | steps: 78 | - uses: actions/checkout@v3 79 | - uses: ./.github/actions/composer-cache 80 | 81 | - name: Setup PHP with Xdebug 82 | uses: shivammathur/setup-php@v2 83 | with: 84 | php-version: '8.1' 85 | coverage: xdebug 86 | 87 | - name: PHP Unit 88 | run: composer phpunit 89 | 90 | - name: phpunit-coverage-badge 91 | uses: timkrase/phpunit-coverage-badge@v1.2.0 92 | with: 93 | push_badge: true 94 | commit_message: "Update coverage badge" 95 | repo_token: ${{ secrets.GITHUB_TOKEN }} 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .env 3 | .editorconfig 4 | vendor/ 5 | composer.lock 6 | .phpunit.result.cache 7 | .phpunit.cache/ 8 | -------------------------------------------------------------------------------- /.php-version: -------------------------------------------------------------------------------- 1 | 8.1 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Vladyslav Podorozhnyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Magento 2](https://img.shields.io/badge/Magento-2.4.*-orange) 2 | ![PHP](https://img.shields.io/badge/php-7.4-blue) 3 | ![PHP](https://img.shields.io/badge/php-8.0-blue) 4 | ![PHP](https://img.shields.io/badge/php-8.1-blue) 5 | ![PHP](https://img.shields.io/badge/php-8.2-blue) 6 | ![PHP](https://img.shields.io/badge/php-8.3-blue) 7 | ![composer](https://shields.io/badge/composer-v2-darkgreen) 8 | ![packagist](https://img.shields.io/badge/packagist-f28d1a) 9 | ![build](https://github.com/run-as-root/magento-cli-auto-proxy/actions/workflows/test_extension.yml/badge.svg) 10 | 11 |
12 |
13 | Logo 14 | 15 |

Magento 2 - Auto Proxy to CLI class arguments

16 | 17 |

18 | Automatically injects Proxy for any argument defined in CLI command class constructor. 19 |
20 |

21 |
22 | 23 | ## About The Project 24 | 25 | ### Purpose: 26 | * eliminate issues while installation of your project with a fresh database (usually used with integration tests) - caused by not using Proxy in CLI of 3rd parties: `SQLSTATE[42S02]: Base table or view not found: 1146 Table 'magento2.flag' doesn't exist, query was: SELECT flag.* FROM flag WHERE (flag.flag_code='staging')` 27 | * speed up `php bin/magento` command execution; 28 | 29 | ## Getting Started 30 | 31 | ### Prerequisites 32 | * Magento v2.4.* and upper 33 | * composer v2 and upper 34 | 35 | ### Structure 36 | * magento2-component - see [README.md](component/README.md) 37 | * library - see [README.md](lib/README.md) 38 | 39 | ### Installation 40 | 41 | ```bash 42 | composer req run_as_root/magento-cli-auto-proxy:^1 43 | ``` 44 | 45 | ## Roadmap 46 | 47 | - [x] MVP release 48 | - [x] Documentation 49 | - [x] PHP 8 support 50 | - [x] Unit tests coverage 51 | - [ ] Static tests coverage 52 | - [ ] php linting 53 | - [ ] phpcs 54 | - [ ] phpmd 55 | - [x] phpstan 56 | - [ ] Integration tests coverage 57 | - [ ] Pipelines tests automation 58 | - [ ] Static tests 59 | - [x] Unit tests 60 | - [ ] Integration tests 61 | - [ ] Magento multiversions tests 62 | 63 | ## License 64 | 65 | Distributed under the MIT License. See `LICENSE.txt` for more information. 66 | 67 | ## Contact 68 | 69 | [_Vlad Podorozhnyi_](https://github.com/vpodorozh) 70 | Twitter: [![@vpodorozh](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Fvpodorozh)](https://twitter.com/vpodorozh) 71 | Email: `vpodorozh@gmail.com` | `vlad.podorozhnyi@run-as-root.sh` 72 |
73 | [_run_as_root GmbH_](https://github.com/run-as-root) 74 | Twitter: [![@run_as_root](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Frun_as_root)](https://twitter.com/run_as_root) 75 | Email: `info@run-as-root.sh` 76 | -------------------------------------------------------------------------------- /component/README.md: -------------------------------------------------------------------------------- 1 | ## \[magento2-component\] Cli Constructor Arg Auto Proxy Component 2 | 3 | 4 | Magneto 2 component that provides global DI config via `app/etc/di.xml` to create entry point for configuration enrichment. 5 | -------------------------------------------------------------------------------- /component/app/etc/cli_arg_auto_proxy/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run_as_root/magento-cli-auto-proxy", 3 | "description": "Makes all Magento CLI commands construct dependencies be injected as Proxy.", 4 | "type": "magento2-component", 5 | "license": "MIT", 6 | "require": { 7 | "php": "^7.4 | ^8", 8 | "magento/framework": ">=102.0.7" 9 | }, 10 | "require-dev": { 11 | "roave/security-advisories": "dev-latest", 12 | "squizlabs/php_codesniffer": "^3.7", 13 | "phpcompatibility/php-compatibility": "^9.3", 14 | "phpstan/phpstan": "^1.9", 15 | "phpunit/phpunit": ">9" 16 | }, 17 | "repositories": [ 18 | { 19 | "type": "composer", 20 | "url": "https://mirror.mage-os.org/", 21 | "only": [ 22 | "magento/*" 23 | ] 24 | } 25 | ], 26 | "autoload": { 27 | "psr-4": { 28 | "RunAsRoot\\CliConstructorArgAutoProxy\\": "lib" 29 | } 30 | }, 31 | "extra": { 32 | "map": [ 33 | [ 34 | "component/app/etc/cli_arg_auto_proxy/di.xml", 35 | "app/etc/cli_arg_auto_proxy/di.xml" 36 | ] 37 | ] 38 | }, 39 | "config": { 40 | "allow-plugins": { 41 | "magento/composer-dependency-version-audit-plugin": false 42 | } 43 | }, 44 | "scripts": { 45 | "sniffer:php7.4": "phpcs -p ./lib --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 7.4", 46 | "sniffer:php8.0": "phpcs -p ./lib --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 8.0", 47 | "sniffer:php8.1": "phpcs -p ./lib --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 8.1", 48 | "sniffer:php8.2": "phpcs -p ./lib --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 8.2", 49 | "sniffer:php8.3": "phpcs -p ./lib --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 8.3", 50 | "phpstan": "phpstan", 51 | "phpunit": "vendor/bin/phpunit -c phpunit.xml" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/run-as-root/magento-cli-auto-proxy/ac6bbb417f8c2876329e64bc4d69e6c6c038fc29/images/logo.png -------------------------------------------------------------------------------- /lib/Enum/ProxyClassEntityInterfaceEnum.php: -------------------------------------------------------------------------------- 1 | enrichCliConfigWithProxyService = $enrichCliConfigWithProxyService; 21 | $this->logger = $logger; 22 | } 23 | 24 | /** 25 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 26 | */ 27 | public function afterRead(Interceptor $subject, array $result, ?string $scope): array 28 | { 29 | if ($scope !== 'global') { 30 | return $result; 31 | } 32 | 33 | try { 34 | return $this->enrichCliConfigWithProxyService->execute($result); 35 | } catch (ReflectionException $exception) { 36 | $this->logger->error((string) $exception); 37 | return $result; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /lib/Preference/Framework/ObjectManager/Config/Reader/Dom/Interceptor.php: -------------------------------------------------------------------------------- 1 | runPlugin($result, $scope); 17 | } 18 | 19 | /** 20 | * Workaround for plugin execution. 21 | * You can not define plugin over Dom config reader, as it is created before Magento plugin functionality starts. 22 | */ 23 | private function runPlugin(array $result, ?string $scope): array 24 | { 25 | /** @var EnrichCliConfigWithProxyPlugin $enrichPlugin */ 26 | $enrichPlugin = ObjectManager::getInstance()->get(EnrichCliConfigWithProxyPlugin::class); 27 | return $enrichPlugin->afterRead($this, $result, $scope); 28 | } 29 | } -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | ## **\[library\] Cli Constructor Arg Auto Proxy** 2 | 3 | ## Features 4 | 5 | ### Auto add proxies as arguments to CLI command classes 6 | 7 | Automatically injects Proxy for any argument defined in CLI command class constructor. 8 | Entry point of functionality is based on DI config reader that is used in both cases - developer and production modes. 9 | 10 | ## Technical Specification 11 | 12 | ### Plugins 13 | 14 | #### `\RunAsRoot\CliConstructorArgAutoProxy\Plugin\Dom\EnrichCliConfigWithProxyPlugin` 15 | * responsible for enriching DI config with Proxies for CLI command constructor arguments; 16 | * executed after DI config reading; 17 | * plugin is executed in not trivial way - via preference on DOM config reader of DI (see section bellow for more details) 18 | * Caller class: `\RunAsRoot\CliConstructorArgAutoProxy\Preference\Framework\ObjectManager\Config\Reader\Dom\Interceptor` 19 | 20 | ### Preferences 21 | 22 | | source-class | custom-class | 23 | |----------------------------------------------------|-----------------------------------------------------------------------| 24 | | Magento\Framework\ObjectManager\Config\Reader\Dom | ...\Preference\Framework\ObjectManager\Config\Reader\Dom\Interceptor | 25 | 26 | 27 | #### `\RunAsRoot\CliConstructorArgAutoProxy\Preference\Framework\ObjectManager\Config\Reader\Dom\Interceptor` 28 | Workaround for plugin execution. 29 | This override has the same purpose as regular Magento 2 Interceptors - hook for calling plugins. 30 | It is not possible to define plugin over DOM config reader, as it is created before Magento plugin functionality starts. 31 | Preference is the only way to hook in. 32 | 33 | ### Services 34 | 35 | #### `\RunAsRoot\CliConstructorArgAutoProxy\Service\EnrichCliConfigWithProxyService` 36 | Enrich provided DI config with proxies for CLI class commands only. 37 | 38 | #### `\RunAsRoot\CliConstructorArgAutoProxy\Service\GetProxiedConstructArgsConfigService` 39 | Receives CLI command constructor arguments types and reformat them to Proxy types. 40 | Using `IsClassEligibleForProxyValidator` to determine is class eligible to be Proxied. 41 | 42 | ### Validator 43 | 44 | #### `\RunAsRoot\CliConstructorArgAutoProxy\Validator\IsClassEligibleForProxyValidator` 45 | Check is Proxy applicable for this specific class. 46 | 47 | ### Mapper 48 | 49 | #### `\RunAsRoot\CliConstructorArgAutoProxy\Mapper\ProxiedConstructArgsToDiConfigMapper` 50 | Adds Proxy DI configs for specific CLI class command to DI configs pool. 51 | -------------------------------------------------------------------------------- /lib/Service/EnrichCliConfigWithProxyService.php: -------------------------------------------------------------------------------- 1 | classReader = $classReader; 23 | $this->argsConfigService = $argsConfigService; 24 | $this->diConfigMapper = $diConfigMapper; 25 | } 26 | 27 | /** 28 | * @throws ReflectionException 29 | */ 30 | public function execute(array $diConfig): array 31 | { 32 | $cliCommandsList = $diConfig[CommandListInterface::class]['arguments']['commands'] ?? null; 33 | 34 | if ($cliCommandsList === null) { 35 | return $diConfig; 36 | } 37 | 38 | foreach ($cliCommandsList as $cliCommandConfig) { 39 | 40 | $cliInstanceClassName = $cliCommandConfig['instance'] ?? null; 41 | if ($cliInstanceClassName === null) { 42 | continue; 43 | } 44 | 45 | $constructConfig = $this->classReader->getConstructor($cliInstanceClassName); 46 | if ($constructConfig === null) { 47 | continue; 48 | } 49 | 50 | $proxiedConstructArgsConfig = $this->argsConfigService->get($constructConfig); 51 | $diConfig = $this->diConfigMapper->map($diConfig, $cliInstanceClassName, $proxiedConstructArgsConfig); 52 | } 53 | 54 | return $diConfig; 55 | } 56 | } -------------------------------------------------------------------------------- /lib/Service/GetProxiedConstructArgsConfigService.php: -------------------------------------------------------------------------------- 1 | proxyValidator = $proxyValidator; 16 | } 17 | 18 | public function get(array $constructConfig): array 19 | { 20 | $proxiedConstructorParams = []; 21 | 22 | foreach ($constructConfig as $constructorArgument) { 23 | list($paramName, $className) = $constructorArgument; 24 | 25 | try { 26 | $this->proxyValidator->validate($className); 27 | } catch (\Exception $exception) { 28 | continue; 29 | } 30 | 31 | $proxiedConstructorParams[$paramName] = [ 32 | 'instance' => $className . ProxyClassEntityInterfaceEnum::PROXY_CLASS_SUFFIX 33 | ]; 34 | } 35 | 36 | return $proxiedConstructorParams; 37 | } 38 | } -------------------------------------------------------------------------------- /lib/Test/Unit/Map/ProxiedConstructArgsToDiConfigMapperTest.php: -------------------------------------------------------------------------------- 1 | sut = new ProxiedConstructArgsToDiConfigMapper(); 19 | } 20 | 21 | /** 22 | * @dataProvider mapDataProvider 23 | */ 24 | public function test_map(array $diConfig, string $instanceClassName, array $proxiedConstructArgsConfig, array $expected): void 25 | { 26 | $result = $this->sut->map($diConfig, $instanceClassName, $proxiedConstructArgsConfig); 27 | $this->assertEquals($expected, $result); 28 | } 29 | 30 | public static function mapDataProvider(): array 31 | { 32 | return [ 33 | 'case1' => [ 34 | 'diConfig' => [ 35 | 'instance1' => [ 36 | 'arguments' => [ 37 | 'arg1' => 'value1', 38 | 'arg2' => 'value2', 39 | ], 40 | ], 41 | ], 42 | 'instanceClassName' => 'instance1', 43 | 'proxiedConstructArgsConfig' => [ 44 | 'arg3' => 'value3', 45 | 'arg4' => 'value4', 46 | ], 47 | 'expected' => [ 48 | 'instance1' => [ 49 | 'arguments' => [ 50 | 'arg1' => 'value1', 51 | 'arg2' => 'value2', 52 | 'arg3' => 'value3', 53 | 'arg4' => 'value4', 54 | ], 55 | ], 56 | ], 57 | ], 58 | 'case2' => [ 59 | 'diConfig' => [ 60 | 'instance1' => [], 61 | ], 62 | 'instanceClassName' => 'instance1', 63 | 'proxiedConstructArgsConfig' => [ 64 | 'arg3' => 'value3', 65 | 'arg4' => 'value4', 66 | ], 67 | 'expected' => [ 68 | 'instance1' => [ 69 | 'arguments' => [ 70 | 'arg4' => 'value4', 71 | 'arg3' => 'value3', 72 | ], 73 | ], 74 | ] 75 | ], 76 | 'case3' => [ 77 | 'diConfig' => [ 78 | 'instance1' => [ 79 | 'arguments' => [ 80 | 'arg1' => 'value1', 81 | 'arg2' => 'value2', 82 | ], 83 | ], 84 | ], 85 | 'instanceClassName' => 'instance1', 86 | 'proxiedConstructArgsConfig' => [], 87 | 'expected' => [ 88 | 'instance1' => [ 89 | 'arguments' => [ 90 | 'arg1' => 'value1', 91 | 'arg2' => 'value2', 92 | ], 93 | ], 94 | ], 95 | ], 96 | 'case4' => [ 97 | 'diConfig' => [ 98 | 'instance1' => [], 99 | ], 100 | 'instanceClassName' => 'instance1', 101 | 'proxiedConstructArgsConfig' => [], 102 | 'expected' => [ 103 | 'instance1' => [ 104 | 'arguments' => [], 105 | ], 106 | ], 107 | ], 108 | 109 | ]; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/Test/Unit/Plugin/Dom/EnrichCliConfigWithProxyPluginTest.php: -------------------------------------------------------------------------------- 1 | service = $this->createMock(EnrichCliConfigWithProxyService::class); 21 | $this->logger = $this->createMock(LoggerInterface::class); 22 | $this->sut = new EnrichCliConfigWithProxyPlugin($this->service, $this->logger); 23 | } 24 | 25 | public function test_after_read(): void 26 | { 27 | $subject = $this->createMock(Interceptor::class); 28 | $result = ['foo' => 'bar']; 29 | $scope = 'global'; 30 | 31 | $this->service->expects($this->once())->method('execute')->with($result) 32 | ->willReturn(['abc' => 'def']); 33 | $this->logger->expects($this->never())->method('error'); 34 | 35 | $this->assertSame(['abc' => 'def'], $this->sut->afterRead($subject, $result, $scope)); 36 | } 37 | 38 | public function test_after_read_with_non_global_scope(): void 39 | { 40 | $subject = $this->createMock(Interceptor::class); 41 | $result = ['foo' => 'bar']; 42 | $scope = 'foo'; 43 | 44 | $this->service->expects($this->never())->method('execute'); 45 | $this->logger->expects($this->never())->method('error'); 46 | 47 | $this->assertSame($result, $this->sut->afterRead($subject, $result, $scope)); 48 | } 49 | 50 | public function test_after_read_with_exception(): void 51 | { 52 | $subject = $this->createMock(Interceptor::class); 53 | $result = ['foo' => 'bar']; 54 | $scope = 'global'; 55 | 56 | $this->service->expects($this->once())->method('execute') 57 | ->with($result)->willThrowException(new \ReflectionException('foo')); 58 | $this->logger->expects($this->once())->method('error'); 59 | 60 | $this->assertSame($result, $this->sut->afterRead($subject, $result, $scope)); 61 | } 62 | } -------------------------------------------------------------------------------- /lib/Test/Unit/Preference/Framework/ObjectManager/Config/Reader/Dom/InterceptorTest.php: -------------------------------------------------------------------------------- 1 | fileResolver = $this->createMock(FileResolverInterface::class); 23 | $converter = $this->createMock(Dom::class); 24 | $schemaLocator = $this->createMock(SchemaLocator::class); 25 | $validationState = $this->createMock(ValidationStateInterface::class); 26 | 27 | $this->sut = new Interceptor( 28 | $this->fileResolver, 29 | $converter, 30 | $schemaLocator, 31 | $validationState 32 | ); 33 | } 34 | 35 | public function test_read(): void 36 | { 37 | $this->fileResolver->method('get')->willReturn([]); 38 | 39 | $pluginMock = $this->createMock(EnrichCliConfigWithProxyPlugin::class); 40 | $pluginRes = ['some' => 'result']; 41 | $pluginMock->method('afterRead')->willReturn($pluginRes); 42 | 43 | $objMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); 44 | $objMock->method('get')->with(EnrichCliConfigWithProxyPlugin::class) 45 | ->willReturn($pluginMock); 46 | 47 | ObjectManager::setInstance($objMock); 48 | 49 | $this->assertEquals($pluginRes, $this->sut->read('global')); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/Test/Unit/Service/EnrichCliConfigWithProxyServiceTest.php: -------------------------------------------------------------------------------- 1 | classReader = $this->createMock(ClassReaderInterface::class); 26 | $this->argsConfigService = $this->createMock(GetProxiedConstructArgsConfigService::class); 27 | $this->diConfigMapper = $this->createMock(ProxiedConstructArgsToDiConfigMapper::class); 28 | $this->sut = new EnrichCliConfigWithProxyService( 29 | $this->argsConfigService, 30 | $this->classReader, 31 | $this->diConfigMapper 32 | ); 33 | } 34 | 35 | public function test_execute(): void 36 | { 37 | $diConfig = [ 38 | 'some' => 'config', 39 | ]; 40 | 41 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 42 | } 43 | 44 | public function test_execute_with_cli_commands(): void 45 | { 46 | $diConfig = [ 47 | 'some' => 'config', 48 | 'Magento\Framework\Console\CommandListInterface' => [ 49 | 'arguments' => [ 50 | 'commands' => [ 51 | [ 52 | 'instance' => 'Some\CliCommand', 53 | ], 54 | ], 55 | ], 56 | ], 57 | ]; 58 | 59 | $this->classReader->expects($this->once()) 60 | ->method('getConstructor') 61 | ->with('Some\CliCommand') 62 | ->willReturn([]); 63 | 64 | $this->argsConfigService->expects($this->once()) 65 | ->method('get') 66 | ->with([]) 67 | ->willReturn([]); 68 | 69 | $this->diConfigMapper->expects($this->once()) 70 | ->method('map') 71 | ->with($diConfig, 'Some\CliCommand', []) 72 | ->willReturn($diConfig); 73 | 74 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 75 | } 76 | 77 | public function test_execute_with_cli_commands_and_no_construct(): void 78 | { 79 | $diConfig = [ 80 | 'some' => 'config', 81 | 'Magento\Framework\Console\CommandListInterface' => [ 82 | 'arguments' => [ 83 | 'commands' => [ 84 | [ 85 | 'instance' => 'Some\CliCommand', 86 | ], 87 | ], 88 | ], 89 | ], 90 | ]; 91 | 92 | $this->classReader->expects($this->once()) 93 | ->method('getConstructor') 94 | ->with('Some\CliCommand') 95 | ->willReturn(null); 96 | 97 | $this->argsConfigService->expects($this->never()) 98 | ->method('get'); 99 | 100 | $this->diConfigMapper->expects($this->never()) 101 | ->method('map'); 102 | 103 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 104 | } 105 | 106 | public function test_execute_with_cli_commands_and_no_instance(): void 107 | { 108 | $diConfig = [ 109 | 'some' => 'config', 110 | 'Magento\Framework\Console\CommandListInterface' => [ 111 | 'arguments' => [ 112 | 'commands' => [ 113 | [ 114 | 'some' => 'config', 115 | ], 116 | ], 117 | ], 118 | ], 119 | ]; 120 | 121 | $this->classReader->expects($this->never()) 122 | ->method('getConstructor'); 123 | 124 | $this->argsConfigService->expects($this->never()) 125 | ->method('get'); 126 | 127 | $this->diConfigMapper->expects($this->never()) 128 | ->method('map'); 129 | 130 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 131 | } 132 | 133 | public function test_execute_with_cli_commands_and_no_commands(): void 134 | { 135 | $diConfig = [ 136 | 'some' => 'config', 137 | 'Magento\Framework\Console\CommandListInterface' => [ 138 | 'arguments' => [ 139 | 'some' => 'config', 140 | ], 141 | ], 142 | ]; 143 | 144 | $this->classReader->expects($this->never()) 145 | ->method('getConstructor'); 146 | 147 | $this->argsConfigService->expects($this->never()) 148 | ->method('get'); 149 | 150 | $this->diConfigMapper->expects($this->never()) 151 | ->method('map'); 152 | 153 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 154 | } 155 | 156 | public function test_execute_with_cli_commands_and_no_arguments(): void 157 | { 158 | $diConfig = [ 159 | 'some' => 'config', 160 | 'Magento\Framework\Console\CommandListInterface' => [ 161 | 'some' => 'config', 162 | ], 163 | ]; 164 | 165 | $this->classReader->expects($this->never()) 166 | ->method('getConstructor'); 167 | 168 | $this->argsConfigService->expects($this->never()) 169 | ->method('get'); 170 | 171 | $this->diConfigMapper->expects($this->never()) 172 | ->method('map'); 173 | 174 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 175 | } 176 | 177 | public function test_execute_with_cli_commands_and_no_command_list_interface(): void 178 | { 179 | $diConfig = [ 180 | 'some' => 'config', 181 | ]; 182 | 183 | $this->classReader->expects($this->never()) 184 | ->method('getConstructor'); 185 | 186 | $this->argsConfigService->expects($this->never()) 187 | ->method('get'); 188 | 189 | $this->diConfigMapper->expects($this->never()) 190 | ->method('map'); 191 | 192 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 193 | } 194 | 195 | public function test_execute_with_cli_commands_and_no_arguments_key(): void 196 | { 197 | $diConfig = [ 198 | 'some' => 'config', 199 | 'Magento\Framework\Console\CommandListInterface' => [ 200 | 'some' => 'config', 201 | ], 202 | ]; 203 | 204 | $this->classReader->expects($this->never()) 205 | ->method('getConstructor'); 206 | 207 | $this->argsConfigService->expects($this->never()) 208 | ->method('get'); 209 | 210 | $this->diConfigMapper->expects($this->never()) 211 | ->method('map'); 212 | 213 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 214 | } 215 | 216 | public function test_execute_with_cli_commands_and_no_commands_key(): void 217 | { 218 | $diConfig = [ 219 | 'some' => 'config', 220 | 'Magento\Framework\Console\CommandListInterface' => [ 221 | 'arguments' => [ 222 | 'some' => 'config', 223 | ], 224 | ], 225 | ]; 226 | 227 | $this->classReader->expects($this->never()) 228 | ->method('getConstructor'); 229 | 230 | $this->argsConfigService->expects($this->never()) 231 | ->method('get'); 232 | 233 | $this->diConfigMapper->expects($this->never()) 234 | ->method('map'); 235 | 236 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 237 | } 238 | 239 | public function test_execute_with_cli_commands_and_no_instance_key(): void 240 | { 241 | $diConfig = [ 242 | 'some' => 'config', 243 | 'Magento\Framework\Console\CommandListInterface' => [ 244 | 'arguments' => [ 245 | 'commands' => [ 246 | [ 247 | 'some' => 'config', 248 | ], 249 | ], 250 | ], 251 | ], 252 | ]; 253 | 254 | $this->classReader->expects($this->never()) 255 | ->method('getConstructor'); 256 | 257 | $this->argsConfigService->expects($this->never()) 258 | ->method('get'); 259 | 260 | $this->diConfigMapper->expects($this->never()) 261 | ->method('map'); 262 | 263 | $this->assertEquals($diConfig, $this->sut->execute($diConfig)); 264 | } 265 | } -------------------------------------------------------------------------------- /lib/Test/Unit/Service/GetProxiedConstructArgsConfigServiceTest.php: -------------------------------------------------------------------------------- 1 | proxyValidator = $this->createMock(IsClassEligibleForProxyValidator::class); 22 | $this->sut = new GetProxiedConstructArgsConfigService($this->proxyValidator); 23 | } 24 | 25 | public function test_get(): void 26 | { 27 | $constructConfig = [ 28 | ['some', 'Some\Class'], 29 | ['someOther', 'Some\OtherClass'], 30 | ]; 31 | 32 | $this->proxyValidator->expects($this->exactly(2)) 33 | ->method('validate') 34 | ->willReturnCallback(function () use (&$i) { 35 | switch ($i) { 36 | case 0: 37 | return ['Some\Class']; 38 | case 1: 39 | return ['Some\OtherClass']; 40 | } 41 | $i++; 42 | 43 | return []; 44 | }) 45 | ->willReturnOnConsecutiveCalls(true, false); 46 | 47 | $this->assertEquals( 48 | [ 49 | 'some' => [ 50 | 'instance' => 'Some\Class\Proxy', 51 | ], 52 | 'someOther' => [ 53 | 'instance' => 'Some\OtherClass\Proxy', 54 | ], 55 | ], 56 | $this->sut->get($constructConfig) 57 | ); 58 | } 59 | 60 | public function test_get_with_no_eligible_classes(): void 61 | { 62 | $constructConfig = [ 63 | ['some', 'Some\Class'], 64 | ['someOther', 'Some\OtherClass'], 65 | ]; 66 | 67 | $this->proxyValidator->expects($this->exactly(2)) 68 | ->method('validate') 69 | ->willReturnCallback(function () use (&$i) { 70 | switch ($i) { 71 | case 0: 72 | return ['Some\Class']; 73 | case 1: 74 | return ['Some\OtherClass']; 75 | } 76 | $i++; 77 | 78 | return []; 79 | }) 80 | ->willThrowException(new ClassIsNotEligibleForProxyException()); 81 | 82 | $this->assertEquals([], $this->sut->get($constructConfig)); 83 | } 84 | 85 | public function testGetWithNoConstructConfig(): void 86 | { 87 | $this->assertEquals([], $this->sut->get([])); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/Validator/IsClassEligibleForProxyValidator.php: -------------------------------------------------------------------------------- 1 | 0) { 21 | throw new ClassIsNotEligibleForProxyException('Class is already a Proxy'); 22 | } 23 | 24 | // skipp - in case Proxy exists and is not a child of original class 25 | if (!is_a( 26 | $className . ProxyClassEntityInterfaceEnum::PROXY_CLASS_SUFFIX, 27 | $className, 28 | true 29 | )) { 30 | throw new ClassIsNotEligibleForProxyException( 31 | 'Proxy already exists and is not a child of original class: ' . $className 32 | ); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 5 3 | paths: 4 | - lib 5 | excludePaths: 6 | analyseAndScan: 7 | - lib/Test 8 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lib/Test/Unit 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | lib 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------