From 46f93254047b6769a1954f838d035b683e7c5294 Mon Sep 17 00:00:00 2001 From: "hubert.lenoir" Date: Fri, 30 Dec 2022 15:41:58 +0100 Subject: [PATCH 1/2] add PHPStan --- .bootstrap.atoum.php | 2 - .github/workflows/ci.yml | 86 ++++++++++----------- composer.json | 6 +- phpstan.neon.dist | 34 ++++++++ src/Context/JsonContext.php | 12 +-- src/Context/RestContext.php | 2 +- src/Context/XmlContext.php | 10 ++- src/HttpCall/HttpCallResultPoolResolver.php | 28 ++++--- src/HttpCall/Request.php | 13 ++++ tests/units/Json/JsonInspector.php | 2 +- 10 files changed, 122 insertions(+), 73 deletions(-) delete mode 100644 .bootstrap.atoum.php create mode 100644 phpstan.neon.dist diff --git a/.bootstrap.atoum.php b/.bootstrap.atoum.php deleted file mode 100644 index 60e31a04..00000000 --- a/.bootstrap.atoum.php +++ /dev/null @@ -1,2 +0,0 @@ -> $GITHUB_OUTPUT - # - name: Cache dependencies - # uses: actions/cache@v2 - # with: - # path: ${{ steps.composercache.outputs.dir }} - # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - # restore-keys: ${{ runner.os }}-composer- - # - name: Update project dependencies - # run: composer update --no-interaction --no-progress --ansi - # run: bin/simple-phpunit --version - # - name: Cache PHPStan results - # uses: actions/cache@v2 - # with: - # path: /tmp/phpstan - # key: phpstan-php${{ matrix.php }}-${{ github.sha }} - # restore-keys: | - # phpstan-php${{ matrix.php }}- - # phpstan- - # continue-on-error: true - # - name: Clear test app cache - # run: | - # tests/Fixtures/app/console cache:clear --ansi - # - name: Run PHPStan analysis - # run: ./bin/phpstan analyse --no-interaction --no-progress --no-interaction --ansi + phpstan: + name: PHPStan + runs-on: ubuntu-latest + strategy: + matrix: + php: + - '8.1' + fail-fast: false + env: + APP_DEBUG: '1' # https://github.com/phpstan/phpstan-symfony/issues/37 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: pecl, composer + extensions: intl, bcmath, curl, openssl, mbstring + coverage: none + ini-values: memory_limit=-1 + - name: Get composer cache directory + id: composercache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Update project dependencies + run: composer update --no-interaction --no-progress --ansi + - name: Cache PHPStan results + uses: actions/cache@v2 + with: + path: /tmp/phpstan + key: phpstan-php${{ matrix.php }}-${{ github.sha }} + restore-keys: | + phpstan-php${{ matrix.php }}- + phpstan- + continue-on-error: true + - name: Run PHPStan analysis + run: ./bin/phpstan analyse --no-interaction --no-progress --no-interaction --ansi atoum: name: Atoum (PHP ${{ matrix.php }}) diff --git a/composer.json b/composer.json index 8db8b892..8561d0a4 100644 --- a/composer.json +++ b/composer.json @@ -21,12 +21,14 @@ "behat/mink-selenium2-driver": "^1.6", "atoum/atoum": "^4.0", "fabpot/goutte": "^3.2", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "atoum/stubs": "^2.6" }, "autoload": { "psr-4": { - "Behatch\\": "src/" + "Behatch\\": "src/", + "Behatch\\Tests\\Units\\": "tests/units/" } }, diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..3aa54859 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,34 @@ +parameters: + level: 3 + paths: + - src + - tests + bootstrapFiles: + - vendor/autoload.php + +# inferPrivatePropertyTypeFromConstructor: true +# symfony: +# containerXmlPath: tests/Fixtures/app/var/cache/test/AppKernelTestDebugContainer.xml +# constantHassers: false +# doctrine: +# objectManagerLoader: tests/Fixtures/app/object-manager.php +# bootstrapFiles: +# - vendor/bin/.phpunit/phpunit/vendor/autoload.php +# # We're aliasing classes for phpunit in this file, it needs to be added here see phpstan/#2194 +# - src/Symfony/Bundle/Test/Constraint/ArraySubset.php +# - tests/Fixtures/app/AppKernel.php +# excludePaths: +# # Symfony config +# - tests/Fixtures/app/config/config_swagger.php +# # Symfony cache +# - tests/Fixtures/app/var/ +# - tests/Fixtures/Symfony/Maker +# # The Symfony Configuration API isn't good enough to be analysed +# - src/Symfony/Bundle/DependencyInjection/Configuration.php +# # Templates for Maker +# - src/Symfony/Maker/Resources/skeleton +# earlyTerminatingMethodCalls: +# PHPUnit\Framework\Constraint\Constraint: +# - fail +# ApiPlatform\Metadata\Resource\ResourceMetadataCollection: +# - handleNotFound diff --git a/src/Context/JsonContext.php b/src/Context/JsonContext.php index 8d0ed15b..81b5042f 100644 --- a/src/Context/JsonContext.php +++ b/src/Context/JsonContext.php @@ -313,9 +313,7 @@ public function theJsonShouldBeValidAccordingToThisSchema(PyStringNode $schema): */ public function theJsonShouldBeInvalidAccordingToThisSchema(PyStringNode $schema): void { - $this->not(function () use ($schema) { - return $this->theJsonShouldBeValidAccordingToThisSchema($schema); - }, 'Expected to receive invalid json, got valid one'); + $this->not([$this, 'theJsonShouldBeValidAccordingToThisSchema'], 'Expected to receive invalid json, got valid one'); } /** @@ -341,9 +339,7 @@ public function theJsonShouldBeInvalidAccordingToTheSchema($filename): void { $this->checkSchemaFile($filename); - $this->not(function () use ($filename) { - return $this->theJsonShouldBeValidAccordingToTheSchema($filename); - }, 'The schema was valid'); + $this->not([$this, 'theJsonShouldBeValidAccordingToTheSchema'], 'The schema was valid'); } /** @@ -404,9 +400,7 @@ public function theJsonShouldBeValidAccordingToTheSwaggerSchema($dumpPath, $sche */ public function theJsonShouldNotBeValidAccordingToTheSwaggerSchema($dumpPath, $schemaName): void { - $this->not(function () use ($dumpPath, $schemaName) { - return $this->theJsonShouldBeValidAccordingToTheSwaggerSchema($dumpPath, $schemaName); - }, 'JSON Schema matches but it should not'); + $this->not([$this, 'theJsonShouldBeValidAccordingToTheSwaggerSchema'], 'JSON Schema matches but it should not'); } protected function getJson() diff --git a/src/Context/RestContext.php b/src/Context/RestContext.php index 86fdbeec..d75d4130 100644 --- a/src/Context/RestContext.php +++ b/src/Context/RestContext.php @@ -33,7 +33,7 @@ public function iSendARequestTo($method, $url, PyStringNode $body = null, $files $this->locatePath($url), [], $files, - null !== $body ? $body->getRaw() : null + $body?->getRaw() ); } diff --git a/src/Context/XmlContext.php b/src/Context/XmlContext.php index b73e2868..1f5f44da 100644 --- a/src/Context/XmlContext.php +++ b/src/Context/XmlContext.php @@ -90,9 +90,7 @@ public function theXmlElementShouldBeEqualTo($element, $text): void */ public function theXmlElementShouldNotBeEqualTo($element, $text): void { - $this->not(function () use ($element, $text): void { - $this->theXmlElementShouldBeEqualTo($element, $text); - }, "The element '$element' value is not '$text'"); + $this->not([$this, 'theXmlElementShouldBeEqualTo'], "The element '$element' value is not '$text'"); } /** @@ -104,7 +102,11 @@ public function theXmlAttributeShouldExist($attribute, $element) { $elements = $this->theXmlElementShouldExist("{$element}[@{$attribute}]"); - $actual = $elements->item(0)->getAttribute($attribute); + if (!($element = $elements->item(0)) instanceof \DOMElement) { + throw new \Exception('Unable to retrieve the node attribute'); + } + + $actual = $element->getAttribute($attribute); if (empty($actual)) { throw new \Exception("The attribute value is '$actual'"); diff --git a/src/HttpCall/HttpCallResultPoolResolver.php b/src/HttpCall/HttpCallResultPoolResolver.php index 5ffdac86..aa8917fd 100644 --- a/src/HttpCall/HttpCallResultPoolResolver.php +++ b/src/HttpCall/HttpCallResultPoolResolver.php @@ -8,7 +8,7 @@ class HttpCallResultPoolResolver implements ArgumentResolver { - private $dependencies; + private array $dependencies; public function __construct(/* ... */) { @@ -21,17 +21,23 @@ public function __construct(/* ... */) public function resolveArguments(\ReflectionClass $classReflection, array $arguments) { - $constructor = $classReflection->getConstructor(); - if (null !== $constructor) { - $parameters = $constructor->getParameters(); - foreach ($parameters as $parameter) { - $class = \PHP_VERSION_ID < 80000 ? $parameter->getClass() : ($parameter->getType() && !$parameter->getType()->isBuiltin() - ? new \ReflectionClass($parameter->getType()->getName()) - : null - ); - if (null !== $class && isset($this->dependencies[$class->name])) { - $arguments[$parameter->name] = $this->dependencies[$class->name]; + if (null !== ($constructor = $classReflection->getConstructor())) { + foreach ($constructor->getParameters() as $parameter) { + if (!($type = $parameter->getType()) instanceof \ReflectionNamedType) { + continue; } + + if ($type->isBuiltin()) { + continue; + } + + $class = new \ReflectionClass($type->getName()); + + if (!isset($this->dependencies[$class->name])) { + continue; + } + + $arguments[$parameter->name] = $this->dependencies[$class->name]; } } diff --git a/src/HttpCall/Request.php b/src/HttpCall/Request.php index 98d4dd79..cedc7160 100644 --- a/src/HttpCall/Request.php +++ b/src/HttpCall/Request.php @@ -4,8 +4,21 @@ namespace Behatch\HttpCall; +use Behat\Mink\Element\DocumentElement; use Behat\Mink\Mink; +/** + * @method DocumentElement send(string $method, string $url, array $parameters = [], array $files = [], string $content = null, array $headers = []) + * @method string getMethod() + * @method string getUri() + * @method array getServer() + * @method array getParameters() + * @method array getHttpHeaders() + * @method string getHttpHeader(string $name) + * @method void setHttpHeader(string $name, string $value) + * @method string getHttpRawHeader(string $name) + * @method string getContent() + */ class Request { /** diff --git a/tests/units/Json/JsonInspector.php b/tests/units/Json/JsonInspector.php index e8fa1a8e..44876340 100644 --- a/tests/units/Json/JsonInspector.php +++ b/tests/units/Json/JsonInspector.php @@ -51,7 +51,7 @@ public function test_validate(): void { $json = new \Behatch\Json\Json('{ "foo": { "bar": "foobar" } }'); $inspector = $this->newTestedInstance('php'); - $schema = new \mock\Behatch\Json\JsonSchema('{}'); + $schema = new \Behatch\Json\JsonSchema('{}'); $result = $inspector->validate($json, $schema); From c6f4d9963b8c5c6e3e4d358d056e96fcecae19d6 Mon Sep 17 00:00:00 2001 From: "hubert.lenoir" Date: Fri, 30 Dec 2022 16:21:59 +0100 Subject: [PATCH 2/2] add atoum stubs --- .github/workflows/ci.yml | 6 ++--- phpstan.neon.dist | 31 ++--------------------- tests/units/Context/JsonContext.php | 39 +++++++++++++---------------- 3 files changed, 22 insertions(+), 54 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b41210bb..2afec8ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,10 +46,10 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - tools: pecl, composer extensions: intl, bcmath, curl, openssl, mbstring - coverage: none ini-values: memory_limit=-1 + tools: pecl, composer, phpstan + coverage: none - name: Get composer cache directory id: composercache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT @@ -71,7 +71,7 @@ jobs: phpstan- continue-on-error: true - name: Run PHPStan analysis - run: ./bin/phpstan analyse --no-interaction --no-progress --no-interaction --ansi + run: phpstan analyse --no-interaction --no-progress --no-interaction --ansi atoum: name: Atoum (PHP ${{ matrix.php }}) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3aa54859..0abdfbad 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -3,32 +3,5 @@ parameters: paths: - src - tests - bootstrapFiles: - - vendor/autoload.php - -# inferPrivatePropertyTypeFromConstructor: true -# symfony: -# containerXmlPath: tests/Fixtures/app/var/cache/test/AppKernelTestDebugContainer.xml -# constantHassers: false -# doctrine: -# objectManagerLoader: tests/Fixtures/app/object-manager.php -# bootstrapFiles: -# - vendor/bin/.phpunit/phpunit/vendor/autoload.php -# # We're aliasing classes for phpunit in this file, it needs to be added here see phpstan/#2194 -# - src/Symfony/Bundle/Test/Constraint/ArraySubset.php -# - tests/Fixtures/app/AppKernel.php -# excludePaths: -# # Symfony config -# - tests/Fixtures/app/config/config_swagger.php -# # Symfony cache -# - tests/Fixtures/app/var/ -# - tests/Fixtures/Symfony/Maker -# # The Symfony Configuration API isn't good enough to be analysed -# - src/Symfony/Bundle/DependencyInjection/Configuration.php -# # Templates for Maker -# - src/Symfony/Maker/Resources/skeleton -# earlyTerminatingMethodCalls: -# PHPUnit\Framework\Constraint\Constraint: -# - fail -# ApiPlatform\Metadata\Resource\ResourceMetadataCollection: -# - handleNotFound + scanDirectories: + - vendor/atoum/stubs/classes diff --git a/tests/units/Context/JsonContext.php b/tests/units/Context/JsonContext.php index fbab773c..1ca2a45e 100644 --- a/tests/units/Context/JsonContext.php +++ b/tests/units/Context/JsonContext.php @@ -10,31 +10,26 @@ class JsonContext extends \atoum { - /** - * @var HttpCallResultPool - */ - private $httpCallResultPool; + private HttpCallResultPool $httpCallResultPool; public function beforeTestMethod($methodName): void { - $this->mockGenerator->orphanize('__construct'); - $httpCallResult = $this->newMockInstance(HttpCallResult::class); - $httpCallResult->getMockController()->getValue = json_encode([ - 'a string node' => 'some string', - 'another string node' => 'some other string', - 'a null node' => null, - 'a true node' => true, - 'a false node' => false, - 'a number node' => 3, - 'an array node' => [ - 'one', - 'two', - 'three', - ], - ]); - - $this->httpCallResultPool = $this->newMockInstance(HttpCallResultPool::class); - $this->httpCallResultPool->getMockController()->getResult = $httpCallResult; + $this->httpCallResultPool = new HttpCallResultPool(); + $this->httpCallResultPool->store( + new HttpCallResult(json_encode([ + 'a string node' => 'some string', + 'another string node' => 'some other string', + 'a null node' => null, + 'a true node' => true, + 'a false node' => false, + 'a number node' => 3, + 'an array node' => [ + 'one', + 'two', + 'three', + ], + ], \JSON_THROW_ON_ERROR)) + ); } public function testTheJsonNodeShouldBeEqualTo(): void