From 96c337d34da76e8eb3c8e79447c83a9542909163 Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:57:18 +0200 Subject: [PATCH 1/8] Drop php8.1, change to min php 8.2 --- .github/workflows/ci.yml | 12 ++++++------ .gitignore | 2 +- README.md | 6 +++--- composer.json | 10 +++++----- tools/.phpstan/composer.json | 2 +- tools/.phpstan/composer.lock | 36 +++++++++++++++++++++--------------- tools/.phpunit/composer.json | 2 +- tools/.psalm/composer.json | 2 +- tools/.rector/composer.json | 2 +- 9 files changed, 40 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 557a7733..9529e4b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.1', '8.2', '8.3'] + php-versions: ['8.2', '8.3', '8.4', '8.5'] experimental: [false] steps: @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.1', '8.2', '8.3'] + php-versions: ['8.2', '8.3', '8.4', '8.5'] experimental: [false] steps: - name: Checkout @@ -64,14 +64,14 @@ jobs: run: tools/phpunit --testsuite="BigBlueButton unit test suite,BigBlueButton integration test suite" continue-on-error: true - name: Upload coverage - if: ${{ matrix.php-versions == '8.1' }} + if: ${{ matrix.php-versions == '8.2' }} uses: codecov/codecov-action@v3 - name: Run rector - if: ${{ matrix.php-versions == '8.1' }} + if: ${{ matrix.php-versions == '8.2' }} run: tools/rector process --dry-run - name: Run psalm - if: ${{ matrix.php-versions == '8.1' }} + if: ${{ matrix.php-versions == '8.2' }} run: tools/psalm - name: Run phpstan - if: ${{ matrix.php-versions == '8.1' }} + if: ${{ matrix.php-versions == '8.2' }} run: tools/phpstan diff --git a/.gitignore b/.gitignore index 3fdf8edd..c714baae 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ reports # The library does not locks down the dependencies for several reasons: # # * In the outer world, the lock is not effective. Only the lock of the requiring application is accounted. -# * It makes it hard, to run the library with its deps in the CI as when running `composer require` under PHP 8.1, +# * It makes it hard, to run the library with its deps in the CI as when running `composer require` under PHP 8.2, # an incompatible lock file is created which is unsuable unter PHP 7.x. # # See https://github.com/littleredbutton/bigbluebutton-api-php/pull/115 for the discussion. diff --git a/README.md b/README.md index 9b29b0ef..915ba49f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # :tada: Best BigBlueButton API for PHP The unofficial and easiest to use **BigBlueButton API for PHP**, makes easy for -developers to use [BigBlueButton API] v2.2+ for **PHP 8.1+**. +developers to use [BigBlueButton API] v2.2+ for **PHP 8.2+**. ![Build Status](https://github.com/littleredbutton/bigbluebutton-api-php/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/littleredbutton/bigbluebutton-api-php/badge.svg?branch=master)](https://coveralls.io/github/littleredbutton/bigbluebutton-api-php?branch=master) @@ -56,7 +56,7 @@ following advantages: - Development is simplified through git hooks and contributor guidelines - Documentation is up-to-date and complete - API is fixed and extended to exploit the full potential -- Require at least PHP 8.1, which allows to make the code more efficient and +- Require at least PHP 8.2, which allows to make the code more efficient and readable ## :gear: Installation and usage @@ -64,7 +64,7 @@ following advantages: In order to use this library you have to make sure to meet the following requirements: -- PHP 8.1 or above. +- PHP 8.2 or above. - curl library installed. - mbstring library installed. - xml library installed. diff --git a/composer.json b/composer.json index d27ba7f3..30d48549 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "docs": "https://github.com/littleredbutton/bigbluebutton-api-php/blob/master/README.md" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "ext-curl": "*", "ext-simplexml": "*", "ext-mbstring": "*", @@ -143,10 +143,10 @@ } }, "friendsofphp/php-cs-fixer": "^3.3", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10", - "rector/rector": "^1.0", - "vimeo/psalm": "^5.23" + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11", + "rector/rector": "^2.0", + "vimeo/psalm": "^6.0" } } } diff --git a/tools/.phpstan/composer.json b/tools/.phpstan/composer.json index 90c99dfd..6dea17a8 100644 --- a/tools/.phpstan/composer.json +++ b/tools/.phpstan/composer.json @@ -1,5 +1,5 @@ { "require": { - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^2.0" } } diff --git a/tools/.phpstan/composer.lock b/tools/.phpstan/composer.lock index 2c3ffe96..eebbe4ff 100644 --- a/tools/.phpstan/composer.lock +++ b/tools/.phpstan/composer.lock @@ -4,24 +4,19 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "14812c2a05a5972f00f9d67abbd710a9", + "content-hash": "a1a9a305e36d2c71272f49ce0f9c92fe", "packages": [ { "name": "phpstan/phpstan", - "version": "1.12.3", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" - }, + "version": "2.2.5", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", + "reference": "909c1e5fef7989ac0d0c1c5c42e32a5c4f6198a0", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -40,6 +35,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -62,16 +68,16 @@ "type": "github" } ], - "time": "2024-09-09T08:10:35+00:00" + "time": "2026-07-05T06:31:06+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/tools/.phpunit/composer.json b/tools/.phpunit/composer.json index 2dc16d75..71198119 100644 --- a/tools/.phpunit/composer.json +++ b/tools/.phpunit/composer.json @@ -1,6 +1,6 @@ { "require": { - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^11.0", "fakerphp/faker": "1.24.*" } } diff --git a/tools/.psalm/composer.json b/tools/.psalm/composer.json index 70de5195..7ee4658e 100644 --- a/tools/.psalm/composer.json +++ b/tools/.psalm/composer.json @@ -1,5 +1,5 @@ { "require": { - "vimeo/psalm": "^5.23" + "vimeo/psalm": "^6.0" } } diff --git a/tools/.rector/composer.json b/tools/.rector/composer.json index 056d015c..d4420aba 100644 --- a/tools/.rector/composer.json +++ b/tools/.rector/composer.json @@ -1,5 +1,5 @@ { "require": { - "rector/rector": "^1.0" + "rector/rector": "^2.0" } } From 2901550c86f4521c642f7e9c46f2e7df07302726 Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:08:53 +0200 Subject: [PATCH 2/8] Fix rector --- src/Http/SetCookie.php | 2 +- .../Bridge/PsrHttpClient/PsrHttpClientTransport.php | 4 ++-- .../SymfonyHttpClient/SymfonyHttpClientTransport.php | 4 ++-- src/Http/Transport/CurlTransport.php | 4 ++-- src/Util/UrlBuilder.php | 8 ++++---- tests/integration/Http/Transport/Fixtures/web/dump.php | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Http/SetCookie.php b/src/Http/SetCookie.php index 923ad56f..af895158 100644 --- a/src/Http/SetCookie.php +++ b/src/Http/SetCookie.php @@ -62,7 +62,7 @@ public static function fromString(string $cookie): self // Create the default return array $data = self::$defaults; // Explode the cookie string using a series of semicolons - $pieces = array_filter(array_map('trim', explode(';', $cookie))); + $pieces = array_filter(array_map(trim(...), explode(';', $cookie))); // The name of the cookie (first kvp) must exist and include an equal sign. if (!isset($pieces[0]) || !str_contains($pieces[0], '=')) { return new self($data); diff --git a/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php b/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php index befa70d9..2db4c8de 100644 --- a/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php +++ b/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php @@ -67,12 +67,12 @@ /** * Allows to send requests to the BBB server with a {@link https://www.php-fig.org/psr/psr-18/} implementation. */ -final class PsrHttpClientTransport implements TransportInterface +final readonly class PsrHttpClientTransport implements TransportInterface { /** * @param string[] $defaultHeaders additional headers to pass on each request */ - public function __construct(private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly array $defaultHeaders = []) + public function __construct(private ClientInterface $httpClient, private RequestFactoryInterface $requestFactory, private StreamFactoryInterface $streamFactory, private array $defaultHeaders = []) { } diff --git a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php index 0545c74d..d0b3e5fd 100644 --- a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php +++ b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php @@ -51,13 +51,13 @@ /** * Allows to send requests to the BBB server with a Symfony HTTP Client contract implementation. */ -final class SymfonyHttpClientTransport implements TransportInterface +final readonly class SymfonyHttpClientTransport implements TransportInterface { /** * @param string[] $defaultHeaders additional HTTP headers to pass on each request * @param mixed[] $defaultOptions Options for Symfony HTTP client passed on every request. See {@link https://symfony.com/doc/current/http_client.html} for details. */ - public function __construct(private readonly HttpClientInterface $httpClient, private readonly array $defaultHeaders = [], private readonly array $defaultOptions = []) + public function __construct(private HttpClientInterface $httpClient, private array $defaultHeaders = [], private array $defaultOptions = []) { } diff --git a/src/Http/Transport/CurlTransport.php b/src/Http/Transport/CurlTransport.php index 8f537677..903fe4d3 100644 --- a/src/Http/Transport/CurlTransport.php +++ b/src/Http/Transport/CurlTransport.php @@ -28,7 +28,7 @@ /** * Allows to send requests to the BBB server with the pure PHP cURL implementation. */ -final class CurlTransport implements TransportInterface +final readonly class CurlTransport implements TransportInterface { public const DEFAULT_CURL_OPTIONS = [ \CURLOPT_SSL_VERIFYPEER => 1, @@ -64,7 +64,7 @@ final class CurlTransport implements TransportInterface * * @param mixed[] $curlOptions A list of cURL options to pass to the cURL handle. Option name as key, option value as value. */ - public function __construct(private readonly array $curlOptions = []) + public function __construct(private array $curlOptions = []) { } diff --git a/src/Util/UrlBuilder.php b/src/Util/UrlBuilder.php index 822e04a9..0a4bba9f 100644 --- a/src/Util/UrlBuilder.php +++ b/src/Util/UrlBuilder.php @@ -30,12 +30,12 @@ * * @internal */ -final class UrlBuilder +final readonly class UrlBuilder { public function __construct( - private readonly string $securitySalt, - private readonly string $bbbServerBaseUrl, - private readonly HashingAlgorithm $hashingAlgorithm, + private string $securitySalt, + private string $bbbServerBaseUrl, + private HashingAlgorithm $hashingAlgorithm, ) { } diff --git a/tests/integration/Http/Transport/Fixtures/web/dump.php b/tests/integration/Http/Transport/Fixtures/web/dump.php index 31445fdc..95c82f62 100644 --- a/tests/integration/Http/Transport/Fixtures/web/dump.php +++ b/tests/integration/Http/Transport/Fixtures/web/dump.php @@ -38,7 +38,7 @@ foreach ($_SERVER as $k => $v) { switch ($k) { default: - if (!str_starts_with($k, 'HTTP_')) { + if (!str_starts_with((string) $k, 'HTTP_')) { continue 2; } // no break From 3ac3366a217c6ac95a919224390f5445d1399b8b Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:26:08 +0200 Subject: [PATCH 3/8] Fix psalm --- composer.json | 2 +- psalm.xml | 1 + src/Http/SetCookie.php | 27 +++++++++++-------- .../PsrHttpClient/PsrHttpClientTransport.php | 1 + .../SymfonyHttpClientTransport.php | 3 ++- src/Http/Transport/Cookie.php | 2 +- src/Http/Transport/CurlTransport.php | 1 + src/Parameters/CreateMeetingParameters.php | 1 + src/Parameters/MetaParameters.php | 1 + src/Parameters/UserDataParameters.php | 1 + src/Responses/BaseResponseAsJson.php | 2 +- 11 files changed, 27 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 30d48549..faf69532 100644 --- a/composer.json +++ b/composer.json @@ -114,7 +114,7 @@ "cs-test": "tools/php-cs-fixer fix --dry-run --allow-risky=yes", "psalm": "tools/psalm --threads=1", "psalm-clear": "tools/psalm --clear-cache && tools/psalm --clear-global-cache", - "psalm-fix": "tools/psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType", + "psalm-fix": "tools/psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType,MissingOverrideAttribute", "phpstan": "tools/phpstan analyse", "rector": "tools/rector process --dry-run src/ tests/", "rector-fix": "tools/rector process src/ tests/", diff --git a/psalm.xml b/psalm.xml index 7c0333df..27372f7a 100644 --- a/psalm.xml +++ b/psalm.xml @@ -5,6 +5,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" + findUnusedCode="false" > diff --git a/src/Http/SetCookie.php b/src/Http/SetCookie.php index af895158..429e528b 100644 --- a/src/Http/SetCookie.php +++ b/src/Http/SetCookie.php @@ -110,6 +110,7 @@ public function __construct(array $data = []) } } + #[\Override] public function __toString(): string { $str = $this->data['Name'].'='.$this->data['Value'].'; '; @@ -126,8 +127,12 @@ public function __toString(): string return rtrim($str, '; '); } - /** @return array */ - public function toArray(): array + /** + * @return (bool|int|string|null)[]|null + * + * @psalm-return array|null + */ + public function toArray(): ?array { return $this->data; } @@ -135,7 +140,7 @@ public function toArray(): array /** * Get the cookie name. */ - public function getName(): string + public function getName(): string|bool|int|null { return $this->data['Name']; } @@ -153,7 +158,7 @@ public function setName(string $name): void /** * Get the cookie value. */ - public function getValue(): ?string + public function getValue(): string|bool|int|null { return $this->data['Value']; } @@ -161,9 +166,9 @@ public function getValue(): ?string /** * Set the cookie value. * - * @param string $value Cookie value + * @param string|bool|int|null $value Cookie value */ - public function setValue(string $value): void + public function setValue(string|bool|int|null $value): void { $this->data['Value'] = $value; } @@ -171,7 +176,7 @@ public function setValue(string $value): void /** * Get the domain. */ - public function getDomain(): ?string + public function getDomain(): string|bool|int|null { return $this->data['Domain']; } @@ -187,7 +192,7 @@ public function setDomain(string $domain): void /** * Get the path. */ - public function getPath(): string + public function getPath(): string|bool|int|null { return $this->data['Path']; } @@ -223,7 +228,7 @@ public function setMaxAge(int $maxAge): void /** * The UNIX timestamp when the cookie Expires. */ - public function getExpires(): int|string|null + public function getExpires(): bool|int|string|null { return $this->data['Expires']; } @@ -243,7 +248,7 @@ public function setExpires(int|string $timestamp): void /** * Get whether or not this is a secure cookie. */ - public function getSecure(): ?bool + public function getSecure(): string|bool|int|null { return $this->data['Secure']; } @@ -261,7 +266,7 @@ public function setSecure(bool $secure): void /** * Get whether or not this is a session cookie. */ - public function getDiscard(): ?bool + public function getDiscard(): string|bool|int|null { return $this->data['Discard']; } diff --git a/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php b/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php index 2db4c8de..ed9f94ab 100644 --- a/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php +++ b/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php @@ -76,6 +76,7 @@ public function __construct(private ClientInterface $httpClient, private Request { } + #[\Override] public function request(TransportRequest $request): TransportResponse { if ('' !== $payload = $request->getPayload()) { diff --git a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php index d0b3e5fd..6c093a13 100644 --- a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php +++ b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php @@ -83,6 +83,7 @@ public static function create(array $defaultHeaders = [], array $defaultOptions // @codeCoverageIgnoreEnd } + #[\Override] public function request(TransportRequest $request): TransportResponse { $headers = $this->defaultHeaders; @@ -125,7 +126,7 @@ public function request(TransportRequest $request): TransportResponse * @throws RedirectionExceptionInterface * @throws ServerExceptionInterface */ - private static function extractJsessionCookie(ResponseInterface $symfonyResponse): ?string + private static function extractJsessionCookie(ResponseInterface $symfonyResponse): bool|int|string|null { $responseHeaders = $symfonyResponse->getHeaders(); diff --git a/src/Http/Transport/Cookie.php b/src/Http/Transport/Cookie.php index 6f7aae89..511bb6d0 100644 --- a/src/Http/Transport/Cookie.php +++ b/src/Http/Transport/Cookie.php @@ -33,7 +33,7 @@ final class Cookie /** * @param string[] $headerValues */ - public static function extractJsessionId(array $headerValues): ?string + public static function extractJsessionId(array $headerValues): bool|int|string|null { foreach ($headerValues as $headerValue) { $cookie = SetCookie::fromString($headerValue); diff --git a/src/Http/Transport/CurlTransport.php b/src/Http/Transport/CurlTransport.php index 903fe4d3..524f48e8 100644 --- a/src/Http/Transport/CurlTransport.php +++ b/src/Http/Transport/CurlTransport.php @@ -78,6 +78,7 @@ public static function createWithDefaultOptions(array $additionalCurlOptions = [ // @codeCoverageIgnoreEnd } + #[\Override] public function request(TransportRequest $request): TransportResponse { // @codeCoverageIgnoreStart diff --git a/src/Parameters/CreateMeetingParameters.php b/src/Parameters/CreateMeetingParameters.php index 89e5edd0..4a14e5c4 100644 --- a/src/Parameters/CreateMeetingParameters.php +++ b/src/Parameters/CreateMeetingParameters.php @@ -460,6 +460,7 @@ public function addPresentationsModule(SimpleXMLElementExtended $xml): void } } + #[\Override] public function getHTTPQuery(): string { $queries = $this->getHTTPQueryArray(); diff --git a/src/Parameters/MetaParameters.php b/src/Parameters/MetaParameters.php index 2adab816..14dc0c99 100644 --- a/src/Parameters/MetaParameters.php +++ b/src/Parameters/MetaParameters.php @@ -42,6 +42,7 @@ public function addMeta(string $key, bool|string $value): static return $this; } + #[\Override] protected function getHTTPQueryArray(): array { $properties = parent::getHTTPQueryArray(); diff --git a/src/Parameters/UserDataParameters.php b/src/Parameters/UserDataParameters.php index a1f788f0..4529a147 100644 --- a/src/Parameters/UserDataParameters.php +++ b/src/Parameters/UserDataParameters.php @@ -42,6 +42,7 @@ public function addUserData(string $key, bool|string $value): self return $this; } + #[\Override] protected function getHTTPQueryArray(): array { $properties = parent::getHTTPQueryArray(); diff --git a/src/Responses/BaseResponseAsJson.php b/src/Responses/BaseResponseAsJson.php index 77b8fa56..aff7bebd 100644 --- a/src/Responses/BaseResponseAsJson.php +++ b/src/Responses/BaseResponseAsJson.php @@ -43,7 +43,7 @@ public function __construct($rawJson) $this->data = json_decode($rawJson); } - public function getRawJson(): string + public function getRawJson(): string|false { return json_encode($this->data); } From 41d95a1551fa6e7095bf7b612eb2c65bc244c575 Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:11:03 +0200 Subject: [PATCH 4/8] Fix phpstan # Conflicts: # src/Parameters/CreateMeetingParameters.php --- phpstan.neon | 2 ++ src/Http/SetCookie.php | 30 ++++++++-------- src/Http/Transport/CurlTransport.php | 2 +- tests/bootstrap.php | 20 ++--------- .../Http/Transport/CurlTransportTest.php | 4 ++- tests/unit/Http/SetCookieTest.php | 2 +- .../SymfonyHttpClientTransportTest.php | 36 +++++-------------- tests/unit/Util/ArrayHelperTest.php | 10 +++--- 8 files changed, 38 insertions(+), 68 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 9110ad71..4817ce70 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,6 +9,8 @@ parameters: bootstrapFiles: - tools/bootstrap.php ignoreErrors: + - + identifier: method.alreadyNarrowedType - message: '#^Offset ''input'' does not exist on array\{\}\.$#' path: tests/integration/Http/Transport/CurlTransportTest.php diff --git a/src/Http/SetCookie.php b/src/Http/SetCookie.php index 429e528b..99302b05 100644 --- a/src/Http/SetCookie.php +++ b/src/Http/SetCookie.php @@ -50,7 +50,7 @@ final class SetCookie implements \Stringable /** * @var array Cookie data */ - private ?array $data; + private array $data; /** * Create a new SetCookie object from a string. @@ -96,7 +96,7 @@ public static function fromString(string $cookie): self } /** - * @param array $data Array of cookie data provided by a Cookie parser + * @param array $data Array of cookie data provided by a Cookie parser */ public function __construct(array $data = []) { @@ -128,11 +128,9 @@ public function __toString(): string } /** - * @return (bool|int|string|null)[]|null - * - * @psalm-return array|null + * @return array */ - public function toArray(): ?array + public function toArray(): array { return $this->data; } @@ -140,7 +138,7 @@ public function toArray(): ?array /** * Get the cookie name. */ - public function getName(): string|bool|int|null + public function getName(): string { return $this->data['Name']; } @@ -158,7 +156,7 @@ public function setName(string $name): void /** * Get the cookie value. */ - public function getValue(): string|bool|int|null + public function getValue(): ?string { return $this->data['Value']; } @@ -166,9 +164,9 @@ public function getValue(): string|bool|int|null /** * Set the cookie value. * - * @param string|bool|int|null $value Cookie value + * @param string $value Cookie value */ - public function setValue(string|bool|int|null $value): void + public function setValue(string $value): void { $this->data['Value'] = $value; } @@ -176,7 +174,7 @@ public function setValue(string|bool|int|null $value): void /** * Get the domain. */ - public function getDomain(): string|bool|int|null + public function getDomain(): ?string { return $this->data['Domain']; } @@ -192,7 +190,7 @@ public function setDomain(string $domain): void /** * Get the path. */ - public function getPath(): string|bool|int|null + public function getPath(): string { return $this->data['Path']; } @@ -228,7 +226,7 @@ public function setMaxAge(int $maxAge): void /** * The UNIX timestamp when the cookie Expires. */ - public function getExpires(): bool|int|string|null + public function getExpires(): ?int { return $this->data['Expires']; } @@ -246,9 +244,9 @@ public function setExpires(int|string $timestamp): void } /** - * Get whether or not this is a secure cookie. + * Get whether this is a secure cookie. */ - public function getSecure(): string|bool|int|null + public function getSecure(): ?bool { return $this->data['Secure']; } @@ -266,7 +264,7 @@ public function setSecure(bool $secure): void /** * Get whether or not this is a session cookie. */ - public function getDiscard(): string|bool|int|null + public function getDiscard(): ?bool { return $this->data['Discard']; } diff --git a/src/Http/Transport/CurlTransport.php b/src/Http/Transport/CurlTransport.php index 524f48e8..fea68831 100644 --- a/src/Http/Transport/CurlTransport.php +++ b/src/Http/Transport/CurlTransport.php @@ -194,7 +194,7 @@ private static function getHeadersAndContentFromCurlHandle(\CurlHandle $curlHand { $headers = []; - curl_setopt($curlHandle, \CURLOPT_HEADER, 1); + curl_setopt($curlHandle, \CURLOPT_HEADER, true); $responseContent = curl_exec($curlHandle); // @codeCoverageIgnoreStart diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d22a1a3c..c4419793 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -30,21 +30,5 @@ // Load environment $dotenv = new Dotenv(); -// usePutenv was not available in version 3.4 und early 4.x versions of symfony/dotenv, so make it optional here -if (method_exists($dotenv, 'usePutenv')) { - $dotenv->usePutenv(true); -} - -// loadEnv was not available in version 3.4 und early 4.x versions of symfony/dotenv, so make it optional here -if (method_exists($dotenv, 'loadEnv')) { - $dotenv->loadEnv(dirname(__DIR__).'/.env'); -} else { - $files = []; - foreach ([dirname(__DIR__).'/.env', dirname(__DIR__).'/.env.local'] as $file) { - if (file_exists($file)) { - $files[] = $file; - } - } - - $dotenv->load(...$files); -} +$dotenv->usePutenv(); +$dotenv->loadEnv(dirname(__DIR__).'/.env'); diff --git a/tests/integration/Http/Transport/CurlTransportTest.php b/tests/integration/Http/Transport/CurlTransportTest.php index fb51caa5..4b094207 100644 --- a/tests/integration/Http/Transport/CurlTransportTest.php +++ b/tests/integration/Http/Transport/CurlTransportTest.php @@ -41,7 +41,9 @@ public static function setUpBeforeClass(): void TestHttpServer::start(); } - /** @return array> */ + /** + * @return iterable> + */ public static function provideBadResponseCodes(): iterable { // cURL does not understand codes below 200 properly. diff --git a/tests/unit/Http/SetCookieTest.php b/tests/unit/Http/SetCookieTest.php index 91a3b834..7ef3765e 100644 --- a/tests/unit/Http/SetCookieTest.php +++ b/tests/unit/Http/SetCookieTest.php @@ -181,7 +181,7 @@ public function testMatchesPath(string $cookiePath, string $requestPath, bool $i self::assertSame($isMatch, $cookie->matchesPath($requestPath)); } - /** @return array> */ + /** @return array> */ public static function cookieValidateProvider(): array { return [ diff --git a/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php b/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php index 10dc5a99..4e571871 100644 --- a/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php +++ b/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php @@ -63,9 +63,7 @@ public function testRequestWithoutPayload(): void $this->assertSame('Hi Marty!', $response->getBody(), 'body is OK'); $this->assertSame('MartyMcFlySession', $response->getSessionId(), 'session ID is OK'); - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); - } + $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); } public function testRequestWithPayload(): void @@ -87,9 +85,7 @@ public function testRequestWithPayload(): void $this->assertSame('Hi Marty!', $response->getBody(), 'body is OK'); $this->assertSame('MartyMcFlySession', $response->getSessionId(), 'session ID is OK'); - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); - } + $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); } public function testRequestWithoutCookie(): void @@ -110,9 +106,7 @@ public function testRequestWithoutCookie(): void $this->assertSame('Hi Marty!', $response->getBody(), 'body is OK'); $this->assertNull($response->getSessionId(), 'session ID is OK'); - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); - } + $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); } public function testRequestWithEmptyCookie(): void @@ -133,9 +127,7 @@ public function testRequestWithEmptyCookie(): void $this->assertSame('Hi Marty!', $response->getBody(), 'body is OK'); $this->assertNull($response->getSessionId(), 'session ID is OK'); - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); - } + $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); } public function testRequestWithDefaultHeaders(): void @@ -163,9 +155,7 @@ public function testRequestWithDefaultHeaders(): void $this->assertSame('Hi Marty!', $response->getBody(), 'body is OK'); $this->assertSame('MartyMcFlySession', $response->getSessionId(), 'session ID is OK'); - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); - } + $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); } public function testRequestWithDefaultOptions(): void @@ -196,9 +186,7 @@ public function testRequestWithDefaultOptions(): void $this->assertSame('Hi Marty!', $response->getBody(), 'body is OK'); $this->assertSame('MartyMcFlySession', $response->getSessionId(), 'session ID is OK'); - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); - } + $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); } /** @return iterable> */ @@ -237,9 +225,7 @@ public function testRequestWithBadResponseCode(int $badCode): void try { $transport->request($request); } finally { - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); - } + $this->assertSame(1, $mockHttpClient->getRequestsCount(), 'one request was made'); } } @@ -275,9 +261,7 @@ public function testRequestWithBadResponseException(HttpExceptionInterface $exce try { $transport->request($request); } finally { - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(0, $mockHttpClient->getRequestsCount(), 'no full request was made'); - } + $this->assertSame(0, $mockHttpClient->getRequestsCount(), 'no full request was made'); } } @@ -302,9 +286,7 @@ public function testRequestWithTransportException(): void try { $transport->request($request); } finally { - if (method_exists($mockHttpClient, 'getRequestsCount')) { - $this->assertSame(0, $mockHttpClient->getRequestsCount(), 'no full request was made'); - } + $this->assertSame(0, $mockHttpClient->getRequestsCount(), 'no full request was made'); } } } diff --git a/tests/unit/Util/ArrayHelperTest.php b/tests/unit/Util/ArrayHelperTest.php index 9c1f5b75..192e5ea6 100644 --- a/tests/unit/Util/ArrayHelperTest.php +++ b/tests/unit/Util/ArrayHelperTest.php @@ -29,7 +29,9 @@ */ final class ArrayHelperTest extends TestCase { - /** @return iterable> */ + /** + * @return iterable, 1: array, 2: bool, 3: array}> + */ public static function provideArrays(): iterable { yield 'simple flat arrays' => [ @@ -61,9 +63,9 @@ public static function provideArrays(): iterable /** * @dataProvider provideArrays * - * @param array $input1 - * @param array $input2 - * @param array $output + * @param array $input1 + * @param array $input2 + * @param array $output */ public function testMergeRecursive(array $input1, array $input2, bool $reorderNested, array $output): void { From 88ec27537fd3e0067c386bd864893e1ecb5f7a5f Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:47:56 +0200 Subject: [PATCH 5/8] Fix rector & psalm --- src/Core/Hook.php | 14 +++++++------- src/Core/ImagePreview.php | 10 +++++----- src/Core/InlinePresentation.php | 4 +++- src/Core/Track.php | 12 ++++++------ src/Core/UrlPresentation.php | 2 ++ src/Http/SetCookie.php | 14 +++++++------- src/Http/Transport/TransportRequest.php | 4 ++-- src/Http/Transport/TransportResponse.php | 4 ++-- src/Responses/GetMeetingInfoResponse.php | 4 +--- 9 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/Core/Hook.php b/src/Core/Hook.php index da8c1ba5..7eb49836 100644 --- a/src/Core/Hook.php +++ b/src/Core/Hook.php @@ -25,19 +25,19 @@ /** * Class Meeting. */ -final class Hook +final readonly class Hook { - private readonly string $hookId; + private string $hookId; - private readonly string $meetingId; + private string $meetingId; - private readonly string $callbackUrl; + private string $callbackUrl; - private readonly bool $permanentHook; + private bool $permanentHook; - private readonly bool $rawData; + private bool $rawData; - public function __construct(private readonly \SimpleXMLElement $rawXml) + public function __construct(private \SimpleXMLElement $rawXml) { $this->hookId = $this->rawXml->hookID->__toString(); $this->callbackUrl = $this->rawXml->callbackURL->__toString(); diff --git a/src/Core/ImagePreview.php b/src/Core/ImagePreview.php index d7584aab..3c1e3b14 100644 --- a/src/Core/ImagePreview.php +++ b/src/Core/ImagePreview.php @@ -21,13 +21,13 @@ namespace BigBlueButton\Core; -final class ImagePreview +final readonly class ImagePreview { public function __construct( - private readonly int $width, - private readonly int $height, - private readonly string $alt, - private readonly string $url, + private int $width, + private int $height, + private string $alt, + private string $url, ) { } diff --git a/src/Core/InlinePresentation.php b/src/Core/InlinePresentation.php index 6b1f839c..44db7336 100644 --- a/src/Core/InlinePresentation.php +++ b/src/Core/InlinePresentation.php @@ -31,11 +31,13 @@ public function __construct(private readonly string $content, string $filename) $this->filename = $filename; } - public function getArrayKey(): string + #[\Override] + public function getArrayKey(): string|null { return $this->filename; } + #[\Override] public function addDocumentToXML(SimpleXMLElementExtended $module): ?SimpleXMLElementExtended { $document = parent::addDocumentToXML($module); diff --git a/src/Core/Track.php b/src/Core/Track.php index ff805c0a..da7ba15b 100644 --- a/src/Core/Track.php +++ b/src/Core/Track.php @@ -25,17 +25,17 @@ /** * Class Track. */ -final class Track +final readonly class Track { - private readonly string $href; + private string $href; - private readonly string $kind; + private string $kind; - private readonly string $label; + private string $label; - private readonly string $lang; + private string $lang; - private readonly string $source; + private string $source; public function __construct(object $track) { diff --git a/src/Core/UrlPresentation.php b/src/Core/UrlPresentation.php index db416bd9..f1ba7875 100644 --- a/src/Core/UrlPresentation.php +++ b/src/Core/UrlPresentation.php @@ -30,11 +30,13 @@ public function __construct(private readonly string $url) { } + #[\Override] public function getArrayKey(): string { return $this->url; } + #[\Override] public function addDocumentToXML(SimpleXMLElementExtended $module): ?SimpleXMLElementExtended { $document = parent::addDocumentToXML($module); diff --git a/src/Http/SetCookie.php b/src/Http/SetCookie.php index 99302b05..25bca177 100644 --- a/src/Http/SetCookie.php +++ b/src/Http/SetCookie.php @@ -138,7 +138,7 @@ public function toArray(): array /** * Get the cookie name. */ - public function getName(): string + public function getName(): string|bool|int|null { return $this->data['Name']; } @@ -156,7 +156,7 @@ public function setName(string $name): void /** * Get the cookie value. */ - public function getValue(): ?string + public function getValue(): string|bool|int|null { return $this->data['Value']; } @@ -174,7 +174,7 @@ public function setValue(string $value): void /** * Get the domain. */ - public function getDomain(): ?string + public function getDomain(): string|bool|int|null { return $this->data['Domain']; } @@ -190,7 +190,7 @@ public function setDomain(string $domain): void /** * Get the path. */ - public function getPath(): string + public function getPath(): string|bool|int|null { return $this->data['Path']; } @@ -226,7 +226,7 @@ public function setMaxAge(int $maxAge): void /** * The UNIX timestamp when the cookie Expires. */ - public function getExpires(): ?int + public function getExpires(): string|bool|int|null { return $this->data['Expires']; } @@ -246,7 +246,7 @@ public function setExpires(int|string $timestamp): void /** * Get whether this is a secure cookie. */ - public function getSecure(): ?bool + public function getSecure(): string|bool|int|null { return $this->data['Secure']; } @@ -264,7 +264,7 @@ public function setSecure(bool $secure): void /** * Get whether or not this is a session cookie. */ - public function getDiscard(): ?bool + public function getDiscard(): string|bool|int|null { return $this->data['Discard']; } diff --git a/src/Http/Transport/TransportRequest.php b/src/Http/Transport/TransportRequest.php index b5ac4c0c..456bdc89 100644 --- a/src/Http/Transport/TransportRequest.php +++ b/src/Http/Transport/TransportRequest.php @@ -24,9 +24,9 @@ /** * Represents request data to be passed to {@link TransportInterface} for talking to BBB server. */ -final class TransportRequest +final readonly class TransportRequest { - public function __construct(private readonly string $url, private readonly string $payload, private readonly string $contentType) + public function __construct(private string $url, private string $payload, private string $contentType) { } diff --git a/src/Http/Transport/TransportResponse.php b/src/Http/Transport/TransportResponse.php index fb1bf02c..f08e539f 100644 --- a/src/Http/Transport/TransportResponse.php +++ b/src/Http/Transport/TransportResponse.php @@ -24,9 +24,9 @@ /** * Represents response returned by {@link TransportInterface} from BBB server. */ -final class TransportResponse +final readonly class TransportResponse { - public function __construct(private readonly string $body, private readonly ?string $sessionId) + public function __construct(private string $body, private ?string $sessionId) { } diff --git a/src/Responses/GetMeetingInfoResponse.php b/src/Responses/GetMeetingInfoResponse.php index 123970c2..b02b9338 100644 --- a/src/Responses/GetMeetingInfoResponse.php +++ b/src/Responses/GetMeetingInfoResponse.php @@ -33,9 +33,7 @@ final class GetMeetingInfoResponse extends BaseResponse public function getMeeting(): Meeting { - if ($this->meeting === null) { - $this->meeting = new Meeting($this->rawXml); - } + $this->meeting ??= new Meeting($this->rawXml); return $this->meeting; } From cc46c56dd93505764ff137644a29e5862f1bf38d Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:59:39 +0200 Subject: [PATCH 6/8] Fix phpstan --- src/Core/InlinePresentation.php | 12 +++++------- src/Http/Transport/CurlTransport.php | 2 +- src/Http/Transport/Header.php | 4 ++-- src/Parameters/CreateMeetingParameters.php | 12 +++++------- src/Parameters/HooksListParameters.php | 4 ++-- src/Parameters/InsertDocumentParameters.php | 4 +--- 6 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Core/InlinePresentation.php b/src/Core/InlinePresentation.php index 44db7336..6bd901c1 100644 --- a/src/Core/InlinePresentation.php +++ b/src/Core/InlinePresentation.php @@ -32,22 +32,20 @@ public function __construct(private readonly string $content, string $filename) } #[\Override] - public function getArrayKey(): string|null + public function getArrayKey(): string { return $this->filename; } #[\Override] - public function addDocumentToXML(SimpleXMLElementExtended $module): ?SimpleXMLElementExtended + public function addDocumentToXML(SimpleXMLElementExtended $module): SimpleXMLElementExtended { $document = parent::addDocumentToXML($module); - /* @phpstan-ignore-next-line */ - $document[0] = base64_encode($this->content); + $element = dom_import_simplexml($document); + $element->nodeValue = base64_encode($this->content); - if (isset($this->filename)) { - $document->addAttribute('name', $this->filename); - } + $document->addAttribute('name', $this->filename); return $document; } diff --git a/src/Http/Transport/CurlTransport.php b/src/Http/Transport/CurlTransport.php index fea68831..34695d44 100644 --- a/src/Http/Transport/CurlTransport.php +++ b/src/Http/Transport/CurlTransport.php @@ -219,7 +219,7 @@ private static function getHeadersAndContentFromCurlHandle(\CurlHandle $curlHand $splitHeader = explode(': ', $line, 2); // @codeCoverageIgnoreStart - if (!isset($splitHeader[0], $splitHeader[1])) { + if (2 !== \count($splitHeader)) { throw new \InvalidArgumentException(\sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $line)); } // @codeCoverageIgnoreEnd diff --git a/src/Http/Transport/Header.php b/src/Http/Transport/Header.php index ea339ee2..7d2865df 100644 --- a/src/Http/Transport/Header.php +++ b/src/Http/Transport/Header.php @@ -32,7 +32,7 @@ final class Header * Merges multiple arrays of HTTP headers as passed to CURLOPT_HTTPHEADER. Headers given in later arrays will * overwrite the previous one with the same name. * - * @param string[] ...$headers + * @param list ...$headers * * @return string[] */ @@ -50,7 +50,7 @@ public static function mergeCurlHeaders(array ...$headers): array } $splitHeader = explode(': ', $header, 2); - if (!isset($splitHeader[0], $splitHeader[1])) { + if (2 !== \count($splitHeader)) { throw new \InvalidArgumentException(\sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $header)); } diff --git a/src/Parameters/CreateMeetingParameters.php b/src/Parameters/CreateMeetingParameters.php index 4a14e5c4..3d038c66 100644 --- a/src/Parameters/CreateMeetingParameters.php +++ b/src/Parameters/CreateMeetingParameters.php @@ -139,10 +139,10 @@ * @method $this setUserCameraCap(int $cap) * @method int|null getMeetingCameraCap() * @method $this setMeetingCameraCap(int $cap) - * @method array getDisabledFeatures() - * @method $this setDisabledFeatures(array $disabledFeatures) - * @method array getDisabledFeaturesExclude() - * @method $this setDisabledFeaturesExclude(array $disabledFeaturesExclude) + * @method array getDisabledFeatures() + * @method $this setDisabledFeatures(array $disabledFeatures) + * @method array getDisabledFeaturesExclude() + * @method $this setDisabledFeaturesExclude(array $disabledFeaturesExclude) * @method bool|null isPreUploadedPresentationOverrideDefault() * @method $this setPreUploadedPresentationOverrideDefault(bool $preUploadedPresentationOverrideDefault) * @method string|null getPresentationUploadExternalUrl() @@ -453,9 +453,7 @@ public function addPresentationsModule(SimpleXMLElementExtended $xml): void $module->addAttribute('name', 'presentation'); foreach ($this->presentations as $data) { - if ($data instanceof Presentation) { - $data->addDocumentToXML($module); - } + $data->addDocumentToXML($module); } } } diff --git a/src/Parameters/HooksListParameters.php b/src/Parameters/HooksListParameters.php index 0df6305c..1356c49a 100644 --- a/src/Parameters/HooksListParameters.php +++ b/src/Parameters/HooksListParameters.php @@ -25,8 +25,8 @@ /** * Class HooksListParameters. * - * @method string getMeetingID() - * @method $this setMeetingID(string $id) + * @method string|null getMeetingID() + * @method $this setMeetingID(string $id) */ final class HooksListParameters extends MetaParameters { diff --git a/src/Parameters/InsertDocumentParameters.php b/src/Parameters/InsertDocumentParameters.php index 1d799895..8ffad3c8 100644 --- a/src/Parameters/InsertDocumentParameters.php +++ b/src/Parameters/InsertDocumentParameters.php @@ -82,9 +82,7 @@ public function getPresentationsAsXML(): string|false $module->addAttribute('name', 'presentation'); foreach ($this->presentations as $content) { - if ($content instanceof Presentation) { - $content->addDocumentToXML($module); - } + $content->addDocumentToXML($module); } $result = $xml->asXML(); } From a3b42a4348387362171f28941d44af40459c364c Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:46:43 +0200 Subject: [PATCH 7/8] Fixes --- src/Core/InlinePresentation.php | 3 +-- src/Core/Presentation.php | 14 ----------- src/Core/UrlPresentation.php | 9 +++++++ src/Http/SetCookie.php | 25 +++++++++++-------- .../SymfonyHttpClientTransport.php | 2 +- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/Core/InlinePresentation.php b/src/Core/InlinePresentation.php index 6bd901c1..145d8121 100644 --- a/src/Core/InlinePresentation.php +++ b/src/Core/InlinePresentation.php @@ -26,9 +26,8 @@ final class InlinePresentation extends Presentation { - public function __construct(private readonly string $content, string $filename) + public function __construct(private readonly string $content, private readonly string $filename) { - $this->filename = $filename; } #[\Override] diff --git a/src/Core/Presentation.php b/src/Core/Presentation.php index 06b074a2..927f1f23 100644 --- a/src/Core/Presentation.php +++ b/src/Core/Presentation.php @@ -26,8 +26,6 @@ abstract class Presentation { - protected ?string $filename = null; - protected ?bool $current = null; protected ?bool $downloadable = null; @@ -55,18 +53,6 @@ public function addDocumentToXML(SimpleXMLElementExtended $module): ?SimpleXMLEl abstract public function getArrayKey(): string; - public function getFilename(): ?string - { - return $this->filename; - } - - public function setFilename(string $filename): self - { - $this->filename = $filename; - - return $this; - } - public function getCurrent(): ?bool { return $this->current; diff --git a/src/Core/UrlPresentation.php b/src/Core/UrlPresentation.php index f1ba7875..ae4c9976 100644 --- a/src/Core/UrlPresentation.php +++ b/src/Core/UrlPresentation.php @@ -26,6 +26,8 @@ final class UrlPresentation extends Presentation { + private ?string $filename = null; + public function __construct(private readonly string $url) { } @@ -48,4 +50,11 @@ public function addDocumentToXML(SimpleXMLElementExtended $module): ?SimpleXMLEl return $document; } + + public function setFilename(string $filename): self + { + $this->filename = $filename; + + return $this; + } } diff --git a/src/Http/SetCookie.php b/src/Http/SetCookie.php index 25bca177..052abbcd 100644 --- a/src/Http/SetCookie.php +++ b/src/Http/SetCookie.php @@ -47,9 +47,7 @@ final class SetCookie implements \Stringable 'HttpOnly' => false, ]; - /** - * @var array Cookie data - */ + /** @var array Cookie data */ private array $data; /** @@ -238,17 +236,22 @@ public function getExpires(): string|bool|int|null */ public function setExpires(int|string $timestamp): void { - $this->data['Expires'] = is_numeric($timestamp) - ? (int) $timestamp - : strtotime($timestamp); + if (is_numeric($timestamp)) { + $this->data['Expires'] = (int) $timestamp; + + return; + } + + $expires = strtotime($timestamp); + $this->data['Expires'] = $expires !== false ? $expires : null; } /** * Get whether this is a secure cookie. */ - public function getSecure(): string|bool|int|null + public function getSecure(): bool { - return $this->data['Secure']; + return (bool) $this->data['Secure']; } /** @@ -264,9 +267,9 @@ public function setSecure(bool $secure): void /** * Get whether or not this is a session cookie. */ - public function getDiscard(): string|bool|int|null + public function getDiscard(): bool { - return $this->data['Discard']; + return (bool) $this->data['Discard']; } /** @@ -381,7 +384,7 @@ public function isExpired(): bool public function validate(): bool|string { $name = $this->getName(); - if ($name === '') { + if ($name === null || $name === '') { return 'The cookie name must not be empty'; } diff --git a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php index 6c093a13..608092fb 100644 --- a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php +++ b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php @@ -126,7 +126,7 @@ public function request(TransportRequest $request): TransportResponse * @throws RedirectionExceptionInterface * @throws ServerExceptionInterface */ - private static function extractJsessionCookie(ResponseInterface $symfonyResponse): bool|int|string|null + private static function extractJsessionCookie(ResponseInterface $symfonyResponse): string|int|bool|null { $responseHeaders = $symfonyResponse->getHeaders(); From 29166dd139a08d29e4c194276780602af9e1ef66 Mon Sep 17 00:00:00 2001 From: Samuel Weirich <4281791+SamuelWei@users.noreply.github.com> Date: Fri, 2 Oct 2026 10:29:06 +0200 Subject: [PATCH 8/8] Simplify cookie helper class to basics --- src/Http/SetCookie.php | 370 +----------------- .../SymfonyHttpClientTransport.php | 2 +- src/Http/Transport/Cookie.php | 6 +- tests/unit/Http/SetCookieTest.php | 357 +---------------- 4 files changed, 35 insertions(+), 700 deletions(-) diff --git a/src/Http/SetCookie.php b/src/Http/SetCookie.php index 052abbcd..277c10fc 100644 --- a/src/Http/SetCookie.php +++ b/src/Http/SetCookie.php @@ -28,390 +28,56 @@ /** * Value object for HTTP cookies, - * based on https://github.com/guzzle/guzzle/blob/master/src/Cookie/SetCookie.php. + * based on https://github.com/guzzle/guzzle/blob/8.2/src/Cookie/SetCookie.php. * * @internal */ -final class SetCookie implements \Stringable +final readonly class SetCookie { - /** @var array */ - private static array $defaults = [ - 'Name' => null, - 'Value' => null, - 'Domain' => null, - 'Path' => '/', - 'Max-Age' => null, - 'Expires' => null, - 'Secure' => false, - 'Discard' => false, - 'HttpOnly' => false, - ]; - - /** @var array Cookie data */ - private array $data; - /** * Create a new SetCookie object from a string. * * @param string $cookie Set-Cookie header string */ - public static function fromString(string $cookie): self + public static function fromString(#[\SensitiveParameter] string $cookie): ?self { - // Create the default return array - $data = self::$defaults; // Explode the cookie string using a series of semicolons - $pieces = array_filter(array_map(trim(...), explode(';', $cookie))); + $pieces = array_filter(array_map(static fn (string $piece): string => trim($piece, " \t"), explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. if (!isset($pieces[0]) || !str_contains($pieces[0], '=')) { - return new self($data); - } - - // Add the cookie pieces into the parsed data array - foreach ($pieces as $part) { - $cookieParts = explode('=', $part, 2); - $key = trim($cookieParts[0]); - $value = isset($cookieParts[1]) - ? trim($cookieParts[1], " \n\r\t\0\x0B") - : true; - - // Only check for non-cookies when cookies have been found - if (!isset($data['Name'])) { - $data['Name'] = $key; - $data['Value'] = $value; - } else { - foreach (array_keys(self::$defaults) as $search) { - if (!strcasecmp($search, $key)) { - $data[$search] = $value; - - continue 2; - } - } - $data[$key] = $value; - } - } - - return new self($data); - } - - /** - * @param array $data Array of cookie data provided by a Cookie parser - */ - public function __construct(array $data = []) - { - $this->data = array_replace(self::$defaults, $data); - // Extract the Expires value and turn it into a UNIX timestamp if needed - if (!$this->getExpires() && $this->getMaxAge()) { - // Calculate the Expires date - $this->setExpires(time() + $this->getMaxAge()); - } elseif (null !== ($expires = $this->getExpires()) && !is_numeric($expires)) { - $this->setExpires($expires); + return null; } - } - #[\Override] - public function __toString(): string - { - $str = $this->data['Name'].'='.$this->data['Value'].'; '; - foreach ($this->data as $k => $v) { - if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { - if ($k === 'Expires') { - $str .= 'Expires='.gmdate('D, d M Y H:i:s \G\M\T', (int) $v).'; '; - } else { - $str .= ($v === true ? $k : "{$k}={$v}").'; '; - } - } - } + // Only get the first kvp, everything else is not needed for this BBB API + $cookieParts = explode('=', $pieces[0], 2); + $name = trim($cookieParts[0], " \t"); + $value = trim($cookieParts[1], " \t"); - return rtrim($str, '; '); + return new self($name, $value); } /** - * @return array + * @param string $name Cookie name + * @param string $value Cookie value */ - public function toArray(): array + private function __construct(#[\SensitiveParameter] private string $name, #[\SensitiveParameter] private string $value) { - return $this->data; } /** * Get the cookie name. */ - public function getName(): string|bool|int|null - { - return $this->data['Name']; - } - - /** - * Set the cookie name. - * - * @param string $name Cookie name - */ - public function setName(string $name): void + public function getName(): string { - $this->data['Name'] = $name; + return $this->name; } /** * Get the cookie value. */ - public function getValue(): string|bool|int|null - { - return $this->data['Value']; - } - - /** - * Set the cookie value. - * - * @param string $value Cookie value - */ - public function setValue(string $value): void - { - $this->data['Value'] = $value; - } - - /** - * Get the domain. - */ - public function getDomain(): string|bool|int|null + public function getValue(): string { - return $this->data['Domain']; - } - - /** - * Set the domain of the cookie. - */ - public function setDomain(string $domain): void - { - $this->data['Domain'] = $domain; - } - - /** - * Get the path. - */ - public function getPath(): string|bool|int|null - { - return $this->data['Path']; - } - - /** - * Set the path of the cookie. - * - * @param string $path Path of the cookie - */ - public function setPath(string $path): void - { - $this->data['Path'] = $path; - } - - /** - * Maximum lifetime of the cookie in seconds. - */ - public function getMaxAge(): ?int - { - return $this->data['Max-Age'] === null ? null : (int) $this->data['Max-Age']; - } - - /** - * Set the max-age of the cookie. - * - * @param int $maxAge Max age of the cookie in seconds - */ - public function setMaxAge(int $maxAge): void - { - $this->data['Max-Age'] = $maxAge; - } - - /** - * The UNIX timestamp when the cookie Expires. - */ - public function getExpires(): string|bool|int|null - { - return $this->data['Expires']; - } - - /** - * Set the unix timestamp for which the cookie will expire. - * - * @param int|string $timestamp unix timestamp or any English textual datetime description - */ - public function setExpires(int|string $timestamp): void - { - if (is_numeric($timestamp)) { - $this->data['Expires'] = (int) $timestamp; - - return; - } - - $expires = strtotime($timestamp); - $this->data['Expires'] = $expires !== false ? $expires : null; - } - - /** - * Get whether this is a secure cookie. - */ - public function getSecure(): bool - { - return (bool) $this->data['Secure']; - } - - /** - * Set whether or not the cookie is secure. - * - * @param bool $secure Set to true or false if secure - */ - public function setSecure(bool $secure): void - { - $this->data['Secure'] = $secure; - } - - /** - * Get whether or not this is a session cookie. - */ - public function getDiscard(): bool - { - return (bool) $this->data['Discard']; - } - - /** - * Set whether or not this is a session cookie. - * - * @param bool $discard Set to true or false if this is a session cookie - */ - public function setDiscard(bool $discard): void - { - $this->data['Discard'] = $discard; - } - - /** - * Get whether or not this is an HTTP only cookie. - */ - public function getHttpOnly(): bool - { - return (bool) $this->data['HttpOnly']; - } - - /** - * Set whether or not this is an HTTP only cookie. - * - * @param bool $httpOnly Set to true or false if this is HTTP only - */ - public function setHttpOnly(bool $httpOnly): void - { - $this->data['HttpOnly'] = $httpOnly; - } - - /** - * Check if the cookie matches a path value. - * - * A request-path path-matches a given cookie-path if at least one of - * the following conditions holds: - * - * - The cookie-path and the request-path are identical. - * - The cookie-path is a prefix of the request-path, and the last - * character of the cookie-path is %x2F ("/"). - * - The cookie-path is a prefix of the request-path, and the first - * character of the request-path that is not included in the cookie- - * path is a %x2F ("/") character. - * - * @param string $requestPath Path to check against - */ - public function matchesPath(string $requestPath): bool - { - $cookiePath = $this->getPath(); - - // Match on exact matches or when path is the default empty "/" - if ($cookiePath === '/' || $cookiePath === $requestPath) { - return true; - } - - // Ensure that the cookie-path is a prefix of the request path. - if (!str_starts_with($requestPath, $cookiePath)) { - return false; - } - - // Match if the last character of the cookie-path is "/" - if (str_ends_with($cookiePath, '/')) { - return true; - } - - // Match if the first character not included in cookie path is "/" - return $requestPath[\strlen($cookiePath)] === '/'; - } - - /** - * Check if the cookie matches a domain value. - * - * @param string $domain Domain to check against - */ - public function matchesDomain(string $domain): bool - { - $cookieDomain = $this->getDomain(); - if (null === $cookieDomain) { - return true; - } - - // Remove the leading '.' as per spec in RFC 6265. - // https://tools.ietf.org/html/rfc6265#section-5.2.3 - $cookieDomain = ltrim($cookieDomain, '.'); - - // Domain not set or exact match. - if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { - return true; - } - - // Matching the subdomain according to RFC 6265. - // https://tools.ietf.org/html/rfc6265#section-5.1.3 - if (filter_var($domain, \FILTER_VALIDATE_IP)) { - return false; - } - - return (bool) preg_match('/\.'.preg_quote($cookieDomain, '/').'$/', $domain); - } - - /** - * Check if the cookie is expired. - */ - public function isExpired(): bool - { - return $this->getExpires() !== null && time() > $this->getExpires(); - } - - /** - * Check if the cookie is valid according to RFC 6265. - * - * @return bool|string Returns true if valid or an error message if invalid - */ - public function validate(): bool|string - { - $name = $this->getName(); - if ($name === null || $name === '') { - return 'The cookie name must not be empty'; - } - - // Check if any of the invalid characters are present in the cookie name - if (preg_match( - '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', - $name - )) { - return 'Cookie name must not contain invalid characters: ASCII ' - .'Control characters (0-31;127), space, tab and the ' - .'following characters: ()<>@,;:\"/?={}'; - } - - // Value must not be null. 0 and empty string are valid. Empty strings - // are technically against RFC 6265, but known to happen in the wild. - $value = $this->getValue(); - if ($value === null) { - return 'The cookie value must not be empty'; - } - - // Domains must not be empty, but can be 0. "0" is not a valid internet - // domain, but may be used as server name in a private network. - $domain = $this->getDomain(); - if ($domain === null || $domain === '') { - return 'The cookie domain must not be empty'; - } - - return true; + return $this->value; } } diff --git a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php index 608092fb..43c0976c 100644 --- a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php +++ b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php @@ -126,7 +126,7 @@ public function request(TransportRequest $request): TransportResponse * @throws RedirectionExceptionInterface * @throws ServerExceptionInterface */ - private static function extractJsessionCookie(ResponseInterface $symfonyResponse): string|int|bool|null + private static function extractJsessionCookie(ResponseInterface $symfonyResponse): ?string { $responseHeaders = $symfonyResponse->getHeaders(); diff --git a/src/Http/Transport/Cookie.php b/src/Http/Transport/Cookie.php index 511bb6d0..f2c878d9 100644 --- a/src/Http/Transport/Cookie.php +++ b/src/Http/Transport/Cookie.php @@ -33,11 +33,15 @@ final class Cookie /** * @param string[] $headerValues */ - public static function extractJsessionId(array $headerValues): bool|int|string|null + public static function extractJsessionId(array $headerValues): ?string { foreach ($headerValues as $headerValue) { $cookie = SetCookie::fromString($headerValue); + if ($cookie === null) { + continue; + } + if ($cookie->getName() === 'JSESSIONID') { $value = $cookie->getValue(); diff --git a/tests/unit/Http/SetCookieTest.php b/tests/unit/Http/SetCookieTest.php index 7ef3765e..ba8421ce 100644 --- a/tests/unit/Http/SetCookieTest.php +++ b/tests/unit/Http/SetCookieTest.php @@ -30,211 +30,12 @@ use PHPUnit\Framework\TestCase; /** - * Test of Value object for HTTP cookies, - * based on https://github.com/guzzle/guzzle/blob/master/tests/Cookie/SetCookieTest.php. + * Test of Value object for HTTP cookies. * * @covers \BigBlueButton\Http\SetCookie */ final class SetCookieTest extends TestCase { - public function testInitializesDefaultValues(): void - { - $cookie = new SetCookie(); - self::assertSame('/', $cookie->getPath()); - } - - public function testConvertsDateTimeMaxAgeToUnixTimestamp(): void - { - $cookie = new SetCookie(['Expires' => 'November 20, 1984']); - self::assertIsInt($cookie->getExpires()); - } - - public function testAddsExpiresBasedOnMaxAge(): void - { - $t = time(); - $cookie = new SetCookie(['Max-Age' => 100]); - self::assertEquals($t + 100, $cookie->getExpires()); - } - - public function testHoldsValues(): void - { - $t = time(); - $data = [ - 'Name' => 'foo', - 'Value' => 'baz', - 'Path' => '/bar', - 'Domain' => 'baz.com', - 'Expires' => $t, - 'Max-Age' => 100, - 'Secure' => true, - 'Discard' => true, - 'HttpOnly' => true, - 'foo' => 'baz', - 'bar' => 'bam', - ]; - - $cookie = new SetCookie($data); - self::assertEquals($data, $cookie->toArray()); - - self::assertSame('foo', $cookie->getName()); - self::assertSame('baz', $cookie->getValue()); - self::assertSame('baz.com', $cookie->getDomain()); - self::assertSame('/bar', $cookie->getPath()); - self::assertSame($t, $cookie->getExpires()); - self::assertSame(100, $cookie->getMaxAge()); - self::assertTrue($cookie->getSecure()); - self::assertTrue($cookie->getDiscard()); - self::assertTrue($cookie->getHttpOnly()); - self::assertSame('baz', $cookie->toArray()['foo']); - self::assertSame('bam', $cookie->toArray()['bar']); - - $cookie->setName('a'); - $cookie->setValue('b'); - $cookie->setPath('c'); - $cookie->setDomain('bar.com'); - $cookie->setExpires(10); - $cookie->setMaxAge(200); - $cookie->setSecure(false); - $cookie->setHttpOnly(false); - $cookie->setDiscard(false); - - self::assertSame('a', $cookie->getName()); - self::assertSame('b', $cookie->getValue()); - self::assertSame('c', $cookie->getPath()); - self::assertSame('bar.com', $cookie->getDomain()); - self::assertSame(10, $cookie->getExpires()); - self::assertSame(200, $cookie->getMaxAge()); - self::assertFalse($cookie->getSecure()); - self::assertFalse($cookie->getDiscard()); - self::assertFalse($cookie->getHttpOnly()); - } - - public function testDeterminesIfExpired(): void - { - $c = new SetCookie(); - $c->setExpires(10); - self::assertTrue($c->isExpired()); - $c->setExpires(time() + 10000); - self::assertFalse($c->isExpired()); - } - - public function testMatchesDomain(): void - { - $cookie = new SetCookie(); - self::assertTrue($cookie->matchesDomain('baz.com')); - - $cookie->setDomain('baz.com'); - self::assertTrue($cookie->matchesDomain('baz.com')); - self::assertFalse($cookie->matchesDomain('bar.com')); - - $cookie->setDomain('.baz.com'); - self::assertTrue($cookie->matchesDomain('.baz.com')); - self::assertTrue($cookie->matchesDomain('foo.baz.com')); - self::assertFalse($cookie->matchesDomain('baz.bar.com')); - self::assertTrue($cookie->matchesDomain('baz.com')); - - $cookie->setDomain('.127.0.0.1'); - self::assertTrue($cookie->matchesDomain('127.0.0.1')); - - $cookie->setDomain('127.0.0.1'); - self::assertTrue($cookie->matchesDomain('127.0.0.1')); - - $cookie->setDomain('.com.'); - self::assertFalse($cookie->matchesDomain('baz.com')); - - $cookie->setDomain('.local'); - self::assertTrue($cookie->matchesDomain('example.local')); - - $cookie->setDomain('example.com/'); // malformed domain - self::assertFalse($cookie->matchesDomain('example.com')); - } - - /** @return array> */ - public static function pathMatchProvider(): array - { - return [ - ['/foo', '/foo', true], - ['/foo', '/Foo', false], - ['/foo', '/fo', false], - ['/foo', '/foo/bar', true], - ['/foo', '/foo/bar/baz', true], - ['/foo', '/foo/bar//baz', true], - ['/foo', '/foobar', false], - ['/foo/bar', '/foo', false], - ['/foo/bar', '/foobar', false], - ['/foo/bar', '/foo/bar', true], - ['/foo/bar', '/foo/bar/', true], - ['/foo/bar', '/foo/bar/baz', true], - ['/foo/bar/', '/foo/bar', false], - ['/foo/bar/', '/foo/bar/', true], - ['/foo/bar/', '/foo/bar/baz', true], - ]; - } - - /** - * @dataProvider pathMatchProvider - */ - public function testMatchesPath(string $cookiePath, string $requestPath, bool $isMatch): void - { - $cookie = new SetCookie(); - $cookie->setPath($cookiePath); - self::assertSame($isMatch, $cookie->matchesPath($requestPath)); - } - - /** @return array> */ - public static function cookieValidateProvider(): array - { - return [ - ['foo', 'baz', 'bar', true], - ['0', '0', '0', true], - ['foo[bar]', 'baz', 'bar', true], - ['foo', '', 'bar', true], - ['', 'baz', 'bar', 'The cookie name must not be empty'], - ['foo', null, 'bar', 'The cookie value must not be empty'], - ['foo', 'baz', '', 'The cookie domain must not be empty'], - ["foo\r", 'baz', '0', 'Cookie name must not contain invalid characters: ASCII Control characters (0-31;127), space, tab and the following characters: ()<>@,;:\"/?={}'], - ]; - } - - /** - * @dataProvider cookieValidateProvider - * - * @param bool|string $result - */ - public function testValidatesCookies(string $name, ?string $value, string $domain, $result): void - { - $cookie = new SetCookie([ - 'Name' => $name, - 'Value' => $value, - 'Domain' => $domain, - ]); - self::assertSame($result, $cookie->validate()); - } - - public function testDoesNotMatchIp(): void - { - $cookie = new SetCookie(['Domain' => '192.168.16.']); - self::assertFalse($cookie->matchesDomain('192.168.16.121')); - } - - public function testConvertsToString(): void - { - $t = 1382916008; - $cookie = new SetCookie([ - 'Name' => 'test', - 'Value' => '123', - 'Domain' => 'foo.com', - 'Expires' => $t, - 'Path' => '/abc', - 'HttpOnly' => true, - 'Secure' => true, - ]); - self::assertSame( - 'test=123; Domain=foo.com; Path=/abc; Expires=Sun, 27 Oct 2013 23:20:08 GMT; Secure; HttpOnly', - (string) $cookie - ); - } - /** * Provides the parsed information from a cookie. * @@ -246,33 +47,18 @@ public static function cookieParserDataProvider(): array [ 'ASIHTTPRequestTestCookie=This+is+the+value; expires=Sat, 26-Jul-2008 17:00:42 GMT; path=/tests; domain=allseeing-i.com; PHPSESSID=6c951590e7a9359bcedde25cda73e43c; path=/;', [ - 'Domain' => 'allseeing-i.com', - 'Path' => '/', - 'PHPSESSID' => '6c951590e7a9359bcedde25cda73e43c', - 'Max-Age' => null, - 'Expires' => 'Sat, 26-Jul-2008 17:00:42 GMT', - 'Secure' => null, - 'Discard' => null, 'Name' => 'ASIHTTPRequestTestCookie', 'Value' => 'This+is+the+value', - 'HttpOnly' => false, ], ], - ['', []], - ['foo', []], - ['; foo', []], + ['', null], + ['foo', null], + ['; foo', null], [ 'foo="bar"', [ 'Name' => 'foo', 'Value' => '"bar"', - 'Discard' => null, - 'Domain' => null, - 'Expires' => null, - 'Max-Age' => null, - 'Path' => '/', - 'Secure' => null, - 'HttpOnly' => false, ], ], // Test setting a blank value for a cookie @@ -281,13 +67,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'foo', 'Value' => '', - 'Discard' => null, - 'Domain' => null, - 'Expires' => null, - 'Max-Age' => null, - 'Path' => '/', - 'Secure' => null, - 'HttpOnly' => false, ], ], // Test setting a value and removing quotes @@ -296,13 +75,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'foo', 'Value' => '1', - 'Discard' => null, - 'Domain' => null, - 'Expires' => null, - 'Max-Age' => null, - 'Path' => '/', - 'Secure' => null, - 'HttpOnly' => false, ], ], // Some of the following tests are based on https://github.com/zendframework/zf1/blob/master/tests/Zend/Http/CookieTest.php @@ -311,13 +83,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'justacookie', 'Value' => 'foo', - 'Domain' => 'example.com', - 'Discard' => null, - 'Expires' => null, - 'Max-Age' => null, - 'Path' => '/', - 'Secure' => null, - 'HttpOnly' => false, ], ], [ @@ -325,13 +90,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'expires', 'Value' => 'tomorrow', - 'Domain' => '.example.com', - 'Path' => '/Space Out/', - 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', - 'Discard' => null, - 'Secure' => true, - 'Max-Age' => null, - 'HttpOnly' => false, ], ], [ @@ -339,13 +97,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'domain', 'Value' => 'unittests', - 'Domain' => 'example.com', - 'Path' => '/some value/', - 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', - 'Secure' => false, - 'Discard' => null, - 'Max-Age' => null, - 'HttpOnly' => false, ], ], [ @@ -353,13 +104,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'path', 'Value' => 'indexAction', - 'Domain' => '.foo.com', - 'Path' => '/', - 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', - 'Secure' => false, - 'Discard' => null, - 'Max-Age' => null, - 'HttpOnly' => false, ], ], [ @@ -367,14 +111,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'secure', 'Value' => 'sha1', - 'Domain' => 'some.really.deep.domain.com', - 'Path' => '/', - 'Secure' => true, - 'Discard' => null, - 'Expires' => time() + 86400, - 'Max-Age' => 86400, - 'HttpOnly' => false, - 'version' => '1', ], ], [ @@ -382,13 +118,6 @@ public static function cookieParserDataProvider(): array [ 'Name' => 'PHPSESSID', 'Value' => '123456789+abcd%2Cef', - 'Domain' => '.localdomain', - 'Path' => '/foo/baz', - 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', - 'Secure' => true, - 'Discard' => true, - 'Max-Age' => null, - 'HttpOnly' => false, ], ], ]; @@ -400,82 +129,18 @@ public static function cookieParserDataProvider(): array * @param array|string $cookie * @param array $parsed */ - public function testParseCookie(array|string $cookie, array $parsed): void + public function testParseCookie(array|string $cookie, ?array $parsed): void { foreach ((array) $cookie as $v) { - $c = SetCookie::fromString($v); - $p = $c->toArray(); + $parsedCookie = SetCookie::fromString($v); - if (isset($p['Expires'])) { - $delta = 40; - $parsedExpires = is_numeric($parsed['Expires']) ? $parsed['Expires'] : strtotime((string) $parsed['Expires']); - self::assertLessThan($delta, abs($p['Expires'] - $parsedExpires), 'Comparing Expires '.var_export($p['Expires'], true).' : '.var_export($parsed, true).' | '.var_export($p, true)); - unset($p['Expires']); - unset($parsed['Expires']); + if ($parsed === null) { + self::assertNull($parsedCookie); + continue; } - if (!empty($parsed)) { - foreach ($parsed as $key => $value) { - self::assertEquals($parsed[$key], $p[$key], 'Comparing '.$key.' '.var_export($value, true).' : '.var_export($parsed, true).' | '.var_export($p, true)); - } - foreach ($p as $key => $value) { - self::assertEquals($p[$key], $parsed[$key], 'Comparing '.$key.' '.var_export($value, true).' : '.var_export($parsed, true).' | '.var_export($p, true)); - } - } else { - self::assertSame([ - 'Name' => null, - 'Value' => null, - 'Domain' => null, - 'Path' => '/', - 'Max-Age' => null, - 'Expires' => null, - 'Secure' => false, - 'Discard' => false, - 'HttpOnly' => false, - ], $p); - } + self::assertEquals($parsedCookie->getName(), $parsed['Name']); + self::assertEquals($parsedCookie->getValue(), $parsed['Value']); } } - - /** - * Provides the data for testing isExpired. - * - * @return array> - */ - public static function isExpiredProvider(): array - { - return [ - [ - 'FOO=bar; expires=Thu, 01 Jan 1970 00:00:00 GMT;', - true, - ], - [ - 'FOO=bar; expires=Thu, 01 Jan 1970 00:00:01 GMT;', - true, - ], - [ - 'FOO=bar; expires='.date(\DateTime::RFC1123, time() + 10).';', - false, - ], - [ - 'FOO=bar; expires='.date(\DateTime::RFC1123, time() - 10).';', - true, - ], - [ - 'FOO=bar;', - false, - ], - ]; - } - - /** - * @dataProvider isExpiredProvider - */ - public function testIsExpired(string $cookie, bool $expired): void - { - self::assertSame( - $expired, - SetCookie::fromString($cookie)->isExpired() - ); - } }