diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27f9284..43a0421 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,9 +16,9 @@ jobs: strategy: matrix: prefer_lowest: ["", "--prefer-lowest"] - php: ["8.1", "8.2", "8.3"] + php: ["8.2", "8.5"] container: - image: skpr/php-cli:${{ matrix.php }}-dev-v2-edge + image: skpr/php-cli:${{ matrix.php }}-dev-v2-stable options: --pull always --user 1001:1001 @@ -30,6 +30,7 @@ jobs: - name: 📦 Composer Update run: composer update --with-all-dependencies --prefer-dist --no-progress --no-interaction ${{ matrix.prefer_lowest }} - name: 🧹 PHPCS + if: matrix.php != '8.5' run: ./bin/phpcs --report=checkstyle -q | ./bin/cs2pr - name: 🧹 PHPStan run: ./bin/phpstan --error-format=github analyse diff --git a/.gitignore b/.gitignore index 55b5178..4298530 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.phpunit.cache /bin /vendor +/.phpunit.result.cache diff --git a/composer.json b/composer.json index 1d4096c..cda1b4c 100644 --- a/composer.json +++ b/composer.json @@ -18,17 +18,17 @@ "psr/http-message": "^1.0 || ^2.0", "psr/http-message-implementation": "*", "psr/log": "^3.0", - "symfony/serializer": "^6.2 || ^6.4" + "symfony/serializer": "^6.4 || ^7.4" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "guzzlehttp/psr7": "^2.6", - "http-interop/http-factory-guzzle": "^1.2", - "php-http/mock-client": "^1.6", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.6", - "previousnext/coding-standard": "^0.1.3", - "staabm/annotate-pull-request-from-checkstyle": "^1.8" + "dealerdirect/phpcodesniffer-composer-installer": "^1.2", + "guzzlehttp/psr7": "^2.9", + "http-interop/http-factory-guzzle": "^1.2.1", + "php-http/mock-client": "^1.6.1", + "phpstan/phpstan": "^2.1.50", + "phpunit/phpunit": "^11.5.55", + "previousnext/coding-standard": "^1.1.1", + "staabm/annotate-pull-request-from-checkstyle": "^1.8.6" }, "autoload": { "psr-4": {"BomWeather\\": "src/"} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3777c04..aab4991 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,6 +1,2 @@ parameters: - ignoreErrors: - - - message: "#^Method BomWeather\\\\Util\\\\BaseNormalizer\\:\\:normalize\\(\\) return type with generic class ArrayObject does not specify its types\\: TKey, TValue$#" - count: 1 - path: src/Util/BaseNormalizer.php + ignoreErrors: [] diff --git a/phpstan.neon b/phpstan.neon index c488226..bea4e79 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,6 +6,5 @@ parameters: paths: - src - tests/src - checkMissingIterableValueType: false ignoreErrors: - "#^Call to an undefined method Symfony\\\\Component\\\\Serializer\\\\SerializerInterface\\:\\:denormalize\\(\\)\\.$#" diff --git a/phpunit.xml b/phpunit.xml index e63b442..eb20458 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,27 +1,25 @@ + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnTestsThatTriggerWarnings="true"> tests/src - + src - + diff --git a/src/BomClient.php b/src/BomClient.php index 6fae6b5..566bec5 100644 --- a/src/BomClient.php +++ b/src/BomClient.php @@ -27,7 +27,7 @@ public function __construct( protected RequestFactoryInterface $requestFactory, protected LoggerInterface $logger, protected ?SerializerInterface $forecastSerializer = NULL, - protected ?SerializerInterface $observationSerializer = NULL + protected ?SerializerInterface $observationSerializer = NULL, ) { if ($forecastSerializer == NULL) { $forecastSerializer = ForecastSerializerFactory::create(); diff --git a/src/Forecast/Area.php b/src/Forecast/Area.php index c377bf4..04c3c8f 100644 --- a/src/Forecast/Area.php +++ b/src/Forecast/Area.php @@ -137,7 +137,7 @@ public function setDescription(string $description): Area { * @return string * The parent. */ - public function getParentAac(): ?string { + public function getParentAac(): string { return $this->parentAac; } diff --git a/src/Forecast/Forecast.php b/src/Forecast/Forecast.php index 0bac899..fb6af06 100644 --- a/src/Forecast/Forecast.php +++ b/src/Forecast/Forecast.php @@ -196,7 +196,7 @@ public function addLocation(Area $location): Forecast { /** * Gets the issue time. */ - public function getIssueTime(): ?\DateTimeImmutable { + public function getIssueTime(): \DateTimeImmutable { return $this->issueTime; } diff --git a/src/Forecast/ForecastPeriod.php b/src/Forecast/ForecastPeriod.php index 13a79c6..eb1b522 100644 --- a/src/Forecast/ForecastPeriod.php +++ b/src/Forecast/ForecastPeriod.php @@ -200,7 +200,7 @@ public function getProbabilityOfPrecipitation(): ?string { * Sets the probability of precipitation. */ public function setProbabilityOfPrecipitation( - string $probabilityOfPrecipitation + string $probabilityOfPrecipitation, ): ForecastPeriod { $this->probabilityOfPrecipitation = $probabilityOfPrecipitation; return $this; diff --git a/src/Forecast/Serializer/AreaNormalizer.php b/src/Forecast/Serializer/AreaNormalizer.php index 990dfb5..865381a 100644 --- a/src/Forecast/Serializer/AreaNormalizer.php +++ b/src/Forecast/Serializer/AreaNormalizer.php @@ -14,12 +14,26 @@ */ class AreaNormalizer extends BaseNormalizer { + /** + * The supported interface or class. + * + * @var string|array + */ protected string|array $supportedInterfaceOrClass = Area::class; /** * {@inheritdoc} + * + * @param mixed $data + * Data to restore. + * @param string $type + * The expected class to instantiate. + * @param string|null $format + * Format the given data was extracted from. + * @param array $context + * Context options for the denormalizer. */ - public function denormalize($data, $type, $format = NULL, array $context = []) { + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed { if (!$this->serializer instanceof DenormalizerInterface) { throw new \RuntimeException('The serializer must implement the DenormalizerInterface.'); } diff --git a/src/Forecast/Serializer/ForecastNormalizer.php b/src/Forecast/Serializer/ForecastNormalizer.php index 7c3514e..4ffa966 100644 --- a/src/Forecast/Serializer/ForecastNormalizer.php +++ b/src/Forecast/Serializer/ForecastNormalizer.php @@ -15,14 +15,25 @@ class ForecastNormalizer extends BaseNormalizer { /** - * {@inheritdoc} + * The supported interface or class. + * + * @var string|array */ protected string|array $supportedInterfaceOrClass = Forecast::class; /** * {@inheritdoc} + * + * @param mixed $data + * Data to restore. + * @param string $type + * The expected class to instantiate. + * @param string|null $format + * Format the given data was extracted from. + * @param array $context + * Context options for the denormalizer. */ - public function denormalize($data, $type, $format = NULL, array $context = []) { + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed { if (!$this->serializer instanceof DenormalizerInterface) { throw new \RuntimeException('The serializer must implement the DenormalizerInterface.'); } @@ -45,7 +56,7 @@ public function denormalize($data, $type, $format = NULL, array $context = []) { /** * Sets the area value. * - * @param array $area + * @param array $area * The area data. * @param \BomWeather\Forecast\Forecast $forecast * The forecast. diff --git a/src/Forecast/Serializer/ForecastPeriodNormalizer.php b/src/Forecast/Serializer/ForecastPeriodNormalizer.php index 70dc9cc..81fd3ed 100644 --- a/src/Forecast/Serializer/ForecastPeriodNormalizer.php +++ b/src/Forecast/Serializer/ForecastPeriodNormalizer.php @@ -12,12 +12,26 @@ */ class ForecastPeriodNormalizer extends BaseNormalizer { + /** + * The supported interface or class. + * + * @var string|array + */ protected string|array $supportedInterfaceOrClass = ForecastPeriod::class; /** * {@inheritdoc} + * + * @param mixed $data + * Data to restore. + * @param string $type + * The expected class to instantiate. + * @param string|null $format + * Format the given data was extracted from. + * @param array $context + * Context options for the denormalizer. */ - public function denormalize($data, $type, $format = NULL, array $context = []) { + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed { $period = (new ForecastPeriod()) ->setStartTime($this->serializer->denormalize($data['@start-time-utc'], \DateTimeImmutable::class)) ->setEndTime($this->serializer->denormalize($data['@end-time-utc'], \DateTimeImmutable::class)); @@ -51,7 +65,7 @@ public function denormalize($data, $type, $format = NULL, array $context = []) { /** * Set an element value. * - * @param array $element + * @param array $element * The element array. * @param \BomWeather\Forecast\ForecastPeriod $period * The period. @@ -80,7 +94,7 @@ protected function setElementValue(array $element, ForecastPeriod $period): void /** * Sets a text value. * - * @param array $text + * @param array $text * The text array. * @param \BomWeather\Forecast\ForecastPeriod $period * The period. diff --git a/src/Observation/Observation.php b/src/Observation/Observation.php index 3deef42..1830d3a 100644 --- a/src/Observation/Observation.php +++ b/src/Observation/Observation.php @@ -79,7 +79,7 @@ public function getTemperature(): ?Temperature { /** * Sets the Temperature. */ - public function setTemperature(Temperature $temperature): ?Observation { + public function setTemperature(Temperature $temperature): self { $this->temperature = $temperature; return $this; } diff --git a/src/Observation/Serializer/ObservationListNormalizer.php b/src/Observation/Serializer/ObservationListNormalizer.php index 86c2683..a4454c8 100644 --- a/src/Observation/Serializer/ObservationListNormalizer.php +++ b/src/Observation/Serializer/ObservationListNormalizer.php @@ -14,12 +14,26 @@ */ class ObservationListNormalizer extends BaseNormalizer { + /** + * The supported interface or class. + * + * @var string|array + */ protected string|array $supportedInterfaceOrClass = ObservationList::class; /** * {@inheritdoc} + * + * @param mixed $data + * Data to restore. + * @param string $type + * The expected class to instantiate. + * @param string|null $format + * Format the given data was extracted from. + * @param array $context + * Context options for the denormalizer. */ - public function denormalize($data, $type, $format = NULL, array $context = []) { + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed { if (!$this->serializer instanceof DenormalizerInterface) { throw new \RuntimeException('The serializer must implement the DenormalizerInterface.'); } diff --git a/src/Observation/Serializer/ObservationNormalizer.php b/src/Observation/Serializer/ObservationNormalizer.php index 283a86c..f24a698 100644 --- a/src/Observation/Serializer/ObservationNormalizer.php +++ b/src/Observation/Serializer/ObservationNormalizer.php @@ -16,12 +16,26 @@ */ class ObservationNormalizer extends BaseNormalizer { + /** + * The supported interface or class. + * + * @var string|array + */ protected string|array $supportedInterfaceOrClass = Observation::class; /** * {@inheritdoc} + * + * @param mixed $data + * Data to restore. + * @param string $type + * The expected class to instantiate. + * @param string|null $format + * Format the given data was extracted from. + * @param array $context + * Context options for the denormalizer. */ - public function denormalize($data, $type, $format = NULL, array $context = []) { + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed { $station = (new Station()) ->setId($data['wmo']) ->setLatitude($data['lat']) diff --git a/src/Util/BaseNormalizer.php b/src/Util/BaseNormalizer.php index 95cbcd8..9f867e3 100644 --- a/src/Util/BaseNormalizer.php +++ b/src/Util/BaseNormalizer.php @@ -15,15 +15,25 @@ abstract class BaseNormalizer extends AbstractNormalizer { /** * {@inheritdoc} + * + * @param mixed $object + * Object to normalize. + * @param string|null $format + * Format the normalization result will be encoded as. + * @param array $context + * Context options for the normalizer. + * + * @return array|\ArrayObject|bool|float|int|string|null + * The normalized data. */ - public function normalize(mixed $object, string $format = NULL, array $context = []) { + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|\ArrayObject|bool|float|int|string|null { throw new \RuntimeException("Method not implemented."); } /** * Checks whether the array is associative. * - * @param array $array + * @param array $array * The array. * * @return bool diff --git a/src/Util/ClassNameSupportNormalizerTrait.php b/src/Util/ClassNameSupportNormalizerTrait.php index 1c93372..494a3eb 100644 --- a/src/Util/ClassNameSupportNormalizerTrait.php +++ b/src/Util/ClassNameSupportNormalizerTrait.php @@ -11,16 +11,23 @@ trait ClassNameSupportNormalizerTrait { /** * The interface or class that this Normalizer supports. + * + * @var string|array */ protected string|array $supportedInterfaceOrClass; /** * The format this Normalizer supports. + * + * @var string|array */ protected string|array $format; /** * Gets the string or array of supported classes. + * + * @return string|array + * The supported class or classes. */ public function getSupportedInterfaceOrClass(): string|array { return $this->supportedInterfaceOrClass; @@ -28,6 +35,9 @@ public function getSupportedInterfaceOrClass(): string|array { /** * Sets the string or array of supported classes. + * + * @param string|array $supported_interface_or_class + * The supported class or classes. */ public function setSupportedInterfaceOrClass(string|array $supported_interface_or_class): self { $this->supportedInterfaceOrClass = $supported_interface_or_class; @@ -36,8 +46,33 @@ public function setSupportedInterfaceOrClass(string|array $supported_interface_o /** * {@inheritdoc} + * + * @param string|null $format + * The format being (de-)serialized from or into. + * + * @return array + * The supported types. */ - public function supportsNormalization(mixed $data, string $format = NULL /* , array $context = [] */): bool { + public function getSupportedTypes(?string $format): array { + $supported = (array) $this->supportedInterfaceOrClass; + $result = []; + foreach ($supported as $class) { + $result[$class] = FALSE; + } + return $result; + } + + /** + * {@inheritdoc} + * + * @param mixed $data + * Data to normalize. + * @param string|null $format + * The format being (de-)serialized from or into. + * @param array $context + * Context options for the normalizer. + */ + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool { // If we aren't dealing with an object or the format is not supported return // now. if (!\is_object($data) || !$this->checkFormat($format)) { @@ -46,15 +81,24 @@ public function supportsNormalization(mixed $data, string $format = NULL /* , ar $supported = (array) $this->supportedInterfaceOrClass; - return (bool) \array_filter($supported, function ($name) use ($data) { + return (bool) \array_filter($supported, static function ($name) use ($data) { return $data instanceof $name; }); } /** * {@inheritdoc} + * + * @param mixed $data + * Data to denormalize from. + * @param string $type + * The class to which the data should be denormalized. + * @param string|null $format + * The format being deserialized from. + * @param array $context + * Context options for the denormalizer. */ - public function supportsDenormalization(mixed $data, string $type, string $format = NULL /* , array $context = [] */): bool { + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool { // If the format is not supported return now. if (!$this->checkFormat($format)) { return FALSE; @@ -62,7 +106,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma $supported = (array) $this->supportedInterfaceOrClass; - $subclass_check = function ($name) use ($type) { + $subclass_check = static function ($name) use ($type) { return (\class_exists($name) || \interface_exists($name)) && \is_subclass_of($type, $name, TRUE); }; @@ -78,7 +122,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma * TRUE if the format is supported, FALSE otherwise. If no format is * specified this will return TRUE. */ - protected function checkFormat(string $format = NULL): bool { + protected function checkFormat(?string $format = NULL): bool { if (!isset($format) || !isset($this->format)) { return TRUE; } diff --git a/tests/src/Functional/BomClientTest.php b/tests/src/Functional/BomClientTest.php index 3dc1af1..cd3599d 100644 --- a/tests/src/Functional/BomClientTest.php +++ b/tests/src/Functional/BomClientTest.php @@ -7,6 +7,7 @@ use BomWeather\BomClient; use GuzzleHttp\Psr7\Stream; use Http\Mock\Client; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; @@ -14,13 +15,13 @@ use Psr\Log\NullLogger; /** - * @coversDefaultClass \BomWeather\BomClient + * Tests the BOM client. */ +#[CoversClass(BomClient::class)] class BomClientTest extends TestCase { /** - * @covers ::__construct() - * @covers ::getForecast() + * Tests the getForecast method. */ public function testGetForecast(): void { $logger = new NullLogger(); @@ -41,8 +42,7 @@ public function testGetForecast(): void { } /** - * @covers ::__construct() - * @covers ::getObservationList() + * Tests the getObservationList method. */ public function testGetObservation(): void { $logger = new NullLogger(); diff --git a/tests/src/Unit/Forecast/Serializer/ForecastSerializerTest.php b/tests/src/Unit/Forecast/Serializer/ForecastSerializerTest.php index 80aa30a..66c3b67 100644 --- a/tests/src/Unit/Forecast/Serializer/ForecastSerializerTest.php +++ b/tests/src/Unit/Forecast/Serializer/ForecastSerializerTest.php @@ -6,15 +6,17 @@ use BomWeather\Forecast\Forecast; use BomWeather\Forecast\Serializer\ForecastSerializerFactory; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \BomWeather\Forecast\Serializer\ForecastSerializerFactory + * Tests the forecast serializer. */ +#[CoversClass(ForecastSerializerFactory::class)] class ForecastSerializerTest extends TestCase { /** - * @covers ::create + * Tests deserialization of Sydney forecast data. */ public function testDeserializeSydney(): void { $factory = new ForecastSerializerFactory(); @@ -24,8 +26,6 @@ public function testDeserializeSydney(): void { /** @var \BomWeather\Forecast\Forecast $forecast */ $forecast = $serializer->deserialize($xml, Forecast::class, 'xml'); - - $this->assertNotNull($forecast); $this->assertEquals('2018-06-20T21:41:57+00:00', $forecast->getIssueTime()->format(DATE_RFC3339)); // Regions. diff --git a/tests/src/Unit/Observation/Serializer/ObservationSerializerTest.php b/tests/src/Unit/Observation/Serializer/ObservationSerializerTest.php index f55146a..3383944 100644 --- a/tests/src/Unit/Observation/Serializer/ObservationSerializerTest.php +++ b/tests/src/Unit/Observation/Serializer/ObservationSerializerTest.php @@ -6,15 +6,17 @@ use BomWeather\Observation\ObservationList; use BomWeather\Observation\Serializer\ObservationSerializerFactory; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; /** - * @coversDefaultClass \BomWeather\Observation\Serializer\ObservationSerializerFactory + * Tests the observation serializer. */ +#[CoversClass(ObservationSerializerFactory::class)] class ObservationSerializerTest extends TestCase { /** - * @covers ::create() + * Tests deserialization of observation data. */ public function testDeserialize(): void { $serializer = ObservationSerializerFactory::create(); @@ -22,9 +24,6 @@ public function testDeserialize(): void { /** @var \BomWeather\Observation\ObservationList $observationList */ $observationList = $serializer->deserialize($json, ObservationList::class, 'json'); - - $this->assertNotNull($observationList); - $this->assertEquals("Issued at 9:31 am EST Monday 25 June 2018", $observationList->getRefreshMessage()); $this->assertCount(144, $observationList->getObservations());