diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..4eed74e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,10 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text eol=lf
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/.github export-ignore
+/phpcs.xml.dist export-ignore
+/phpstan.neon.dist export-ignore
+/phpunit.xml.dist export-ignore
+/psalm.xml.dist export-ignore
+/tests export-ignore
diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
new file mode 100644
index 0000000..2582648
--- /dev/null
+++ b/.github/workflows/coding-standards.yml
@@ -0,0 +1,34 @@
+name: "Coding Standards"
+
+on:
+ pull_request:
+ push:
+ branches:
+ - "master"
+
+jobs:
+ coding-standards:
+ name: "Coding Standards"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "none"
+ php-version: "${{ matrix.php-version }}"
+ tools: "cs2pr"
+
+ - name: "Install dependencies with Composer"
+ uses: "ramsey/composer-install@v1"
+
+ - name: "Run PHP_CodeSniffer"
+ run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
new file mode 100644
index 0000000..7131de5
--- /dev/null
+++ b/.github/workflows/continuous-integration.yml
@@ -0,0 +1,74 @@
+name: "CI"
+
+on:
+ pull_request:
+ push:
+ branches:
+ - "master"
+ schedule:
+ - cron: "42 3 * * *"
+
+jobs:
+ phpunit:
+ name: "PHPUnit"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+ - "8.0"
+ dependencies:
+ - "highest"
+ include:
+ - dependencies: "lowest"
+ php-version: "7.4"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+ with:
+ fetch-depth: 2
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: "pcov"
+ ini-values: "zend.assertions=1"
+
+ - name: "Install dependencies with Composer"
+ uses: "ramsey/composer-install@v1"
+ with:
+ dependency-versions: "${{ matrix.dependencies }}"
+
+ - name: "Run PHPUnit"
+ run: "vendor/bin/phpunit --coverage-clover=coverage.xml"
+
+ - name: "Upload coverage file"
+ uses: "actions/upload-artifact@v2"
+ with:
+ name: "phpunit-${{ matrix.deps }}-${{ matrix.php-version }}.coverage"
+ path: "coverage.xml"
+
+ upload_coverage:
+ name: "Upload coverage to Codecov"
+ runs-on: "ubuntu-20.04"
+ needs:
+ - "phpunit"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+ with:
+ fetch-depth: 2
+
+ - name: "Download coverage files"
+ uses: "actions/download-artifact@v2"
+ with:
+ path: "reports"
+
+ - name: "Upload to Codecov"
+ uses: "codecov/codecov-action@v1"
+ with:
+ directory: reports
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
new file mode 100644
index 0000000..62fd758
--- /dev/null
+++ b/.github/workflows/static-analysis.yml
@@ -0,0 +1,59 @@
+name: "Static Analysis"
+
+on:
+ pull_request:
+ push:
+ branches:
+ - "master"
+
+jobs:
+ static-analysis-phpstan:
+ name: "Static Analysis with PHPStan"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+
+ steps:
+ - name: "Checkout code"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "none"
+ php-version: "${{ matrix.php-version }}"
+ tools: "cs2pr"
+
+ - name: "Install dependencies with Composer"
+ uses: "ramsey/composer-install@v1"
+
+ - name: "Run a static analysis with phpstan/phpstan"
+ run: "vendor/bin/phpstan --error-format=checkstyle | cs2pr"
+
+ static-analysis-psalm:
+ name: "Static Analysis with Psalm"
+ runs-on: "ubuntu-20.04"
+
+ strategy:
+ matrix:
+ php-version:
+ - "7.4"
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Psalm
+ uses: docker://vimeo/psalm-github-actions:4.3.2
+ with:
+ args: --shepherd
+ composer_require_dev: true
+ security_analysis: true
+ report_file: results.sarif
+ - name: Upload Security Analysis results to GitHub
+ uses: github/codeql-action/upload-sarif@v1
+ with:
+ sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
index 202d7c9..56ed85a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,8 @@
-/vendor/
/.phpcs-cache
/.phpunit.result.cache
/composer.lock
/phpcs.xml
/phpstan.neon
+/psalm.xml
/phpunit.xml
+/vendor/
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
deleted file mode 100644
index 96b33ae..0000000
--- a/.scrutinizer.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-build:
- nodes:
- analysis:
- environment:
- php:
- version: 7.2
- cache:
- disabled: false
- directories:
- - ~/.composer/cache
- project_setup:
- override: true
- tests:
- override:
- - php-scrutinizer-run
- - phpcs-run
- dependencies:
- override:
- - COMPOSER_ARGS="--prefer-stable" make
-
-checks:
- php:
- code_rating: true
-
-tools:
- external_code_coverage:
- timeout: 3600
-
-build_failure_conditions:
- - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
- - 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
- - 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ed38a5f..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-dist: xenial
-language: php
-
-php:
- - "7.2"
- - "7.3"
- - 7.4snapshot
- - nightly
-
-env:
- - DEPENDENCIES=
- - DEPENDENCIES=--prefer-lowest
-
-before_install:
- - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
-
-install: COMPOSER_ARGS="--prefer-stable $DEPENDENCIES" make
-
-script: make test
-
-stages:
- - Code Quality
- - Test
-
-jobs:
- allow_failures:
- - php: 7.4snapshot
- - php: nightly
-
- include:
- - stage: Code Quality
- name: Lint
- php: "7.2"
- script: make lint
- - name: Coding Standard
- php: "7.2"
- script: make cs
- - name: Static Analysis
- php: "7.2"
- script: make static-analysis
-
- - stage: Test
- name: COVERAGE
- php: "7.2"
- before_script:
- - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
- - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
- script:
- - ./vendor/bin/phpunit --coverage-clover ./build/logs/clover.xml
- after_script:
- - wget https://github.com/scrutinizer-ci/ocular/releases/download/1.6.0/ocular.phar
- - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
-
-cache:
- directories:
- - $HOME/.composer/cache
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..6d861d3
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,30 @@
+License
+=======
+
+This work is published under the MIT license (see full statement below).
+If you want to deal (as described below) only with a part of this work,
+you have to include this license in the part itself.
+
+The MIT License (MIT)
+---------------------
+
+Copyright (c) 2014-2015 VaĊĦek Purchart
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/README.md b/README.md
index 6e391e5..cfebd40 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,38 @@
# JSON RPC 2.0 PSR-7 Message Factory
-[![Build Status](https://travis-ci.com/simpod/json-rpc.svg?branch=master)](https://travis-ci.com/simpod/json-rpc)
-[![Downloads](https://poser.pugx.org/simpod/json-rpc/d/total.svg)](https://packagist.org/packages/simpod/json-rpc)
-[![Packagist](https://poser.pugx.org/simpod/json-rpc/v/stable.svg)](https://packagist.org/packages/simpod/json-rpc)
-[![Licence](https://poser.pugx.org/simpod/json-rpc/license.svg)](https://packagist.org/packages/simpod/json-rpc)
-[![Quality Score](https://scrutinizer-ci.com/g/simpod/json-rpc/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/simpod/json-rpc)
-[![Code Coverage](https://scrutinizer-ci.com/g/simpod/json-rpc/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/simpod/json-rpc)
+[![GitHub Actions][GA Image]][GA Link]
+[![Shepherd Type][Shepherd Image]][Shepherd Link]
+[![Code Coverage][Coverage Image]][CodeCov Link]
+[![Downloads][Downloads Image]][Packagist Link]
+[![Packagist][Packagist Image]][Packagist Link]
+[![Infection MSI][Infection Image]][Infection Link]
+
+## Installation
+
+Add as [Composer](https://getcomposer.org/) dependency:
+
+```sh
+composer require simpod/json-rpc
+```
+
+[GA Image]: https://github.com/simPod/PhpJsonRpc/workflows/CI/badge.svg
+
+[GA Link]: https://github.com/simPod/PhpJsonRpc/actions?query=workflow%3A%22CI%22+branch%3Amaster
+
+[Shepherd Image]: https://shepherd.dev/github/simPod/PhpJsonRpc/coverage.svg
+
+[Shepherd Link]: https://shepherd.dev/github/simPod/PhpJsonRpc
+
+[Coverage Image]: https://codecov.io/gh/simPod/PhpJsonRpc/branch/master/graph/badge.svg
+
+[CodeCov Link]: https://codecov.io/gh/simPod/PhpJsonRpc/branch/master
+
+[Downloads Image]: https://poser.pugx.org/simpod/json-rpc/d/total.svg
+
+[Packagist Image]: https://poser.pugx.org/simpod/json-rpc/v/stable.svg
+
+[Packagist Link]: https://packagist.org/packages/simpod/json-rpc
+
+[Infection Image]: https://badge.stryker-mutator.io/github.com/simPod/PhpJsonRpc/master
+
+[Infection Link]: https://infection.github.io
diff --git a/composer.json b/composer.json
index fdd0691..d7084b4 100644
--- a/composer.json
+++ b/composer.json
@@ -16,7 +16,7 @@
}
},
"require": {
- "php": "^7.2",
+ "php": "^7.4 || ^8.0",
"ext-json": "*",
"nyholm/psr7": "^1.2",
"php-http/httplug": "^2.0",
@@ -25,14 +25,17 @@
"thecodingmachine/safe": "^1.0.2"
},
"require-dev": {
- "doctrine/coding-standard": "^6.0",
+ "cdn77/coding-standard": "^4.0",
+ "hectorj/safe-php-psalm-plugin": "dev-master#b60ed45a06d5246e2efd193ce9c8940b244d5c7d",
"jakub-onderka/php-parallel-lint": "^1.0",
"phpstan/extension-installer": "^1.0",
- "phpstan/phpstan": "^0.11.16",
- "phpstan/phpstan-phpunit": "^0.11.2",
- "phpstan/phpstan-strict-rules": "^0.11.1",
- "phpunit/phpunit": "^8.4",
- "thecodingmachine/phpstan-safe-rule": "^1.0"
+ "phpstan/phpstan": "0.12.81",
+ "phpstan/phpstan-phpunit": "0.12.18",
+ "phpstan/phpstan-strict-rules": "^0.12.7",
+ "phpunit/phpunit": "^9.5",
+ "psalm/plugin-phpunit": "0.15.1",
+ "thecodingmachine/phpstan-safe-rule": "^1.0",
+ "vimeo/psalm": "4.6.3"
},
"config": {
"sort-packages": true
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index e6d6f46..e8ad2a2 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -3,9 +3,11 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd"
>
-
+
-
+
+
+
src/
tests/
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 9e70cb2..8ea502f 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -1,6 +1,4 @@
parameters:
- bootstrap: %currentWorkingDirectory%/tests/bootstrap.php
- memory-limit: -1
level: max
paths:
- %currentWorkingDirectory%/src
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 9e0190f..9247927 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -13,9 +13,9 @@
tests
-
-
+
+
src
-
-
+
+
diff --git a/psalm.xml.dist b/psalm.xml.dist
new file mode 100644
index 0000000..fe880b6
--- /dev/null
+++ b/psalm.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Extractor/Extractor.php b/src/Extractor/Extractor.php
index adccf46..846ccc9 100644
--- a/src/Extractor/Extractor.php
+++ b/src/Extractor/Extractor.php
@@ -5,16 +5,18 @@
namespace SimPod\JsonRpc\Extractor;
use Psr\Http\Message\MessageInterface;
+
use function Safe\json_decode;
abstract class Extractor
{
- /** @var mixed[] */
- protected $messageContents;
+ /** @var array{id: string, jsonrpc: string, error?: array{code: int, message: string, data?: mixed}, method: string, params?: array} */
+ protected array $messageContents;
public function __construct(MessageInterface $message)
{
- $body = $message->getBody();
- $this->messageContents = json_decode((string) $body, true);
+ /** @var array{id: string, jsonrpc: string, error?: array{code: int, message: string, data?: mixed}, method: string, params?: array} $contents */
+ $contents = json_decode((string) $message->getBody(), true);
+ $this->messageContents = $contents;
}
}
diff --git a/src/Extractor/RequestExtractor.php b/src/Extractor/RequestExtractor.php
index c15d244..c3fa34c 100644
--- a/src/Extractor/RequestExtractor.php
+++ b/src/Extractor/RequestExtractor.php
@@ -6,9 +6,7 @@
final class RequestExtractor extends Extractor
{
- /**
- * @return string|int|null
- */
+ /** @return string|int|null */
public function getId()
{
return $this->messageContents['id'] ?? null;
@@ -19,9 +17,7 @@ public function getMethod() : string
return $this->messageContents['method'];
}
- /**
- * @return mixed[]|null
- */
+ /** @return mixed[]|null */
public function getParams() : ?array
{
return $this->messageContents['params'] ?? null;
diff --git a/src/Extractor/ResponseExtractor.php b/src/Extractor/ResponseExtractor.php
index 4eb97ba..9752f73 100644
--- a/src/Extractor/ResponseExtractor.php
+++ b/src/Extractor/ResponseExtractor.php
@@ -8,33 +8,33 @@ final class ResponseExtractor extends Extractor
{
public function getErrorCode() : ?int
{
- return $this->messageContents['error']['code'] ?? null;
+ $error = $this->messageContents['error'] ?? null;
+
+ return $error === null ? null : $error['code'];
}
public function getErrorMessage() : ?string
{
- return $this->messageContents['error']['message'] ?? null;
+ $error = $this->messageContents['error'] ?? null;
+
+ return $error === null ? null : $error['message'];
}
- /**
- * @return mixed
- */
+ /** @return mixed */
public function getErrorData()
{
- return $this->messageContents['error']['data'] ?? null;
+ $error = $this->messageContents['error'] ?? null;
+
+ return $error === null ? null : ($error['data'] ?? null);
}
- /**
- * @return string|int|null
- */
+ /** @return string|int|null */
public function getId()
{
return $this->messageContents['id'] ?? null;
}
- /**
- * @return mixed
- */
+ /** @return mixed */
public function getResult()
{
return $this->messageContents['result'] ?? null;
diff --git a/src/HttpJsonRpcRequestFactory.php b/src/HttpJsonRpcRequestFactory.php
index 53cb555..a265703 100644
--- a/src/HttpJsonRpcRequestFactory.php
+++ b/src/HttpJsonRpcRequestFactory.php
@@ -7,14 +7,14 @@
use Nyholm\Psr7\Stream;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
+
use function Safe\json_encode;
final class HttpJsonRpcRequestFactory implements JsonRpcRequestFactory
{
private const V2_0 = '2.0';
- /** @var RequestFactoryInterface */
- private $messageFactory;
+ private RequestFactoryInterface $messageFactory;
public function __construct(RequestFactoryInterface $messageFactory)
{
@@ -29,8 +29,8 @@ public function notification(string $method, ?array $params = null) : RequestInt
return $this->createRequest(
[
'jsonrpc' => self::V2_0,
- 'method' => $method,
- 'params' => $params,
+ 'method' => $method,
+ 'params' => $params,
]
);
}
@@ -42,8 +42,8 @@ public function request($id, string $method, ?array $params = null) : RequestInt
{
$body = [
'jsonrpc' => self::V2_0,
- 'method' => $method,
- 'id' => $id,
+ 'method' => $method,
+ 'id' => $id,
];
if ($params !== null) {
@@ -53,9 +53,7 @@ public function request($id, string $method, ?array $params = null) : RequestInt
return $this->createRequest($body);
}
- /**
- * @param mixed[] $body
- */
+ /** @param mixed[] $body */
private function createRequest(array $body = []) : RequestInterface
{
return $this->messageFactory->createRequest('POST', '')
diff --git a/src/JsonRpcRequestFactory.php b/src/JsonRpcRequestFactory.php
index b6a5d35..50647ca 100644
--- a/src/JsonRpcRequestFactory.php
+++ b/src/JsonRpcRequestFactory.php
@@ -8,7 +8,7 @@
interface JsonRpcRequestFactory
{
- public const METHOD_REQUEST = 'REQUEST';
+ public const METHOD_REQUEST = 'REQUEST';
public const METHOD_NOTIFICATION = 'NOTIFICATION';
/**