diff --git a/composer.json b/composer.json index 1d869435d519..e5dc41abca80 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "doctrine/inflector": "^1.4|^2.0", "dragonmantank/cron-expression": "^3.0.2", "egulias/email-validator": "^2.1.10", + "laravel/serializable-closure": "^1.0", "league/commonmark": "^1.3|^2.0.2", "league/flysystem": "^1.1", "monolog/monolog": "^2.0", diff --git a/src/Illuminate/Bus/Batch.php b/src/Illuminate/Bus/Batch.php index 7e5dddbae8e3..db8779d3acaa 100644 --- a/src/Illuminate/Bus/Batch.php +++ b/src/Illuminate/Bus/Batch.php @@ -7,7 +7,6 @@ use Illuminate\Contracts\Queue\Factory as QueueFactory; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Queue\CallQueuedClosure; -use Illuminate\Queue\SerializableClosure; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use JsonSerializable; @@ -420,7 +419,7 @@ public function delete() /** * Invoke a batch callback handler. * - * @param \Illuminate\Queue\SerializableClosure|callable $handler + * @param callable $handler * @param \Illuminate\Bus\Batch $batch * @param \Throwable|null $e * @return void @@ -428,9 +427,7 @@ public function delete() protected function invokeHandlerCallback($handler, Batch $batch, Throwable $e = null) { try { - return $handler instanceof SerializableClosure - ? $handler->__invoke($batch, $e) - : call_user_func($handler, $batch, $e); + return $handler($batch, $e); } catch (Throwable $e) { if (function_exists('report')) { report($e); diff --git a/src/Illuminate/Bus/PendingBatch.php b/src/Illuminate/Bus/PendingBatch.php index a80284b986c6..a831c5fbf7e8 100644 --- a/src/Illuminate/Bus/PendingBatch.php +++ b/src/Illuminate/Bus/PendingBatch.php @@ -6,7 +6,7 @@ use Illuminate\Bus\Events\BatchDispatched; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher as EventDispatcher; -use Illuminate\Queue\SerializableClosure; +use Illuminate\Queue\SerializableClosureFactory; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Throwable; @@ -78,7 +78,7 @@ public function add($jobs) public function then($callback) { $this->options['then'][] = $callback instanceof Closure - ? new SerializableClosure($callback) + ? SerializableClosureFactory::make($callback) : $callback; return $this; @@ -103,7 +103,7 @@ public function thenCallbacks() public function catch($callback) { $this->options['catch'][] = $callback instanceof Closure - ? new SerializableClosure($callback) + ? SerializableClosureFactory::make($callback) : $callback; return $this; @@ -128,7 +128,7 @@ public function catchCallbacks() public function finally($callback) { $this->options['finally'][] = $callback instanceof Closure - ? new SerializableClosure($callback) + ? SerializableClosureFactory::make($callback) : $callback; return $this; diff --git a/src/Illuminate/Bus/Queueable.php b/src/Illuminate/Bus/Queueable.php index b4270e849219..8e9306059c10 100644 --- a/src/Illuminate/Bus/Queueable.php +++ b/src/Illuminate/Bus/Queueable.php @@ -4,7 +4,6 @@ use Closure; use Illuminate\Queue\CallQueuedClosure; -use Illuminate\Queue\SerializableClosure; use Illuminate\Support\Arr; use RuntimeException; @@ -245,7 +244,7 @@ public function dispatchNextJobInChain() public function invokeChainCatchCallbacks($e) { collect($this->chainCatchCallbacks)->each(function ($callback) use ($e) { - $callback instanceof SerializableClosure ? $callback->__invoke($e) : call_user_func($callback, $e); + $callback($e); }); } } diff --git a/src/Illuminate/Encryption/EncryptionServiceProvider.php b/src/Illuminate/Encryption/EncryptionServiceProvider.php index ba19cc1026cf..4ef42ba4c32c 100755 --- a/src/Illuminate/Encryption/EncryptionServiceProvider.php +++ b/src/Illuminate/Encryption/EncryptionServiceProvider.php @@ -4,7 +4,8 @@ use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; -use Opis\Closure\SerializableClosure; +use Laravel\SerializableClosure\SerializableClosure; +use Opis\Closure\SerializableClosure as OpisSerializableClosure; class EncryptionServiceProvider extends ServiceProvider { @@ -17,6 +18,7 @@ public function register() { $this->registerEncrypter(); $this->registerOpisSecurityKey(); + $this->registerSerializableClosureSecurityKey(); } /** @@ -37,8 +39,28 @@ protected function registerEncrypter() * Configure Opis Closure signing for security. * * @return void + * + * @deprecated Will be removed in a future Laravel version. */ protected function registerOpisSecurityKey() + { + if (\PHP_VERSION_ID < 80100) { + $config = $this->app->make('config')->get('app'); + + if (! class_exists(OpisSerializableClosure::class) || empty($config['key'])) { + return; + } + + OpisSerializableClosure::setSecretKey($this->parseKey($config)); + } + } + + /** + * Configure Serializable Closure signing for security. + * + * @return void + */ + protected function registerSerializableClosureSecurityKey() { $config = $this->app->make('config')->get('app'); diff --git a/src/Illuminate/Events/InvokeQueuedClosure.php b/src/Illuminate/Events/InvokeQueuedClosure.php index caabe4577c2b..bc68b19d6443 100644 --- a/src/Illuminate/Events/InvokeQueuedClosure.php +++ b/src/Illuminate/Events/InvokeQueuedClosure.php @@ -7,7 +7,7 @@ class InvokeQueuedClosure /** * Handle the event. * - * @param \Illuminate\Queue\SerializableClosure $closure + * @param \Laravel\SerializableClosure\SerializableClosure $closure * @param array $arguments * @return void */ @@ -19,7 +19,7 @@ public function handle($closure, array $arguments) /** * Handle a job failure. * - * @param \Illuminate\Queue\SerializableClosure $closure + * @param \Laravel\SerializableClosure\SerializableClosure $closure * @param array $arguments * @param array $catchCallbacks * @param \Throwable $exception diff --git a/src/Illuminate/Events/QueuedClosure.php b/src/Illuminate/Events/QueuedClosure.php index 1d640959d3aa..82590598447a 100644 --- a/src/Illuminate/Events/QueuedClosure.php +++ b/src/Illuminate/Events/QueuedClosure.php @@ -3,7 +3,7 @@ namespace Illuminate\Events; use Closure; -use Illuminate\Queue\SerializableClosure; +use Illuminate\Queue\SerializableClosureFactory; class QueuedClosure { @@ -114,10 +114,10 @@ public function resolve() { return function (...$arguments) { dispatch(new CallQueuedListener(InvokeQueuedClosure::class, 'handle', [ - 'closure' => new SerializableClosure($this->closure), + 'closure' => SerializableClosureFactory::make($this->closure), 'arguments' => $arguments, 'catch' => collect($this->catchCallbacks)->map(function ($callback) { - return new SerializableClosure($callback); + return SerializableClosureFactory::make($callback); })->all(), ]))->onConnection($this->connection)->onQueue($this->queue)->delay($this->delay); }; diff --git a/src/Illuminate/Foundation/Bus/PendingChain.php b/src/Illuminate/Foundation/Bus/PendingChain.php index 5931fb3202b2..73ae364c55ea 100644 --- a/src/Illuminate/Foundation/Bus/PendingChain.php +++ b/src/Illuminate/Foundation/Bus/PendingChain.php @@ -5,7 +5,7 @@ use Closure; use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Queue\CallQueuedClosure; -use Illuminate\Queue\SerializableClosure; +use Illuminate\Queue\SerializableClosureFactory; class PendingChain { @@ -112,7 +112,7 @@ public function delay($delay) public function catch($callback) { $this->catchCallbacks[] = $callback instanceof Closure - ? new SerializableClosure($callback) + ? SerializableClosureFactory::make($callback) : $callback; return $this; diff --git a/src/Illuminate/Queue/CallQueuedClosure.php b/src/Illuminate/Queue/CallQueuedClosure.php index 28e1f35b268a..24a72d966c57 100644 --- a/src/Illuminate/Queue/CallQueuedClosure.php +++ b/src/Illuminate/Queue/CallQueuedClosure.php @@ -17,7 +17,7 @@ class CallQueuedClosure implements ShouldQueue /** * The serializable Closure instance. * - * @var \Illuminate\Queue\SerializableClosure + * @var \Laravel\SerializableClosure\SerializableClosure */ public $closure; @@ -38,10 +38,10 @@ class CallQueuedClosure implements ShouldQueue /** * Create a new job instance. * - * @param \Illuminate\Queue\SerializableClosure $closure + * @param \Laravel\SerializableClosure\SerializableClosure $closure * @return void */ - public function __construct(SerializableClosure $closure) + public function __construct($closure) { $this->closure = $closure; } @@ -54,7 +54,7 @@ public function __construct(SerializableClosure $closure) */ public static function create(Closure $job) { - return new self(new SerializableClosure($job)); + return new self(SerializableClosureFactory::make($job)); } /** @@ -77,7 +77,7 @@ public function handle(Container $container) public function onFailure($callback) { $this->failureCallbacks[] = $callback instanceof Closure - ? new SerializableClosure($callback) + ? SerializableClosureFactory::make($callback) : $callback; return $this; @@ -92,7 +92,7 @@ public function onFailure($callback) public function failed($e) { foreach ($this->failureCallbacks as $callback) { - call_user_func($callback instanceof SerializableClosure ? $callback->getClosure() : $callback, $e); + $callback($e); } } diff --git a/src/Illuminate/Queue/QueueServiceProvider.php b/src/Illuminate/Queue/QueueServiceProvider.php index 91d0ed20dd83..b082123204ff 100755 --- a/src/Illuminate/Queue/QueueServiceProvider.php +++ b/src/Illuminate/Queue/QueueServiceProvider.php @@ -17,9 +17,12 @@ use Illuminate\Queue\Failed\NullFailedJobProvider; use Illuminate\Support\Arr; use Illuminate\Support\ServiceProvider; +use Laravel\SerializableClosure\SerializableClosure; class QueueServiceProvider extends ServiceProvider implements DeferrableProvider { + use SerializesAndRestoresModelIdentifiers; + /** * Register the service provider. * @@ -27,6 +30,8 @@ class QueueServiceProvider extends ServiceProvider implements DeferrableProvider */ public function register() { + $this->configureSerializableClosureUses(); + $this->registerManager(); $this->registerConnection(); $this->registerWorker(); @@ -34,6 +39,30 @@ public function register() $this->registerFailedJobServices(); } + /** + * Configure serializable closures uses. + * + * @return void + */ + protected function configureSerializableClosureUses() + { + SerializableClosure::transformUseVariablesUsing(function ($data) { + foreach ($data as $key => $value) { + $data[$key] = $this->getSerializedPropertyValue($value); + } + + return $data; + }); + + SerializableClosure::resolveUseVariablesUsing(function ($data) { + foreach ($data as $key => $value) { + $data[$key] = $this->getRestoredPropertyValue($value); + } + + return $data; + }); + } + /** * Register the queue manager. * diff --git a/src/Illuminate/Queue/SerializableClosure.php b/src/Illuminate/Queue/SerializableClosure.php index 044c06a2bd77..81e73341c20e 100644 --- a/src/Illuminate/Queue/SerializableClosure.php +++ b/src/Illuminate/Queue/SerializableClosure.php @@ -4,6 +4,9 @@ use Opis\Closure\SerializableClosure as OpisSerializableClosure; +/** + * @deprecated It will be removed in Laravel 9. + */ class SerializableClosure extends OpisSerializableClosure { use SerializesAndRestoresModelIdentifiers; diff --git a/src/Illuminate/Queue/SerializableClosureFactory.php b/src/Illuminate/Queue/SerializableClosureFactory.php new file mode 100644 index 000000000000..aa5b8a8a7b03 --- /dev/null +++ b/src/Illuminate/Queue/SerializableClosureFactory.php @@ -0,0 +1,25 @@ +action['missing'] ?? null; return is_string($missing) && - Str::startsWith($missing, 'C:32:"Opis\\Closure\\SerializableClosure') - ? unserialize($missing) - : $missing; + Str::startsWith($missing, [ + 'C:32:"Opis\\Closure\\SerializableClosure', + 'O:47:"Laravel\\SerializableClosure\\SerializableClosure', + ]) ? unserialize($missing) : $missing; } /** @@ -1226,11 +1228,17 @@ public function setContainer(Container $container) public function prepareForSerialization() { if ($this->action['uses'] instanceof Closure) { - $this->action['uses'] = serialize(new SerializableClosure($this->action['uses'])); + $this->action['uses'] = serialize(\PHP_VERSION_ID < 70400 + ? new OpisSerializableClosure($this->action['uses']) + : new SerializableClosure($this->action['uses']) + ); } if (isset($this->action['missing']) && $this->action['missing'] instanceof Closure) { - $this->action['missing'] = serialize(new SerializableClosure($this->action['missing'])); + $this->action['missing'] = serialize(\PHP_VERSION_ID < 70400 + ? new OpisSerializableClosure($this->action['missing']) + : new SerializableClosure($this->action['missing']) + ); } $this->compileRoute(); diff --git a/src/Illuminate/Routing/RouteAction.php b/src/Illuminate/Routing/RouteAction.php index 74035d4ce064..b356f974cc99 100644 --- a/src/Illuminate/Routing/RouteAction.php +++ b/src/Illuminate/Routing/RouteAction.php @@ -43,7 +43,7 @@ public static function parse($uri, $action) $action['uses'] = static::findCallable($action); } - if (is_string($action['uses']) && ! Str::contains($action['uses'], '@')) { + if (! static::containsSerializedClosure($action) && is_string($action['uses']) && ! Str::contains($action['uses'], '@')) { $action['uses'] = static::makeInvokable($action['uses']); } @@ -103,7 +103,9 @@ protected static function makeInvokable($action) */ public static function containsSerializedClosure(array $action) { - return is_string($action['uses']) && - Str::startsWith($action['uses'], 'C:32:"Opis\\Closure\\SerializableClosure') !== false; + return is_string($action['uses']) && Str::startsWith($action['uses'], [ + 'C:32:"Opis\\Closure\\SerializableClosure', + 'O:47:"Laravel\\SerializableClosure\\SerializableClosure', + ]) !== false; } } diff --git a/tests/Routing/RouteActionTest.php b/tests/Routing/RouteActionTest.php index 4b256ff68183..dcc403f0fb10 100644 --- a/tests/Routing/RouteActionTest.php +++ b/tests/Routing/RouteActionTest.php @@ -4,16 +4,22 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Routing\RouteAction; -use Opis\Closure\SerializableClosure; +use Laravel\SerializableClosure\SerializableClosure; +use Opis\Closure\SerializableClosure as OpisSerializableClosure; use PHPUnit\Framework\TestCase; class RouteActionTest extends TestCase { public function test_it_can_detect_a_serialized_closure() { - $action = ['uses' => serialize(new SerializableClosure(function (RouteActionUser $user) { + $callable = function (RouteActionUser $user) { return $user; - }))]; + }; + + $action = ['uses' => serialize(\PHP_VERSION_ID < 70400 + ? new OpisSerializableClosure($callable) + : new SerializableClosure($callable) + )]; $this->assertTrue(RouteAction::containsSerializedClosure($action)); diff --git a/tests/Routing/RouteSignatureParametersTest.php b/tests/Routing/RouteSignatureParametersTest.php index 8edee56cffd7..d3cfe254767a 100644 --- a/tests/Routing/RouteSignatureParametersTest.php +++ b/tests/Routing/RouteSignatureParametersTest.php @@ -4,7 +4,8 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Routing\RouteSignatureParameters; -use Opis\Closure\SerializableClosure; +use Laravel\SerializableClosure\SerializableClosure; +use Opis\Closure\SerializableClosure as OpisSerializableClosure; use PHPUnit\Framework\TestCase; use ReflectionParameter; @@ -12,9 +13,14 @@ class RouteSignatureParametersTest extends TestCase { public function test_it_can_extract_the_route_action_signature_parameters() { - $action = ['uses' => serialize(new SerializableClosure($callable = function (SignatureParametersUser $user) { + $callable = function (SignatureParametersUser $user) { return $user; - }))]; + }; + + $action = ['uses' => serialize(\PHP_VERSION_ID < 70400 + ? new OpisSerializableClosure($callable) + : new SerializableClosure($callable) + )]; $parameters = RouteSignatureParameters::fromAction($action);