diff --git a/src/event/illuminate/CallQueuedListener.php b/src/event/illuminate/CallQueuedListener.php index 53346030c..8db23b99d 100644 --- a/src/event/illuminate/CallQueuedListener.php +++ b/src/event/illuminate/CallQueuedListener.php @@ -4,6 +4,7 @@ namespace Illuminate\Events; +use AllowDynamicProperties; use DateInterval; use DateTimeInterface; use Hyperf\Context\ApplicationContext; @@ -13,6 +14,7 @@ use Hypervel\Queue\InteractsWithQueue; use Throwable; +#[AllowDynamicProperties] class CallQueuedListener implements ShouldQueue { use InteractsWithQueue; diff --git a/src/event/src/CallQueuedListener.php b/src/event/src/CallQueuedListener.php index 8b842ff20..77387e564 100644 --- a/src/event/src/CallQueuedListener.php +++ b/src/event/src/CallQueuedListener.php @@ -4,6 +4,7 @@ namespace Hypervel\Event; +use AllowDynamicProperties; use DateInterval; use DateTimeInterface; use Hyperf\Context\ApplicationContext; @@ -13,6 +14,7 @@ use Hypervel\Queue\InteractsWithQueue; use Throwable; +#[AllowDynamicProperties] class CallQueuedListener implements ShouldQueue { use InteractsWithQueue; diff --git a/src/queue/src/CallQueuedClosure.php b/src/queue/src/CallQueuedClosure.php index 8e9ecfdbf..27927f72e 100644 --- a/src/queue/src/CallQueuedClosure.php +++ b/src/queue/src/CallQueuedClosure.php @@ -27,6 +27,11 @@ class CallQueuedClosure implements ShouldQueue */ public array $failureCallbacks = []; + /** + * The name assigned to the job. + */ + public ?string $name = null; + /** * Indicate if the job should be deleted when models are missing. */ @@ -91,6 +96,18 @@ public function displayName(): string { $reflection = new ReflectionFunction($this->closure->getClosure()); - return 'Closure (' . basename($reflection->getFileName()) . ':' . $reflection->getStartLine() . ')'; + $prefix = is_null($this->name) ? '' : "{$this->name} - "; + + return $prefix . 'Closure (' . basename($reflection->getFileName()) . ':' . $reflection->getStartLine() . ')'; + } + + /** + * Assign a name to the job. + */ + public function name(string $name): static + { + $this->name = $name; + + return $this; } } diff --git a/tests/Event/CallQueuedListenerTest.php b/tests/Event/CallQueuedListenerTest.php new file mode 100644 index 000000000..f89cbe86a --- /dev/null +++ b/tests/Event/CallQueuedListenerTest.php @@ -0,0 +1,58 @@ +assertListenerToleratesUnknownProperties( + HypervelCallQueuedListener::class + ); + } + + public function testIlluminateListenerToleratesUnknownPropertiesOnUnserialization() + { + $this->assertListenerToleratesUnknownProperties( + IlluminateCallQueuedListener::class + ); + } + + /** + * Simulates cross-version deserialization: a job payload serialized by a + * newer Laravel/Hypervel version (which adds extra properties to + * CallQueuedListener) is unserialized by an older version that does not + * declare those properties. Without #[AllowDynamicProperties], PHP 8.2+ + * raises an error on the dynamic property assignment during unserialize(). + */ + private function assertListenerToleratesUnknownProperties(string $class): void + { + $listener = new $class('App\Listeners\OrderShipped', 'handle', []); + $serialized = serialize($listener); + + // Inject a synthetic property absent from the current class definition. + $extra = 's:18:"newPropertyFromV11";s:5:"value";'; + $serialized = preg_replace_callback( + '/^(O:\d+:"[^"]+":)(\d+):/', + fn ($m) => $m[1] . ((int) $m[2] + 1) . ':', + $serialized + ); + $serialized = substr($serialized, 0, -1) . $extra . '}'; + + $result = unserialize($serialized); + + $this->assertInstanceOf($class, $result); + $this->assertSame('App\Listeners\OrderShipped', $result->class); + $this->assertSame('value', $result->newPropertyFromV11); + } +}