Skip to content

Commit a71304a

Browse files
Keep legacy PHP payloads off Avro serializer
1 parent f36fbbd commit a71304a

3 files changed

Lines changed: 47 additions & 0 deletions

File tree

.github/workflows/php.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ jobs:
8383
DB_CONNECTION: mysql
8484
DB_PORT: ${{ job.services.mysql.ports[3306] }}
8585
QUEUE_CONNECTION: redis
86+
XDEBUG_MODE: off
8687

8788
- name: Run test suite (PostgreSQL)
8889
timeout-minutes: 65
@@ -91,6 +92,7 @@ jobs:
9192
DB_CONNECTION: pgsql
9293
DB_PORT: ${{ job.services.postgres.ports[5432] }}
9394
QUEUE_CONNECTION: redis
95+
XDEBUG_MODE: off
9496

9597
- name: Upload laravel.log if tests fail
9698
if: failure()

src/Serializers/Serializer.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Workflow\Serializers;
66

77
use Illuminate\Queue\SerializesAndRestoresModelIdentifiers;
8+
use Laravel\SerializableClosure\SerializableClosure;
89
use Throwable;
910

1011
final class Serializer
@@ -45,6 +46,10 @@ public static function __callStatic(string $name, array $arguments)
4546
$class = self::defaultCodecClass();
4647
}
4748

49+
if ($name === 'serialize' && $class === Avro::class && self::containsPhpOnlyValue($arguments[0] ?? null)) {
50+
$class = Y::class;
51+
}
52+
4853
if ($name === 'serialize' && ! is_subclass_of($class, AbstractSerializer::class)) {
4954
$arguments[0] = self::normalizeForCodec($arguments[0] ?? null);
5055
}
@@ -155,6 +160,29 @@ private static function normalizeForCodec(mixed $data): mixed
155160
return self::serializeModels($data);
156161
}
157162

163+
private static function containsPhpOnlyValue(mixed $data): bool
164+
{
165+
if ($data instanceof Throwable) {
166+
return false;
167+
}
168+
169+
if ($data instanceof SerializableClosure) {
170+
return true;
171+
}
172+
173+
if (is_array($data)) {
174+
foreach ($data as $value) {
175+
if (self::containsPhpOnlyValue($value)) {
176+
return true;
177+
}
178+
}
179+
180+
return false;
181+
}
182+
183+
return is_object($data) || is_resource($data);
184+
}
185+
158186
/**
159187
* @return array{class: class-string<Throwable>, message: string, code: int|string, line: int, file: string, trace: list<array<string, mixed>>}
160188
*/

tests/Unit/Serializers/SerializeTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Tests\Unit\Serializers;
66

7+
use Laravel\SerializableClosure\SerializableClosure;
78
use Tests\Fixtures\TestEnum;
89
use Tests\TestCase;
910
use Throwable;
@@ -105,6 +106,22 @@ public function testLegacyYPayloadStillWinsOverAvroDefault(): void
105106
], Serializer::unserialize($serialized));
106107
}
107108

109+
public function testLegacySerializeKeepsPhpOnlyValuesOnPhpSerializerWhenAvroIsConfigured(): void
110+
{
111+
config([
112+
'workflows.serializer' => 'avro',
113+
]);
114+
115+
$serialized = Serializer::serialize([
116+
new SerializableClosure(static fn (): string => 'ok'),
117+
]);
118+
119+
$unserialized = Serializer::unserialize($serialized);
120+
121+
$this->assertInstanceOf(SerializableClosure::class, $unserialized[0]);
122+
$this->assertSame('ok', $unserialized[0]->getClosure()());
123+
}
124+
108125
private function testSerializeUnserialize($data, $serializer, $unserializer): void
109126
{
110127
config([

0 commit comments

Comments
 (0)