mirror of
https://github.com/php-flasher/php-flasher.git
synced 2026-03-31 15:07:47 +01:00
Fix Laravel 13 JSON session serialization compatibility
This commit is contained in:
@@ -25,10 +25,26 @@ final readonly class SessionBag implements BagInterface
|
||||
{
|
||||
$session = $this->getSession();
|
||||
|
||||
/** @var Envelope[] $envelopes */
|
||||
$envelopes = $session->get(self::ENVELOPES_NAMESPACE, []);
|
||||
|
||||
return $envelopes;
|
||||
if (!\is_array($envelopes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($envelopes as $envelope) {
|
||||
if ($envelope instanceof Envelope) {
|
||||
$result[] = $envelope;
|
||||
} elseif (\is_string($envelope)) {
|
||||
$unserialized = @unserialize($envelope);
|
||||
if ($unserialized instanceof Envelope) {
|
||||
$result[] = $unserialized;
|
||||
}
|
||||
}
|
||||
// Arrays and invalid data silently skipped (graceful degradation)
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function set(array $envelopes): void
|
||||
@@ -41,7 +57,7 @@ final readonly class SessionBag implements BagInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$session->put(self::ENVELOPES_NAMESPACE, $envelopes);
|
||||
$session->put(self::ENVELOPES_NAMESPACE, array_map(serialize(...), $envelopes));
|
||||
}
|
||||
|
||||
private function getSession(): Session|FallbackSessionInterface
|
||||
|
||||
@@ -9,6 +9,8 @@ use Flasher\Laravel\Storage\FallbackSessionInterface;
|
||||
use Flasher\Laravel\Storage\SessionBag;
|
||||
use Flasher\Prime\Notification\Envelope;
|
||||
use Flasher\Prime\Notification\Notification;
|
||||
use Flasher\Prime\Stamp\DelayStamp;
|
||||
use Flasher\Prime\Stamp\HopsStamp;
|
||||
use Flasher\Prime\Stamp\IdStamp;
|
||||
use Flasher\Tests\Laravel\TestCase;
|
||||
use Illuminate\Session\SessionManager;
|
||||
@@ -65,7 +67,7 @@ final class SessionBagTest extends TestCase
|
||||
$sessionMock = \Mockery::mock(Store::class);
|
||||
$sessionMock->allows()->isStarted()->andReturns(true);
|
||||
$sessionMock->allows()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($envelopes);
|
||||
$sessionMock->expects()->put(SessionBag::ENVELOPES_NAMESPACE, $envelopes);
|
||||
$sessionMock->expects()->put(SessionBag::ENVELOPES_NAMESPACE, array_map(serialize(...), $envelopes));
|
||||
|
||||
$this->sessionManagerMock->allows()->driver()->andReturns($sessionMock);
|
||||
|
||||
@@ -193,4 +195,132 @@ final class SessionBagTest extends TestCase
|
||||
|
||||
$this->assertSame($envelopes2, $this->sessionBag->get());
|
||||
}
|
||||
|
||||
public function testEnvelopeSurvivesJsonSerializationRoundTrip(): void
|
||||
{
|
||||
$notification = new Notification();
|
||||
$notification->setType('success');
|
||||
$notification->setMessage('Operation completed');
|
||||
|
||||
$original = new Envelope($notification, [
|
||||
new IdStamp('json-test-id'),
|
||||
new HopsStamp(2),
|
||||
new DelayStamp(500),
|
||||
]);
|
||||
|
||||
$sessionMock = \Mockery::mock(Store::class);
|
||||
$sessionMock->allows()->isStarted()->andReturns(true);
|
||||
|
||||
$captured = null;
|
||||
$sessionMock->expects('put')->with(SessionBag::ENVELOPES_NAMESPACE, \Mockery::on(function ($value) use (&$captured) {
|
||||
$captured = $value;
|
||||
|
||||
return true;
|
||||
}));
|
||||
|
||||
$this->sessionManagerMock->allows()->driver()->andReturns($sessionMock);
|
||||
$this->sessionBag->set([$original]);
|
||||
|
||||
$jsonEncoded = json_encode($captured, \JSON_THROW_ON_ERROR);
|
||||
$jsonDecoded = json_decode($jsonEncoded, true, 512, \JSON_THROW_ON_ERROR);
|
||||
|
||||
$sessionMock->allows()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($jsonDecoded);
|
||||
|
||||
$restored = $this->sessionBag->get();
|
||||
|
||||
$this->assertCount(1, $restored);
|
||||
$this->assertInstanceOf(Envelope::class, $restored[0]);
|
||||
$this->assertSame('success', $restored[0]->getType());
|
||||
$this->assertSame('Operation completed', $restored[0]->getMessage());
|
||||
$this->assertSame('json-test-id', $restored[0]->get(IdStamp::class)->getId());
|
||||
$this->assertSame(2, $restored[0]->get(HopsStamp::class)->getAmount());
|
||||
$this->assertSame(500, $restored[0]->get(DelayStamp::class)->getDelay());
|
||||
}
|
||||
|
||||
public function testGetHandlesEnvelopeObjectsForBackwardCompatibility(): void
|
||||
{
|
||||
$envelopes = [
|
||||
new Envelope(new Notification(), new IdStamp('legacy')),
|
||||
];
|
||||
|
||||
$envelopes[0]->setType('info');
|
||||
$envelopes[0]->setMessage('Old session');
|
||||
|
||||
$sessionMock = \Mockery::mock(Store::class);
|
||||
$sessionMock->expects()->isStarted()->andReturns(true);
|
||||
$sessionMock->expects()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($envelopes);
|
||||
|
||||
$this->sessionManagerMock->expects()->driver()->andReturns($sessionMock);
|
||||
|
||||
$result = $this->sessionBag->get();
|
||||
|
||||
$this->assertSame($envelopes, $result);
|
||||
}
|
||||
|
||||
public function testGetSkipsCorruptedArrayData(): void
|
||||
{
|
||||
$sessionMock = \Mockery::mock(Store::class);
|
||||
$sessionMock->expects()->isStarted()->andReturns(true);
|
||||
$sessionMock->expects()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns([
|
||||
['title' => '', 'message' => '', 'type' => 'success', 'options' => [], 'metadata' => ['id' => 'corrupted']],
|
||||
]);
|
||||
|
||||
$this->sessionManagerMock->expects()->driver()->andReturns($sessionMock);
|
||||
|
||||
$result = $this->sessionBag->get();
|
||||
|
||||
$this->assertSame([], $result);
|
||||
}
|
||||
|
||||
public function testGetSkipsInvalidSerializedStrings(): void
|
||||
{
|
||||
$sessionMock = \Mockery::mock(Store::class);
|
||||
$sessionMock->expects()->isStarted()->andReturns(true);
|
||||
$sessionMock->expects()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns([
|
||||
'invalid-not-serialized-data',
|
||||
'O:8:"stdClass":0:{}', // Valid serialized object but wrong type
|
||||
serialize(new Envelope(new Notification(), new IdStamp('valid'))),
|
||||
]);
|
||||
|
||||
$this->sessionManagerMock->expects()->driver()->andReturns($sessionMock);
|
||||
|
||||
$result = $this->sessionBag->get();
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertInstanceOf(Envelope::class, $result[0]);
|
||||
$this->assertSame('valid', $result[0]->get(IdStamp::class)->getId());
|
||||
}
|
||||
|
||||
public function testPreservesAllStampsAcrossRoundTrip(): void
|
||||
{
|
||||
$notification = new Notification();
|
||||
$notification->setType('warning');
|
||||
$notification->setMessage('Multi-stamp');
|
||||
|
||||
$original = new Envelope($notification, [
|
||||
new IdStamp('stamp-test'),
|
||||
new HopsStamp(3),
|
||||
new DelayStamp(1000),
|
||||
]);
|
||||
|
||||
$serialized = serialize($original);
|
||||
$jsonEncoded = json_encode([$serialized], \JSON_THROW_ON_ERROR);
|
||||
$jsonDecoded = json_decode($jsonEncoded, true, 512, \JSON_THROW_ON_ERROR);
|
||||
|
||||
$sessionMock = \Mockery::mock(Store::class);
|
||||
$sessionMock->expects()->isStarted()->andReturns(true);
|
||||
$sessionMock->expects()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($jsonDecoded);
|
||||
|
||||
$this->sessionManagerMock->expects()->driver()->andReturns($sessionMock);
|
||||
|
||||
$restored = $this->sessionBag->get();
|
||||
|
||||
$this->assertCount(1, $restored);
|
||||
$this->assertInstanceOf(Envelope::class, $restored[0]);
|
||||
$this->assertSame('warning', $restored[0]->getType());
|
||||
$this->assertSame('Multi-stamp', $restored[0]->getMessage());
|
||||
$this->assertSame('stamp-test', $restored[0]->get(IdStamp::class)->getId());
|
||||
$this->assertSame(3, $restored[0]->get(HopsStamp::class)->getAmount());
|
||||
$this->assertSame(1000, $restored[0]->get(DelayStamp::class)->getDelay());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user