From 4c8d45331db658ed01cbf59b51ee8fc7f0356419 Mon Sep 17 00:00:00 2001 From: Marcelo Lipienski Date: Thu, 7 Jan 2021 22:59:33 -0300 Subject: [PATCH 1/3] Adds support to using adapters to parse different config files formats. A config adapter must implement `AdapterInterface`. --- src/Utils/Config.php | 178 ++-------------- src/Utils/Config/AdapterInterface.php | 17 ++ src/Utils/Config/Adapters/JsonAdapter.php | 178 ++++++++++++++++ .../Utils/Config/Adapters/JsonAdapterTest.php | 191 ++++++++++++++++++ tests/fixtures/config/README.md | 38 ++++ tests/fixtures/config/extensions.json | 32 +++ tests/fixtures/config/operating-systems.json | 34 ++++ tests/fixtures/config/php-versions.json | 5 + 8 files changed, 517 insertions(+), 156 deletions(-) create mode 100644 src/Utils/Config/AdapterInterface.php create mode 100644 src/Utils/Config/Adapters/JsonAdapter.php create mode 100644 tests/Utils/Config/Adapters/JsonAdapterTest.php create mode 100644 tests/fixtures/config/README.md create mode 100644 tests/fixtures/config/extensions.json create mode 100644 tests/fixtures/config/operating-systems.json create mode 100644 tests/fixtures/config/php-versions.json diff --git a/src/Utils/Config.php b/src/Utils/Config.php index 8c3cc6a..14ceee9 100644 --- a/src/Utils/Config.php +++ b/src/Utils/Config.php @@ -3,175 +3,41 @@ namespace App\Utils; -use BenTools\CartesianProduct\CartesianProduct; +use App\Utils\Config\AdapterInterface; -use InvalidArgumentException; +use \ReflectionClass; +use \ReflectionMethod; final class Config { - private $basePath; - private $loadedContent = []; + private $adapter; - private function loadJson(string $fileName): void { - if (isset($this->loadedContent[$fileName]) === true) { - return; - } - - $filePath = $this->basePath . DIRECTORY_SEPARATOR . $fileName; - if (! is_file($filePath)) { - throw new InvalidArgumentException('$fileName must be a valid file'); - } - - $raw = file_get_contents($filePath); - if ($raw === false) { - throw new InvalidArgumentException('Could not read from file'); - } - - $json = json_decode($raw, true); - if ($json === false) { - throw new InvalidArgumentException('Could not decode json data'); - } - - $this->loadedContent[$fileName] = $json; + public function __construct(AdapterInterface $adapter) { + $this->adapter = $adapter; + $this->methods = $this->getAdapterPublicMethods(); } - public function __construct(string $basePath) { - $basePath = rtrim($basePath, DIRECTORY_SEPARATOR); - if (! is_dir($basePath)) { - throw new InvalidArgumentException('$basePath must be a valid directory'); - } - - $this->basePath = $basePath; - } + public function __call(string $method, array $arguments): array { + $argument = $this->getArgument($arguments); - public function getExtensionSpecs(string $extName = null): array { - $this->loadJson('extensions.json'); - if ($extName !== null) { - return $this->loadedContent['extensions.json'][$extName] ?? []; + if (in_array($method, $this->methods)) { + return $this->adapter->$method($argument); } - - $extensions = array_filter( - $this->loadedContent['extensions.json'], - function (array $specs): bool { - return (isset($specs['disabled']) === false || $specs['disabled'] === false); - }, - ARRAY_FILTER_USE_BOTH - ); - ksort($extensions); - - return $extensions; - } - - public function getExtensionList(): array { - return array_keys($this->getExtensionSpecs()); } + + private function getAdapterPublicMethods(): array { + $interface = new ReflectionClass('App\Utils\Config\AdapterInterface'); + $reflection = $interface->getMethods(ReflectionMethod::IS_PUBLIC); - public function getExtensionUrls(): array { - return array_map( - function (array $items): string { - return $items['build']['url'] ?? ''; - }, - $this->getExtensionSpecs() - ); + return array_map(function ($item) { + return $item->name; + }, $reflection); } - public function getOsSpecs(string $osName = null): array { - $this->loadJson('operating-systems.json'); - if ($osName !== null) { - return $this->loadedContent['operating-systems.json'][$osName] ?? []; + private function getArgument($arguments): ?string { + if (empty($arguments)) { + return null; } - $os = array_filter( - $this->loadedContent['operating-systems.json'], - function (array $specs): bool { - return (isset($specs['disabled']) === false || $specs['disabled'] === false); - }, - ARRAY_FILTER_USE_BOTH - ); - ksort($os); - - return $os; - } - - public function getOsList(): array { - return array_keys($this->getOSSpecs()); - } - - public function getPhpList(): array { - $this->loadJson('php-versions.json'); - $php = array_map( - function (array $items): string { - return implode('-', array_filter($items)); - }, - iterator_to_array( - new CartesianProduct( - [ - $this->loadedContent['php-versions.json'], - ['', 'zts'] - ] - ) - ) - ); - sort($php); - - return $php; - } - - public function getVersionList(): array { - return ['pecl', 'dev']; - } - - public function getExtensionMatrix(): array { - $ext = array_map( - function (array $items): string { - return implode(':', $items); - }, - iterator_to_array( - new CartesianProduct( - [ - $this->getExtensionList(), - $this->getVersionList() - ] - ) - ) - ); - sort($ext); - - return $ext; - } - - public function getPhpMatrix(): array { - $php = array_map( - function (array $items): string { - return implode('-', $items); - }, - iterator_to_array( - new CartesianProduct( - [ - $this->getPhpList(), - $this->getOSList() - ] - ) - ) - ); - - sort($php); - - return $php; - } - - public function getBuildMatrix(): array { - return array_map( - function (array $items): string { - return implode('@', $items); - }, - iterator_to_array( - new CartesianProduct( - [ - $this->getExtensionMatrix(), - $this->getPhpMatrix() - ] - ) - ) - ); + return array_shift($arguments); } } diff --git a/src/Utils/Config/AdapterInterface.php b/src/Utils/Config/AdapterInterface.php new file mode 100644 index 0000000..d67969c --- /dev/null +++ b/src/Utils/Config/AdapterInterface.php @@ -0,0 +1,17 @@ +loadedContent[$fileName]) === true) { + return; + } + + $filePath = $this->basePath . DIRECTORY_SEPARATOR . $fileName; + if (! is_file($filePath)) { + throw new InvalidArgumentException('$fileName must be a valid file'); + } + + $raw = file_get_contents($filePath); + if ($raw === false) { + throw new InvalidArgumentException('Could not read from file'); + } + + $json = json_decode($raw, true); + if ($json === false) { + throw new InvalidArgumentException('Could not decode json data'); + } + + $this->loadedContent[$fileName] = $json; + } + + public function __construct(string $basePath) { + $basePath = rtrim($basePath, DIRECTORY_SEPARATOR); + if (! is_dir($basePath)) { + throw new InvalidArgumentException('$basePath must be a valid directory'); + } + + $this->basePath = $basePath; + } + + public function getExtensionSpecs(string $extName = null): array { + $this->loadJson('extensions.json'); + if ($extName !== null) { + return $this->loadedContent['extensions.json'][$extName] ?? []; + } + + $extensions = array_filter( + $this->loadedContent['extensions.json'], + function (array $specs): bool { + return (isset($specs['disabled']) === false || $specs['disabled'] === false); + }, + ARRAY_FILTER_USE_BOTH + ); + ksort($extensions); + + return $extensions; + } + + public function getExtensionList(): array { + return array_keys($this->getExtensionSpecs()); + } + + public function getExtensionUrls(): array { + return array_map( + function (array $items): string { + return $items['build']['url'] ?? ''; + }, + $this->getExtensionSpecs() + ); + } + + public function getOSSpecs(string $osName = null): array { + $this->loadJson('operating-systems.json'); + if ($osName !== null) { + return $this->loadedContent['operating-systems.json'][$osName] ?? []; + } + + $os = array_filter( + $this->loadedContent['operating-systems.json'], + function (array $specs): bool { + return (isset($specs['disabled']) === false || $specs['disabled'] === false); + }, + ARRAY_FILTER_USE_BOTH + ); + ksort($os); + + return $os; + } + + public function getOSList(): array { + return array_keys($this->getOSSpecs()); + } + + public function getPHPList(): array { + $this->loadJson('php-versions.json'); + $php = array_map( + function (array $items): string { + return implode('-', array_filter($items)); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->loadedContent['php-versions.json'], + ['', 'zts'] + ] + ) + ) + ); + sort($php); + + return $php; + } + + public function getVersionList(): array { + return ['pecl', 'dev']; + } + + public function getExtensionMatrix(): array { + $ext = array_map( + function (array $items): string { + return implode(':', $items); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->getExtensionList(), + $this->getVersionList() + ] + ) + ) + ); + sort($ext); + + return $ext; + } + + public function getPHPMatrix(): array { + $php = array_map( + function (array $items): string { + return implode('-', $items); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->getPHPList(), + $this->getOSList() + ] + ) + ) + ); + + sort($php); + + return $php; + } + + public function getBuildMatrix(): array { + return array_map( + function (array $items): string { + return implode('@', $items); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->getExtensionMatrix(), + $this->getPHPMatrix() + ] + ) + ) + ); + } +} \ No newline at end of file diff --git a/tests/Utils/Config/Adapters/JsonAdapterTest.php b/tests/Utils/Config/Adapters/JsonAdapterTest.php new file mode 100644 index 0000000..5ecdcfe --- /dev/null +++ b/tests/Utils/Config/Adapters/JsonAdapterTest.php @@ -0,0 +1,191 @@ +adapter = new JsonAdapter($configPath); + + $jsonConfig = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'extensions.json'); + $this->extensions = json_decode($jsonConfig, true); + + $jsonConfig = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'operating-systems.json'); + $this->operatingSystems = json_decode($jsonConfig, true); + + $jsonConfig = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'php-versions.json'); + $this->phpVersions = json_decode($jsonConfig, true); + } + + public function testSingleExtensionSpecs(): void { + $this->assertEquals( + $this->adapter->getExtensionSpecs('amqp'), + $this->extensions['amqp'] + ); + } + + public function testAllExtensionSpecs(): void { + $this->assertEquals( + $this->adapter->getExtensionSpecs(), + $this->extensions + ); + } + + public function testExtensionList(): void { + $expected = ['ahocorasick', 'amqp']; + + $this->assertEquals( + $this->adapter->getExtensionList(), + $expected + ); + } + + public function testExtensionUrls(): void { + $expected = [ + 'ahocorasick' => 'https://github.com/ph4r05/php_aho_corasick', + 'amqp' => 'https://github.com/php-amqp/php-amqp' + ]; + + $this->assertEquals( + $this->adapter->getExtensionUrls(), + $expected + ); + } + + public function testSingleOSSpecs(): void { + $this->assertEquals( + $this->adapter->getOSSpecs('alpine'), + $this->operatingSystems['alpine'] + ); + } + + public function testOSSpecs(): void { + $expected = [ + 'buster' => $this->operatingSystems['buster'] + ]; + + $this->assertEquals( + $this->adapter->getOSSpecs(), + $expected + ); + } + + public function testOSList(): void { + $expected = ['buster']; + + $this->assertEquals( + $this->adapter->getOSList(), + $expected + ); + } + + public function testPHPList(): void { + $expected = array_map( + function (array $items): string { + return implode('-', array_filter($items)); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->phpVersions, + ['', 'zts'] + ] + ) + ) + ); + sort($expected); + + $this->assertEquals( + $this->adapter->getPHPList(), + $expected + ); + } + + public function testVersionList(): void { + $expected = [ + 'pecl', + 'dev' + ]; + + $this->assertEquals( + $this->adapter->getVersionList(), + $expected + ); + } + + public function testExtensionMatrix(): void { + $expected = array_map( + function (array $items): string { + return implode(':', $items); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->adapter->getExtensionList(), + $this->adapter->getVersionList() + ] + ) + ) + ); + sort($expected); + + $this->assertEquals( + $this->adapter->getExtensionMatrix(), + $expected + ); + } + + public function testPHPMatrix(): void { + $expected = array_map( + function (array $items): string { + return implode('-', $items); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->adapter->getPHPList(), + $this->adapter->getOSList() + ] + ) + ) + ); + sort($expected); + + $this->assertEquals( + $this->adapter->getPHPMatrix(), + $expected + ); + } + + public function testBuildMatrix(): void { + $expected = array_map( + function (array $items): string { + return implode('@', $items); + }, + iterator_to_array( + new CartesianProduct( + [ + $this->adapter->getExtensionMatrix(), + $this->adapter->getPHPMatrix() + ] + ) + ) + ); + sort($expected); + + $this->assertEquals( + $this->adapter->getBuildMatrix(), + $expected + ); + } +} \ No newline at end of file diff --git a/tests/fixtures/config/README.md b/tests/fixtures/config/README.md new file mode 100644 index 0000000..86ded22 --- /dev/null +++ b/tests/fixtures/config/README.md @@ -0,0 +1,38 @@ +# Configuration files + +## Extensions + +Extension configuration in [extension.json](extension.json). + +Field | Type | Required | Default | Description +----------------------|---------|----------|---------|------------ +build | object | yes | - | Build definition +build.deps | object | no | - | Build package dependencies +build.deps.`{osname}` | array | yes | `[]` | List of packages to be installed prior to extension building +build.flag | string | no | `empty` | Install flag +build.path | string | no | `empty` | Base path to start building the extension +build.type | string | yes | `empty` | Repository type +build.url | string | yes | `empty` | Source repository URL +disabled | boolean | no | `false` | Disable extension build/check +pecl | boolean | no | `true` | Test PECL install +require | object | no | - | PHP requirements for extension building +require.max | string | no | `empty` | Maximum PHP supported version +require.min | string | no | `empty` | Minimum PHP required version +require.zts | boolean | no | `false` | Require ZTS (Thread Safe) +summary | string | no | `empty` | Extension summary + +## Operating Systems + +Operating System configuration in [operating-systems.json](operating-systems.json). + +Field | Type | Required | Default | Description +----------|---------|----------|---------|------------ +deps | object | yes | - | +deps.cmd | string | yes | `empty` | Command to be used when a dependency has to be installed +deps.list | array | no | `[]` | List of general dependencies that are used in the build process +disabled | boolean | no | `false` | Disable OS from build matrix +pre | array | no | `empty` | List of commands to be executed before building an extension + +## PHP Versions + +PHP version list in [php-versions.json](php-versions.json). diff --git a/tests/fixtures/config/extensions.json b/tests/fixtures/config/extensions.json new file mode 100644 index 0000000..8be617f --- /dev/null +++ b/tests/fixtures/config/extensions.json @@ -0,0 +1,32 @@ +{ + "ahocorasick": { + "build": { + "flag": "--enable-ahocorasick", + "type": "git", + "url": "https://github.com/ph4r05/php_aho_corasick" + }, + "require": { + "min": "7.3.99" + }, + "summary": "Effective Aho-Corasick string pattern matching algorithm" + }, + "amqp": { + "build": { + "deps": { + "alpine": [ + "rabbitmq-c-dev" + ], + "buster": [ + "librabbitmq-dev" + ] + }, + "flag": "--with-amqp", + "type": "git", + "url": "https://github.com/php-amqp/php-amqp" + }, + "require": { + "min": "5.6.0" + }, + "summary": "Communicate with any AMQP compliant server" + } +} \ No newline at end of file diff --git a/tests/fixtures/config/operating-systems.json b/tests/fixtures/config/operating-systems.json new file mode 100644 index 0000000..c08e6dc --- /dev/null +++ b/tests/fixtures/config/operating-systems.json @@ -0,0 +1,34 @@ +{ + "alpine": { + "deps": { + "cmd": "apk add --no-cache", + "list": [ + "git", + "autoconf", + "build-base" + ] + }, + "disabled": true, + "pre": [ + "apk update", + "apk upgrade" + ] + }, + "buster": { + "deps": { + "cmd": "apt install -y --no-install-recommends", + "list": [ + "git", + "autoconf", + "build-essential" + ] + }, + "pre": [ + "apt update", + "apt full-upgrade -y" + ], + "post": [ + "rm -rf /var/lib/apt/lists/*" + ] + } +} diff --git a/tests/fixtures/config/php-versions.json b/tests/fixtures/config/php-versions.json new file mode 100644 index 0000000..9b97154 --- /dev/null +++ b/tests/fixtures/config/php-versions.json @@ -0,0 +1,5 @@ +[ + "7.3.25", + "7.4.13", + "8.0.0" +] From 93a1757efad363562b7e95965eddb80c79e67f1a Mon Sep 17 00:00:00 2001 From: Marcelo Lipienski Date: Thu, 7 Jan 2021 23:15:35 -0300 Subject: [PATCH 2/3] Fixes code formatting. --- src/Utils/Config/AdapterInterface.php | 2 +- src/Utils/Config/Adapters/JsonAdapter.php | 2 +- .../Utils/Config/Adapters/JsonAdapterTest.php | 2 +- tests/fixtures/config/README.md | 38 ------------------- 4 files changed, 3 insertions(+), 41 deletions(-) delete mode 100644 tests/fixtures/config/README.md diff --git a/src/Utils/Config/AdapterInterface.php b/src/Utils/Config/AdapterInterface.php index d67969c..5a65bf4 100644 --- a/src/Utils/Config/AdapterInterface.php +++ b/src/Utils/Config/AdapterInterface.php @@ -14,4 +14,4 @@ public function getVersionList(): array; public function getExtensionMatrix(): array; public function getPhpMatrix(): array; public function getBuildMatrix(): array; -} \ No newline at end of file +} diff --git a/src/Utils/Config/Adapters/JsonAdapter.php b/src/Utils/Config/Adapters/JsonAdapter.php index 100b96e..649b5c0 100644 --- a/src/Utils/Config/Adapters/JsonAdapter.php +++ b/src/Utils/Config/Adapters/JsonAdapter.php @@ -175,4 +175,4 @@ function (array $items): string { ) ); } -} \ No newline at end of file +} diff --git a/tests/Utils/Config/Adapters/JsonAdapterTest.php b/tests/Utils/Config/Adapters/JsonAdapterTest.php index 5ecdcfe..e60e4d9 100644 --- a/tests/Utils/Config/Adapters/JsonAdapterTest.php +++ b/tests/Utils/Config/Adapters/JsonAdapterTest.php @@ -188,4 +188,4 @@ function (array $items): string { $expected ); } -} \ No newline at end of file +} diff --git a/tests/fixtures/config/README.md b/tests/fixtures/config/README.md deleted file mode 100644 index 86ded22..0000000 --- a/tests/fixtures/config/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Configuration files - -## Extensions - -Extension configuration in [extension.json](extension.json). - -Field | Type | Required | Default | Description -----------------------|---------|----------|---------|------------ -build | object | yes | - | Build definition -build.deps | object | no | - | Build package dependencies -build.deps.`{osname}` | array | yes | `[]` | List of packages to be installed prior to extension building -build.flag | string | no | `empty` | Install flag -build.path | string | no | `empty` | Base path to start building the extension -build.type | string | yes | `empty` | Repository type -build.url | string | yes | `empty` | Source repository URL -disabled | boolean | no | `false` | Disable extension build/check -pecl | boolean | no | `true` | Test PECL install -require | object | no | - | PHP requirements for extension building -require.max | string | no | `empty` | Maximum PHP supported version -require.min | string | no | `empty` | Minimum PHP required version -require.zts | boolean | no | `false` | Require ZTS (Thread Safe) -summary | string | no | `empty` | Extension summary - -## Operating Systems - -Operating System configuration in [operating-systems.json](operating-systems.json). - -Field | Type | Required | Default | Description -----------|---------|----------|---------|------------ -deps | object | yes | - | -deps.cmd | string | yes | `empty` | Command to be used when a dependency has to be installed -deps.list | array | no | `[]` | List of general dependencies that are used in the build process -disabled | boolean | no | `false` | Disable OS from build matrix -pre | array | no | `empty` | List of commands to be executed before building an extension - -## PHP Versions - -PHP version list in [php-versions.json](php-versions.json). From 4d02b8c5865e3447c7fa122dd9fe1d1989975c86 Mon Sep 17 00:00:00 2001 From: Marcelo Lipienski Date: Thu, 7 Jan 2021 23:16:38 -0300 Subject: [PATCH 3/3] Fixes JSON formatting. --- tests/fixtures/config/extensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/config/extensions.json b/tests/fixtures/config/extensions.json index 8be617f..efd12c4 100644 --- a/tests/fixtures/config/extensions.json +++ b/tests/fixtures/config/extensions.json @@ -29,4 +29,4 @@ }, "summary": "Communicate with any AMQP compliant server" } -} \ No newline at end of file +}