diff --git a/.github/workflows/php-qa.yml b/.github/workflows/php-qa.yml index 5250f95..36d8fbf 100644 --- a/.github/workflows/php-qa.yml +++ b/.github/workflows/php-qa.yml @@ -1,29 +1,60 @@ name: Quality assurance PHP -on: ['pull_request', 'push', 'workflow_dispatch'] +on: + push: + paths: + - '**workflows/php-qa.yml' + - '**.php' + - '**phpcs.xml.dist' + - '**psalm.xml' + - '**composer.json' + pull_request: + paths: + - '**workflows/php-qa.yml' + - '**.php' + - '**phpcs.xml.dist' + - '**psalm.xml' + - '**composer.json' + workflow_dispatch: + inputs: + jobs: + required: true + type: choice + default: 'Run all' + description: 'Choose jobs to run' + options: + - 'Run all' + - 'Run PHPCS only' + - 'Run Psalm only' + - 'Run lint only' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true jobs: - lint-php: - if: ${{ (github.event_name == 'workflow_dispatch') || (!contains(github.event.head_commit.message, 'skip lint')) }} - strategy: - matrix: - php: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2"] - uses: inpsyde/reusable-workflows/.github/workflows/lint-php.yml@main - with: - PHP_VERSION: ${{ matrix.php }} + lint: + if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run lint only')) }} + uses: inpsyde/reusable-workflows/.github/workflows/lint-php.yml@main + strategy: + matrix: + php: [ '7.4', '8.0', '8.1', '8.2', '8.3' ] + with: + PHP_VERSION: ${{ matrix.php }} + LINT_ARGS: '-e php --colors --show-deprecated ./src' - coding-standards-analysis-php: - if: ${{ (github.event_name == 'workflow_dispatch') || (!contains(github.event.head_commit.message, 'skip cs')) }} - needs: lint-php - uses: inpsyde/reusable-workflows/.github/workflows/coding-standards-php.yml@main - with: - PHPCS_ARGS: '--report=summary' + coding-standards-analysis: + if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run PHPCS only')) }} + uses: inpsyde/reusable-workflows/.github/workflows/coding-standards-php.yml@main + with: + PHP_VERSION: '8.3' - static-analysis-php: - if: ${{ (github.event_name == 'workflow_dispatch') || (!contains(github.event.head_commit.message, 'skip sa')) }} - needs: lint-php - uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-php.yml@main + static-code-analysis: + if: ${{ (github.event_name != 'workflow_dispatch') || ((github.event.inputs.jobs == 'Run all') || (github.event.inputs.jobs == 'Run Psalm only')) }} + uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-php.yml@main + strategy: + matrix: + php: [ '7.4', '8.0', '8.1', '8.2', '8.3' ] + with: + PHP_VERSION: ${{ matrix.php }} + PSALM_ARGS: --output-format=github --no-suggestions --no-cache --no-diff --find-unused-psalm-suppress diff --git a/.github/workflows/php-unit-tests.yml b/.github/workflows/php-unit-tests.yml index 91ccea9..69c0aaf 100644 --- a/.github/workflows/php-unit-tests.yml +++ b/.github/workflows/php-unit-tests.yml @@ -1,60 +1,72 @@ name: PHP unit tests -on: ['pull_request', 'push', 'workflow_dispatch'] +on: + push: + paths: + - '**workflows/php-unit-tests.yml' + - '**.php' + - '**phpunit.xml.dist' + - '**composer.json' + pull_request: + paths: + - '**workflows/php-unit-tests.yml' + - '**.php' + - '**phpunit.xml.dist' + - '**composer.json' + workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true jobs: - tests-unit-php: - runs-on: ubuntu-latest - if: ${{ (github.event_name == 'workflow_dispatch') || (!contains(github.event.head_commit.message, 'skip tests')) }} - - env: - USE_COVERAGE: 'no' - - strategy: - matrix: - php-versions: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2' ] - dependency-versions: [ 'lowest', 'highest' ] - container-versions: [ '^1.1.0', '^2' ] - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Use coverage? - if: ${{ (matrix.php-versions == '8.0') && (matrix.dependency-versions == 'highest') && (matrix.container-versions == '^2') }} - run: echo "USE_COVERAGE=yes" >> $GITHUB_ENV - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - ini-values: zend.assertions=1, error_reporting=-1, display_errors=On - coverage: ${{ ((env.USE_COVERAGE == 'yes') && 'xdebug') || 'none' }} - - - name: Setup dependencies for PSR-11 target version - run: | - composer remove inpsyde/php-coding-standards inpsyde/wp-stubs-versions vimeo/psalm --dev --no-install - composer require "psr/container:${{ matrix.container-versions }}" --no-install - - - name: Install Composer dependencies - uses: ramsey/composer-install@v2 - with: - dependency-versions: ${{ matrix.dependency-versions }} - - - name: Run unit tests - run: | - ./vendor/bin/phpunit --atleast-version 9 && ./vendor/bin/phpunit --migrate-configuration || echo 'Config does not need updates.' - ./vendor/bin/phpunit ${{ ((env.USE_COVERAGE == 'yes') && '--coverage-clover coverage.xml') || '--no-coverage' }} - - - name: Update coverage - if: ${{ env.USE_COVERAGE == 'yes' }} - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage.xml - flags: unittests - verbose: true + tests-unit-php: + runs-on: ubuntu-latest + if: ${{ (github.event_name == 'workflow_dispatch') || (!contains(github.event.head_commit.message, 'skip tests')) }} + + env: + USE_COVERAGE: 'no' + + strategy: + matrix: + php-versions: [ '7.4', '8.0', '8.1', '8.2', '8.3' ] + dependency-versions: [ 'lowest', 'highest' ] + container-versions: [ '^1.1.0', '^2' ] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use coverage? + if: ${{ (matrix.php-versions == '8.2') && (matrix.dependency-versions == 'highest') && (matrix.container-versions == '^2') }} + run: echo "USE_COVERAGE=yes" >> $GITHUB_ENV + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + ini-values: zend.assertions=1, error_reporting=-1, display_errors=On + coverage: ${{ ((env.USE_COVERAGE == 'yes') && 'xdebug') || 'none' }} + + - name: Setup dependencies for PSR-11 target version + run: | + composer remove inpsyde/php-coding-standards inpsyde/wp-stubs-versions vimeo/psalm --dev --no-update + composer require "psr/container:${{ matrix.container-versions }}" --no-update + composer config --no-plugins allow-plugins.roots/wordpress-core-installer false + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + dependency-versions: ${{ matrix.dependency-versions }} + + - name: Run unit tests + run: ./vendor/bin/phpunit ${{ ((env.USE_COVERAGE == 'yes') && '--coverage-clover coverage.xml') || '--no-coverage' }} + + - name: Update coverage + if: ${{ env.USE_COVERAGE == 'yes' }} + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + flags: unittests + verbose: true diff --git a/.gitignore b/.gitignore index 9be8f69..173c379 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ ### Composer template composer.phar composer.lock -/vendor/ +vendor/ # PHPUnit .phpunit.result.cache +phpunit.xml +coverage/ # tmp files tmp/ diff --git a/composer.json b/composer.json index c1f8089..5957dd4 100644 --- a/composer.json +++ b/composer.json @@ -9,31 +9,30 @@ "email": "hello@inpsyde.com", "homepage": "https://inpsyde.com/", "role": "Company" - }, - { - "name": "Christian Leucht", - "email": "c.leucht@inpsyde.com", - "role": "Developer" - }, + } + ], + "repositories": [ { - "name": "Giuseppe Mazzapica", - "email": "g.mazzapica@inpsyde.com", - "role": "Developer" + "type": "composer", + "url": "https://raw.githubusercontent.com/inpsyde/wp-stubs/main", + "only": [ + "inpsyde/wp-stubs-versions" + ] } ], "require": { - "php": ">=7.2", + "php": ">=7.4 <8.4", "ext-json": "*", "psr/container": "^1.1.0 || ^2" }, "require-dev": { "brain/monkey": "^2.6.1", - "inpsyde/php-coding-standards": "^1", - "mikey179/vfsstream": "^v1.6.10", - "phpunit/phpunit": "^8.5.21 || ^9.6.7", - "vimeo/psalm": "^4.13.1", - "php-stubs/wordpress-stubs": ">=5.8@stable", - "johnpbloch/wordpress-core": ">=5.8" + "inpsyde/php-coding-standards": "^2@dev", + "inpsyde/wp-stubs-versions": "dev-latest", + "roots/wordpress-no-content": "@dev", + "mikey179/vfsstream": "^v1.6.11", + "phpunit/phpunit": "^9.6.19", + "vimeo/psalm": "^5.24.0" }, "extra": { "branch-alias": { @@ -55,19 +54,20 @@ "prefer-stable": true, "scripts": { "cs": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs", - "psalm": "@php ./vendor/vimeo/psalm/psalm", + "psalm": "@php ./vendor/vimeo/psalm/psalm --no-suggestions --report-show-info=false --find-unused-psalm-suppress --no-diff --no-cache --no-file-cache --output-format=compact", + "tests": "@php ./vendor/phpunit/phpunit/phpunit --no-coverage", + "tests:coverage": "@php ./vendor/phpunit/phpunit/phpunit", "qa": [ - "@tests:no-cov", "@cs", - "@psalm" - ], - "tests": "@php ./vendor/phpunit/phpunit/phpunit", - "tests:no-cov": "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" + "@psalm", + "@tests" + ] }, "config": { "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "composer/package-versions-deprecated": true + "composer/*": true, + "inpsyde/*": true, + "dealerdirect/phpcodesniffer-composer-installer": true } } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index aa4afaa..f63b65f 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -1,19 +1,46 @@ - ./src/ - ./tests/ + ./src + ./tests - + + + + + + value=" + Inpsyde\Modularity=>src, + Inpsyde\Modularity\Tests=>tests/src, + Inpsyde\Modularity\Tests\Unit=>tests/unit" + /> + + + */tests/* + + + */tests/* + + + */tests/* + + + */tests/* + + + */tests/* + + + */tests/* + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 58ff95a..426facd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,27 +1,32 @@ - - - + + + + + ./src - - ./vendor - - - + + + ./vendor + + + + + + ./tests/unit/ - - - + diff --git a/psalm.xml b/psalm.xml index f55aab2..b64786e 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,11 +1,14 @@ - + - - - - - - + diff --git a/src/Container/ContainerConfigurator.php b/src/Container/ContainerConfigurator.php index 64a8d2b..f58dcdc 100644 --- a/src/Container/ContainerConfigurator.php +++ b/src/Container/ContainerConfigurator.php @@ -1,4 +1,5 @@ - */ - private $services = []; - - /** - * @var array - */ - private $factoryIds = []; - - /** - * @var ServiceExtensions - */ - private $extensions; - - /** - * @var ContainerInterface[] - */ - private $containers = []; + /** @var array */ + private array $services = []; + /** @var array */ + private array $factoryIds = []; + private ServiceExtensions $extensions; + private ?ContainerInterface $compiledContainer = null; + /** @var ContainerInterface[] */ + private array $containers = []; /** - * @var null|ContainerInterface - */ - private $compiledContainer; - - /** - * ContainerConfigurator constructor. - * * @param ContainerInterface[] $containers */ public function __construct(array $containers = [], ?ServiceExtensions $extensions = null) @@ -48,9 +31,8 @@ public function __construct(array $containers = [], ?ServiceExtensions $extensio } /** - * Allowing to add child containers. - * * @param ContainerInterface $container + * @return void */ public function addContainer(ContainerInterface $container): void { @@ -72,7 +54,6 @@ public function addFactory(string $id, callable $factory): void /** * @param string $id * @param Service $service - * * @return void */ public function addService(string $id, callable $service): void @@ -93,7 +74,6 @@ public function addService(string $id, callable $service): void /** * @param string $id - * * @return bool */ public function hasService(string $id): bool @@ -114,7 +94,6 @@ public function hasService(string $id): bool /** * @param string $id * @param ExtendingService $extender - * * @return void */ public function addExtension(string $id, callable $extender): void @@ -124,7 +103,6 @@ public function addExtension(string $id, callable $extender): void /** * @param string $id - * * @return bool */ public function hasExtension(string $id): bool @@ -133,13 +111,13 @@ public function hasExtension(string $id): bool } /** - * Returns a read only version of this Container. - * * @return ContainerInterface + * + * @psalm-assert ContainerInterface $this->compiledContainer */ public function createReadOnlyContainer(): ContainerInterface { - if (!$this->compiledContainer) { + if ($this->compiledContainer === null) { $this->compiledContainer = new ReadOnlyContainer( $this->services, $this->factoryIds, diff --git a/src/Container/PackageProxyContainer.php b/src/Container/PackageProxyContainer.php index d5e3954..6cc6140 100644 --- a/src/Container/PackageProxyContainer.php +++ b/src/Container/PackageProxyContainer.php @@ -10,15 +10,8 @@ class PackageProxyContainer implements ContainerInterface { - /** - * @var Package - */ - private $package; - - /** - * @var ContainerInterface|null - */ - private $container; + private Package $package; + private ?ContainerInterface $container = null; /** * @param Package $package @@ -31,8 +24,6 @@ public function __construct(Package $package) /** * @param string $id * @return mixed - * - * @throws \Exception */ public function get(string $id) { @@ -44,8 +35,6 @@ public function get(string $id) /** * @param string $id * @return bool - * - * @throws \Exception */ public function has(string $id): bool { @@ -55,12 +44,12 @@ public function has(string $id): bool /** * @return bool * - * @throws \Exception * @psalm-assert-if-true ContainerInterface $this->container + * @psalm-assert-if-false null $this->container */ private function tryContainer(): bool { - if ($this->container) { + if ($this->container !== null) { return true; } @@ -72,15 +61,13 @@ private function tryContainer(): bool $this->container = $this->package->container(); } - return (bool)$this->container; + return $this->container !== null; } /** * @param string $id * @return void * - * @throws \Exception - * * @psalm-assert ContainerInterface $this->container */ private function assertPackageBooted(string $id): void @@ -94,9 +81,9 @@ private function assertPackageBooted(string $id): void ? 'is errored' : 'is not ready yet'; - throw new class ("Error retrieving service {$id} because package {$name} {$status}.") - extends \Exception - implements ContainerExceptionInterface { + $error = "Error retrieving service {$id} because package {$name} {$status}."; + throw new class (esc_html($error)) extends \Exception implements ContainerExceptionInterface + { }; } } diff --git a/src/Container/ReadOnlyContainer.php b/src/Container/ReadOnlyContainer.php index 4dd862d..30f2693 100644 --- a/src/Container/ReadOnlyContainer.php +++ b/src/Container/ReadOnlyContainer.php @@ -13,36 +13,17 @@ */ class ReadOnlyContainer implements ContainerInterface { - /** - * @var array - */ - private $services; - - /** - * @var array - */ - private $factoryIds; + /** @var array */ + private array $services; + /** @var array */ + private array $factoryIds; + private ServiceExtensions $extensions; + /** @var ContainerInterface[] */ + private array $containers; + /** @var array */ + private array $resolvedServices = []; /** - * @var ServiceExtensions - */ - private $extensions; - - /** - * Resolved factories. - * - * @var array - */ - private $resolvedServices = []; - - /** - * @var ContainerInterface[] - */ - private $containers; - - /** - * ReadOnlyContainer constructor. - * * @param array $services * @param array $factoryIds * @param ServiceExtensions|array $extensions @@ -54,6 +35,7 @@ public function __construct( $extensions, array $containers ) { + $this->services = $services; $this->factoryIds = $factoryIds; $this->extensions = $this->configureServiceExtensions($extensions); @@ -62,7 +44,6 @@ public function __construct( /** * @param string $id - * * @return mixed */ public function get(string $id) @@ -91,15 +72,14 @@ public function get(string $id) } } - throw new class ("Service with ID {$id} not found.") - extends \Exception - implements NotFoundExceptionInterface { + $error = "Service with ID {$id} not found."; + throw new class (esc_html($error)) extends \Exception implements NotFoundExceptionInterface + { }; } /** * @param string $id - * * @return bool */ public function has(string $id): bool @@ -138,13 +118,14 @@ private function configureServiceExtensions($extensions): ServiceExtensions } if (!is_array($extensions)) { + $type = is_object($extensions) ? get_class($extensions) : gettype($extensions); throw new \TypeError( sprintf( '%s::%s(): Argument #3 ($extensions) must be of type %s|array, %s given', __CLASS__, '__construct', ServiceExtensions::class, - gettype($extensions) + esc_html($type) ) ); } diff --git a/src/Container/ServiceExtensions.php b/src/Container/ServiceExtensions.php index 63fb1f6..345d1df 100644 --- a/src/Container/ServiceExtensions.php +++ b/src/Container/ServiceExtensions.php @@ -15,10 +15,8 @@ class ServiceExtensions private const SERVICE_TYPE_CHANGED = 2; private const SERVICE_TYPE_NOT_OBJECT = 0; - /** - * @var array> - */ - protected $extensions = []; + /** @var array> */ + protected array $extensions = []; /** * @param string $type @@ -87,6 +85,9 @@ protected function resolveById(string $id, $service, Container $container) * @param Container $container * @param array $extendedClasses * @return mixed + * + * phpcs:disable Generic.Metrics.CyclomaticComplexity + * phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration */ protected function resolveByType( string $className, @@ -94,6 +95,8 @@ protected function resolveByType( Container $container, array $extendedClasses = [] ) { + // phpcs:enable Generic.Metrics.CyclomaticComplexity + // phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration $extendedClasses[] = $className; @@ -102,20 +105,28 @@ protected function resolveByType( // 1st group of extensions: targeting exact class $byClass = $this->extensions[self::typeId($className)] ?? null; - $byClass and $allCallbacks[$className] = $byClass; + if (($byClass !== null) && ($byClass !== [])) { + $allCallbacks[$className] = $byClass; + } // 2nd group of extensions: targeting parent classes - /** @var class-string $parentName */ - foreach (class_parents($service, false) ?: [] as $parentName) { + $parents = class_parents($service, false); + ($parents === false) and $parents = []; + foreach ($parents as $parentName) { $byParent = $this->extensions[self::typeId($parentName)] ?? null; - $byParent and $allCallbacks[$parentName] = $byParent; + if (($byParent !== null) && ($byParent !== [])) { + $allCallbacks[$parentName] = $byParent; + } } // 3rd group of extensions: targeting implemented interfaces - /** @var class-string $interfaceName */ - foreach (class_implements($service, false) ?: [] as $interfaceName) { + $interfaces = class_implements($service, false); + ($interfaces === false) and $interfaces = []; + foreach ($interfaces as $interfaceName) { $byInterface = $this->extensions[self::typeId($interfaceName)] ?? null; - $byInterface and $allCallbacks[$interfaceName] = $byInterface; + if (($byInterface !== null) && ($byInterface !== [])) { + $allCallbacks[$interfaceName] = $byInterface; + } } $resultType = self::SERVICE_TYPE_NOT_CHANGED; @@ -126,6 +137,7 @@ protected function resolveByType( if (($resultType === self::SERVICE_TYPE_CHANGED) && !is_a($service, $type)) { continue; } + /** @var object $service */ [$service, $resultType] = $this->extendByType($type, $service, $container, $extenders); if ($resultType === self::SERVICE_TYPE_NOT_OBJECT) { // Service is not an object anymore, let's return it. @@ -151,7 +163,7 @@ protected function resolveByType( * @param object $service * @param Container $container * @param list $extenders - * @return array{mixed, int} + * @return list{mixed, int} */ private function extendByType( string $type, diff --git a/src/Module/ExecutableModule.php b/src/Module/ExecutableModule.php index cd03b8f..8a33fd5 100644 --- a/src/Module/ExecutableModule.php +++ b/src/Module/ExecutableModule.php @@ -8,7 +8,6 @@ interface ExecutableModule extends Module { - /** * Perform actions with objects retrieved from the container. Usually, adding WordPress hooks. * Return true to signal a success, false to signal a failure. diff --git a/src/Module/ExtendingModule.php b/src/Module/ExtendingModule.php index adc1c21..a48a416 100644 --- a/src/Module/ExtendingModule.php +++ b/src/Module/ExtendingModule.php @@ -11,7 +11,6 @@ */ interface ExtendingModule extends Module { - /** * Return application services' extensions. * diff --git a/src/Module/Module.php b/src/Module/Module.php index 353236b..eb3c2a8 100644 --- a/src/Module/Module.php +++ b/src/Module/Module.php @@ -9,12 +9,10 @@ */ interface Module { - /** * Unique identifier for your Module. * * @return string */ public function id(): string; - } diff --git a/src/Module/ModuleClassNameIdTrait.php b/src/Module/ModuleClassNameIdTrait.php index aca7c05..3f63a86 100644 --- a/src/Module/ModuleClassNameIdTrait.php +++ b/src/Module/ModuleClassNameIdTrait.php @@ -4,16 +4,11 @@ namespace Inpsyde\Modularity\Module; -/** - * Trait ModuleClassNameIdTrait - * - * @package Inpsyde\Modularity\Module - */ trait ModuleClassNameIdTrait { - /** * @return string + * * @see Module::id() */ public function id(): string diff --git a/src/Module/ServiceModule.php b/src/Module/ServiceModule.php index 6bf2d77..c6ad230 100644 --- a/src/Module/ServiceModule.php +++ b/src/Module/ServiceModule.php @@ -11,7 +11,6 @@ */ interface ServiceModule extends Module { - /** * Return application services' factories. * diff --git a/src/Package.php b/src/Package.php index f75ccd9..49bf96a 100644 --- a/src/Package.php +++ b/src/Package.php @@ -22,7 +22,6 @@ class Package { /** * All the hooks fired in this class use this prefix. - * @var string */ private const HOOK_PREFIX = 'inpsyde.modularity.'; @@ -38,8 +37,6 @@ class Package * $container->has(Package::PROPERTIES); * $container->get(Package::PROPERTIES); * - * - * @var string */ public const PROPERTIES = 'properties'; @@ -142,6 +139,8 @@ class Package * * $package = Package::new(); * $package->statusIs(Package::IDLE); // true + * $package->build(); + * $package->statusIs(Package::INITIALIZED); // true * $package->boot(); * $package->statusIs(Package::BOOTED); // true * @@ -149,82 +148,47 @@ class Package public const STATUS_IDLE = 2; public const STATUS_INITIALIZED = 4; public const STATUS_MODULES_ADDED = 5; + public const STATUS_BOOTING = self::STATUS_MODULES_ADDED; public const STATUS_READY = 7; public const STATUS_BOOTED = 8; public const STATUS_FAILED = -8; - /** - * Current state of the application. - * - * @see Package::STATUS_* - * - * @var int - */ - private $status = self::STATUS_IDLE; - - /** - * Contains the progress of all modules. - * - * @see Package::moduleProgress() - * - * @var array> - */ - private $moduleStatus = [self::MODULES_ALL => []]; - - /** - * Hashmap of where keys are names of connected packages, and values are boolean, true - * if connection was successful. - * - * @see Package::connect() - * - * @var array - */ - private $connectedPackages = []; - - /** - * @var list - */ - private $executables = []; - - /** - * @var Properties - */ - private $properties; - - /** - * @var ContainerConfigurator - */ - private $containerConfigurator; - - /** - * @var bool - */ - private $built = false; - - /** - * @var bool - */ - private $hasContainer = false; - - /** - * @var \Throwable|null - */ - private $lastError = null; + private const OPERATORS = [ + '<' => '<', + '<=' => '<=', + '>' => '>', + '>=' => '>=', + '==' => '==', + '!=' => '!=', + ]; + + /** @var Package::STATUS_* */ + private int $status = self::STATUS_IDLE; + /** @var array> */ + private array $moduleStatus = [self::MODULES_ALL => []]; + /** @var array */ + private array $connectedPackages = []; + /** @var list */ + private array $executables = []; + private Properties $properties; + private ContainerConfigurator $containerConfigurator; + private bool $built = false; + private bool $hasContainer = false; + private ?\Throwable $lastError = null; /** * @param Properties $properties - * @param ContainerInterface[] $containers - * + * @param ContainerInterface ...$containers * @return Package */ - public static function new(Properties $properties, ContainerInterface ...$containers): Package + public static function new(Properties $properties, ContainerInterface ...$containers): Package { - return new self($properties, ...$containers); + return new self($properties, ...array_values($containers)); } /** * @param Properties $properties - * @param ContainerInterface[] $containers + * @param list $containers */ private function __construct(Properties $properties, ContainerInterface ...$containers) { @@ -233,7 +197,7 @@ private function __construct(Properties $properties, ContainerInterface ...$cont $this->containerConfigurator = new ContainerConfigurator($containers); $this->containerConfigurator->addService( self::PROPERTIES, - static function () use ($properties) { + static function () use ($properties): Properties { return $properties; } ); @@ -241,9 +205,7 @@ static function () use ($properties) { /** * @param Module $module - * * @return static - * @throws \Exception */ public function addModule(Module $module): Package { @@ -284,10 +246,12 @@ public function addModule(Module $module): Package /** * @param Package $package * @return bool - * @throws \Exception + * + * phpcs:disable Inpsyde.CodeQuality.FunctionLength */ public function connect(Package $package): bool { + // phpcs:enable Inpsyde.CodeQuality.FunctionLength try { if ($package === $this) { return false; @@ -373,10 +337,11 @@ public function build(): Package $this->hookName(self::ACTION_INIT), $this ); - // Changing the status here ensures we can not call this method again, and also we can not - // add new modules, because both this and `addModule()` methods check for idle status. - // For backward compatibility, adding new modules via `boot()` will still be possible, even - // if deprecated, at the condition that the container was not yet accessed at that point. + // Changing the status here ensures we can not call this method again, and also we can + // not add new modules, because both here and in `addModule()` we check for idle status. + // For backward compatibility, adding new modules via `boot()` will still be possible, + // even if deprecated, at the condition that the container was not yet accessed at that + // point. $this->progress(self::STATUS_INITIALIZED); } catch (\Throwable $throwable) { $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); @@ -390,8 +355,6 @@ public function build(): Package /** * @param Module ...$defaultModules Deprecated, use `addModule()` to add default modules. * @return bool - * - * @throws \Throwable */ public function boot(Module ...$defaultModules): bool { @@ -401,10 +364,10 @@ public function boot(Module ...$defaultModules): bool $this->doBuild(...$defaultModules); // Don't allow booting the application multiple times. - $this->assertStatus(self::STATUS_MODULES_ADDED, 'boot application', '<'); + $this->assertStatus(self::STATUS_BOOTING, 'boot application', '<'); $this->assertStatus(self::STATUS_FAILED, 'boot application', '!='); - $this->progress(self::STATUS_MODULES_ADDED); + $this->progress(self::STATUS_BOOTING); $this->doExecute(); @@ -444,7 +407,7 @@ private function doBuild(Module ...$defaultModules): void } if (!$this->built) { - array_map([$this, 'addModule'], $defaultModules); + $defaultModules and array_map([$this, 'addModule'], $defaultModules); $this->build(); return; @@ -452,11 +415,11 @@ private function doBuild(Module ...$defaultModules): void if ( !$defaultModules - || ($this->status >= self::STATUS_MODULES_ADDED) + || ($this->checkStatus(self::STATUS_INITIALIZED, '>')) || ($this->statusIs(self::STATUS_FAILED)) ) { - // if we don't have default modules, there's nothing to do, and if the status is beyond - // "modules added" or is failed, we do nothing as well and let `boot()` throw. + // If we don't have default modules, there's nothing to do, and if the status is beyond + // initialized or is failed, we do nothing as well and let `boot()` throw. return; } @@ -486,7 +449,9 @@ private function doBuild(Module ...$defaultModules): void */ private function addModuleServices(Module $module, string $status): bool { + /** @var null|array $services */ $services = null; + /** @var null|callable(string, Service|ExtendingService) $addCallback */ $addCallback = null; switch ($status) { case self::MODULE_REGISTERED: @@ -503,21 +468,16 @@ private function addModuleServices(Module $module, string $status): bool break; } - if (!$services) { + if (($services === null) || ($services === []) || ($addCallback === null)) { return false; } $ids = []; - array_walk( - $services, - static function (callable $service, string $id) use ($addCallback, &$ids) { - /** @var callable(string, Service|ExtendingService) $addCallback */ - $addCallback($id, $service); - /** @var list $ids */ - $ids[] = $id; - } - ); - /** @var list $ids */ + foreach ($services as $id => $service) { + $addCallback($id, $service); + $ids[] = $id; + } + $this->moduleProgress($module->id(), $status, $ids); return true; @@ -525,8 +485,6 @@ static function (callable $service, string $id) use ($addCallback, &$ids) { /** * @return void - * - * @throws \Throwable */ private function doExecute(): void { @@ -534,9 +492,7 @@ private function doExecute(): void $success = $executable->run($this->container()); $this->moduleProgress( $executable->id(), - $success - ? self::MODULE_EXECUTED - : self::MODULE_EXECUTION_FAILED + $success ? self::MODULE_EXECUTED : self::MODULE_EXECUTION_FAILED ); } } @@ -545,15 +501,18 @@ private function doExecute(): void * @param string $moduleId * @param string $status * @param list|null $serviceIds - * - * @return void + * @return void */ - private function moduleProgress(string $moduleId, string $status, ?array $serviceIds = null) - { + private function moduleProgress( + string $moduleId, + string $status, + ?array $serviceIds = null + ): void { + isset($this->moduleStatus[$status]) or $this->moduleStatus[$status] = []; $this->moduleStatus[$status][] = $moduleId; - if (!$serviceIds || !$this->properties->isDebug()) { + if (($serviceIds === null) || ($serviceIds === []) || !$this->properties->isDebug()) { $this->moduleStatus[self::MODULES_ALL][] = "{$moduleId} {$status}"; return; @@ -609,10 +568,9 @@ public function moduleIs(string $moduleId, string $status): bool * `inpsyde.modularity.my-plugin` anyway, so the file name is not relevant. * * @param string $suffix - * * @return string - * @see Package::name() * + * @see Package::name() */ public function hookName(string $suffix = ''): string { @@ -635,8 +593,6 @@ public function properties(): Properties /** * @return ContainerInterface - * - * @throws \Exception */ public function container(): ContainerInterface { @@ -656,27 +612,37 @@ public function name(): string /** * @param int $status + * @return bool */ - private function progress(int $status): void + public function statusIs(int $status): bool { - $this->status = $status; + return $this->checkStatus($status); } /** * @param int $status - * + * @param value-of $operator * @return bool */ - public function statusIs(int $status): bool + private function checkStatus(int $status, string $operator = '=='): bool + { + assert(isset(self::OPERATORS[$operator])); + + return version_compare((string) $this->status, (string) $status, $operator); + } + + /** + * @param Package::STATUS_* $status + */ + private function progress(int $status): void { - return $this->status === $status; + $this->status = $status; } /** * @param \Throwable $throwable * @param Package::ACTION_FAILED_* $action * @return void - * @throws \Throwable */ private function handleFailure(\Throwable $throwable, string $action): void { @@ -694,18 +660,15 @@ private function handleFailure(\Throwable $throwable, string $action): void /** * @param int $status * @param string $action - * @param string $operator - * - * @throws \Exception - * @psalm-suppress ArgumentTypeCoercion + * @param value-of $operator */ private function assertStatus(int $status, string $action, string $operator = '=='): void { - if (!version_compare((string) $this->status, (string) $status, $operator)) { + if (!$this->checkStatus($status, $operator)) { throw new \Exception( - sprintf("Can't %s at this point of application.", $action), + sprintf("Can't %s at this point of application.", esc_html($action)), 0, - $this->lastError + $this->lastError // phpcs:ignore ); } } @@ -717,7 +680,6 @@ private function assertStatus(int $status, string $action, string $operator = '= * @param string $message * @param string $function * @param string $version - * * @return void */ private function deprecatedArgument(string $message, string $function, string $version): void @@ -725,7 +687,8 @@ private function deprecatedArgument(string $message, string $function, string $v do_action('deprecated_argument_run', $function, $message, $version); if (apply_filters('deprecated_argument_trigger_error', true)) { - trigger_error($message, \E_USER_DEPRECATED); + do_action('wp_trigger_error_run', $function, $message, \E_USER_DEPRECATED); + trigger_error(esc_html($message), \E_USER_DEPRECATED); } } } diff --git a/src/Properties/BaseProperties.php b/src/Properties/BaseProperties.php index 8bbc730..9921747 100644 --- a/src/Properties/BaseProperties.php +++ b/src/Properties/BaseProperties.php @@ -6,30 +6,11 @@ class BaseProperties implements Properties { - /** - * @var null|bool - */ - protected $isDebug = null; - - /** - * @var string - */ - protected $baseName; - - /** - * @var string - */ - protected $basePath; - - /** - * @var string|null - */ - protected $baseUrl; - - /** - * @var array - */ - protected $properties; + protected ?bool $isDebug = null; + protected string $baseName; + protected string $basePath; + protected ?string $baseUrl; + protected array $properties; /** * @param string $baseName @@ -43,10 +24,11 @@ protected function __construct( string $baseUrl = null, array $properties = [] ) { + $baseName = $this->sanitizeBaseName($baseName); - $basePath = (string) trailingslashit($basePath); - if ($baseUrl) { - $baseUrl = (string) trailingslashit($baseUrl); + $basePath = trailingslashit($basePath); + if ($baseUrl !== null) { + $baseUrl = trailingslashit($baseUrl); } $this->baseName = $baseName; @@ -57,8 +39,7 @@ protected function __construct( /** * @param string $name - * - * @return string + * @return lowercase-string */ protected function sanitizeBaseName(string $name): string { @@ -162,7 +143,7 @@ public function requiresWp(): ?string { $value = $this->get(self::PROP_REQUIRES_WP); - return $value && is_string($value) ? $value : null; + return (($value !== '') && is_string($value)) ? $value : null; } /** @@ -172,7 +153,7 @@ public function requiresPhp(): ?string { $value = $this->get(self::PROP_REQUIRES_PHP); - return $value && is_string($value) ? $value : null; + return (($value !== '') && is_string($value)) ? $value : null; } /** @@ -185,7 +166,7 @@ public function tags(): array /** * @param string $key - * @param null $default + * @param mixed $default * @return mixed */ public function get(string $key, $default = null) @@ -209,6 +190,7 @@ public function has(string $key): bool public function isDebug(): bool { if ($this->isDebug === null) { + /** @psalm-suppress TypeDoesNotContainType */ $this->isDebug = defined('WP_DEBUG') && WP_DEBUG; } diff --git a/src/Properties/LibraryProperties.php b/src/Properties/LibraryProperties.php index 77539dd..22fc079 100644 --- a/src/Properties/LibraryProperties.php +++ b/src/Properties/LibraryProperties.php @@ -4,18 +4,9 @@ namespace Inpsyde\Modularity\Properties; -/** - * Class LibraryProperties - * - * @package Inpsyde\Modularity\Properties - */ class LibraryProperties extends BaseProperties { - /** - * Allowed configuration in composer.json "extra.modularity". - * - * @var array - */ + /** Allowed configuration in composer.json "extra.modularity" */ public const EXTRA_KEYS = [ self::PROP_DOMAIN_PATH, self::PROP_NAME, @@ -28,16 +19,17 @@ class LibraryProperties extends BaseProperties /** * @param string $composerJsonFile * @param string|null $baseUrl - * * @return LibraryProperties * - * @throws \Exception - * @psalm-suppress MixedArrayAccess + * phpcs:disable Generic.Metrics.CyclomaticComplexity */ public static function new(string $composerJsonFile, ?string $baseUrl = null): LibraryProperties { + // phpcs:enable Generic.Metrics.CyclomaticComplexity if (!\is_file($composerJsonFile) || !\is_readable($composerJsonFile)) { - throw new \Exception("File {$composerJsonFile} does not exist or is not readable."); + throw new \Exception( + esc_html("File {$composerJsonFile} does not exist or is not readable.") + ); } $content = (string) file_get_contents($composerJsonFile); @@ -49,14 +41,18 @@ public static function new(string $composerJsonFile, ?string $baseUrl = null): L $properties[self::PROP_TAGS] = $composerJsonData['keywords'] ?? []; $authors = $composerJsonData['authors'] ?? []; + is_array($authors) or $authors = []; $names = []; - foreach ((array) $authors as $author) { - $name = $author['name'] ?? null; - if ($name && is_string($name)) { + foreach ($authors as $author) { + if (!is_array($author)) { + continue; + } + $name = $author['name'] ?? ''; + if (($name !== '') && is_string($name)) { $names[] = $name; } - $url = $author['homepage'] ?? null; - if ($url && !$properties['authorUri'] && is_string($url)) { + $url = $author['homepage'] ?? ''; + if (($url !== '') && ($properties[self::PROP_AUTHOR_URI] === '') && is_string($url)) { $properties[self::PROP_AUTHOR_URI] = $url; } } @@ -66,6 +62,7 @@ public static function new(string $composerJsonFile, ?string $baseUrl = null): L // Custom settings which can be stored in composer.json "extra.modularity" $extra = $composerJsonData['extra']['modularity'] ?? []; + is_array($extra) or $extra = []; foreach (self::EXTRA_KEYS as $key) { $properties[$key] = $extra[$key] ?? ''; } @@ -74,36 +71,45 @@ public static function new(string $composerJsonFile, ?string $baseUrl = null): L $properties[self::PROP_REQUIRES_PHP] = self::extractPhpVersion($composerJsonData); // composer.json might have "version" in root - $version = $composerJsonData['version'] ?? null; - if ($version && is_string($version)) { + $version = $composerJsonData['version'] ?? ''; + if (($version !== '') && is_string($version)) { $properties[self::PROP_VERSION] = $version; } [$baseName, $name] = static::buildNames($composerJsonData); $basePath = dirname($composerJsonFile); - if (empty($properties[self::PROP_NAME])) { + if (($properties[self::PROP_NAME] === '') || !is_string($properties[self::PROP_NAME])) { $properties[self::PROP_NAME] = $name; } - return new self( - $baseName, - $basePath, - $baseUrl, - $properties - ); + return new self($baseName, $basePath, $baseUrl, $properties); + } + + /** + * @param string $url + * @return static + */ + public function withBaseUrl(string $url): LibraryProperties + { + if ($this->baseUrl !== null) { + throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__)); + } + + $this->baseUrl = trailingslashit($url); + + return $this; } /** * @param array $composerJsonData - * - * @return array{string, string} + * @return list{string, string} */ private static function buildNames(array $composerJsonData): array { $composerName = (string) ($composerJsonData['name'] ?? ''); $packageNamePieces = explode('/', $composerName, 2); $basename = implode('-', $packageNamePieces); - // "inpsyde/foo-bar-baz" => "Inpsyde Foo Bar Baz" + // From "inpsyde/foo-bar-baz" to "Inpsyde Foo Bar Baz" $name = mb_convert_case( str_replace(['-', '_', '.'], ' ', implode(' ', $packageNamePieces)), MB_CASE_TITLE @@ -124,86 +130,73 @@ private static function buildNames(array $composerJsonData): array * * @param array $composerData * @param string $key + * @return string * - * @return string|null + * phpcs:disable Generic.Metrics.CyclomaticComplexity */ - private static function extractPhpVersion(array $composerData, string $key = 'require'): ?string + private static function extractPhpVersion(array $composerData, string $key = 'require'): string { - $nextKey = ($key === 'require') - ? 'require-dev' - : null; - $base = (array) ($composerData[$key] ?? []); - $requirement = $base['php'] ?? null; - $version = ($requirement && is_string($requirement)) - ? trim($requirement) - : null; - if (!$version) { - return $nextKey + // phpcs:enable Generic.Metrics.CyclomaticComplexity + $nextKey = ($key === 'require') ? 'require-dev' : null; + $base = $composerData[$key] ?? null; + $requirement = is_array($base) ? ($base['php'] ?? '') : ''; + $version = (($requirement !== '') && is_string($requirement)) ? trim($requirement) : ''; + if ($version === '') { + return ($nextKey !== null) ? static::extractPhpVersion($composerData, $nextKey) - : null; + : ''; } - static $matcher; - $matcher or $matcher = static function (string $version): ?string { - $version = trim($version); - if (!$version) { - return null; - } - - // versions range like `>= 7.2.4 < 8` - if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) { - return trim($matches[1], " \t\n\r\0\x0B."); - } - - // aliases like `dev-src#abcde as 7.4` - if (preg_match('{as\s*([\s0-9\.]+)}', $version, $matches)) { - return trim($matches[1], " \t\n\r\0\x0B."); - } - - // Basic requirements like 7.2, >=7.2, ^7.2, ~7.2 - if (preg_match('{^(?:[>=\s~\^]+)?([0-9\.]+)}', $version, $matches)) { - return trim($matches[1], " \t\n\r\0\x0B."); - } - - return null; - }; - // support for simpler requirements like `7.3`, `>=7.4` or alternative like `5.6 || >=7` $alternatives = explode('||', $version); + /** @var non-empty-string|null $found */ $found = null; foreach ($alternatives as $alternative) { - /** @var callable(string):?string $matcher */ - $itemFound = $matcher($alternative); - if ($itemFound && (!$found || version_compare($itemFound, $found, '<'))) { + $itemFound = static::parseVersion($alternative); + if ( + ($itemFound !== '') + && (($found === null) || version_compare($itemFound, $found, '<')) + ) { $found = $itemFound; } } - if ($found) { + if ($found !== null) { return $found; } - return $nextKey + return ($nextKey !== null) ? static::extractPhpVersion($composerData, $nextKey) - : null; + : ''; } /** - * @param string $url - * - * @return static - * - * @throws \Exception + * @param string $version + * @return string */ - public function withBaseUrl(string $url): LibraryProperties + private static function parseVersion(string $version): string { - if ($this->baseUrl !== null) { - throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__)); + $version = trim($version); + if ($version === '') { + return ''; } - $this->baseUrl = trailingslashit($url); + // versions range like `>= 7.2.4 < 8` + if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } - return $this; + // aliases like `dev-src#abcde as 7.4` + if (preg_match('{as\s*([\s0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + // Basic requirements like 7.2, >=7.2, ^7.2, ~7.2 + if (preg_match('{^(?:[>=\s~\^]+)?([0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + return ''; } } diff --git a/src/Properties/PluginProperties.php b/src/Properties/PluginProperties.php index 050abd3..0aacd60 100644 --- a/src/Properties/PluginProperties.php +++ b/src/Properties/PluginProperties.php @@ -4,25 +4,14 @@ namespace Inpsyde\Modularity\Properties; -/** - * Class PluginProperties - * - * @package Inpsyde\Modularity\Properties - * - * @psalm-suppress PossiblyFalseArgument, InvalidArgument - */ class PluginProperties extends BaseProperties { - /** - * Custom properties for Plugins. - */ + // Custom properties for Plugins public const PROP_NETWORK = 'network'; public const PROP_REQUIRES_PLUGINS = 'requiresPlugins'; + /** - * Available methods of Properties::__call() - * from plugin headers. - * - * @link https://developer.wordpress.org/reference/functions/get_plugin_data/ + * @see https://developer.wordpress.org/reference/functions/get_plugin_data/ */ protected const HEADERS = [ self::PROP_AUTHOR => 'Author', @@ -41,34 +30,14 @@ class PluginProperties extends BaseProperties self::PROP_REQUIRES_PLUGINS => 'RequiresPlugins', ]; - /** - * @var string - */ - private $pluginMainFile; - - /** - * @var string - */ - private $pluginBaseName; - - /** - * @var bool|null - */ - protected $isMu; - - /** - * @var bool|null - */ - protected $isActive; - - /** - * @var bool|null - */ - protected $isNetworkActive; + private string $pluginMainFile; + private string $pluginBaseName; + protected ?bool $isMu = null; + protected ?bool $isActive = null; + protected ?bool $isNetworkActive = null; /** * @param string $pluginMainFile - * * @return PluginProperties */ public static function new(string $pluginMainFile): PluginProperties @@ -77,8 +46,6 @@ public static function new(string $pluginMainFile): PluginProperties } /** - * PluginProperties constructor. - * * @param string $pluginMainFile */ protected function __construct(string $pluginMainFile) @@ -87,9 +54,10 @@ protected function __construct(string $pluginMainFile) require_once ABSPATH . 'wp-admin/includes/plugin.php'; } - // $markup = false, to avoid an incorrect early wptexturize call. Also we probably don't want HTML here anyway + // $markup = false, to avoid an incorrect early wptexturize call. + // We also probably don't want HTML here anyway // @see https://core.trac.wordpress.org/ticket/49965 - $pluginData = get_plugin_data($pluginMainFile, false); + $pluginData = (array) get_plugin_data($pluginMainFile, false); $properties = Properties::DEFAULT_PROPERTIES; // Map pluginData to internal structure. @@ -123,8 +91,6 @@ public function pluginMainFile(): string /** * @return bool - * - * @psalm-suppress PossiblyFalseArgument */ public function network(): bool { @@ -177,10 +143,6 @@ public function isNetworkActive(): bool public function isMuPlugin(): bool { if ($this->isMu === null) { - /** - * @psalm-suppress UndefinedConstant - * @psalm-suppress MixedArgument - */ $muPluginDir = wp_normalize_path(WPMU_PLUGIN_DIR); $this->isMu = strpos($this->pluginMainFile, $muPluginDir) === 0; } diff --git a/src/Properties/Properties.php b/src/Properties/Properties.php index 0efed8d..40c9325 100644 --- a/src/Properties/Properties.php +++ b/src/Properties/Properties.php @@ -17,9 +17,7 @@ interface Properties public const PROP_REQUIRES_WP = 'requiresWp'; public const PROP_REQUIRES_PHP = 'requiresPhp'; public const PROP_TAGS = 'tags'; - /** - * @var array - */ + public const DEFAULT_PROPERTIES = [ self::PROP_AUTHOR => '', self::PROP_AUTHOR_URI => '', @@ -36,15 +34,13 @@ interface Properties /** * @param string $key - * @param null $default - * + * @param mixed $default * @return mixed */ public function get(string $key, $default = null); /** * @param string $key - * * @return bool */ public function has(string $key): bool; @@ -103,6 +99,7 @@ public function name(): string; /** * The home page of the plugin, theme or library. + * * @return string */ public function uri(): string; @@ -130,10 +127,10 @@ public function requiresPhp(): ?string; * Optional. Currently, only available for Theme and Library. * Plugins do not have support for "tags"/"keywords" in header. * - * @link https://developer.wordpress.org/reference/classes/wp_theme/#properties - * @link https://getcomposer.org/doc/04-schema.md#keywords - * * @return array + * + * @see https://developer.wordpress.org/reference/classes/wp_theme/#properties + * @see https://getcomposer.org/doc/04-schema.md#keywords */ public function tags(): array; } diff --git a/src/Properties/ThemeProperties.php b/src/Properties/ThemeProperties.php index b620609..c311dd9 100644 --- a/src/Properties/ThemeProperties.php +++ b/src/Properties/ThemeProperties.php @@ -4,26 +4,12 @@ namespace Inpsyde\Modularity\Properties; -/** - * Class ThemeProperties - * - * @package Inpsyde\Modularity\Properties - * - * @psalm-suppress PossiblyFalseArgument, InvalidArgument - */ class ThemeProperties extends BaseProperties { - /** - * Additional properties specific for themes. - */ public const PROP_STATUS = 'status'; public const PROP_TEMPLATE = 'template'; - /** - * Available methods of Properties::__call() - * from theme headers. - * - * @link https://developer.wordpress.org/reference/classes/wp_theme/ - */ + + /** @see https://developer.wordpress.org/reference/classes/wp_theme/ */ protected const HEADERS = [ self::PROP_AUTHOR => 'Author', self::PROP_AUTHOR_URI => 'AuthorURI', @@ -53,8 +39,6 @@ public static function new(string $themeDirectory): ThemeProperties } /** - * ThemeProperties constructor. - * * @param string $themeDirectory */ protected function __construct(string $themeDirectory) @@ -67,13 +51,15 @@ protected function __construct(string $themeDirectory) $properties = Properties::DEFAULT_PROPERTIES; foreach (self::HEADERS as $key => $themeKey) { - /** @psalm-suppress DocblockTypeContradiction */ - $properties[$key] = $theme->get($themeKey) ?? ''; + $property = $theme->get($themeKey); + if (is_string($property) || is_array($property)) { + $properties[$key] = $property; + } } $baseName = $theme->get_stylesheet(); $basePath = $theme->get_stylesheet_directory(); - $baseUrl = (string) trailingslashit($theme->get_stylesheet_directory_uri()); + $baseUrl = trailingslashit($theme->get_stylesheet_directory_uri()); parent::__construct( $baseName, @@ -84,8 +70,6 @@ protected function __construct(string $themeDirectory) } /** - * If the theme is published. - * * @return string */ public function status(): string @@ -93,6 +77,9 @@ public function status(): string return (string) $this->get(self::PROP_STATUS); } + /** + * @return string + */ public function template(): string { return (string) $this->get(self::PROP_TEMPLATE); @@ -120,7 +107,7 @@ public function isCurrentTheme(): bool public function parentThemeProperties(): ?ThemeProperties { $template = $this->template(); - if (!$template) { + if ($template === '') { return null; } diff --git a/tests/boot.php b/tests/boot.php index f9477ec..e3ad92d 100644 --- a/tests/boot.php +++ b/tests/boot.php @@ -1,5 +1,9 @@ allows('id')->andReturn($id); - if (in_array(ServiceModule::class, $interfaces, true) ) { + if (in_array(ServiceModule::class, $interfaces, true)) { $stub->allows('services')->byDefault()->andReturn([]); } - if (in_array(FactoryModule::class, $interfaces, true) ) { + if (in_array(FactoryModule::class, $interfaces, true)) { $stub->allows('factories')->byDefault()->andReturn([]); } - if (in_array(ExtendingModule::class, $interfaces, true) ) { + if (in_array(ExtendingModule::class, $interfaces, true)) { $stub->allows('extensions')->byDefault()->andReturn([]); } - if (in_array(ExecutableModule::class, $interfaces, true) ) { + if (in_array(ExecutableModule::class, $interfaces, true)) { $stub->allows('run')->byDefault()->andReturn(false); } return $stub; } + /** + * @param string $suffix + * @param bool $debug + * @return Package + */ + protected function stubSimplePackage(string $suffix, bool $debug = false): Package + { + $module = $this->stubModule("module_{$suffix}", ServiceModule::class); + $module->expects('services')->andReturn($this->stubServices("service_{$suffix}")); + $properties = $this->stubProperties("package_{$suffix}", $debug); + + return Package::new($properties)->addModule($module); + } + /** * @param string ...$ids * @return array @@ -103,7 +117,7 @@ protected function stubServices(string ...$ids): array { $services = []; foreach ($ids as $id) { - $services[$id] = static function () use ($id) { + $services[$id] = static function () use ($id): \ArrayObject { return new \ArrayObject(['id' => $id]); }; } @@ -111,11 +125,50 @@ protected function stubServices(string ...$ids): array return $services; } + /** + * @param string ...$ids + * @return ContainerInterface + * + * phpcs:disable Inpsyde.CodeQuality.NestingLevel + */ + protected function stubContainer(string ...$ids): ContainerInterface + { + // phpcs:enable Inpsyde.CodeQuality.NestingLevel + return new class ($this->stubServices(...$ids)) implements ContainerInterface + { + /** @var array */ + private array $services; // phpcs:ignore + + /** @param array $services */ + public function __construct(array $services) + { + $this->services = $services; + } + + /** @return mixed */ + public function get(string $id) + { + if (!isset($this->services[$id])) { + throw new \Exception("Service {$id} not found."); + } + + return $this->services[$id]($this); + } + + public function has(string $id): bool + { + return isset($this->services[$id]); + } + }; + } + /** * @return void */ protected function ignoreDeprecations(): void { + \Brain\Monkey\Actions\expectDone('wp_trigger_error_run')->atLeast()->once(); + $this->currentErrorReporting = error_reporting(); error_reporting($this->currentErrorReporting & ~\E_DEPRECATED & ~\E_USER_DEPRECATED); } diff --git a/tests/unit/Container/ContainerConfiguratorTest.php b/tests/unit/Container/ContainerConfiguratorTest.php index f0a8c09..68a5122 100644 --- a/tests/unit/Container/ContainerConfiguratorTest.php +++ b/tests/unit/Container/ContainerConfiguratorTest.php @@ -38,6 +38,7 @@ public function testAddHasService(): void $testee->addService( $expectedKey, + /** @return mixed */ static function () use ($expectedValue) { return $expectedValue; } @@ -61,7 +62,8 @@ public function testAddHasFactory(): void $testee->addFactory( $expectedKey, - function () use ($expectedValue) { + /** @return mixed */ + static function () use ($expectedValue) { return $expectedValue; } ); @@ -72,20 +74,20 @@ function () use ($expectedValue) { /** * @test */ - public function testServiceOverride() + public function testServiceOverride(): void { $expectedKey = 'key'; $testee = new ContainerConfigurator(); $testee->addService( $expectedKey, - function () { + static function (): \DateTime { return new \DateTime(); } ); $testee->addService( $expectedKey, - function () { + static function (): \DateTimeImmutable { return new \DateTimeImmutable(); } ); @@ -98,20 +100,20 @@ function () { /** * @test */ - public function testFactoryOverride() + public function testFactoryOverride(): void { $expectedKey = 'key'; $testee = new ContainerConfigurator(); $testee->addFactory( $expectedKey, - function () { + static function (): \DateTime { return new \DateTime(); } ); $testee->addFactory( $expectedKey, - function () { + static function (): \DateTimeImmutable { return new \DateTimeImmutable(); } ); @@ -124,20 +126,20 @@ function () { /** * @test */ - public function testFactoryOverridesService() + public function testFactoryOverridesService(): void { $expectedKey = 'key'; $testee = new ContainerConfigurator(); $testee->addService( $expectedKey, - function () { + static function (): \DateTime { return new \DateTime(); } ); $testee->addFactory( $expectedKey, - function () { + static function (): \DateTimeImmutable { return new \DateTimeImmutable(); } ); @@ -153,26 +155,25 @@ function () { $secondResult, 'Container should return new instances after overriding the initial service' ); - } /** * @test */ - public function testServiceOverridesFactory() + public function testServiceOverridesFactory(): void { $expectedKey = 'key'; $testee = new ContainerConfigurator(); $testee->addFactory( $expectedKey, - function () { + static function (): \DateTime { return new \DateTime(); } ); $testee->addService( $expectedKey, - function () { + static function (): \DateTimeImmutable { return new \DateTimeImmutable(); } ); @@ -205,32 +206,7 @@ public function testHasServiceNotFound(): void public function testHasServiceInChildContainer(): void { $expectedKey = 'key'; - $expectedValue = new \stdClass(); - - $childContainer = new class($expectedKey, $expectedValue) implements ContainerInterface { - private $data = []; - - public function __construct(string $key, object $value) - { - $this->data[$key] = function () use ($value) { - return $value; - }; - } - - public function get(string $id) - { - if (!$this->has($id)) { - return null; - } - - return $this->data[$id](); - } - - public function has(string $id): bool - { - return array_key_exists($id, $this->data); - } - }; + $childContainer = $this->stubContainer($expectedKey); $testee = new ContainerConfigurator(); $testee->addContainer($childContainer); @@ -247,14 +223,18 @@ public function testExtensionById(): void $expectedKey = 'key'; $expected = 'expectedValue'; - $expectedOriginalValue = new class { + + $expectedOriginalValue = new class + { public function __toString() { return 'original'; } }; - $expectedExtendedValue = new class($expected) { - private $expected; + + $expectedExtendedValue = new class ($expected) + { + private string $expected; public function __construct(string $expected) { @@ -269,14 +249,22 @@ public function __toString() $testee->addService( $expectedKey, - function () use ($expectedOriginalValue) { + /** + * @param mixed $previous + * @return mixed + */ + static function () use ($expectedOriginalValue) { return $expectedOriginalValue; } ); $testee->addExtension( $expectedKey, - function ($previous) use ($expectedOriginalValue, $expectedExtendedValue) { + /** + * @param mixed $previous + * @return mixed + */ + static function ($previous) use ($expectedOriginalValue, $expectedExtendedValue) { static::assertSame($expectedOriginalValue, $previous); return $expectedExtendedValue; @@ -296,7 +284,7 @@ public function testExtensionByType(): void $string = 'Test'; $array = ['test' => 'Test']; $iterator = new \ArrayIterator($array); - $object = (object)$array; + $object = (object) $array; $int = 0; $configurator = new ContainerConfigurator(); @@ -319,7 +307,7 @@ public function has(string $id): bool $configurator->addExtension( '@instanceof', - function (\ArrayIterator $object): array { + static function (\ArrayIterator $object): array { $array = $object->getArrayCopy(); $array['works'] = 'Works!'; @@ -329,42 +317,42 @@ function (\ArrayIterator $object): array { $configurator->addExtension( '@instanceof', - function (): string { + static function (): string { throw new \Error('Failed!'); } ); // Invalid code does not break resolution $configurator->addExtension( '@instanceof', - function (): array { + static function (): array { throw new \Error('Failed!'); } ); // Undefined classes are ignored $configurator->addExtension( '@instanceof', - function (): array { + static function (): array { throw new \Error('Failed!'); } ); // This is fine, but we don't expect it running because there are no stdClass in services $configurator->addExtension( '@instanceof', - function (\stdClass $object): \stdClass { + static function (\stdClass $object): \stdClass { $array = get_object_vars($object); $array['works'] = 'Works!'; - return (object)$array; + return (object) $array; } ); $configurator->addExtension( '@instanceof', - function (): bool { + static function (): bool { throw new \Error('Failed!'); } ); $configurator->addExtension( '@instanceof', - function (): int { + static function (): int { throw new \Error('Failed!'); } ); @@ -378,7 +366,7 @@ function (): int { static::assertSame( ['test' => 'Test', 'works' => 'Works!'], - (array)$container->get('object') + (array) $container->get('object') ); static::assertSame('Test', $container->get('string')); @@ -389,17 +377,14 @@ function (): int { /** * @test * @runInSeparateProcess - * - * @noinspection PhpUndefinedClassInspection */ public function testExtensionByTypeNoInfiniteRecursion(): void { // We can't declare classes inside a class, but we can eval it. $php = <<<'PHP' -class A {} -class B extends A {} -PHP; - + class A {} + class B extends A {} + PHP; eval($php); $called = []; @@ -432,11 +417,11 @@ static function () use (&$called): \B { * @test * @runInSeparateProcess * - * @noinspection PhpUndefinedClassInspection - * @noinspection PhpIncompatibleReturnTypeInspection + * phpcs:disable Inpsyde.CodeQuality.NestingLevel */ public function testExtensionByTypeNested(): void { + // phpcs:enable Inpsyde.CodeQuality.NestingLevel $logs = []; $log = static function (object $object, int ...$nums) use (&$logs): object { foreach ($nums as $num) { @@ -449,77 +434,78 @@ public function testExtensionByTypeNested(): void }; $configurator = new ContainerConfigurator(); - $configurator->addService('test', function () { + $configurator->addService('test', static function (): \ArrayObject { return new \ArrayObject(); }); // We can't declare classes inside a class, but we can eval it. $php = <<<'PHP' -class A {} -class B extends A {} -class C {} -class D {}; -class E extends D {}; -PHP; + class A {} + class B extends A {} + class C {} + class D {}; + class E extends D {}; + PHP; eval($php); $configurator->addExtension( - '@instanceof', static function (\D $o) use (&$log): \E { + '@instanceof', + static function (\D $object) use (&$log): \E { return $log(new \E(), 6, 9); } ); $configurator->addExtension( '@instanceof', - static function (\A $o) use (&$log): \A { - return $log($o, -1); // we never expect this to run + static function (\A $object) use (&$log): \A { + return $log($object, -1); // we never expect this to run } ); $configurator->addExtension( '@instanceof', - static function (\ArrayAccess $o) use (&$log): \ArrayAccess { - return $log($o, 2); + static function (\ArrayAccess $object) use (&$log): \ArrayAccess { + return $log($object, 2); } ); $configurator->addExtension( "@instanceof", - static function (\B $o) use (&$log): \C { + static function (\B $object) use (&$log): \C { return $log(new \C(), 4); } ); $configurator->addExtension( 'test', - static function (object $o) use ($log): object { - return $log($o, 0); + static function (object $object) use ($log): object { + return $log($object, 0); } ); $configurator->addExtension( '@instanceof', - static function (\ArrayObject $o) use (&$log): \ArrayObject { - return $log($o, 1); + static function (\ArrayObject $object) use (&$log): \ArrayObject { + return $log($object, 1); } ); $configurator->addExtension( '@instanceof', - static function (\C $o) use (&$log): \D { + static function (\C $object) use (&$log): \D { return $log(new \D(), 5); } ); $configurator->addExtension( '@instanceof', - static function (\ArrayAccess $o) use (&$log): \B { + static function (\ArrayAccess $object) use (&$log): \B { return $log(new \B(), 3); } ); $configurator->addExtension( "@instanceof", - static function (\E $o) use (&$log): \E { - return $log($o, 8); + static function (\E $object) use (&$log): \E { + return $log($object, 8); } ); $configurator->addExtension( "@instanceof", - static function (\D $o) use (&$log): \D { - return $log($o, 7, 10); + static function (\D $object) use (&$log): \D { + return $log($object, 7, 10); } ); @@ -538,8 +524,9 @@ public function testCustomContainer(): void $expectedId = 'expected-id'; $expectedValue = new \stdClass(); - $childContainer = new class($expectedId, $expectedValue) implements ContainerInterface { - private $values; + $childContainer = new class ($expectedId, $expectedValue) implements ContainerInterface + { + private array $values = []; public function __construct(string $expectedId, object $expectedValue) { diff --git a/tests/unit/Container/PackageProxyContainerTest.php b/tests/unit/Container/PackageProxyContainerTest.php index 3ce1ab2..bcef09f 100644 --- a/tests/unit/Container/PackageProxyContainerTest.php +++ b/tests/unit/Container/PackageProxyContainerTest.php @@ -16,7 +16,7 @@ class PackageProxyContainerTest extends TestCase */ public function testAccessingContainerEarlyThrows(): void { - $package = Package::new($this->mockProperties()); + $package = Package::new($this->stubProperties()); $container = new PackageProxyContainer($package); static::assertFalse($container->has('test')); @@ -30,7 +30,7 @@ public function testAccessingContainerEarlyThrows(): void */ public function testAccessingFailedPackageEarlyThrows(): void { - $package = Package::new($this->mockProperties()); + $package = Package::new($this->stubProperties()); Monkey\Actions\expectDone($package->hookName(Package::ACTION_INIT)) ->once() diff --git a/tests/unit/Container/ReadOnlyContainerTest.php b/tests/unit/Container/ReadOnlyContainerTest.php index 4c77837..f393cc7 100644 --- a/tests/unit/Container/ReadOnlyContainerTest.php +++ b/tests/unit/Container/ReadOnlyContainerTest.php @@ -16,7 +16,7 @@ class ReadOnlyContainerTest extends TestCase */ public function testBasic(): void { - $testee = $this->createContainer(); + $testee = $this->factoryContainer(); static::assertInstanceOf(ContainerInterface::class, $testee); static::assertFalse($testee->has('unknown')); @@ -29,20 +29,22 @@ public function testGetUnknown(): void { static::expectException(\Exception::class); - $testee = $this->createContainer(); + $testee = $this->factoryContainer(); $testee->get('unknown'); } /** * @test - * * @dataProvider provideServices + * + * @param mixed $expected + * @param callable $service */ public function testHasGetService($expected, callable $service): void { $expectedId = 'service'; $services = [$expectedId => $service]; - $testee = $this->createContainer($services); + $testee = $this->factoryContainer($services); // check in Services static::assertTrue($testee->has($expectedId)); @@ -52,12 +54,15 @@ public function testHasGetService($expected, callable $service): void static::assertTrue($testee->has($expectedId)); } - public function provideServices(): \Generator + /** + * @return \Generator + */ + public static function provideServices(): \Generator { $service = new \stdClass(); yield 'object service' => [ $service, - function () use ($service) { + static function () use ($service): object { return $service; }, ]; @@ -65,7 +70,7 @@ function () use ($service) { $service = 'foo'; yield 'string service' => [ $service, - function () use ($service) { + static function () use ($service): string { return $service; }, ]; @@ -73,7 +78,7 @@ function () use ($service) { $service = ['foo', 'bar']; yield 'array service' => [ $service, - function () use ($service) { + static function () use ($service): array { return $service; }, ]; @@ -84,11 +89,12 @@ function () use ($service) { */ public function testHasGetServiceFromChildContainer(): void { - $expectedServiceKey = 'service'; + $expectedKey = 'service'; $expectedValue = new \stdClass(); - $childContainer = new class($expectedServiceKey, $expectedValue) implements ContainerInterface { - private $data = []; + $childContainer = new class ($expectedKey, $expectedValue) implements ContainerInterface + { + private array $data = []; public function __construct(string $key, \stdClass $value) { @@ -106,28 +112,32 @@ public function has(string $id): bool } }; - $testee = $this->createContainer([], [], [$childContainer]); + $testee = $this->factoryContainer([], [], [$childContainer]); // check in child Container - static::assertTrue($testee->has($expectedServiceKey)); + static::assertTrue($testee->has($expectedKey)); // resolve Service - static::assertSame($expectedValue, $testee->get($expectedServiceKey)); + static::assertSame($expectedValue, $testee->get($expectedKey)); // check in resolved Services - static::assertTrue($testee->has($expectedServiceKey)); + static::assertTrue($testee->has($expectedKey)); } /** * @test + * + * phpcs:disable Inpsyde.CodeQuality.NestingLevel */ public function testFactoriesAndServices(): void { + // phpcs:enable Inpsyde.CodeQuality.NestingLevel $expectedServiceKey = 'service'; $expectedFactoryKey = 'factory'; $services = [ - $expectedServiceKey => function () { - return new class { - protected $counter = 0; + $expectedServiceKey => function (): object { + return new class + { + protected int $counter = 0; public function count(): int { @@ -137,9 +147,10 @@ public function count(): int } }; }, - $expectedFactoryKey => function () { - return new class { - protected $counter = 0; + $expectedFactoryKey => function (): object { + return new class + { + protected int $counter = 0; public function count(): int { @@ -152,7 +163,7 @@ public function count(): int ]; $factoryIds = [$expectedFactoryKey => true]; - $testee = $this->createContainer($services, $factoryIds); + $testee = $this->factoryContainer($services, $factoryIds); // Services are cached and same instance is returned. static::assertSame(1, $testee->get($expectedServiceKey)->count()); @@ -200,10 +211,9 @@ public function testServiceExtensionsBackwardCompatibilityBreaksOnWrongType(): v * @param array $services * @param array $factoryIds * @param array $containers - * * @return Container */ - private function createContainer( + private function factoryContainer( array $services = [], array $factoryIds = [], array $containers = [] diff --git a/tests/unit/Container/ServiceExtensionsTest.php b/tests/unit/Container/ServiceExtensionsTest.php index 1a8e95e..85e2210 100644 --- a/tests/unit/Container/ServiceExtensionsTest.php +++ b/tests/unit/Container/ServiceExtensionsTest.php @@ -6,8 +6,6 @@ use Inpsyde\Modularity\Container\ServiceExtensions; use Inpsyde\Modularity\Tests\TestCase; -use Psr\Container\ContainerInterface; -use Psr\Container\NotFoundExceptionInterface; class ServiceExtensionsTest extends TestCase { @@ -74,25 +72,4 @@ static function (object $thing): object { static::assertTrue($serviceExtensions->has(ServiceExtensions::typeId(\stdClass::class))); static::assertSame($expected, $thing->count); } - - /** - * @return ContainerInterface - */ - private function stubContainer(): ContainerInterface - { - return new class implements ContainerInterface - { - public function get(string $id) - { - throw new class () extends \Exception implements NotFoundExceptionInterface - { - }; - } - - public function has(string $id): bool - { - return false; - } - }; - } } diff --git a/tests/unit/Module/ModuleClassNameIdTraitTest.php b/tests/unit/Module/ModuleClassNameIdTraitTest.php index b58739c..834665b 100644 --- a/tests/unit/Module/ModuleClassNameIdTraitTest.php +++ b/tests/unit/Module/ModuleClassNameIdTraitTest.php @@ -9,14 +9,13 @@ class ModuleClassNameIdTraitTest extends TestCase { - /** * @test */ public function testIdMatchesClassName(): void { - $module = new class implements Modularity\Module\Module { - + $module = new class implements Modularity\Module\Module + { use Modularity\Module\ModuleClassNameIdTrait; }; diff --git a/tests/unit/PackageTest.php b/tests/unit/PackageTest.php index 3f810b5..45dd174 100644 --- a/tests/unit/PackageTest.php +++ b/tests/unit/PackageTest.php @@ -22,7 +22,7 @@ class PackageTest extends TestCase public function testBasic(): void { $expectedName = 'foo'; - $propertiesStub = $this->mockProperties($expectedName); + $propertiesStub = $this->stubProperties($expectedName); $package = Package::new($propertiesStub); @@ -36,16 +36,16 @@ public function testBasic(): void } /** + * @test + * @dataProvider provideHookNameSuffix + * * @param string $suffix * @param string $baseName * @param string $expectedHookName - * - * @test - * @dataProvider provideHookNameSuffix */ public function testHookName(string $suffix, string $baseName, string $expectedHookName): void { - $propertiesStub = $this->mockProperties($baseName); + $propertiesStub = $this->stubProperties($baseName); $package = Package::new($propertiesStub); static::assertSame($expectedHookName, $package->hookName($suffix)); } @@ -53,7 +53,7 @@ public function testHookName(string $suffix, string $baseName, string $expectedH /** * @return \Generator */ - public function provideHookNameSuffix(): \Generator + public static function provideHookNameSuffix(): \Generator { $expectedName = 'baseName'; $baseHookName = 'inpsyde.modularity.' . $expectedName; @@ -89,8 +89,8 @@ public function testBootWithEmptyModule(): void { $expectedId = 'my-module'; - $moduleStub = $this->mockModule($expectedId); - $propertiesStub = $this->mockProperties('name', false); + $moduleStub = $this->stubModule($expectedId); + $propertiesStub = $this->stubProperties('name', false); $package = Package::new($propertiesStub)->addModule($moduleStub); @@ -101,7 +101,7 @@ public function testBootWithEmptyModule(): void static::assertFalse($package->moduleIs($expectedId, Package::MODULE_EXTENDED)); static::assertFalse($package->moduleIs($expectedId, Package::MODULE_ADDED)); - // booting again will do nothing. + // booting again fails, but does not throw because debug is false static::assertFalse($package->boot()); } @@ -113,10 +113,10 @@ public function testBootWithServiceModule(): void $moduleId = 'my-service-module'; $serviceId = 'service-id'; - $module = $this->mockModule($moduleId, ServiceModule::class); + $module = $this->stubModule($moduleId, ServiceModule::class); $module->expects('services')->andReturn($this->stubServices($serviceId)); - $package = Package::new($this->mockProperties())->addModule($module); + $package = Package::new($this->stubProperties())->addModule($module); static::assertTrue($package->boot()); static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); @@ -135,10 +135,10 @@ public function testBootWithFactoryModule(): void $moduleId = 'my-factory-module'; $factoryId = 'factory-id'; - $module = $this->mockModule($moduleId, FactoryModule::class); + $module = $this->stubModule($moduleId, FactoryModule::class); $module->expects('factories')->andReturn($this->stubServices($factoryId)); - $package = Package::new($this->mockProperties())->addModule($module); + $package = Package::new($this->stubProperties())->addModule($module); static::assertTrue($package->boot()); static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); @@ -157,10 +157,10 @@ public function testBootWithExtendingModuleWithNonExistingService(): void $moduleId = 'my-extension-module'; $extensionId = 'extension-id'; - $module = $this->mockModule($moduleId, ExtendingModule::class); + $module = $this->stubModule($moduleId, ExtendingModule::class); $module->expects('extensions')->andReturn($this->stubServices($extensionId)); - $package = Package::new($this->mockProperties())->addModule($module); + $package = Package::new($this->stubProperties())->addModule($module); static::assertTrue($package->boot()); static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); @@ -180,11 +180,11 @@ public function testBootWithExtendingModuleWithExistingService(): void $moduleId = 'my-extension-module'; $serviceId = 'service-id'; - $module = $this->mockModule($moduleId, ServiceModule::class, ExtendingModule::class); + $module = $this->stubModule($moduleId, ServiceModule::class, ExtendingModule::class); $module->expects('services')->andReturn($this->stubServices($serviceId)); $module->expects('extensions')->andReturn($this->stubServices($serviceId)); - $package = Package::new($this->mockProperties())->addModule($module); + $package = Package::new($this->stubProperties())->addModule($module); static::assertTrue($package->boot()); static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); @@ -201,10 +201,10 @@ public function testBootWithExtendingModuleWithExistingService(): void public function testBootWithExecutableModule(): void { $moduleId = 'executable-module'; - $module = $this->mockModule($moduleId, ExecutableModule::class); + $module = $this->stubModule($moduleId, ExecutableModule::class); $module->expects('run')->andReturn(true); - $package = Package::new($this->mockProperties())->addModule($module); + $package = Package::new($this->stubProperties())->addModule($module); static::assertTrue($package->boot()); static::assertTrue($package->moduleIs($moduleId, Package::MODULE_ADDED)); @@ -220,10 +220,10 @@ public function testBootWithExecutableModule(): void public function testBootWithExecutableModuleFailed(): void { $moduleId = 'executable-module'; - $module = $this->mockModule($moduleId, ExecutableModule::class); + $module = $this->stubModule($moduleId, ExecutableModule::class); $module->expects('run')->andReturn(false); - $package = Package::new($this->mockProperties())->addModule($module); + $package = Package::new($this->stubProperties())->addModule($module); static::assertTrue($package->boot()); static::assertTrue($package->moduleIs($moduleId, Package::MODULE_ADDED)); @@ -237,10 +237,10 @@ public function testBootWithExecutableModuleFailed(): void */ public function testBootPassingModulesEmitDeprecation(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->allows('services')->andReturn($this->stubServices('service_1')); - $package = Package::new($this->mockProperties('test', true)); + $package = Package::new($this->stubProperties('test', true)); $this->convertDeprecationsToExceptions(); try { @@ -259,11 +259,11 @@ public function testBootPassingModulesEmitDeprecation(): void */ public function testAddModuleFailsAfterBuild(): void { - $package = Package::new($this->mockProperties('test', true))->build(); + $package = Package::new($this->stubProperties('test', true))->build(); $this->expectExceptionMessageMatches("/can't add module/i"); - $package->addModule($this->mockModule()); + $package->addModule($this->stubModule()); } /** @@ -271,7 +271,7 @@ public function testAddModuleFailsAfterBuild(): void */ public function testPropertiesCanBeRetrievedFromContainer(): void { - $expected = $this->mockProperties(); + $expected = $this->stubProperties(); $actual = Package::new($expected)->build()->container()->get(Package::PROPERTIES); static::assertSame($expected, $actual); @@ -284,21 +284,21 @@ public function testPropertiesCanBeRetrievedFromContainer(): void */ public function testStatusForMultipleModulesWhenDebug(): void { - $emptyModule = $this->mockModule('empty'); - $emptyServicesModule = $this->mockModule('empty_services', ServiceModule::class); - $emptyFactoriesModule = $this->mockModule('empty_factories', FactoryModule::class); - $emptyExtensionsModule = $this->mockModule('empty_extensions', ExtendingModule::class); + $emptyModule = $this->stubModule('empty'); + $emptyServicesModule = $this->stubModule('empty_services', ServiceModule::class); + $emptyFactoriesModule = $this->stubModule('empty_factories', FactoryModule::class); + $emptyExtensionsModule = $this->stubModule('empty_extensions', ExtendingModule::class); - $servicesModule = $this->mockModule('service', ServiceModule::class); + $servicesModule = $this->stubModule('service', ServiceModule::class); $servicesModule->expects('services')->andReturn($this->stubServices('S1', 'S2')); - $factoriesModule = $this->mockModule('factory', FactoryModule::class); + $factoriesModule = $this->stubModule('factory', FactoryModule::class); $factoriesModule->expects('factories')->andReturn($this->stubServices('F')); - $extendingModule = $this->mockModule('extension', ExtendingModule::class); + $extendingModule = $this->stubModule('extension', ExtendingModule::class); $extendingModule->expects('extensions')->andReturn($this->stubServices('E')); - $multiModule = $this->mockModule( + $multiModule = $this->stubModule( 'multi', ServiceModule::class, ExtendingModule::class, @@ -308,7 +308,7 @@ public function testStatusForMultipleModulesWhenDebug(): void $multiModule->expects('factories')->andReturn($this->stubServices('MF1', 'MF2')); $multiModule->expects('extensions')->andReturn($this->stubServices('ME1', 'ME2')); - $package = Package::new($this->mockProperties('name', true)) + $package = Package::new($this->stubProperties('name', true)) ->addModule($emptyModule) ->addModule($extendingModule) ->addModule($emptyServicesModule) @@ -378,21 +378,21 @@ public function testStatusForMultipleModulesWhenDebug(): void */ public function testStatusForMultipleModulesWhenNotDebug(): void { - $emptyModule = $this->mockModule('empty'); - $emptyServicesModule = $this->mockModule('empty_services', ServiceModule::class); - $emptyFactoriesModule = $this->mockModule('empty_factories', FactoryModule::class); - $emptyExtensionsModule = $this->mockModule('empty_extensions', ExtendingModule::class); + $emptyModule = $this->stubModule('empty'); + $emptyServicesModule = $this->stubModule('empty_services', ServiceModule::class); + $emptyFactoriesModule = $this->stubModule('empty_factories', FactoryModule::class); + $emptyExtensionsModule = $this->stubModule('empty_extensions', ExtendingModule::class); - $servicesModule = $this->mockModule('service', ServiceModule::class); + $servicesModule = $this->stubModule('service', ServiceModule::class); $servicesModule->expects('services')->andReturn($this->stubServices('S1', 'S2')); - $factoriesModule = $this->mockModule('factory', FactoryModule::class); + $factoriesModule = $this->stubModule('factory', FactoryModule::class); $factoriesModule->expects('factories')->andReturn($this->stubServices('F')); - $extendingModule = $this->mockModule('extension', ExtendingModule::class); + $extendingModule = $this->stubModule('extension', ExtendingModule::class); $extendingModule->expects('extensions')->andReturn($this->stubServices('E')); - $multiModule = $this->mockModule( + $multiModule = $this->stubModule( 'multi', ServiceModule::class, ExtendingModule::class, @@ -402,7 +402,7 @@ public function testStatusForMultipleModulesWhenNotDebug(): void $multiModule->expects('factories')->andReturn($this->stubServices('MF1', 'MF2')); $multiModule->expects('extensions')->andReturn($this->stubServices('ME1', 'ME2')); - $package = Package::new($this->mockProperties('name', false)) + $package = Package::new($this->stubProperties('name', false)) ->addModule($emptyModule) ->addModule($extendingModule) ->addModule($emptyServicesModule) @@ -472,14 +472,14 @@ public function testStatusForMultipleModulesWhenNotDebug(): void */ public function testPackageConnection(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', false)) + $package1 = Package::new($this->stubProperties('package_1', false)) ->addModule($module1); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $package2 = Package::new($this->mockProperties('package_2', false)) + $package2 = Package::new($this->stubProperties('package_2', false)) ->addModule($module2); Monkey\Actions\expectDone($package2->hookName(Package::ACTION_PACKAGE_CONNECTED)) @@ -504,14 +504,14 @@ public function testPackageConnection(): void */ public function testPackageConnectionFailsIfBootedWithDebugOff(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', false)) + $package1 = Package::new($this->stubProperties('package_1', false)) ->addModule($module1); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $package2 = Package::new($this->mockProperties('package_2', false)) + $package2 = Package::new($this->stubProperties('package_2', false)) ->addModule($module2); $package1->boot(); @@ -542,14 +542,14 @@ function (\Throwable $throwable): void { */ public function testPackageConnectionFailsIfBootedWithDebugOn(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', true)) + $package1 = Package::new($this->stubProperties('package_1', true)) ->addModule($module1); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $package2 = Package::new($this->mockProperties('package_2', true)) + $package2 = Package::new($this->stubProperties('package_2', true)) ->addModule($module2); $package1->boot(); @@ -562,7 +562,7 @@ public function testPackageConnectionFailsIfBootedWithDebugOn(): void Monkey\Actions\expectDone($package2->hookName(Package::ACTION_FAILED_BUILD)) ->once() ->whenHappen( - function (\Throwable $throwable) { + function (\Throwable $throwable): void { $this->assertThrowableMessageMatches($throwable, 'failed connect.+?booted'); } ); @@ -578,14 +578,14 @@ function (\Throwable $throwable) { */ public function testPackageConnectionWithProxyContainer(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', false)) + $package1 = Package::new($this->stubProperties('package_1', false)) ->addModule($module1); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $package2 = Package::new($this->mockProperties('package_2', false)) + $package2 = Package::new($this->stubProperties('package_2', false)) ->addModule($module2); $connected = $package2->connect($package1); @@ -615,14 +615,14 @@ public function testPackageConnectionWithProxyContainer(): void */ public function testPackageConnectionWithProxyContainerFailsIfNoBoot(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', false)) + $package1 = Package::new($this->stubProperties('package_1', false)) ->addModule($module1); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $package2 = Package::new($this->mockProperties('package_2', false)) + $package2 = Package::new($this->stubProperties('package_2', false)) ->addModule($module2); $connected = $package2->connect($package1); @@ -644,14 +644,14 @@ public function testPackageConnectionWithProxyContainerFailsIfNoBoot(): void */ public function testPackageCanOnlyBeConnectedOnceDebugOff(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', false)) + $package1 = Package::new($this->stubProperties('package_1', false)) ->addModule($module1); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $package2 = Package::new($this->mockProperties('package_2', false)) + $package2 = Package::new($this->stubProperties('package_2', false)) ->addModule($module2); Monkey\Actions\expectDone($package2->hookName(Package::ACTION_PACKAGE_CONNECTED)) @@ -686,14 +686,14 @@ function (\Throwable $throwable): void { */ public function testPackageCanOnlyBeConnectedOnceDebugOn(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', false)) + $package1 = Package::new($this->stubProperties('package_1', false)) ->addModule($module1); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $package2 = Package::new($this->mockProperties('package_2', true)) + $package2 = Package::new($this->stubProperties('package_2', true)) ->addModule($module2); Monkey\Actions\expectDone($package2->hookName(Package::ACTION_PACKAGE_CONNECTED)) @@ -706,7 +706,7 @@ public function testPackageCanOnlyBeConnectedOnceDebugOn(): void Monkey\Actions\expectDone($package2->hookName(Package::ACTION_FAILED_BUILD)) ->once() ->whenHappen( - function (\Throwable $throwable) { + function (\Throwable $throwable): void { $this->assertThrowableMessageMatches($throwable, 'failed connect.+?already'); } ); @@ -724,9 +724,9 @@ function (\Throwable $throwable) { */ public function testPackageCanNotBeConnectedWithThemselves(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $package1 = Package::new($this->mockProperties('package_1', true)) + $package1 = Package::new($this->stubProperties('package_1', true)) ->addModule($module1); $action = $package1->hookName(Package::ACTION_FAILED_CONNECTION); @@ -737,10 +737,13 @@ public function testPackageCanNotBeConnectedWithThemselves(): void /** * @test + * + * phpcs:disable Inpsyde.CodeQuality.NestingLevel */ public function testBuildResolveServices(): void { - $module = new class() implements ServiceModule, ExtendingModule, ExecutableModule + // phpcs:enable phpcs:disable Inpsyde.CodeQuality.NestingLevel + $module = new class () implements ServiceModule, ExtendingModule, ExecutableModule { public function id(): string { @@ -750,23 +753,27 @@ public function id(): string public function services(): array { return [ - 'dependency' => function () { - return (object)['x' => 'Works!']; + 'dependency' => static function (): object { + return (object) ['x' => 'Works!']; }, - 'service' => function (ContainerInterface $container) { + 'service' => static function (ContainerInterface $container): object { $works = $container->get('dependency')->x; - return new class(['works?' => $works]) extends \ArrayObject {}; - } + return new class (['works?' => $works]) extends \ArrayObject + { + }; + }, ]; } public function extensions(): array { return [ - 'service' => function (\ArrayObject $current) { - return new class ($current) { - public $object; + 'service' => function (\ArrayObject $current): object { + return new class ($current) + { + public \ArrayObject $object; // phpcs:ignore + public function __construct(\ArrayObject $object) { $this->object = $object; @@ -777,7 +784,7 @@ public function works(): string return $this->object->offsetGet('works?'); } }; - } + }, ]; } @@ -787,7 +794,7 @@ public function run(ContainerInterface $container): bool } }; - $actual = Package::new($this->mockProperties()) + $actual = Package::new($this->stubProperties()) ->addModule($module) ->build() ->container() @@ -802,16 +809,16 @@ public function run(ContainerInterface $container): bool */ public function testBuildPassingModulesToBoot(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $module3 = $this->mockModule('module_3', ServiceModule::class); + $module3 = $this->stubModule('module_3', ServiceModule::class); $module3->expects('services')->andReturn($this->stubServices('service_3')); - $package = Package::new($this->mockProperties('test', true)) + $package = Package::new($this->stubProperties('test', true)) ->addModule($module1) ->addModule($module2) ->build(); @@ -831,16 +838,16 @@ public function testBuildPassingModulesToBoot(): void */ public function testBootFailsIfPassingNotAddedModulesAfterContainer(): void { - $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1 = $this->stubModule('module_1', ServiceModule::class); $module1->expects('services')->andReturn($this->stubServices('service_1')); - $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2 = $this->stubModule('module_2', ServiceModule::class); $module2->expects('services')->andReturn($this->stubServices('service_2')); - $module3 = $this->mockModule('module_3', ServiceModule::class); + $module3 = $this->stubModule('module_3', ServiceModule::class); $module3->allows('services')->andReturn($this->stubServices('service_3')); - $package = Package::new($this->mockProperties('test', true)) + $package = Package::new($this->stubProperties('test', true)) ->addModule($module1) ->addModule($module2) ->build(); @@ -865,10 +872,10 @@ public function testFailureFlowWithFailureOnBootDebugModeOff(): void { $exception = new \Exception('Test'); - $module = $this->mockModule('id', ExecutableModule::class); + $module = $this->stubModule('id', ExecutableModule::class); $module->expects('run')->andThrow($exception); - $package = Package::new($this->mockProperties())->addModule($module); + $package = Package::new($this->stubProperties())->addModule($module); Monkey\Actions\expectDone($package->hookName(Package::ACTION_FAILED_BOOT)) ->once() @@ -887,10 +894,10 @@ public function testFailureFlowWithFailureOnBootDebugModeOn(): void { $exception = new \Exception('Test'); - $module = $this->mockModule('id', ExecutableModule::class); + $module = $this->stubModule('id', ExecutableModule::class); $module->expects('run')->andThrow($exception); - $package = Package::new($this->mockProperties('basename', true))->addModule($module); + $package = Package::new($this->stubProperties('basename', true))->addModule($module); Monkey\Actions\expectDone($package->hookName(Package::ACTION_FAILED_BOOT)) ->once() @@ -904,7 +911,7 @@ public function testFailureFlowWithFailureOnBootDebugModeOn(): void * When multiple calls to `Package::addPackage()` throw an exception, and debug is off, we * expect none of them to bubble up, and the first to cause the "build failed" action. * We also expect the Package to be in errored status. - * We expect all other `Package::addPackage()` exceptions to do not fire action hook.@psalm-allow-private-mutation + * We expect all other `Package::addPackage()` exceptions to do not fire action hook. * We expect Package::build()` to fail without doing anything. Finally, when `Package::boot()` * is called, we expect the action "boot failed" to be called, and the passed exception to have * an exception hierarchy with all the thrown exceptions. @@ -915,13 +922,13 @@ public function testFailureFlowWithFailureOnAddModuleDebugModeOff(): void { $exception = new \Exception('Test 1'); - $module1 = $this->mockModule('one', ServiceModule::class); + $module1 = $this->stubModule('one', ServiceModule::class); $module1->expects('services')->andThrow($exception); - $module2 = $this->mockModule('two', ServiceModule::class); + $module2 = $this->stubModule('two', ServiceModule::class); $module2->expects('services')->never(); - $package = Package::new($this->mockProperties()); + $package = Package::new($this->stubProperties()); Monkey\Actions\expectDone($package->hookName(Package::ACTION_FAILED_BUILD)) ->once() @@ -960,15 +967,15 @@ public function testFailureFlowWithFailureOnAddModuleWithoutBuildDebugModeOff(): { $exception = new \Exception('Test 1'); - $module1 = $this->mockModule('one', ServiceModule::class); + $module1 = $this->stubModule('one', ServiceModule::class); $module1->expects('services')->andThrow($exception); - $module2 = $this->mockModule('two', ServiceModule::class); + $module2 = $this->stubModule('two', ServiceModule::class); $module2->expects('services')->never(); - $package = Package::new($this->mockProperties()); + $package = Package::new($this->stubProperties()); - $connected = Package::new($this->mockProperties()); + $connected = Package::new($this->stubProperties()); $connected->boot(); Monkey\Actions\expectDone($package->hookName(Package::ACTION_FAILED_BUILD)) @@ -1015,7 +1022,7 @@ public function testFailureFlowWithFailureOnBuildDebugModeOff(): void { $exception = new \Exception('Test'); - $package = Package::new($this->mockProperties()); + $package = Package::new($this->stubProperties()); Monkey\Actions\expectDone($package->hookName(Package::ACTION_INIT)) ->once() @@ -1053,7 +1060,7 @@ public function testFailureFlowWithFailureOnBuildDebugModeOn(): void { $exception = new \Exception('Test'); - $package = Package::new($this->mockProperties('basename', true)); + $package = Package::new($this->stubProperties('basename', true)); Monkey\Actions\expectDone($package->hookName(Package::ACTION_INIT)) ->once() diff --git a/tests/unit/Properties/BasePropertiesTest.php b/tests/unit/Properties/BasePropertiesTest.php index 9f3a645..6b4b8fb 100644 --- a/tests/unit/Properties/BasePropertiesTest.php +++ b/tests/unit/Properties/BasePropertiesTest.php @@ -18,7 +18,7 @@ public function testBasic(): void $expectedName = 'foo'; $expectedPath = __DIR__ . '/'; - $testee = $this->createBaseProperties( + $testee = $this->factoryBaseProperties( $expectedName, $expectedPath ); @@ -44,11 +44,12 @@ public function testBasic(): void } /** - * @dataProvider baseNameDataProvider + * @test + * @dataProvider provideBaseNameData */ public function testBaseNameSanitization(string $baseName, string $sanitizedBaseName): void { - $testee = $this->createBaseProperties( + $testee = $this->factoryBaseProperties( $baseName, '' ); @@ -56,7 +57,10 @@ public function testBaseNameSanitization(string $baseName, string $sanitizedBase static::assertSame($sanitizedBaseName, $testee->baseName()); } - public function baseNameDataProvider(): iterable + /** + * @return \Generator + */ + public static function provideBaseNameData(): \Generator { yield 'empty' => ['', '']; yield 'word' => ['foo', 'foo']; @@ -66,19 +70,29 @@ public function baseNameDataProvider(): iterable yield 'single file' => ['package.php', 'package']; } - private function createBaseProperties( + /** + * @param string $baseName + * @param string $basePath + * @param string|null $baseUrl + * @param array $properties + * @return BaseProperties + */ + private function factoryBaseProperties( string $baseName, string $basePath, string $baseUrl = null, array $properties = [] ): BaseProperties { - return new class($baseName, $basePath, $baseUrl, $properties) extends BaseProperties { + + return new class ($baseName, $basePath, $baseUrl, $properties) extends BaseProperties + { public function __construct( string $baseName, string $basePath, string $baseUrl = null, array $properties = [] ) { + parent::__construct($baseName, $basePath, $baseUrl, $properties); } }; diff --git a/tests/unit/Properties/LibraryPropertiesTest.php b/tests/unit/Properties/LibraryPropertiesTest.php index 8523b11..30ace60 100644 --- a/tests/unit/Properties/LibraryPropertiesTest.php +++ b/tests/unit/Properties/LibraryPropertiesTest.php @@ -16,8 +16,9 @@ class LibraryPropertiesTest extends TestCase */ public function testForLibraryInvalidFile(): void { - static::expectException(\Exception::class); - LibraryProperties::new('non-existing.file'); + $this->expectException(\Exception::class); + + LibraryProperties::new('non-existing.file')->basePath(); } /** @@ -104,7 +105,7 @@ public function testForLibraryAllProperties(): void $expectedDomainPath = 'languages/'; $expectedName = "Properties Test"; $expectedTextDomain = 'properties-test'; - $expectedUri = 'http://github.com/inpsyde/modularity'; + $expectedUri = 'https://github.com/inpsyde/modularity'; $expectedVersion = '1.0'; $expectedPhpVersion = "7.4"; $expectedWpVersion = "5.3"; @@ -118,6 +119,7 @@ public function testForLibraryAllProperties(): void "name" => $expectedAuthor, "homepage" => $expectedAuthorUri, ], + "Invalid Author ", ], "keywords" => $expectedKeywords, "require" => [ @@ -165,7 +167,7 @@ public function testForLibraryAllProperties(): void */ public function testPhpDevRequireParsing(string $requirement, ?string $expected): void { - $which = random_int(1, 10) > 5 ? 'require-dev' : 'require'; + $which = (random_int(1, 10) > 5) ? 'require-dev' : 'require'; $composerJsonData = [ 'name' => 'inpsyde/some-package_name', @@ -181,54 +183,18 @@ public function testPhpDevRequireParsing(string $requirement, ?string $expected) ]; $root = vfsStream::setup('root', null, $structure); - $testee = LibraryProperties::new($root->url() . '/json/composer.json'); - $php = $testee->requiresPhp(); - - static::assertSame($expected, $php, "For requirement: '{$requirement}'"); - } - - /** - * @test - */ - public function testBaseUrlCanBeSet(): void - { - $root = vfsStream::setup('root', null, ['composer.json' => '{"name": "vendor/test"}']); - - $properties = LibraryProperties::new($root->url() . '/composer.json'); - - static::assertNull($properties->baseUrl()); - - $properties->withBaseUrl('https://example.com'); - static::assertSame('https://example.com/', $properties->baseUrl()); - } - - /** - * @test - */ - public function testBaseUrlCanNotBeOverridden(): void - { - $root = vfsStream::setup('root', null, ['composer.json' => '{"name": "vendor/test"}']); - - $properties = LibraryProperties::new( - $root->url() . '/composer.json', - 'https://example.com' - ); + $properties = LibraryProperties::new($root->url() . '/json/composer.json'); + $requiresPhp = $properties->requiresPhp(); - static::assertSame('https://example.com/', $properties->baseUrl()); - - $this->expectExceptionMessageMatches('/not overridable/i'); - - $properties->withBaseUrl('https://example.com/something'); + static::assertSame($expected, $requiresPhp, "For requirement: '{$requirement}'"); } /** - * @return array + * @return \Generator */ - public function providePhpRequirements(): array + public static function providePhpRequirements(): \Generator { - // [requirement, expected] - - return [ + yield from [ // simple requirements ['7.1', '7.1'], ['7.1.3-dev', '7.1.3'], @@ -277,4 +243,38 @@ public function providePhpRequirements(): array ['dev-foo#abcde', null], ]; } + + /** + * @test + */ + public function testBaseUrlCanBeSet(): void + { + $root = vfsStream::setup('root', null, ['composer.json' => '{"name": "vendor/test"}']); + + $properties = LibraryProperties::new($root->url() . '/composer.json'); + + static::assertNull($properties->baseUrl()); + + $properties->withBaseUrl('https://example.com'); + static::assertSame('https://example.com/', $properties->baseUrl()); + } + + /** + * @test + */ + public function testBaseUrlCanNotBeOverridden(): void + { + $root = vfsStream::setup('root', null, ['composer.json' => '{"name": "vendor/test"}']); + + $properties = LibraryProperties::new( + $root->url() . '/composer.json', + 'https://example.com' + ); + + static::assertSame('https://example.com/', $properties->baseUrl()); + + $this->expectExceptionMessageMatches('/not overridable/i'); + + $properties->withBaseUrl('https://example.com/something'); + } } diff --git a/tests/unit/Properties/PluginPropertiesTest.php b/tests/unit/Properties/PluginPropertiesTest.php index a9f5fce..23c3523 100644 --- a/tests/unit/Properties/PluginPropertiesTest.php +++ b/tests/unit/Properties/PluginPropertiesTest.php @@ -7,7 +7,7 @@ use Inpsyde\Modularity\Properties\Properties; use Inpsyde\Modularity\Properties\PluginProperties; use Inpsyde\Modularity\Tests\TestCase; -use \Brain\Monkey\Functions; +use Brain\Monkey\Functions; class PluginPropertiesTest extends TestCase { @@ -22,11 +22,11 @@ public function testBasic(): void $expectedDomainPath = 'languages/'; $expectedName = "Properties Test"; $expectedTextDomain = 'properties-test'; - $expectedUri = 'http://github.com/inpsyde/modularity'; + $expectedUri = 'https://github.com/inpsyde/modularity'; $expectedVersion = '1.0'; $expectedPhpVersion = "7.4"; $expectedWpVersion = "5.3"; - $expectedNetwork = true; + $expectedNetwork = random_int(1, 1000) > 500; $expectedPluginMainFile = '/app/wp-content/plugins/plugin-dir/plugin-name.php'; $expectedBaseName = 'plugin-dir/plugin-name.php'; @@ -55,34 +55,32 @@ public function testBasic(): void Functions\expect('plugin_basename')->andReturn($expectedBaseName); Functions\expect('plugin_dir_path')->andReturn($expectedBasePath); - $testee = PluginProperties::new($expectedPluginMainFile); - - static::assertInstanceOf(Properties::class, $testee); - static::assertSame($expectedDescription, $testee->description()); - static::assertSame($expectedAuthor, $testee->author()); - static::assertSame($expectedAuthorUri, $testee->authorUri()); - static::assertSame($expectedDomainPath, $testee->domainPath()); - static::assertSame($expectedName, $testee->name()); - static::assertSame($expectedTextDomain, $testee->textDomain()); - static::assertSame($expectedUri, $testee->uri()); - static::assertSame($expectedVersion, $testee->version()); - static::assertSame($expectedWpVersion, $testee->requiresWp()); - static::assertSame($expectedPhpVersion, $testee->requiresPhp()); - static::assertSame($expectedSanitizedBaseName, $testee->baseName()); + $properties = PluginProperties::new($expectedPluginMainFile); + + static::assertInstanceOf(Properties::class, $properties); + static::assertSame($expectedDescription, $properties->description()); + static::assertSame($expectedAuthor, $properties->author()); + static::assertSame($expectedAuthorUri, $properties->authorUri()); + static::assertSame($expectedDomainPath, $properties->domainPath()); + static::assertSame($expectedName, $properties->name()); + static::assertSame($expectedTextDomain, $properties->textDomain()); + static::assertSame($expectedUri, $properties->uri()); + static::assertSame($expectedVersion, $properties->version()); + static::assertSame($expectedWpVersion, $properties->requiresWp()); + static::assertSame($expectedPhpVersion, $properties->requiresPhp()); + static::assertSame($expectedSanitizedBaseName, $properties->baseName()); // Custom to Plugins - static::assertSame($expectedNetwork, $testee->network()); - static::assertSame($expectedPluginMainFile, $testee->pluginMainFile()); + static::assertSame($expectedNetwork, $properties->network()); + static::assertSame($expectedPluginMainFile, $properties->pluginMainFile()); } /** - * @param string $requiresPlugins - * @param array $expected - * * @test - * * @runInSeparateProcess - * * @dataProvider provideRequiresPluginsData + * + * @param string $requiresPlugins + * @param array $expected */ public function testRequiresPlugins(string $requiresPlugins, array $expected): void { @@ -98,16 +96,16 @@ public function testRequiresPlugins(string $requiresPlugins, array $expected): v Functions\expect('wp_normalize_path')->andReturnFirstArg(); - $testee = PluginProperties::new($pluginMainFile); - static::assertEquals($expected, $testee->requiresPlugins()); + $properties = PluginProperties::new($pluginMainFile); + static::assertEquals($expected, $properties->requiresPlugins()); } /** - * @return array[] + * @return \Generator */ - public function provideRequiresPluginsData(): array + public static function provideRequiresPluginsData(): \Generator { - return [ + yield from [ 'no dependencies' => [ '', [], @@ -144,14 +142,14 @@ public function testIsActive(): void Functions\expect('plugin_basename')->andReturn($expectedBaseName); Functions\expect('plugin_dir_path')->andReturn($expectedBasePath); - $testee = PluginProperties::new($pluginMainFile); + $properties = PluginProperties::new($pluginMainFile); Functions\expect('is_plugin_active') ->andReturnUsing(static function (string $baseName) use ($expectedBaseName): bool { return $baseName === $expectedBaseName; }); - static::assertTrue($testee->isActive()); + static::assertTrue($properties->isActive()); } /** @@ -174,8 +172,8 @@ public function testIsNetworkActive(): void return $baseName === $expectedBaseName; }); - $testee = PluginProperties::new($pluginMainFile); - static::assertTrue($testee->isNetworkActive()); + $properties = PluginProperties::new($pluginMainFile); + static::assertTrue($properties->isNetworkActive()); } /** @@ -198,7 +196,6 @@ public function testCustomPluginHeaders(array $customHeaders): void [ 'Author' => $expectedAuthor, 'AuthorURI' => $expectedAuthorUri, - ], $customHeaders ); @@ -209,33 +206,32 @@ public function testCustomPluginHeaders(array $customHeaders): void Functions\expect('plugin_basename')->andReturn($expectedBaseName); Functions\expect('plugin_dir_path')->andReturn($expectedBasePath); - $testee = PluginProperties::new($pluginMainFile); + $properties = PluginProperties::new($pluginMainFile); // Check if PluginProperties do behave as normal - static::assertSame($expectedSanitizedBaseName, $testee->baseName()); - static::assertSame($expectedBasePath, $testee->basePath()); + static::assertSame($expectedSanitizedBaseName, $properties->baseName()); + static::assertSame($expectedBasePath, $properties->basePath()); // Test default Headers - static::assertSame($expectedAuthor, $testee->author()); - static::assertSame($expectedAuthor, $testee->get(Properties::PROP_AUTHOR)); - static::assertSame($expectedAuthorUri, $testee->authorUri()); - static::assertSame($expectedAuthorUri, $testee->get(Properties::PROP_AUTHOR_URI)); + static::assertSame($expectedAuthor, $properties->author()); + static::assertSame($expectedAuthor, $properties->get(Properties::PROP_AUTHOR)); + static::assertSame($expectedAuthorUri, $properties->authorUri()); + static::assertSame($expectedAuthorUri, $properties->get(Properties::PROP_AUTHOR_URI)); // Test headers from get_plugin_data() are removed from properties // "Author" will be mapped to Properties::PROP_AUTHOR - static::assertFalse($testee->has('Author')); - static::assertFalse($testee->has('AuthorURI')); + static::assertFalse($properties->has('Author')); + static::assertFalse($properties->has('AuthorURI')); // Test custom Headers foreach ($customHeaders as $key => $value) { - static::assertTrue($testee->has($key)); - static::assertSame($value, $testee->get($key)); + static::assertTrue($properties->has($key)); + static::assertSame($value, $properties->get($key)); } } /** - * Provides custom Plugin Headers which will - * be returned by get_plugin_data() + * @return \Generator */ public function provideCustomHeaders(): \Generator { @@ -255,16 +251,13 @@ public function provideCustomHeaders(): \Generator } /** + * @test + * @runInSeparateProcess + * @dataProvider provideIsMuPluginData * * @param string $pluginPath * @param string $muPluginDir * @param bool $expected - * - * @test - * - * @runInSeparateProcess - * - * @dataProvider provideIsMuPluginData */ public function testIsMuPlugin(string $pluginMainFile, string $muPluginDir, bool $expected): void { @@ -279,16 +272,16 @@ public function testIsMuPlugin(string $pluginMainFile, string $muPluginDir, bool define('WPMU_PLUGIN_DIR', $muPluginDir); - $testee = PluginProperties::new($pluginMainFile); - static::assertSame($expected, $testee->isMuPlugin()); + $properties = PluginProperties::new($pluginMainFile); + static::assertSame($expected, $properties->isMuPlugin()); } /** - * @return array[] + * @return \Generator */ - public function provideIsMuPluginData(): array + public static function provideIsMuPluginData(): \Generator { - return [ + yield from [ 'is not mu-plugin' => [ '/wp-content/plugins/the-plugin/index.php', '/wp-content/mu-plugins/', diff --git a/tests/unit/Properties/ThemePropertiesTest.php b/tests/unit/Properties/ThemePropertiesTest.php index 34ea8bf..b21dc30 100644 --- a/tests/unit/Properties/ThemePropertiesTest.php +++ b/tests/unit/Properties/ThemePropertiesTest.php @@ -5,10 +5,9 @@ namespace Inpsyde\Modularity\Tests\Unit\Properties; use Inpsyde\Modularity\Properties\Properties; -use Inpsyde\Modularity\Properties\PluginProperties; use Inpsyde\Modularity\Properties\ThemeProperties; use Inpsyde\Modularity\Tests\TestCase; -use \Brain\Monkey\Functions; +use Brain\Monkey\Functions; class ThemePropertiesTest extends TestCase { @@ -23,7 +22,7 @@ public function testBasic(): void $expectedDomainPath = 'languages/'; $expectedName = "Properties Test"; $expectedTextDomain = 'properties-test'; - $expectedUri = 'http://github.com/inpsyde/modularity'; + $expectedUri = 'https://github.com/inpsyde/modularity'; $expectedVersion = '1.0'; $expectedPhpVersion = "7.4"; $expectedWpVersion = "5.3"; @@ -62,34 +61,34 @@ public function testBasic(): void Functions\expect('wp_get_theme')->with($expectedBasePath)->andReturn($themeStub); Functions\expect('get_stylesheet')->andReturn($expectedBaseName); - $testee = ThemeProperties::new($expectedBasePath); + $properties = ThemeProperties::new($expectedBasePath); - static::assertInstanceOf(Properties::class, $testee); + static::assertInstanceOf(Properties::class, $properties); - static::assertSame($expectedBaseName, $testee->baseName()); - static::assertSame($expectedBasePath, $testee->basePath()); - static::assertSame($expectedBaseUrl, $testee->baseUrl()); + static::assertSame($expectedBaseName, $properties->baseName()); + static::assertSame($expectedBasePath, $properties->basePath()); + static::assertSame($expectedBaseUrl, $properties->baseUrl()); - static::assertSame($expectedDescription, $testee->description()); - static::assertSame($expectedAuthor, $testee->author()); - static::assertSame($expectedAuthorUri, $testee->authorUri()); - static::assertSame($expectedDomainPath, $testee->domainPath()); - static::assertSame($expectedName, $testee->name()); - static::assertSame($expectedTextDomain, $testee->textDomain()); - static::assertSame($expectedUri, $testee->uri()); - static::assertSame($expectedVersion, $testee->version()); - static::assertSame($expectedWpVersion, $testee->requiresWp()); - static::assertSame($expectedPhpVersion, $testee->requiresPhp()); + static::assertSame($expectedDescription, $properties->description()); + static::assertSame($expectedAuthor, $properties->author()); + static::assertSame($expectedAuthorUri, $properties->authorUri()); + static::assertSame($expectedDomainPath, $properties->domainPath()); + static::assertSame($expectedName, $properties->name()); + static::assertSame($expectedTextDomain, $properties->textDomain()); + static::assertSame($expectedUri, $properties->uri()); + static::assertSame($expectedVersion, $properties->version()); + static::assertSame($expectedWpVersion, $properties->requiresWp()); + static::assertSame($expectedPhpVersion, $properties->requiresPhp()); // specific methods for Themes. - static::assertSame($expectedTags, $testee->tags()); - static::assertSame('', $testee->template()); - static::assertSame($expectedStatus, $testee->status()); + static::assertSame($expectedTags, $properties->tags()); + static::assertSame('', $properties->template()); + static::assertSame($expectedStatus, $properties->status()); // API for Themes - static::assertFalse($testee->isChildTheme()); - static::assertTrue($testee->isCurrentTheme()); - static::assertNull($testee->parentThemeProperties()); + static::assertFalse($properties->isChildTheme()); + static::assertTrue($properties->isCurrentTheme()); + static::assertNull($properties->parentThemeProperties()); } /** @@ -114,9 +113,9 @@ public function testChildTheme(): void Functions\expect('wp_get_theme')->with($expectedBasePath)->andReturn($themeStub); - $testee = ThemeProperties::new($expectedBasePath); + $properties = ThemeProperties::new($expectedBasePath); - static::assertSame($expectedTemplate, $testee->template()); - static::assertTrue($testee->isChildTheme()); + static::assertSame($expectedTemplate, $properties->template()); + static::assertTrue($properties->isChildTheme()); } }