From 69fcbbd11d9dd06c7f84b2bdb417e0943d6edbce Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 20 Jun 2024 11:25:09 +0400 Subject: [PATCH] require php8.1 --- .github/workflows/ci.yml | 10 +- .gitignore | 2 + composer.json | 9 +- phpunit.xml | 14 ++- src/Provider/SymfonyConnect.php | 52 ++-------- src/Provider/SymfonyConnectResourceOwner.php | 38 ++++--- .../{src => }/Provider/SymfonyConnectTest.php | 98 ++++++++++--------- .../{ => Provider}/current_user_response.xml | 0 8 files changed, 94 insertions(+), 129 deletions(-) rename tests/{src => }/Provider/SymfonyConnectTest.php (57%) rename tests/{ => Provider}/current_user_response.xml (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 436cfe4..a60b0c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [ master ] + branches: [ main ] pull_request: jobs: @@ -14,14 +14,14 @@ jobs: - name: 'Setup PHP' uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.1' - uses: "ramsey/composer-install@v3" with: composer-options: "--prefer-dist" - - name: Code Sniffer - run: vendor/bin/phpcs --standard=psr2 src/ + - name: PHP CS Fixer + run: ./vendor/bin/php-cs-fixer check src test: runs-on: ubuntu-latest @@ -29,8 +29,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.4" - - "8.0" - "8.1" - "8.2" - "8.3" diff --git a/.gitignore b/.gitignore index 37f47f1..936a389 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ composer.lock .phpunit.result.cache /build/ +.phpunit.cache +.php-cs-fixer.cache diff --git a/composer.json b/composer.json index a852db5..fd86021 100644 --- a/composer.json +++ b/composer.json @@ -21,12 +21,13 @@ ], "require": { "league/oauth2-client": "^2.0", - "ext-dom": "*" + "ext-dom": "*", + "php": ">=8.1" }, "require-dev": { "mockery/mockery": "^1.3", - "squizlabs/php_codesniffer": "^3.5", - "phpunit/phpunit": ">=8.0 < 10" + "phpunit/phpunit": ">=8.0", + "friendsofphp/php-cs-fixer": "^3.59" }, "autoload": { "psr-4": { @@ -35,7 +36,7 @@ }, "autoload-dev": { "psr-4": { - "Qdequippe\\OAuth2\\Client\\Test\\": "tests/src/" + "Qdequippe\\OAuth2\\Client\\Test\\": "tests/" } }, "extra": { diff --git a/phpunit.xml b/phpunit.xml index 08c14e4..61fa7a1 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,6 @@ - + - - src/ - @@ -22,4 +15,9 @@ + + + src/ + + diff --git a/src/Provider/SymfonyConnect.php b/src/Provider/SymfonyConnect.php index 9f819b3..66a46ac 100644 --- a/src/Provider/SymfonyConnect.php +++ b/src/Provider/SymfonyConnect.php @@ -4,65 +4,39 @@ use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; -use League\OAuth2\Client\Provider\ResourceOwnerInterface; use League\OAuth2\Client\Token\AccessToken; use Psr\Http\Message\ResponseInterface; class SymfonyConnect extends AbstractProvider { + protected string $api = 'https://connect.symfony.com'; - protected $api = 'https://connect.symfony.com'; - - /** - * @return string - */ - #[\ReturnTypeWillChange] - public function getBaseAuthorizationUrl() + public function getBaseAuthorizationUrl(): string { return $this->api . '/oauth/authorize'; } - /** - * @return string - */ - #[\ReturnTypeWillChange] - public function getBaseAccessTokenUrl(array $params) + public function getBaseAccessTokenUrl(array $params): string { return $this->api . '/oauth/access_token'; } - /** - * @return string - */ - #[\ReturnTypeWillChange] - public function getResourceOwnerDetailsUrl(AccessToken $token) + public function getResourceOwnerDetailsUrl(AccessToken $token): string { return $this->api . '/api?access_token='.$token->getToken(); } - /** - * @return string - */ - #[\ReturnTypeWillChange] - protected function getScopeSeparator() + protected function getScopeSeparator(): string { return ' '; } - /** - * @return array - */ - #[\ReturnTypeWillChange] - protected function getDefaultScopes() + protected function getDefaultScopes(): array { return ['SCOPE_PUBLIC']; } - /** - * @return array - */ - #[\ReturnTypeWillChange] - protected function parseResponse(ResponseInterface $response) + protected function parseResponse(ResponseInterface $response): array { $type = $this->getContentType($response); @@ -73,11 +47,7 @@ protected function parseResponse(ResponseInterface $response) return ['xml' => (string)$response->getBody()]; } - /** - * @return void - */ - #[\ReturnTypeWillChange] - protected function checkResponse(ResponseInterface $response, $data) + protected function checkResponse(ResponseInterface $response, $data): void { if ($response->getStatusCode() >= 400) { throw new IdentityProviderException( @@ -88,11 +58,7 @@ protected function checkResponse(ResponseInterface $response, $data) } } - /** - * @return ResourceOwnerInterface - */ - #[\ReturnTypeWillChange] - protected function createResourceOwner(array $response, AccessToken $token) + protected function createResourceOwner(array $response, AccessToken $token): SymfonyConnectResourceOwner { return new SymfonyConnectResourceOwner($response); } diff --git a/src/Provider/SymfonyConnectResourceOwner.php b/src/Provider/SymfonyConnectResourceOwner.php index 92f1847..ae605f1 100644 --- a/src/Provider/SymfonyConnectResourceOwner.php +++ b/src/Provider/SymfonyConnectResourceOwner.php @@ -6,15 +6,8 @@ class SymfonyConnectResourceOwner implements ResourceOwnerInterface { - /** - * @var \DOMElement - */ - private $data; - - /** - * @var \DOMXpath - */ - private $xpath; + private \DOMElement|null|\DOMNode $data; + private \DOMXpath $xpath; public function __construct($response) { @@ -35,15 +28,12 @@ public function __construct($response) $this->data = $user->item(0); } - /** - * @return mixed - **/ - public function getId() + public function getId(): ?string { return $this->data->attributes->getNamedItem('id')->value; } - public function getUsername() + public function getUsername(): ?string { $accounts = $this->xpath->query('./foaf:account/foaf:OnlineAccount', $this->data); for ($i = 0; $i < $accounts->length; ++$i) { @@ -56,22 +46,22 @@ public function getUsername() return null; } - public function getName() + public function getName(): ?string { return $this->getUsername() ?: $this->getNodeValue('./foaf:name', $this->data); } - public function getEmail() + public function getEmail(): ?string { return $this->getNodeValue('./foaf:mbox', $this->data); } - public function getProfilePicture() + public function getProfilePicture(): ?string { return $this->getLinkNodeHref('./atom:link[@rel="foaf:depiction"]', $this->data); } - public function getData() + public function getData(): \DOMNode|\DOMElement|null { return $this->data; } @@ -97,23 +87,29 @@ public function toArray(): array ]; } - protected function getNodeValue($query, \DOMNode $element = null, $index = 0) + protected function getNodeValue($query, \DOMNode $element = null, $index = 0): mixed { $nodeList = $this->xpath->query($query, $element); + if ($nodeList->length > 0 && $index <= $nodeList->length) { return $this->sanitizeValue($nodeList->item($index)->nodeValue); } + + return null; } - protected function getLinkNodeHref($query, \DOMNode $element = null, $position = 0) + protected function getLinkNodeHref($query, \DOMNode $element = null, $position = 0): mixed { $nodeList = $this->xpath->query($query, $element); + if ($nodeList && $nodeList->length > 0 && $nodeList->item($position)) { return $this->sanitizeValue($nodeList->item($position)->attributes->getNamedItem('href')->value); } + + return null; } - protected function sanitizeValue($value) + protected function sanitizeValue(mixed $value): mixed { if ('true' === $value) { return true; diff --git a/tests/src/Provider/SymfonyConnectTest.php b/tests/Provider/SymfonyConnectTest.php similarity index 57% rename from tests/src/Provider/SymfonyConnectTest.php rename to tests/Provider/SymfonyConnectTest.php index cda6386..7571949 100644 --- a/tests/src/Provider/SymfonyConnectTest.php +++ b/tests/Provider/SymfonyConnectTest.php @@ -4,16 +4,19 @@ use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use League\OAuth2\Client\Tool\QueryBuilderTrait; -use Qdequippe\OAuth2\Client\Provider\SymfonyConnect; -use PHPUnit\Framework\TestCase; use Mockery as m; +use PHPUnit\Framework\TestCase; +use Qdequippe\OAuth2\Client\Provider\SymfonyConnect; use Qdequippe\OAuth2\Client\Provider\SymfonyConnectResourceOwner; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\ResponseInterface; +use GuzzleHttp\ClientInterface; class SymfonyConnectTest extends TestCase { use QueryBuilderTrait; - protected $provider; + protected SymfonyConnect $provider; public function setUp(): void { @@ -24,7 +27,7 @@ public function setUp(): void ]); } - public function testAuthorizationUrl() + public function testAuthorizationUrl(): void { $url = $this->provider->getAuthorizationUrl(); $uri = parse_url($url); @@ -38,14 +41,14 @@ public function testAuthorizationUrl() $this->assertNotNull($this->provider->getState()); } - public function testGetAuthorizationUrl() + public function testGetAuthorizationUrl(): void { $url = $this->provider->getAuthorizationUrl(); $uri = parse_url($url); $this->assertEquals('/oauth/authorize', $uri['path']); } - public function testGetBaseAccessTokenUrl() + public function testGetBaseAccessTokenUrl(): void { $params = []; $url = $this->provider->getBaseAccessTokenUrl($params); @@ -53,46 +56,46 @@ public function testGetBaseAccessTokenUrl() $this->assertEquals('/oauth/access_token', $uri['path']); } - public function testGetAccessToken() + public function testGetAccessToken(): void { $testResponse = [ 'access_token' => 'mock_access_token', ]; - $response = m::mock('Psr\Http\Message\ResponseInterface'); - $stream = m::mock('Psr\Http\Message\StreamInterface'); - $stream->shouldReceive('__toString')->andReturn(json_encode($testResponse)); - $response->shouldReceive('getBody')->andReturn($stream); - $response->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); - $response->shouldReceive('getStatusCode')->andReturn(200); - $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send')->times(1)->andReturn($response); + $response = m::mock(ResponseInterface::class); + $stream = m::mock(StreamInterface::class); + $stream->allows('__toString')->andReturns(json_encode($testResponse, JSON_THROW_ON_ERROR)); + $response->allows('getBody')->andReturns($stream); + $response->allows('getHeader')->andReturns(['content-type' => 'json']); + $response->allows('getStatusCode')->andReturns(200); + $client = m::mock(ClientInterface::class); + $client->expects('send')->times(1)->andReturns($response); $this->provider->setHttpClient($client); $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); $this->assertEquals($testResponse['access_token'], $token->getToken()); } - public function testUserData() + public function testUserData(): void { - $xml = file_get_contents(dirname(__FILE__, 3) .'/current_user_response.xml'); - - $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $streamPost = m::mock('Psr\Http\Message\StreamInterface'); - $streamPost->shouldReceive('__toString')->andReturn('{"access_token":"mock_access_token"}'); - $postResponse->shouldReceive('getBody')->andReturn($streamPost); - $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); - $postResponse->shouldReceive('getStatusCode')->andReturn(200); - - $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $streamUser = m::mock('Psr\Http\Message\StreamInterface'); - $streamUser->shouldReceive('__toString')->andReturn($xml); - $userResponse->shouldReceive('getBody')->andReturn($streamUser); - $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/vnd.com.symfony.connect+xml']); - $userResponse->shouldReceive('getStatusCode')->andReturn(200); - $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send') + $xml = file_get_contents(__DIR__.'/current_user_response.xml'); + + $postResponse = m::mock(ResponseInterface::class); + $streamPost = m::mock(StreamInterface::class); + $streamPost->allows('__toString')->andReturns('{"access_token":"mock_access_token"}'); + $postResponse->allows('getBody')->andReturns($streamPost); + $postResponse->allows('getHeader')->andReturns(['content-type' => 'json']); + $postResponse->allows('getStatusCode')->andReturns(200); + + $userResponse = m::mock(ResponseInterface::class); + $streamUser = m::mock(StreamInterface::class); + $streamUser->allows('__toString')->andReturns($xml); + $userResponse->allows('getBody')->andReturns($streamUser); + $userResponse->allows('getHeader')->andReturns(['content-type' => 'application/vnd.com.symfony.connect+xml']); + $userResponse->allows('getStatusCode')->andReturns(200); + $client = m::mock(ClientInterface::class); + $client->expects('send') ->times(2) - ->andReturn($postResponse, $userResponse); + ->andReturns($postResponse, $userResponse); $this->provider->setHttpClient($client); $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); @@ -127,22 +130,23 @@ public function testUserData() $this->assertInstanceOf(\DOMElement::class, $user->getData()); } - public function testExceptionThrownWhenErrorObjectReceived() + public function testExceptionThrownWhenErrorObjectReceived(): void { $this->expectException(IdentityProviderException::class); - $message = uniqid(); - $status = rand(400, 600); - $code = uniqid(); - $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $streamPost = m::mock('Psr\Http\Message\StreamInterface'); - $streamPost->shouldReceive('__toString')->andReturn('{"message": "'.$message.'", "error": "'.$code.'"}'); - $postResponse->shouldReceive('getBody')->andReturn($streamPost); - $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); - $postResponse->shouldReceive('getStatusCode')->andReturn($status); - $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send') + $message = uniqid('', true); + $status = random_int(400, 600); + $code = uniqid('', true); + + $postResponse = m::mock(ResponseInterface::class); + $streamPost = m::mock(StreamInterface::class); + $streamPost->allows('__toString')->andReturns('{"message": "'.$message.'", "error": "'.$code.'"}'); + $postResponse->allows('getBody')->andReturns($streamPost); + $postResponse->allows('getHeader')->andReturns(['content-type' => 'json']); + $postResponse->allows('getStatusCode')->andReturns($status); + $client = m::mock(ClientInterface::class); + $client->expects('send') ->times(1) - ->andReturn($postResponse); + ->andReturns($postResponse); $this->provider->setHttpClient($client); $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); } diff --git a/tests/current_user_response.xml b/tests/Provider/current_user_response.xml similarity index 100% rename from tests/current_user_response.xml rename to tests/Provider/current_user_response.xml