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();
|
$session = $this->getSession();
|
||||||
|
|
||||||
/** @var Envelope[] $envelopes */
|
|
||||||
$envelopes = $session->get(self::ENVELOPES_NAMESPACE, []);
|
$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
|
public function set(array $envelopes): void
|
||||||
@@ -41,7 +57,7 @@ final readonly class SessionBag implements BagInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$session->put(self::ENVELOPES_NAMESPACE, $envelopes);
|
$session->put(self::ENVELOPES_NAMESPACE, array_map(serialize(...), $envelopes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSession(): Session|FallbackSessionInterface
|
private function getSession(): Session|FallbackSessionInterface
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ use Flasher\Laravel\Storage\FallbackSessionInterface;
|
|||||||
use Flasher\Laravel\Storage\SessionBag;
|
use Flasher\Laravel\Storage\SessionBag;
|
||||||
use Flasher\Prime\Notification\Envelope;
|
use Flasher\Prime\Notification\Envelope;
|
||||||
use Flasher\Prime\Notification\Notification;
|
use Flasher\Prime\Notification\Notification;
|
||||||
|
use Flasher\Prime\Stamp\DelayStamp;
|
||||||
|
use Flasher\Prime\Stamp\HopsStamp;
|
||||||
use Flasher\Prime\Stamp\IdStamp;
|
use Flasher\Prime\Stamp\IdStamp;
|
||||||
use Flasher\Tests\Laravel\TestCase;
|
use Flasher\Tests\Laravel\TestCase;
|
||||||
use Illuminate\Session\SessionManager;
|
use Illuminate\Session\SessionManager;
|
||||||
@@ -65,7 +67,7 @@ final class SessionBagTest extends TestCase
|
|||||||
$sessionMock = \Mockery::mock(Store::class);
|
$sessionMock = \Mockery::mock(Store::class);
|
||||||
$sessionMock->allows()->isStarted()->andReturns(true);
|
$sessionMock->allows()->isStarted()->andReturns(true);
|
||||||
$sessionMock->allows()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($envelopes);
|
$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);
|
$this->sessionManagerMock->allows()->driver()->andReturns($sessionMock);
|
||||||
|
|
||||||
@@ -193,4 +195,132 @@ final class SessionBagTest extends TestCase
|
|||||||
|
|
||||||
$this->assertSame($envelopes2, $this->sessionBag->get());
|
$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