diff --git a/.github/workflows/php-test-workflow.yml b/.github/workflows/php-test-workflow.yml index c540210e..19319489 100644 --- a/.github/workflows/php-test-workflow.yml +++ b/.github/workflows/php-test-workflow.yml @@ -11,10 +11,6 @@ jobs: strategy: matrix: include: - - php: '7.4' - dependencies-preference: " " - - php: '7.4' - dependencies-preference: "--prefer-lowest" - php: '8.0' dependencies-preference: " " - php: '8.0' @@ -50,9 +46,6 @@ jobs: composer config --no-plugins allow-plugins.php-http/discovery true COMPOSER_PROCESS_TIMEOUT=0 composer install --dev --no-suggest --no-interaction - - name: Install Guzzle - run: composer require guzzlehttp/guzzle:^6.3.0 ${{ matrix.dependencies-preference }} --no-progress; - - run: composer show - name: Check style for only changed files diff --git a/composer.json b/composer.json index 8c4ec25d..79734f95 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "homepage": "http://github.com/apigee/apigee-client-php", "license": "Apache-2.0", "require": { - "php": "^7.4 || ^8.0", + "php": "^8.0", "ext-json": "*", "ext-openssl": "*", "ext-reflection": "*", @@ -22,32 +22,32 @@ "league/period": "^4.12", "php-http/client-common": "^2.0", "php-http/client-implementation": "^1.0", - "php-http/discovery": "^1.6.0", + "php-http/discovery": "^1.18", "php-http/httplug": "^2.0", "php-http/message": "^1.13", "php-http/message-factory": "^1.0", "phpdocumentor/reflection-docblock": "^3.0 || ^4.0 || ^5.0", "psr/http-message": "^1.0", - "symfony/options-resolver": "^4.0", - "symfony/property-access": "^4.4.9", - "symfony/property-info": "^4.0", - "symfony/serializer": "^4.4" + "symfony/options-resolver": "^4.0 || ^5.4", + "symfony/property-access": "^4.4.9 || ^6.2", + "symfony/property-info": "^4.0 || ^6.2", + "symfony/serializer": "^4.4 || ^6.2" }, "require-dev": { "dms/phpunit-arraysubset-asserts": "^0.4.0", "friendsofphp/php-cs-fixer": "^2.19", "fzaninotto/faker": "^1.7", - "guzzlehttp/psr7": "^1.0", + "guzzlehttp/psr7": "^1.0 || ^2.0", "league/flysystem": "^1.0", "limedeck/phpunit-detailed-printer": "^6", "monolog/monolog": "^1.23", - "php-http/guzzle6-adapter": "^2.0", + "php-http/guzzle7-adapter": "^1.0", "php-http/mock-client": "^1.1.0", "phpmetrics/phpmetrics": "^2.7", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^9.6", "sebastian/comparator": "^4.0.5", "symfony/cache": "~3.4 || ~4.0", - "vimeo/psalm": "^3.18.2" + "vimeo/psalm": "^3.18.2 || ^5.0" }, "conflict": { "guzzlehttp/guzzle": "<6.1.0", diff --git a/psalm.xml.dist b/psalm.xml.dist index 0c31e235..b0582145 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -20,7 +20,9 @@ - + + + @@ -34,7 +36,7 @@ - + @@ -47,5 +49,8 @@ + + + diff --git a/src/Api/ApigeeX/Entity/Property/StartTimePropertyAwareTrait.php b/src/Api/ApigeeX/Entity/Property/StartTimePropertyAwareTrait.php index 35a4b7d5..eddb2213 100755 --- a/src/Api/ApigeeX/Entity/Property/StartTimePropertyAwareTrait.php +++ b/src/Api/ApigeeX/Entity/Property/StartTimePropertyAwareTrait.php @@ -41,8 +41,8 @@ public function getStartTime(): ?string /** * {@inheritdoc} */ - public function setStartTime(string $startTime): void + public function setStartTime(string $startDate): void { - $this->startTime = $startTime; + $this->startTime = $startDate; } } diff --git a/src/Api/ApigeeX/Normalizer/ApiProductNormalizer.php b/src/Api/ApigeeX/Normalizer/ApiProductNormalizer.php index 8ebb08db..3facc018 100755 --- a/src/Api/ApigeeX/Normalizer/ApiProductNormalizer.php +++ b/src/Api/ApigeeX/Normalizer/ApiProductNormalizer.php @@ -50,9 +50,9 @@ public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory */ public function normalize($object, $format = null, array $context = []) { - $normalized = (array) parent::normalize($object, $format, $context); + $normalized = parent::normalize($object, $format, $context); - return (object) $normalized; + return $normalized; } /** diff --git a/src/Api/Management/Controller/AppCredentialController.php b/src/Api/Management/Controller/AppCredentialController.php index ad1471fe..f906415f 100644 --- a/src/Api/Management/Controller/AppCredentialController.php +++ b/src/Api/Management/Controller/AppCredentialController.php @@ -167,9 +167,9 @@ public function overrideScopes(string $consumerKey, array $scopes): AppCredentia /** * {@inheritdoc} */ - public function load(string $entityId): AppCredentialInterface + public function load(string $consumerKey): AppCredentialInterface { - $response = $this->client->get($this->getEntityEndpointUri($entityId)); + $response = $this->client->get($this->getEntityEndpointUri($consumerKey)); return $this->entitySerializer->deserialize( (string) $response->getBody(), @@ -181,9 +181,9 @@ public function load(string $entityId): AppCredentialInterface /** * {@inheritdoc} */ - public function delete(string $entityId): AppCredentialInterface + public function delete(string $consumerKey): AppCredentialInterface { - $response = $this->client->delete($this->getEntityEndpointUri($entityId)); + $response = $this->client->delete($this->getEntityEndpointUri($consumerKey)); return $this->entitySerializer->deserialize( (string) $response->getBody(), diff --git a/src/Api/Management/Entity/ApiProduct.php b/src/Api/Management/Entity/ApiProduct.php index 3a2eb8e2..aa0b84c7 100644 --- a/src/Api/Management/Entity/ApiProduct.php +++ b/src/Api/Management/Entity/ApiProduct.php @@ -95,9 +95,9 @@ public function getProxies(): array /** * {@inheritdoc} */ - public function setProxies(string ...$proxies): void + public function setProxies(string ...$proxy): void { - $this->proxies = $proxies; + $this->proxies = $proxy; } /** diff --git a/src/Api/Management/Normalizer/CompanyMembershipNormalizer.php b/src/Api/Management/Normalizer/CompanyMembershipNormalizer.php index 227b9b34..c4f856a9 100644 --- a/src/Api/Management/Normalizer/CompanyMembershipNormalizer.php +++ b/src/Api/Management/Normalizer/CompanyMembershipNormalizer.php @@ -39,7 +39,9 @@ public function normalize($object, $format = null, array $context = []) $normalized['developer'][] = (object) ['email' => $member, 'role' => $role]; } - return (object) $normalized; + //convert to ArrayObject as symfony normalizer throws error for std object. + //set ARRAY_AS_PROPS flag as we need entries to be accessed as properties. + return new \ArrayObject($normalized, \ArrayObject::ARRAY_AS_PROPS); } /** diff --git a/src/Api/Monetization/Builder/RatePlanRevisionBuilder.php b/src/Api/Monetization/Builder/RatePlanRevisionBuilder.php index 505f0d43..e14d1d7d 100644 --- a/src/Api/Monetization/Builder/RatePlanRevisionBuilder.php +++ b/src/Api/Monetization/Builder/RatePlanRevisionBuilder.php @@ -85,7 +85,9 @@ public static function buildRatePlanRevision(RatePlanInterface $ratePlan, DateTi unset($normalized->id); // Remove the end date inherited from the parent rate plan because // it may overlap with the new start date. - unset($normalized->endDate); + if (isset($normalized->endDate)) { + unset($normalized->endDate); + } // Create a new rate plan revision from the "copy" of parent rate plan. // We have to disable the type enforcement because an empty "addresses" // array is being passed to the organization which causes failures. diff --git a/src/Api/Monetization/Entity/OrganizationProfile.php b/src/Api/Monetization/Entity/OrganizationProfile.php index 351d188d..eb67116e 100644 --- a/src/Api/Monetization/Entity/OrganizationProfile.php +++ b/src/Api/Monetization/Entity/OrganizationProfile.php @@ -192,9 +192,9 @@ public function getCurrencyCode(): ?string /** * {@inheritdoc} */ - public function setCurrencyCode(string $currencyCode): void + public function setCurrencyCode(string $currency): void { - $this->currencyCode = $currencyCode; + $this->currencyCode = $currency; } /** @@ -216,9 +216,9 @@ public function hasBillingAdjustment(): bool /** * {@inheritdoc} */ - public function setBillingAdjustment(bool $billingAdjustment): void + public function setBillingAdjustment(bool $hasBillingAdjustment): void { - $this->billingAdjustment = $billingAdjustment; + $this->billingAdjustment = $hasBillingAdjustment; } /** @@ -240,9 +240,9 @@ public function hasSeparateInvoiceForProduct(): bool /** * {@inheritdoc} */ - public function setSeparateInvoiceForProduct(bool $separateInvoiceForProduct): void + public function setSeparateInvoiceForProduct(bool $hasSeparateInvoiceForProduct): void { - $this->separateInvoiceForProduct = $separateInvoiceForProduct; + $this->separateInvoiceForProduct = $hasSeparateInvoiceForProduct; } /** diff --git a/src/Api/Monetization/NameConverter/NameConverterBase.php b/src/Api/Monetization/NameConverter/NameConverterBase.php index 5f684780..1b960005 100644 --- a/src/Api/Monetization/NameConverter/NameConverterBase.php +++ b/src/Api/Monetization/NameConverter/NameConverterBase.php @@ -25,7 +25,7 @@ abstract class NameConverterBase implements NameConverterInterface /** * {@inheritdoc} */ - public function normalize($propertyName) + public function normalize($propertyName): string { if ($externalPropertyName = array_search($propertyName, $this->getExternalToLocalMapping())) { return $externalPropertyName; @@ -37,7 +37,7 @@ public function normalize($propertyName) /** * {@inheritdoc} */ - public function denormalize($propertyName) + public function denormalize($propertyName): string { if (array_key_exists($propertyName, $this->getExternalToLocalMapping())) { return $this->getExternalToLocalMapping()[$propertyName]; diff --git a/src/Api/Monetization/Normalizer/ApiPackageNormalizer.php b/src/Api/Monetization/Normalizer/ApiPackageNormalizer.php index 6c086684..5a30f3d9 100644 --- a/src/Api/Monetization/Normalizer/ApiPackageNormalizer.php +++ b/src/Api/Monetization/Normalizer/ApiPackageNormalizer.php @@ -54,10 +54,10 @@ public function normalize($object, $format = null, array $context = []) // Do not send redundant API product information to Apigee Edge, the // id of a referenced API product is enough. foreach ($normalized['product'] as $id => $data) { - $normalized['product'][$id] = (object) ['id' => $data->id]; + $normalized['product'][$id] = (object) ['id' => $data['id']]; } - return (object) $normalized; + return $this->convertToArrayObject($normalized); } /** diff --git a/src/Api/Monetization/Normalizer/EntityNormalizer.php b/src/Api/Monetization/Normalizer/EntityNormalizer.php index ade03680..f3124a9b 100644 --- a/src/Api/Monetization/Normalizer/EntityNormalizer.php +++ b/src/Api/Monetization/Normalizer/EntityNormalizer.php @@ -96,7 +96,7 @@ public function normalize($object, $format = null, array $context = []) } } - return (object) $normalized; + return $this->convertToArrayObject($normalized); } /** diff --git a/src/Api/Monetization/Normalizer/ReportCriteriaNormalizer.php b/src/Api/Monetization/Normalizer/ReportCriteriaNormalizer.php index 857a8e18..eb28d0b5 100644 --- a/src/Api/Monetization/Normalizer/ReportCriteriaNormalizer.php +++ b/src/Api/Monetization/Normalizer/ReportCriteriaNormalizer.php @@ -75,14 +75,19 @@ public function normalize($object, $format = null, array $context = []) // According to the API documentation it is always UTC. // https://docs.apigee.com/api-platform/monetization/create-reports#createreportdefapi $this->fixTimeZoneOnNormalization($object, $normalized, new \DateTimeZone('UTC')); - + $arr_empty = []; // Just in case, do not send empty array values either to this API. foreach ($normalized as $property => $value) { if (is_array($value) && empty($value)) { - unset($normalized->{$property}); + //Get all the array which is empty + $arr_empty[] = $property; } } + foreach ($arr_empty as $val) { + unset($normalized->{$val}); + } + return $normalized; } diff --git a/src/Api/Monetization/Serializer/ReportDefinitionSerializer.php b/src/Api/Monetization/Serializer/ReportDefinitionSerializer.php index d04d17e7..6826ade0 100644 --- a/src/Api/Monetization/Serializer/ReportDefinitionSerializer.php +++ b/src/Api/Monetization/Serializer/ReportDefinitionSerializer.php @@ -69,8 +69,9 @@ public static function getEntityTypeSpecificDefaultNormalizers(): array /** * {@inheritdoc} */ - public function deserialize($data, $type, $format, array $context = []) + public function deserialize($data, $type, $format, array $context = []): mixed { + $context['json_decode_associative'] = false; // Because of the decorator pattern in the EntitySerializer we have to // repeat this in here as well. if ($this->supportsDecoding($format)) { diff --git a/src/Controller/PaginationHelperTrait.php b/src/Controller/PaginationHelperTrait.php index 5496b47d..0db6a697 100644 --- a/src/Controller/PaginationHelperTrait.php +++ b/src/Controller/PaginationHelperTrait.php @@ -203,7 +203,7 @@ private function listEntitiesWithCps(PagerInterface $pager = null, array $query_ // Apigee Edge response always starts with the requested entity // (startKey). array_shift($tmp); - $tmpEntities = $this->responseArrayToArrayOfEntities($tmp, $key_provider); + $tmpEntities = $this->responseArrayToArrayOfEntities((array) $tmp, $key_provider); if (count($tmpEntities) > 0) { // The returned entity array is keyed by entity id which diff --git a/src/Denormalizer/EdgeDateDenormalizer.php b/src/Denormalizer/EdgeDateDenormalizer.php index 3a5ae6df..a87dfc61 100644 --- a/src/Denormalizer/EdgeDateDenormalizer.php +++ b/src/Denormalizer/EdgeDateDenormalizer.php @@ -60,7 +60,10 @@ public function denormalize($data, $type, $format = null, array $context = []) $context[$this->normalizer::FORMAT_KEY] = 'U'; $context[$this->normalizer::TIMEZONE_KEY] = new \DateTimeZone('UTC'); - return $this->normalizer->denormalize(intval($data / 1000), $type, $format, $context); + //convert data in string format for denormalizer. + $data = (string) intval($data / 1000); + + return $this->normalizer->denormalize($data, $type, $format, $context); } /** diff --git a/src/Denormalizer/ObjectDenormalizer.php b/src/Denormalizer/ObjectDenormalizer.php index 43acb4b4..4aaf0200 100644 --- a/src/Denormalizer/ObjectDenormalizer.php +++ b/src/Denormalizer/ObjectDenormalizer.php @@ -69,7 +69,6 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $propertyTypeExtractor = new PropertyInfoExtractor( [ $reflectionExtractor, - $phpDocExtractor, ], // Type extractors [ diff --git a/src/Exception/ApiException.php b/src/Exception/ApiException.php index 3d65fb05..149b5832 100644 --- a/src/Exception/ApiException.php +++ b/src/Exception/ApiException.php @@ -28,7 +28,7 @@ class ApiException extends RuntimeException implements Exception /** * {@inheritdoc} */ - public function __toString() + public function __toString(): string { // This is just a wrapper around the base class and if it contains a reference to the previous // exception we should display that as a string. diff --git a/src/Exception/ApiRequestException.php b/src/Exception/ApiRequestException.php index 99d82afa..8bea9e56 100644 --- a/src/Exception/ApiRequestException.php +++ b/src/Exception/ApiRequestException.php @@ -60,7 +60,7 @@ public function __construct( /** * {@inheritdoc} */ - public function __toString() + public function __toString(): string { $output = [ get_called_class() . PHP_EOL, diff --git a/src/Exception/ApiResponseException.php b/src/Exception/ApiResponseException.php index 16908a21..ee4a9e26 100644 --- a/src/Exception/ApiResponseException.php +++ b/src/Exception/ApiResponseException.php @@ -63,7 +63,7 @@ public function __construct( /** * {@inheritdoc} */ - public function __toString() + public function __toString(): string { $output = [ get_called_class() . PHP_EOL, diff --git a/src/Exception/CpsNotEnabledException.php b/src/Exception/CpsNotEnabledException.php index 2a8bb040..078355ac 100644 --- a/src/Exception/CpsNotEnabledException.php +++ b/src/Exception/CpsNotEnabledException.php @@ -48,7 +48,7 @@ public function __construct(string $organization, $code = 0, Throwable $previous $this->organization = $organization; } - public function __toString() + public function __toString(): string { return "Core Persistence Services is not enabled on {$this->organization} organization."; } diff --git a/src/Exception/InvalidJsonException.php b/src/Exception/InvalidJsonException.php index 623b27a1..57509a27 100644 --- a/src/Exception/InvalidJsonException.php +++ b/src/Exception/InvalidJsonException.php @@ -53,7 +53,7 @@ public function __construct( /** * {@inheritdoc} */ - public function __toString() + public function __toString(): string { return sprintf("%s\n%s", $this->jsonErrorMessage, parent::__toString()); } diff --git a/src/HttpClient/Plugin/Authentication/ApigeeOnGcpOauth2.php b/src/HttpClient/Plugin/Authentication/ApigeeOnGcpOauth2.php index 39c7634c..15b57fae 100644 --- a/src/HttpClient/Plugin/Authentication/ApigeeOnGcpOauth2.php +++ b/src/HttpClient/Plugin/Authentication/ApigeeOnGcpOauth2.php @@ -116,7 +116,7 @@ protected function getAccessToken(): void try { $jwt = JWT::encode($token, $this->privateKey, 'RS256'); } catch (DomainException $e) { - throw new ApigeeOnGcpOauth2AuthenticationException($e->getMessage(), (int) $e->getCode(), $e); + throw new ApigeeOnGcpOauth2AuthenticationException($e->getMessage(), $e->getCode(), $e); } $body = [ @@ -129,7 +129,7 @@ protected function getAccessToken(): void $decodedResponse = json_decode((string) $response->getBody(), true); $this->tokenStorage->saveToken($decodedResponse); } catch (Exception $e) { - throw new ApigeeOnGcpOauth2AuthenticationException($e->getMessage(), (int) $e->getCode(), $e); + throw new ApigeeOnGcpOauth2AuthenticationException($e->getMessage(), $e->getCode(), $e); } } } diff --git a/src/HttpClient/Plugin/Authentication/GceServiceAccount.php b/src/HttpClient/Plugin/Authentication/GceServiceAccount.php index 7a93f8f0..f23c9044 100644 --- a/src/HttpClient/Plugin/Authentication/GceServiceAccount.php +++ b/src/HttpClient/Plugin/Authentication/GceServiceAccount.php @@ -86,7 +86,7 @@ protected function getAccessToken(): void $decoded_token = json_decode((string) $response->getBody(), true); $this->tokenStorage->saveToken($decoded_token); } catch (Exception $e) { - throw new ApigeeOnGcpOauth2AuthenticationException($e->getMessage(), (int) $e->getCode(), $e); + throw new ApigeeOnGcpOauth2AuthenticationException($e->getMessage(), $e->getCode(), $e); } } diff --git a/src/HttpClient/Plugin/Authentication/HybridOauth2.php b/src/HttpClient/Plugin/Authentication/HybridOauth2.php index 3b54ef10..7e0cc34c 100644 --- a/src/HttpClient/Plugin/Authentication/HybridOauth2.php +++ b/src/HttpClient/Plugin/Authentication/HybridOauth2.php @@ -118,7 +118,7 @@ protected function getAccessToken(): void try { $jwt = JWT::encode($token, $this->privateKey, 'RS256'); } catch (DomainException $e) { - throw new HybridOauth2AuthenticationException($e->getMessage(), (int) $e->getCode(), $e); + throw new HybridOauth2AuthenticationException($e->getMessage(), $e->getCode(), $e); } $body = [ @@ -131,7 +131,7 @@ protected function getAccessToken(): void $decodedResponse = json_decode((string) $response->getBody(), true); $this->tokenStorage->saveToken($decodedResponse); } catch (Exception $e) { - throw new HybridOauth2AuthenticationException($e->getMessage(), (int) $e->getCode(), $e); + throw new HybridOauth2AuthenticationException($e->getMessage(), $e->getCode(), $e); } } } diff --git a/src/HttpClient/Plugin/Authentication/Oauth.php b/src/HttpClient/Plugin/Authentication/Oauth.php index 099ed4ce..3a41a8c7 100644 --- a/src/HttpClient/Plugin/Authentication/Oauth.php +++ b/src/HttpClient/Plugin/Authentication/Oauth.php @@ -143,7 +143,7 @@ protected function getAccessToken(): void // id and secret. $this->getAccessToken(); } catch (Exception $e) { - throw new OauthAuthenticationException($e->getMessage(), (int) $e->getCode(), $e); + throw new OauthAuthenticationException($e->getMessage(), $e->getCode(), $e); } } } diff --git a/src/HttpClient/Plugin/ResponseHandlerPlugin.php b/src/HttpClient/Plugin/ResponseHandlerPlugin.php index 41fcb154..7209c902 100644 --- a/src/HttpClient/Plugin/ResponseHandlerPlugin.php +++ b/src/HttpClient/Plugin/ResponseHandlerPlugin.php @@ -70,10 +70,10 @@ public function handleRequest(RequestInterface $request, callable $next, callabl if (is_a($e, HttpException::class)) { $this->decodeResponse($e->getResponse(), $request); } elseif (is_a($e, RequestException::class) || is_a($e, NetworkException::class)) { - throw new ApiRequestException($request, $e->getMessage(), (int) $e->getCode(), $e); + throw new ApiRequestException($request, $e->getMessage(), $e->getCode(), $e); } - throw new ApiException($e->getMessage(), (int) $e->getCode(), $e); + throw new ApiException($e->getMessage(), $e->getCode(), $e); }); } diff --git a/src/HttpClient/Utility/Builder.php b/src/HttpClient/Utility/Builder.php index e19ccb64..c5c0f167 100644 --- a/src/HttpClient/Utility/Builder.php +++ b/src/HttpClient/Utility/Builder.php @@ -21,10 +21,10 @@ use Http\Client\Common\Plugin; use Http\Client\Common\Plugin\HeaderAppendPlugin; use Http\Client\Common\PluginClient; -use Http\Client\HttpClient; -use Http\Discovery\HttpClientDiscovery; -use Http\Message\RequestFactory; -use Http\Message\StreamFactory; +use Http\Discovery\Psr18ClientDiscovery; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; /** * Class Builder. @@ -33,7 +33,7 @@ */ class Builder implements BuilderInterface { - /** @var HttpClient */ + /** @var \Psr\Http\Client\ClientInterface */ private $httpClient; /** @var PluginClient */ @@ -55,16 +55,16 @@ class Builder implements BuilderInterface /** * Builder constructor. * - * @param \Http\Client\HttpClient|null $httpClient - * @param \Http\Message\RequestFactory|null $requestFactory - * @param \Http\Message\StreamFactory|null $streamFactory + * @param \Psr\Http\Client\ClientInterface|null $httpClient + * @param \Psr\Http\Message\RequestFactoryInterface|null $requestFactory + * @param \Psr\Http\Message\StreamFactoryInterface|null $streamFactory */ public function __construct( - HttpClient $httpClient = null, - RequestFactory $requestFactory = null, - StreamFactory $streamFactory = null + ClientInterface $httpClient = null, + RequestFactoryInterface $requestFactory = null, + StreamFactoryInterface $streamFactory = null ) { - $this->httpClient = $httpClient ?: HttpClientDiscovery::find(); + $this->httpClient = $httpClient ?: Psr18ClientDiscovery::find(); if (null !== $requestFactory) { @trigger_error('The $requestFactory parameter is deprecated since version 2.0.3 and will be removed in 3.0.0. Omit the second parameter.', E_USER_DEPRECATED); } @@ -77,7 +77,7 @@ public function __construct( /** * {@inheritdoc} */ - public function getHttpClient(): HttpClient + public function getHttpClient(): ClientInterface { if ($this->rebuild()) { $this->pluginClient = new PluginClient($this->httpClient, $this->plugins); diff --git a/src/HttpClient/Utility/BuilderInterface.php b/src/HttpClient/Utility/BuilderInterface.php index c0b7c140..a4dcd8d8 100644 --- a/src/HttpClient/Utility/BuilderInterface.php +++ b/src/HttpClient/Utility/BuilderInterface.php @@ -19,7 +19,7 @@ namespace Apigee\Edge\HttpClient\Utility; use Http\Client\Common\Plugin; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; /** * Interface BuilderInterface. @@ -29,9 +29,9 @@ interface BuilderInterface { /** - * @return \Http\Client\HttpClient + * @return \Psr\Http\Client\ClientInterface */ - public function getHttpClient(): HttpClient; + public function getHttpClient(): ClientInterface; /** * @param array $headers Associate array of HTTP headers. diff --git a/src/Normalizer/CredentialProductNormalizer.php b/src/Normalizer/CredentialProductNormalizer.php index 361337da..2f882a70 100644 --- a/src/Normalizer/CredentialProductNormalizer.php +++ b/src/Normalizer/CredentialProductNormalizer.php @@ -35,10 +35,14 @@ class CredentialProductNormalizer implements NormalizerInterface public function normalize($object, $format = null, array $context = []) { /* @var \Apigee\Edge\Structure\CredentialProductInterface $object */ - return (object) [ + $asObject = [ 'apiproduct' => $object->getApiproduct(), 'status' => $object->getStatus(), ]; + + //Need to convert to ArrayObject as symfony normalizer throws error for std object. + //Need to set ARRAY_AS_PROPS flag as we need Entries to be accessed as properties. + return new \ArrayObject($asObject, \ArrayObject::ARRAY_AS_PROPS); } /** diff --git a/src/Normalizer/ObjectNormalizer.php b/src/Normalizer/ObjectNormalizer.php index eff2fab6..f24e5ce4 100644 --- a/src/Normalizer/ObjectNormalizer.php +++ b/src/Normalizer/ObjectNormalizer.php @@ -69,7 +69,6 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $propertyTypeExtractor = new PropertyInfoExtractor( [ $reflectionExtractor, - $phpDocExtractor, ], // Type extractors [ @@ -104,7 +103,7 @@ public function normalize($object, $format = null, array $context = []) }); ksort($asArray); - return (object) $asArray; + return $this->convertToArrayObject($asArray); } /** @@ -126,4 +125,13 @@ public function setSerializer(SerializerInterface $serializer): void $this->serializer = $serializer; $this->objectNormalizer->setSerializer($serializer); } + + /** + * {@inheritDoc} + */ + public function convertToArrayObject($normalized, $array_as_props = \ArrayObject::ARRAY_AS_PROPS) + { + //default set ARRAY_AS_PROPS flag as we need entries to be accessed as properties. + return new \ArrayObject($normalized, $array_as_props); + } } diff --git a/src/Normalizer/PropertiesPropertyNormalizer.php b/src/Normalizer/PropertiesPropertyNormalizer.php index 57661da8..1ea099d0 100644 --- a/src/Normalizer/PropertiesPropertyNormalizer.php +++ b/src/Normalizer/PropertiesPropertyNormalizer.php @@ -39,7 +39,9 @@ public function normalize($object, $format = null, array $context = []) 'property' => parent::normalize($object, $format, $context), ]; - return (object) $return; + //convert to ArrayObject as symfony normalizer throws error for std object. + //set ARRAY_AS_PROPS flag as we need entries to be accessed as properties. + return new \ArrayObject($return, \ArrayObject::ARRAY_AS_PROPS); } /** diff --git a/src/PropertyAccess/PropertyAccessorDecorator.php b/src/PropertyAccess/PropertyAccessorDecorator.php index e90cba7d..0254c569 100644 --- a/src/PropertyAccess/PropertyAccessorDecorator.php +++ b/src/PropertyAccess/PropertyAccessorDecorator.php @@ -20,7 +20,6 @@ use Apigee\Edge\Exception\UnexpectedValueException; use Apigee\Edge\Exception\UninitializedPropertyException; -use Symfony\Component\PropertyAccess\Exception\AccessException; use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -76,7 +75,7 @@ public function setValue(&$objectOrArray, $propertyPath, $value): void $objectOrArray->{$setter}(...$value); } } catch (\TypeError $typeError) { - self::processTypeErrorOnSetValue($typeError->getMessage(), $typeError->getTrace(), 0, $typeError); + self::processTypeErrorOnSetValue($typeError->getMessage(), $typeError->getTrace(), 0, (string) $typeError); // Rethrow the exception if it could not be transformed // to an invalid argument exception. @@ -91,7 +90,7 @@ public function setValue(&$objectOrArray, $propertyPath, $value): void /** * {@inheritdoc} */ - public function getValue($objectOrArray, $propertyPath) + public function getValue($objectOrArray, $propertyPath): mixed { try { $value = $this->propertyAccessor->getValue($objectOrArray, $propertyPath); @@ -112,7 +111,7 @@ public function getValue($objectOrArray, $propertyPath) /** * {@inheritdoc} */ - public function isWritable($objectOrArray, $propertyPath) + public function isWritable($objectOrArray, $propertyPath): bool { return $this->propertyAccessor->isWritable($objectOrArray, $propertyPath); } @@ -120,7 +119,7 @@ public function isWritable($objectOrArray, $propertyPath) /** * {@inheritdoc} */ - public function isReadable($objectOrArray, $propertyPath) + public function isReadable($objectOrArray, $propertyPath): bool { return $this->propertyAccessor->isReadable($objectOrArray, $propertyPath); } @@ -174,6 +173,7 @@ private static function processTypeErrorOnGetValue($object, string $property, \T * @param $message * @param $trace * @param $i + * @param $propertyPath * @param $previous * * @see \Symfony\Component\PropertyAccess\PropertyAccessor::throwInvalidArgumentException() @@ -181,30 +181,20 @@ private static function processTypeErrorOnGetValue($object, string $property, \T * @psalm-suppress PossiblyFalseOperand * @psalm-suppress PossiblyFalseArgument */ - private static function processTypeErrorOnSetValue($message, $trace, $i, $previous = null): void + private static function processTypeErrorOnSetValue($message, $trace, $i, string $propertyPath, $previous = null): void { if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) { return; } + if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/', $message, $matches)) { + [, $expectedType, $actualType] = $matches; - if (\PHP_VERSION_ID < 80000) { - if (0 !== strpos($message, 'Argument ')) { - return; - } - - $pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface ')); - $pos += \strlen($delim); - $j = strpos($message, ',', $pos); - $type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2); - $message = substr($message, $pos, $j - $pos); - - throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given.', $message, 'NULL' === $type ? 'null' : $type), 0, $previous); + throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous); } + if (preg_match('/^Cannot assign (\S+) to property \S+::\$\S+ of type (\S+)$/', $message, $matches)) { + [, $actualType, $expectedType] = $matches; - if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/', $message, $matches)) { - list(, $expectedType, $actualType) = $matches; - - throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given.', $expectedType, 'NULL' === $actualType ? 'null' : $actualType), 0, $previous); + throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous); } } } diff --git a/src/Serializer/EntitySerializer.php b/src/Serializer/EntitySerializer.php index b9643d64..87223c94 100755 --- a/src/Serializer/EntitySerializer.php +++ b/src/Serializer/EntitySerializer.php @@ -103,7 +103,7 @@ public function supportsNormalization($data, $format = null) /** * {@inheritdoc} */ - public function serialize($data, $format, array $context = []) + public function serialize($data, $format, array $context = []): string { if (!$this->supportsEncoding($format)) { throw new NotEncodableValueException(sprintf('Serialization for the format %s is not supported. Only %s supported.', $format, $this->format)); @@ -115,12 +115,14 @@ public function serialize($data, $format, array $context = []) /** * {@inheritdoc} */ - public function deserialize($data, $type, $format, array $context = []) + public function deserialize($data, $type, $format, array $context = []): mixed { if (!$this->supportsDecoding($format)) { throw new NotEncodableValueException(sprintf('Deserialization for the format %s is not supported. Only %s is supported.', $format, $this->format)); } + $context['json_decode_associative'] = false; + return $this->serializer->deserialize($data, $type, $format, $context); } @@ -202,7 +204,7 @@ public function supportsDecoding($format) /** * {@inheritdoc} */ - public function encode($data, $format, array $context = []) + public function encode($data, $format, array $context = []): string { return $this->serializer->encode($data, $format, $context = []); } @@ -210,7 +212,7 @@ public function encode($data, $format, array $context = []) /** * {@inheritdoc} */ - public function supportsEncoding($format) + public function supportsEncoding($format): bool { return $this->format === $format && $this->serializer->supportsEncoding($format); } diff --git a/src/Serializer/JsonDecode.php b/src/Serializer/JsonDecode.php index 9b09a041..cea70ddb 100644 --- a/src/Serializer/JsonDecode.php +++ b/src/Serializer/JsonDecode.php @@ -67,7 +67,7 @@ public function __construct($defaultContext = [], int $depth = 512) ]; } - parent::__construct($defaultContext, $depth); + parent::__construct($defaultContext); // Following the same logic as in JsonEcode. $this->options = JSON_PRESERVE_ZERO_FRACTION; } @@ -75,7 +75,7 @@ public function __construct($defaultContext = [], int $depth = 512) /** * {@inheritdoc} */ - public function decode($data, $format, array $context = []) + public function decode($data, $format, array $context = []): mixed { $context['json_decode_options'] = empty($context['json_decode_options']) ? $this->options : $context['json_decode_options']; diff --git a/src/Structure/KeyValueMapInterface.php b/src/Structure/KeyValueMapInterface.php index 7b6502da..a380b672 100644 --- a/src/Structure/KeyValueMapInterface.php +++ b/src/Structure/KeyValueMapInterface.php @@ -20,6 +20,8 @@ /** * Interface KeyValueMapInterface. + * + * @extends \IteratorAggregate */ interface KeyValueMapInterface extends \IteratorAggregate { diff --git a/tests/Api/Monetization/EntitySerializer/PropertyValidator/OrganizationProfileEntityReferencePropertyValidator.php b/tests/Api/Monetization/EntitySerializer/PropertyValidator/OrganizationProfileEntityReferencePropertyValidator.php index 3da842bb..8e42624e 100644 --- a/tests/Api/Monetization/EntitySerializer/PropertyValidator/OrganizationProfileEntityReferencePropertyValidator.php +++ b/tests/Api/Monetization/EntitySerializer/PropertyValidator/OrganizationProfileEntityReferencePropertyValidator.php @@ -41,7 +41,7 @@ public function validate(\stdClass $input, \stdClass $output, EntityInterface $e return; } - Assert::assertEquals($output->{static::validatedProperty()}, (object) ['id' => $entity->getOrganization()->id()]); + Assert::assertEquals((object) ['id' => $output->{static::validatedProperty()}->id], (object) ['id' => $entity->getOrganization()->id()]); $actual = json_decode($this->entitySerializer->serialize($entity->getOrganization(), 'json')); $expected = $input->{static::validatedProperty()}; diff --git a/tests/HttpClient/Utility/BuilderTest.php b/tests/HttpClient/Utility/BuilderTest.php index 1a8c4edd..61a41bc7 100644 --- a/tests/HttpClient/Utility/BuilderTest.php +++ b/tests/HttpClient/Utility/BuilderTest.php @@ -132,7 +132,7 @@ public function testShouldAddPlugin() $builder = new Builder(self::$httpClient); $client = $builder->getHttpClient(); $uriFactory = UriFactoryDiscovery::find(); - $builder->addPlugin(new AddPathPlugin($uriFactory->createUri('edge'))); + $builder->addPlugin(new AddPathPlugin($uriFactory->createUri('/edge'))); $this->assertNotEquals($client, $builder->getHttpClient()); $request = new Request('GET', 'http://example.com'); $builder->getHttpClient()->sendRequest($request); @@ -162,7 +162,7 @@ public function testShouldRemoveAllPlugins(): void { $builder = new Builder(self::$httpClient); $uriFactory = UriFactoryDiscovery::find(); - $builder->addPlugin(new AddPathPlugin($uriFactory->createUri('edge'))); + $builder->addPlugin(new AddPathPlugin($uriFactory->createUri('/edge'))); $headers = ['Foo' => 'bar']; $builder->setHeaders($headers); $client = $builder->getHttpClient(); diff --git a/tests/Serializer/EntitySerializerTest.php b/tests/Serializer/EntitySerializerTest.php index fc645a71..4fff0271 100644 --- a/tests/Serializer/EntitySerializerTest.php +++ b/tests/Serializer/EntitySerializerTest.php @@ -28,7 +28,7 @@ use Apigee\Edge\Serializer\EntitySerializer; use Apigee\Edge\Tests\Test\Entity\MockEntity; use GuzzleHttp\Psr7\Response; -use function GuzzleHttp\Psr7\stream_for; +use GuzzleHttp\Psr7\Utils; use PHPUnit\Framework\TestCase; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparisonFactory; @@ -106,7 +106,7 @@ public function testNormalize() * * @param \stdClass $normalized */ - public function testDenormalize(\stdClass $normalized): void + public function testDenormalize(mixed $normalized): void { // Set value of this nullable value to ensure that a special condition is triggered in the EntityDenormalizer. $normalized->nullable = null; @@ -153,7 +153,7 @@ public function testSetPropertiesFromResponseWithValidValues(): void 'propertyWithoutSetter' => 1, 'propertyWithoutGetter' => true, ]; - static::$serializer->setPropertiesFromResponse(new Response('200', ['Content-type' => 'application/json'], stream_for(json_encode($response))), $entity); + static::$serializer->setPropertiesFromResponse(new Response('200', ['Content-type' => 'application/json'], Utils::streamFor(json_encode($response))), $entity); // These properties should change. $this->assertEquals($response->int, $entity->getInt()); $this->assertEquals($response->bool, $entity->isBool()); @@ -175,7 +175,7 @@ public function testSetPropertiesFromResponseWithValidValues(): void $response = (object) [ 'variableLengthArgs' => [], ]; - static::$serializer->setPropertiesFromResponse(new Response('200', ['Content-type' => 'application/json'], stream_for(json_encode($response))), $entity); + static::$serializer->setPropertiesFromResponse(new Response('200', ['Content-type' => 'application/json'], Utils::streamFor(json_encode($response))), $entity); $this->assertEmpty($entity->getVariableLengthArgs()); } @@ -184,8 +184,7 @@ public function testSetPropertiesFromResponseWithInvalidValue(): void if (\PHP_VERSION_ID < 80000) { $this->expectException('\Symfony\Component\Serializer\Exception\NotNormalizableValueException'); $this->expectExceptionMessage('Expected argument of type "string", "object" given.'); - } - else{ + } else { $this->expectException(\TypeError::class); $this->expectExceptionMessage('Argument #1 must be of type string, stdClass given'); } @@ -197,6 +196,6 @@ public function testSetPropertiesFromResponseWithInvalidValue(): void // what we can not fix. 'variableLengthArgs' => [(object) []], ]; - static::$serializer->setPropertiesFromResponse(new Response('200', ['Content-type' => 'application/json'], stream_for(json_encode($response))), $entity); + static::$serializer->setPropertiesFromResponse(new Response('200', ['Content-type' => 'application/json'], Utils::streamFor(json_encode($response))), $entity); } } diff --git a/tests/Structure/PropertiesPropertyTransformationTest.php b/tests/Structure/PropertiesPropertyTransformationTest.php index b4392960..8dff7849 100644 --- a/tests/Structure/PropertiesPropertyTransformationTest.php +++ b/tests/Structure/PropertiesPropertyTransformationTest.php @@ -71,7 +71,7 @@ public function testNormalize() * * @param \stdClass $normalized */ - public function testDenormalize(\stdClass $normalized): void + public function testDenormalize(mixed $normalized): void { /** @var \Apigee\Edge\Structure\PropertiesProperty $object */ $object = static::$denormalizer->denormalize($normalized, PropertiesProperty::class); diff --git a/tests/Test/HttpClient/DebuggerHttpClient.php b/tests/Test/HttpClient/DebuggerHttpClient.php index 7b43674c..72c0959d 100644 --- a/tests/Test/HttpClient/DebuggerHttpClient.php +++ b/tests/Test/HttpClient/DebuggerHttpClient.php @@ -22,9 +22,9 @@ use GuzzleHttp\TransferStats; use Http\Adapter\Guzzle6\Client; use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient; use Http\Message\Formatter; use Http\Message\Formatter\SimpleFormatter; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; @@ -38,7 +38,7 @@ * * @see https://github.com/guzzle/guzzle/pull/1202 */ -class DebuggerHttpClient implements HttpClient, HttpAsyncClient +class DebuggerHttpClient implements ClientInterface, HttpAsyncClient { /** * @var \GuzzleHttp\ClientInterface diff --git a/tests/Test/HttpClient/FileSystemHttpMockClient.php b/tests/Test/HttpClient/FileSystemHttpMockClient.php index b95ddf55..5ab1c9ad 100644 --- a/tests/Test/HttpClient/FileSystemHttpMockClient.php +++ b/tests/Test/HttpClient/FileSystemHttpMockClient.php @@ -20,7 +20,6 @@ use Apigee\Edge\Tests\Test\HttpClient\Exception\MockHttpClientException; use Http\Client\Common\HttpAsyncClientEmulator; -use Http\Client\HttpClient; use League\Flysystem\AdapterInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; diff --git a/tests/Test/HttpClient/FileSystemResponseFactory.php b/tests/Test/HttpClient/FileSystemResponseFactory.php index decc8b80..185eaa6c 100644 --- a/tests/Test/HttpClient/FileSystemResponseFactory.php +++ b/tests/Test/HttpClient/FileSystemResponseFactory.php @@ -62,7 +62,7 @@ public function __construct(AdapterInterface $adapter = null, DecoderInterface $ $adapter = new Local($folder); } $this->filesystem = new Filesystem($adapter); - $this->decoder = $decoder ?: new JsonDecode(true); + $this->decoder = $decoder ?: new JsonDecode([true]); } /** diff --git a/tests/Test/HttpClient/MockHttpClientInterface.php b/tests/Test/HttpClient/MockHttpClientInterface.php index 73eccb40..aea0a748 100644 --- a/tests/Test/HttpClient/MockHttpClientInterface.php +++ b/tests/Test/HttpClient/MockHttpClientInterface.php @@ -19,7 +19,7 @@ namespace Apigee\Edge\Tests\Test\HttpClient; use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; /** * Interface MockClientInterface. @@ -29,6 +29,6 @@ * * @see https://github.com/php-http/mock-client/pull/24 */ -interface MockHttpClientInterface extends HttpClient, HttpAsyncClient +interface MockHttpClientInterface extends ClientInterface, HttpAsyncClient { } diff --git a/tests/Test/HttpClient/Plugin/MockApigeeOnGcpOauth2.php b/tests/Test/HttpClient/Plugin/MockApigeeOnGcpOauth2.php index d677d2b5..230ca95c 100644 --- a/tests/Test/HttpClient/Plugin/MockApigeeOnGcpOauth2.php +++ b/tests/Test/HttpClient/Plugin/MockApigeeOnGcpOauth2.php @@ -27,7 +27,7 @@ use Apigee\Edge\HttpClient\Utility\JournalInterface; use Apigee\Edge\Tests\Test\HttpClient\MockHttpClient; use Apigee\Edge\Tests\Test\HttpClient\Utility\TestJournal; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface as HttpClient; /** * MockApigeeOnGcpOauth2 authentication plugin that uses mock API client for authorisation. @@ -40,7 +40,7 @@ class MockApigeeOnGcpOauth2 extends ApigeeOnGcpOauth2 */ private $journal; /** - * @var \Http\Client\HttpClient + * @var \Psr\Http\Client\ClientInterface */ private $httpClient; diff --git a/tests/Test/HttpClient/Plugin/MockHybridOauth2.php b/tests/Test/HttpClient/Plugin/MockHybridOauth2.php index 557ec45b..f6dde69a 100644 --- a/tests/Test/HttpClient/Plugin/MockHybridOauth2.php +++ b/tests/Test/HttpClient/Plugin/MockHybridOauth2.php @@ -27,7 +27,7 @@ use Apigee\Edge\HttpClient\Utility\JournalInterface; use Apigee\Edge\Tests\Test\HttpClient\MockHttpClient; use Apigee\Edge\Tests\Test\HttpClient\Utility\TestJournal; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface as HttpClient; /** * MockHybridOauth2 authentication plugin that uses mock API client for authorisation. @@ -43,7 +43,7 @@ class MockHybridOauth2 extends HybridOauth2 */ private $journal; /** - * @var \Http\Client\HttpClient + * @var \Psr\Http\Client\ClientInterface */ private $httpClient; diff --git a/tests/Test/HttpClient/Plugin/MockOauth.php b/tests/Test/HttpClient/Plugin/MockOauth.php index d0630ead..4bfeaa11 100644 --- a/tests/Test/HttpClient/Plugin/MockOauth.php +++ b/tests/Test/HttpClient/Plugin/MockOauth.php @@ -26,8 +26,8 @@ use Apigee\Edge\HttpClient\Utility\JournalInterface; use Apigee\Edge\Tests\Test\HttpClient\MockHttpClient; use Apigee\Edge\Tests\Test\HttpClient\Utility\TestJournal; -use Http\Client\HttpClient; use Http\Message\Authentication\BasicAuth; +use Psr\Http\Client\ClientInterface as HttpClient; /** * OAuth authentication plugin that uses mock API client for authorisation. @@ -40,7 +40,7 @@ class MockOauth extends Oauth */ private $journal; /** - * @var \Http\Client\HttpClient + * @var \Psr\Http\Client\ClientInterface */ private $httpClient; diff --git a/tests/Test/OnlineClientBase.php b/tests/Test/OnlineClientBase.php index a6551f4d..d21eeba6 100644 --- a/tests/Test/OnlineClientBase.php +++ b/tests/Test/OnlineClientBase.php @@ -65,7 +65,7 @@ protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault(static::CONFIG_HTTP_CLIENT, null); $resolver->setAllowedTypes(static::CONFIG_HTTP_CLIENT, [ - '\Http\Client\HttpClient', + '\Psr\Http\Client\ClientInterface', '\Http\Client\HttpAsyncClient', ]); parent::configureOptions($resolver);