feat: Divi BlockTree – byte-exact parser/serializer with round-trip fixtures

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 11:26:14 +02:00
co-authored by Claude Opus 5.5
parent c9b31a61da
commit 8222240ed6
18 changed files with 819 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\BlockTree;
use JsonException;
use stdClass;
/**
* JSON of block attributes. PHP arrays cannot tell an empty JSON object from an empty list,
* nor an object with the keys "0", "1", … from a list. decodeObject() therefore keeps such
* objects as stdClass, so a regenerated opener encodes them as {} again (Divi breaks on
* "[]" where it expects "{}" – see its EmptyArrayCorruptionMigration).
*/
final class Json
{
/** @return array<string, mixed>|null Null when $json is not a JSON object. */
public static function decodeObject(string $json): ?array
{
try {
$value = json_decode($json, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
return null;
}
if (! $value instanceof stdClass) {
return null;
}
$converted = self::fromJson($value);
return is_array($converted) ? $converted : [];
}
/** Converts json_decode(…, false) output: objects become arrays unless that would lose their object-ness. */
public static function fromJson(mixed $value): mixed
{
if ($value instanceof stdClass) {
$array = [];
foreach (get_object_vars($value) as $key => $item) {
$array[$key] = self::fromJson($item);
}
return $array === [] || array_is_list($array) ? (object) $array : $array;
}
if (is_array($value)) {
return array_map([self::class, 'fromJson'], $value);
}
return $value;
}
/** @throws JsonException */
public static function encode(mixed $value): string
{
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- pure PHP (spec §4.4).
return json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}
}