Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save codebymikey/f94b2620b99cf26d3833afc9a9641e17 to your computer and use it in GitHub Desktop.

Select an option

Save codebymikey/f94b2620b99cf26d3833afc9a9641e17 to your computer and use it in GitHub Desktop.
https://github.com/EdeMeijer/serialize-debugger is also available (but doesn't work as well).
<?php
class ValidatePhpSerialization {
/**
* Diagnose which property on an object cannot be serialized.
*
* Typically shown with an obscure error:
* "Serialization of 'Closure' is not allowed".
*
* @param mixed $value
* The value to inspect (object, array, or scalar).
* @param string $path
* Internal – breadcrumb path built up during recursion.
* @param array $seen
* Internal – set of already-visited spl_object_ids.
*
* @return array
* List of offending paths, e.g. ['root->foo->bar (Closure)'].
*
* @throws \ReflectionException
*/
public static function findUnserializableProperties(
mixed $value,
string $path = 'root',
array &$seen = [],
): array {
$offenders = [];
// --- Closures are the primary culprit for failed serializations. ---
if ($value instanceof \Closure) {
return ["{$path} (Closure)"];
}
// --- Objects ---
if (is_object($value)) {
$id = spl_object_id($value);
// Cycle / diamond guard – skip any object we have already walked.
if (isset($seen[$id])) {
// Return ["{$path} (recursion)"];.
return [];
}
$seen[$id] = TRUE;
// If the class declares __sleep(), honour it – serialize() will call it
// and only persist the properties it names.
$class = get_class($value);
$reflection = new \ReflectionClass($class);
$properties = [];
if (method_exists($value, '__sleep')) {
try {
$names = $value->__sleep();
}
catch (\Throwable $e) {
$offenders[] = "$path ({$e->getMessage()})";
$names = [];
}
foreach ((array) $names as $name) {
if ($reflection->hasProperty($name)) {
$prop = $reflection->getProperty($name);
$prop->setAccessible(TRUE);
$properties[$name] = $prop->getValue($value);
}
}
}
else {
foreach ($reflection->getProperties() as $prop) {
if ($prop->isStatic()) {
continue;
}
$prop->setAccessible(TRUE);
if (!$prop->isInitialized($value)) {
continue;
}
$properties[$prop->getName()] = $prop->getValue($value);
}
}
foreach ($properties as $name => $propValue) {
$nested = static::findUnserializableProperties(
$propValue,
"{$path}->{$name}",
$seen
);
array_push($offenders, ...$nested);
}
return $offenders;
}
// --- Arrays ---
if (is_array($value)) {
foreach ($value as $key => $item) {
$nested = static::findUnserializableProperties(
$item,
"{$path}[{$key}]",
$seen
);
array_push($offenders, ...$nested);
}
return $offenders;
}
// Scalars / null are fine for serialization purposes.
return [];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment