From c1284fb27de06246c90f6795ac98b860595adcf4 Mon Sep 17 00:00:00 2001 From: blaipr Date: Thu, 13 Aug 2026 23:53:59 +0200 Subject: [PATCH] Let Serde hand back the scalars it accepts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit serialize() takes a string or an int — its own test passes both through it — but deserialize() was declared object|array, so reading either back raised a TypeError on the way out. Anything that cached a bare scalar could store it and never read it again; the file cache is one call away from that, and today only gets away with it because every caller happens to cache an array or an object. Widen the native return type to what the other half accepts. The phpDoc is left exactly as it was: the parenthesised form that would satisfy the parser reintroduces template-resolution errors at every call site, which is why it is baselined with that note. The one place the wider type rippled to is the public link view, where the deserialized vault is annotated at the call site instead — so its baseline entry goes, one fewer than before. --- phpstan.baseline.neon | 6 ------ src/Domain/Common/Adapters/Serde.php | 7 +++++-- .../Controllers/Account/ViewLinkController.php | 1 + tests/Unit/Domain/Common/Adapters/SerdeTest.php | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/phpstan.baseline.neon b/phpstan.baseline.neon index d939412ff..eb50532a0 100644 --- a/phpstan.baseline.neon +++ b/phpstan.baseline.neon @@ -915,12 +915,6 @@ parameters: count: 2 path: src/Infrastructure/Adapter/In/Web/Controllers/Account/SaveRequestController.php - - - message: '#^Cannot call method getData\(\) on array\|object\.$#' - identifier: method.nonObject - count: 1 - path: src/Infrastructure/Adapter/In/Web/Controllers/Account/ViewLinkController.php - - message: '#^Parameter \#1 \$type of method SP\\Domain\\Core\\Events\\EventMessage\\:\:addExtra\(\) expects class\-string\, string given\.$#' identifier: argument.type diff --git a/src/Domain/Common/Adapters/Serde.php b/src/Domain/Common/Adapters/Serde.php index 2c0938306..ba7ba4631 100644 --- a/src/Domain/Common/Adapters/Serde.php +++ b/src/Domain/Common/Adapters/Serde.php @@ -69,8 +69,11 @@ public static function serializeJson(array|object|string|int $data, int $flags = * * @throws SPException */ - public static function deserialize(string $data, ?string $class = null, string ...$nestedClasses): object|array - { + public static function deserialize( + string $data, + ?string $class = null, + string ...$nestedClasses + ): object|array|string|int { $value = @unserialize( $data, ['allowed_classes' => $class !== null ? [$class, ...$nestedClasses] : true] diff --git a/src/Infrastructure/Adapter/In/Web/Controllers/Account/ViewLinkController.php b/src/Infrastructure/Adapter/In/Web/Controllers/Account/ViewLinkController.php index 3be5c9697..249ca734f 100644 --- a/src/Infrastructure/Adapter/In/Web/Controllers/Account/ViewLinkController.php +++ b/src/Infrastructure/Adapter/In/Web/Controllers/Account/ViewLinkController.php @@ -110,6 +110,7 @@ public function viewLinkAction(string $hash): ActionResponse $this->accountService->incrementViewCounter($publicLink->getItemId()); $this->accountService->incrementDecryptCounter($publicLink->getItemId()); + /** @var Vault $vault The stored blob is a Vault; Serde answers with whatever it holds. */ $vault = Serde::deserialize($publicLink->getData() ?? '', Vault::class, Crypt::class); $accountViewDto = AccountViewDto::fromModel( diff --git a/tests/Unit/Domain/Common/Adapters/SerdeTest.php b/tests/Unit/Domain/Common/Adapters/SerdeTest.php index 6d5854eca..03d0d6364 100644 --- a/tests/Unit/Domain/Common/Adapters/SerdeTest.php +++ b/tests/Unit/Domain/Common/Adapters/SerdeTest.php @@ -84,6 +84,21 @@ public function testDeserialize() $this->assertEquals('0f099212786ab8090432f2889ac37c2a977f164a', $out->attributes['configHash']); } + /** + * serialize() takes a scalar — the provider above passes a string and an int through it — so + * deserialize() has to hand one back. Declared as object|array, it raised a TypeError on the + * way out instead: anything that cached a bare string, the file cache included, could store it + * and never read it again. + * + * @throws SPException + */ + #[DataProvider('serializeDataProvider')] + public function testDeserializeReturnsWhateverWasSerialized(mixed $data, string $serialized) + { + // Equals, not same: an object comes back as a copy, which is the point of serializing it. + self::assertEquals($data, Serde::deserialize($serialized)); + } + /** * @throws SPException */