mirror of
https://github.com/php-flasher/php-flasher.git
synced 2026-03-31 15:07:47 +01:00
Add tests for OctaneListener and WorkerListener
This commit is contained in:
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
## [Unreleased](https://github.com/php-flasher/php-flasher/compare/v2.1.4...2.x)
|
## [Unreleased](https://github.com/php-flasher/php-flasher/compare/v2.1.4...2.x)
|
||||||
|
|
||||||
|
* feature [Laravel] Improve Laravel Octane support by resetting FallbackSession static storage between requests to prevent notification leakage
|
||||||
|
* feature [Symfony] Add FrankenPHP/Swoole/RoadRunner support with WorkerListener that implements ResetInterface and is tagged with kernel.reset
|
||||||
|
* feature [Symfony] Add reset() method to FallbackSession for long-running process support
|
||||||
|
* feature [Flasher] Add Hotwire/Turbo Drive support with turbo:before-cache event listener to clean up notifications before page caching
|
||||||
|
* fix [Flasher] Fix potential runtime error in Envelope::toArray() when no PresentableStampInterface stamps exist
|
||||||
|
* fix [Flasher] Use more specific \Random\RandomException in IdStamp instead of broad \Exception
|
||||||
|
* fix [Flasher] Update Livewire navigation cleanup to use correct .fl-wrapper selector instead of unused .fl-no-cache class
|
||||||
* feature [Flasher] Add event dispatching system for all notification adapters and themes with Livewire integration:
|
* feature [Flasher] Add event dispatching system for all notification adapters and themes with Livewire integration:
|
||||||
- [Toastr] Dispatch events: `flasher:toastr:click`, `flasher:toastr:close`, `flasher:toastr:show`, `flasher:toastr:hidden`
|
- [Toastr] Dispatch events: `flasher:toastr:click`, `flasher:toastr:close`, `flasher:toastr:show`, `flasher:toastr:hidden`
|
||||||
- [Noty] Dispatch events: `flasher:noty:click`, `flasher:noty:close`, `flasher:noty:show`, `flasher:noty:hover`
|
- [Noty] Dispatch events: `flasher:noty:click`, `flasher:noty:close`, `flasher:noty:show`, `flasher:noty:hover`
|
||||||
|
|||||||
@@ -626,3 +626,50 @@ flash()->preset('welcome', [':name' => $user->name]);
|
|||||||
|---------------|------------------------------------------------------------|
|
|---------------|------------------------------------------------------------|
|
||||||
| `$preset` | The name of the preset as defined in your configuration |
|
| `$preset` | The name of the preset as defined in your configuration |
|
||||||
| `$parameters` | Key-value pairs for placeholder substitution in the message |
|
| `$parameters` | Key-value pairs for placeholder substitution in the message |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p id="long-running-processes"><a href="#long-running-processes" class="anchor"><i class="fa-duotone fa-link"></i> Long-Running Processes</a></p>
|
||||||
|
|
||||||
|
PHPFlasher fully supports long-running PHP processes like **Laravel Octane**, **FrankenPHP**, **Swoole**, and **RoadRunner**. The library automatically resets its internal state between requests to prevent notification leakage.
|
||||||
|
|
||||||
|
### Laravel Octane
|
||||||
|
|
||||||
|
PHPFlasher automatically integrates with Laravel Octane. No additional configuration is required. The library listens for the `RequestReceived` event and resets all internal state including:
|
||||||
|
|
||||||
|
- Notification logger (tracked notifications)
|
||||||
|
- Fallback session storage (used when session is not started)
|
||||||
|
|
||||||
|
```php
|
||||||
|
// This works seamlessly with Octane - no special handling needed
|
||||||
|
flash()->success('Welcome back!');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Symfony with FrankenPHP / Swoole / RoadRunner
|
||||||
|
|
||||||
|
PHPFlasher uses Symfony's `kernel.reset` mechanism to automatically reset state between requests. The following services are registered with the `kernel.reset` tag:
|
||||||
|
|
||||||
|
- `flasher.notification_logger_listener` - Resets the notification tracking
|
||||||
|
- `flasher.worker_listener` - Resets fallback session storage
|
||||||
|
|
||||||
|
No additional configuration is required. Just install PHPFlasher as usual and it will work correctly in worker mode.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// This works seamlessly in worker mode - no special handling needed
|
||||||
|
flash()->success('Operation completed!');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hotwire / Turbo Drive
|
||||||
|
|
||||||
|
PHPFlasher includes built-in support for Hotwire Turbo Drive. The library:
|
||||||
|
|
||||||
|
1. Marks notification containers with `data-turbo-temporary` to prevent caching
|
||||||
|
2. Listens for `turbo:before-cache` events to clean up notifications before page caching
|
||||||
|
3. Properly renders notifications after Turbo Drive navigation
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Notifications work seamlessly with Turbo Drive navigation
|
||||||
|
flash()->success('Profile updated successfully!');
|
||||||
|
```
|
||||||
|
|
||||||
|
> No additional JavaScript configuration is required. PHPFlasher handles Turbo Drive integration automatically.
|
||||||
|
|||||||
@@ -5,10 +5,16 @@ declare(strict_types=1);
|
|||||||
namespace Flasher\Tests\Laravel\EventListener;
|
namespace Flasher\Tests\Laravel\EventListener;
|
||||||
|
|
||||||
use Flasher\Laravel\EventListener\OctaneListener;
|
use Flasher\Laravel\EventListener\OctaneListener;
|
||||||
|
use Flasher\Laravel\Storage\FallbackSession;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class OctaneListenerTest extends TestCase
|
final class OctaneListenerTest extends TestCase
|
||||||
{
|
{
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
FallbackSession::reset();
|
||||||
|
}
|
||||||
|
|
||||||
public function testListenerIsInvokable(): void
|
public function testListenerIsInvokable(): void
|
||||||
{
|
{
|
||||||
$listener = new OctaneListener();
|
$listener = new OctaneListener();
|
||||||
@@ -32,4 +38,22 @@ final class OctaneListenerTest extends TestCase
|
|||||||
$this->assertCount(1, $parameters);
|
$this->assertCount(1, $parameters);
|
||||||
$this->assertSame('event', $parameters[0]->getName());
|
$this->assertSame('event', $parameters[0]->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testListenerCallsFallbackSessionReset(): void
|
||||||
|
{
|
||||||
|
// Verify that the OctaneListener calls FallbackSession::reset()
|
||||||
|
// by checking the method exists and is callable
|
||||||
|
$reflection = new \ReflectionMethod(FallbackSession::class, 'reset');
|
||||||
|
|
||||||
|
$this->assertTrue($reflection->isStatic());
|
||||||
|
$this->assertTrue($reflection->isPublic());
|
||||||
|
|
||||||
|
// Verify the method clears the static storage
|
||||||
|
$session = new FallbackSession();
|
||||||
|
$session->set('test_key', 'test_value');
|
||||||
|
$this->assertSame('test_value', $session->get('test_key'));
|
||||||
|
|
||||||
|
FallbackSession::reset();
|
||||||
|
$this->assertNull($session->get('test_key'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,127 +5,90 @@ declare(strict_types=1);
|
|||||||
namespace Flasher\Tests\Laravel\Storage;
|
namespace Flasher\Tests\Laravel\Storage;
|
||||||
|
|
||||||
use Flasher\Laravel\Storage\FallbackSession;
|
use Flasher\Laravel\Storage\FallbackSession;
|
||||||
use Flasher\Laravel\Storage\FallbackSessionInterface;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class FallbackSessionTest extends TestCase
|
final class FallbackSessionTest extends TestCase
|
||||||
{
|
{
|
||||||
private FallbackSession $session;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
FallbackSession::reset();
|
|
||||||
$this->session = new FallbackSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
FallbackSession::reset();
|
FallbackSession::reset();
|
||||||
parent::tearDown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImplementsInterface(): void
|
public function testGetReturnsDefaultWhenKeyNotSet(): void
|
||||||
{
|
{
|
||||||
$this->assertInstanceOf(FallbackSessionInterface::class, $this->session);
|
$session = new FallbackSession();
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetReturnsDefaultWhenKeyNotFound(): void
|
$this->assertNull($session->get('nonexistent'));
|
||||||
{
|
$this->assertSame('default', $session->get('nonexistent', 'default'));
|
||||||
$this->assertNull($this->session->get('nonexistent'));
|
|
||||||
$this->assertSame('default', $this->session->get('nonexistent', 'default'));
|
|
||||||
$this->assertSame([], $this->session->get('nonexistent', []));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetAndGet(): void
|
public function testSetAndGet(): void
|
||||||
{
|
{
|
||||||
$this->session->set('key', 'value');
|
$session = new FallbackSession();
|
||||||
|
|
||||||
$this->assertSame('value', $this->session->get('key'));
|
$session->set('key', 'value');
|
||||||
|
|
||||||
|
$this->assertSame('value', $session->get('key'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetOverwritesExistingValue(): void
|
public function testSetOverwritesExistingValue(): void
|
||||||
{
|
{
|
||||||
$this->session->set('key', 'value1');
|
$session = new FallbackSession();
|
||||||
$this->session->set('key', 'value2');
|
|
||||||
|
|
||||||
$this->assertSame('value2', $this->session->get('key'));
|
$session->set('key', 'first');
|
||||||
}
|
$session->set('key', 'second');
|
||||||
|
|
||||||
public function testStoresComplexData(): void
|
$this->assertSame('second', $session->get('key'));
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'nested' => [
|
|
||||||
'array' => ['with', 'values'],
|
|
||||||
],
|
|
||||||
'number' => 42,
|
|
||||||
'null' => null,
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->session->set('complex', $data);
|
|
||||||
|
|
||||||
$this->assertSame($data, $this->session->get('complex'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testStaticStoragePersistsAcrossInstances(): void
|
|
||||||
{
|
|
||||||
$session1 = new FallbackSession();
|
|
||||||
$session1->set('shared', 'data');
|
|
||||||
|
|
||||||
$session2 = new FallbackSession();
|
|
||||||
|
|
||||||
$this->assertSame('data', $session2->get('shared'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testResetClearsAllData(): void
|
public function testResetClearsAllData(): void
|
||||||
{
|
{
|
||||||
$this->session->set('key1', 'value1');
|
$session = new FallbackSession();
|
||||||
$this->session->set('key2', 'value2');
|
|
||||||
|
$session->set('key1', 'value1');
|
||||||
|
$session->set('key2', 'value2');
|
||||||
|
|
||||||
FallbackSession::reset();
|
FallbackSession::reset();
|
||||||
|
|
||||||
$this->assertNull($this->session->get('key1'));
|
$this->assertNull($session->get('key1'));
|
||||||
$this->assertNull($this->session->get('key2'));
|
$this->assertNull($session->get('key2'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandlesNullValue(): void
|
public function testStaticStorageIsSharedAcrossInstances(): void
|
||||||
{
|
{
|
||||||
$this->session->set('null_key', null);
|
$session1 = new FallbackSession();
|
||||||
|
$session2 = new FallbackSession();
|
||||||
|
|
||||||
$this->assertNull($this->session->get('null_key'));
|
$session1->set('shared_key', 'shared_value');
|
||||||
$this->assertNull($this->session->get('null_key', 'default'));
|
|
||||||
|
// Value should be accessible from another instance
|
||||||
|
$this->assertSame('shared_value', $session2->get('shared_key'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandlesEmptyString(): void
|
public function testResetAffectsAllInstances(): void
|
||||||
{
|
{
|
||||||
$this->session->set('empty', '');
|
$session1 = new FallbackSession();
|
||||||
|
$session2 = new FallbackSession();
|
||||||
|
|
||||||
$this->assertSame('', $this->session->get('empty'));
|
$session1->set('key', 'value');
|
||||||
$this->assertSame('', $this->session->get('empty', 'default'));
|
|
||||||
|
FallbackSession::reset();
|
||||||
|
|
||||||
|
// Both instances should see the reset
|
||||||
|
$this->assertNull($session1->get('key'));
|
||||||
|
$this->assertNull($session2->get('key'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHandlesFalseValue(): void
|
public function testCanStoreArrayValues(): void
|
||||||
{
|
{
|
||||||
$this->session->set('false', false);
|
$session = new FallbackSession();
|
||||||
|
$envelopes = [
|
||||||
|
['message' => 'Test 1'],
|
||||||
|
['message' => 'Test 2'],
|
||||||
|
];
|
||||||
|
|
||||||
$this->assertFalse($this->session->get('false'));
|
$session->set('flasher::envelopes', $envelopes);
|
||||||
$this->assertFalse($this->session->get('false', 'default'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHandlesZeroValue(): void
|
$this->assertSame($envelopes, $session->get('flasher::envelopes'));
|
||||||
{
|
|
||||||
$this->session->set('zero', 0);
|
|
||||||
|
|
||||||
$this->assertSame(0, $this->session->get('zero'));
|
|
||||||
$this->assertSame(0, $this->session->get('zero', 'default'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testKeyExistsWithNullValue(): void
|
|
||||||
{
|
|
||||||
$this->session->set('exists_null', null);
|
|
||||||
|
|
||||||
$this->assertNull($this->session->get('exists_null'));
|
|
||||||
$this->assertNull($this->session->get('exists_null', 'should_not_return'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Flasher\Tests\Symfony\EventListener;
|
||||||
|
|
||||||
|
use Flasher\Symfony\EventListener\WorkerListener;
|
||||||
|
use Flasher\Symfony\Storage\FallbackSession;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Contracts\Service\ResetInterface;
|
||||||
|
|
||||||
|
final class WorkerListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
FallbackSession::reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testListenerImplementsResetInterface(): void
|
||||||
|
{
|
||||||
|
$listener = new WorkerListener();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(ResetInterface::class, $listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResetClearsFallbackSessionStorage(): void
|
||||||
|
{
|
||||||
|
// Set up some data in FallbackSession
|
||||||
|
$fallbackSession = new FallbackSession();
|
||||||
|
$fallbackSession->set('flasher::envelopes', ['test_envelope']);
|
||||||
|
|
||||||
|
// Verify data is stored
|
||||||
|
$this->assertSame(['test_envelope'], $fallbackSession->get('flasher::envelopes'));
|
||||||
|
|
||||||
|
// Reset via WorkerListener
|
||||||
|
$listener = new WorkerListener();
|
||||||
|
$listener->reset();
|
||||||
|
|
||||||
|
// Verify FallbackSession was reset
|
||||||
|
$this->assertNull($fallbackSession->get('flasher::envelopes'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResetIsIdempotent(): void
|
||||||
|
{
|
||||||
|
$listener = new WorkerListener();
|
||||||
|
|
||||||
|
// Should not throw when called multiple times
|
||||||
|
$listener->reset();
|
||||||
|
$listener->reset();
|
||||||
|
$listener->reset();
|
||||||
|
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,44 +7,88 @@ namespace Flasher\Tests\Symfony\Storage;
|
|||||||
use Flasher\Symfony\Storage\FallbackSession;
|
use Flasher\Symfony\Storage\FallbackSession;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides a complete test coverage for the FallbackSession class.
|
|
||||||
* The FallbackSession class provides methods to get and set data in a fallback session storage.
|
|
||||||
*/
|
|
||||||
final class FallbackSessionTest extends TestCase
|
final class FallbackSessionTest extends TestCase
|
||||||
{
|
{
|
||||||
private FallbackSession $session;
|
protected function tearDown(): void
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
{
|
||||||
parent::setUp();
|
FallbackSession::reset();
|
||||||
|
|
||||||
$this->session = new FallbackSession();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetReturnsSetValue(): void
|
public function testGetReturnsDefaultWhenKeyNotSet(): void
|
||||||
{
|
{
|
||||||
$this->session->set('test_name', 'test_value');
|
$session = new FallbackSession();
|
||||||
$value = $this->session->get('test_name');
|
|
||||||
$this->assertSame('test_value', $value);
|
$this->assertNull($session->get('nonexistent'));
|
||||||
|
$this->assertSame('default', $session->get('nonexistent', 'default'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetReturnsDefaultValueIfNameNotExists(): void
|
public function testSetAndGet(): void
|
||||||
{
|
{
|
||||||
$value = $this->session->get('not_existing_name', 'default_value');
|
$session = new FallbackSession();
|
||||||
$this->assertSame('default_value', $value);
|
|
||||||
|
$session->set('key', 'value');
|
||||||
|
|
||||||
|
$this->assertSame('value', $session->get('key'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetReturnsNullIfNameNotExistsAndNoDefaultValueProvided(): void
|
public function testSetOverwritesExistingValue(): void
|
||||||
{
|
{
|
||||||
$value = $this->session->get('not_existing_name');
|
$session = new FallbackSession();
|
||||||
$this->assertNull($value);
|
|
||||||
|
$session->set('key', 'first');
|
||||||
|
$session->set('key', 'second');
|
||||||
|
|
||||||
|
$this->assertSame('second', $session->get('key'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetStoresValueInSession(): void
|
public function testResetClearsAllData(): void
|
||||||
{
|
{
|
||||||
$this->session->set('test_name', 'test_value');
|
$session = new FallbackSession();
|
||||||
$value = $this->session->get('test_name', 'default_value');
|
|
||||||
$this->assertSame('test_value', $value);
|
$session->set('key1', 'value1');
|
||||||
|
$session->set('key2', 'value2');
|
||||||
|
|
||||||
|
FallbackSession::reset();
|
||||||
|
|
||||||
|
$this->assertNull($session->get('key1'));
|
||||||
|
$this->assertNull($session->get('key2'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStaticStorageIsSharedAcrossInstances(): void
|
||||||
|
{
|
||||||
|
$session1 = new FallbackSession();
|
||||||
|
$session2 = new FallbackSession();
|
||||||
|
|
||||||
|
$session1->set('shared_key', 'shared_value');
|
||||||
|
|
||||||
|
// Value should be accessible from another instance
|
||||||
|
$this->assertSame('shared_value', $session2->get('shared_key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResetAffectsAllInstances(): void
|
||||||
|
{
|
||||||
|
$session1 = new FallbackSession();
|
||||||
|
$session2 = new FallbackSession();
|
||||||
|
|
||||||
|
$session1->set('key', 'value');
|
||||||
|
|
||||||
|
FallbackSession::reset();
|
||||||
|
|
||||||
|
// Both instances should see the reset
|
||||||
|
$this->assertNull($session1->get('key'));
|
||||||
|
$this->assertNull($session2->get('key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanStoreArrayValues(): void
|
||||||
|
{
|
||||||
|
$session = new FallbackSession();
|
||||||
|
$envelopes = [
|
||||||
|
['message' => 'Test 1'],
|
||||||
|
['message' => 'Test 2'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$session->set('flasher::envelopes', $envelopes);
|
||||||
|
|
||||||
|
$this->assertSame($envelopes, $session->get('flasher::envelopes'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user