Serialising an object with a closure in it, and why it fails

A closure has no serialisable representation in PHP, so any object holding one — a validator with a custom rule, a collection with a lazy callback — cannot be queued or cached.

$job = new ProcessOrder($order);
$job->onSuccess = function () { /* ... */ };

dispatch($job);
// Exception: Serialization of 'Closure' is not allowed

// the fixes, in order of preference:
//   an invokable class instead of a closure
//   a string key into a registry
//   __sleep()/__serialize() that drops it, and a rebuild on wake

An invokable class is the right answer nearly every time and it is also more testable, because a closure buried in a constructor cannot be asserted on. The __sleep route is what to reach for when the closure belongs to a dependency you do not own. The failure surfaces at dispatch rather than at construction, which is late enough that a rarely used code path can ship broken — a test that queues the job is the cheapest way to catch it.