getMessage()}" ); } } /** * Convierte datos a XML */ public static function toXml( mixed $data, string $rootElement = "root" ): string { $dom = new DOMDocument(self::XML_VERSION, self::XML_ENCODING); $dom->formatOutput = true; $root = $dom->createElement($rootElement); $dom->appendChild($root); self::arrayToDomElement($data, $root, $dom); return $dom->saveXML() ?: throw new RuntimeException("Error generando XML"); } /** * Método auxiliar para convertir array a elementos DOM */ private static function arrayToDomElement( mixed $data, DOMElement $parent, DOMDocument $dom ): void { $data = match (true) { $data instanceof Stringable => (string) $data, is_object($data) => (array) $data, default => $data, }; foreach ((array) $data as $key => $value) { $key = preg_replace("/[^a-z0-9_]/i", "_", (string) $key) ?: "item"; $child = match (true) { is_array($value), is_object($value) => self::createComplexElement($dom, $key, $value), default => self::createSimpleElement($dom, $key, $value), }; $parent->appendChild($child); } } /** * Crea un elemento XML complejo */ private static function createComplexElement( DOMDocument $dom, string $key, mixed $value ): DOMElement { $element = $dom->createElement($key); self::arrayToDomElement($value, $element, $dom); return $element; } /** * Crea un elemento XML simple */ private static function createSimpleElement( DOMDocument $dom, string $key, mixed $value ): DOMElement { $element = $dom->createElement($key); $element->appendChild($dom->createTextNode((string) $value)); return $element; } /** * Serializa datos de manera optimizada */ public static function serialize(mixed $data): string { return match (true) { extension_loaded("igbinary") => igbinary_serialize($data), default => serialize($data), }; } /** * Deserializa datos */ public static function unserialize(string $data): mixed { return match (true) { extension_loaded("igbinary") && !str_starts_with($data, "a:") && !str_starts_with($data, "O:") => igbinary_unserialize($data), default => unserialize($data, ["allowed_classes" => true]), }; } /** * Compara objetos o arrays */ public static function compare(mixed $obj1, mixed $obj2): array { return self::calculateDifferences( self::normalize($obj1), self::normalize($obj2) ); } /** * Calcula diferencias entre arrays */ private static function calculateDifferences( array $array1, array $array2, string $path = "" ): array { $differences = []; foreach ($array1 as $key => $value) { $currentPath = $path ? "{$path}.{$key}" : $key; if (!array_key_exists($key, $array2)) { $differences[$currentPath] = [ "type" => "removed", "value" => $value, ]; continue; } $differences = match (true) { is_array($value) && is_array($array2[$key]) => [ ...$differences, ...self::calculateDifferences( $value, $array2[$key], $currentPath ), ], $value !== $array2[$key] => [ ...$differences, $currentPath => [ "type" => "modified", "old" => $value, "new" => $array2[$key], ], ], default => $differences, }; } // Verificar elementos adicionales foreach ($array2 as $key => $value) { if (!array_key_exists($key, $array1)) { $currentPath = $path ? "{$path}.{$key}" : $key; $differences[$currentPath] = [ "type" => "added", "value" => $value, ]; } } return $differences; } /** * Normaliza datos para comparación */ public static function normalize(mixed $data): array { return match (true) { is_object($data) => self::normalizeObject($data), is_array($data) => self::normalizeArray($data), default => [$data], }; } /** * Normaliza un objeto */ private static function normalizeObject(object $object): array { return match (true) { method_exists($object, "toArray") => $object->toArray(), default => self::normalizeArray(get_object_vars($object)), }; } /** * Normaliza un array */ private static function normalizeArray(array $array): array { return array_map( fn($value) => match (true) { is_object($value), is_array($value) => self::normalize($value), default => $value, }, $array ); } /** * Convierte a array */ public static function toArray(mixed $data): array { return match (true) { is_object($data) && method_exists($data, "toArray") => $data->toArray(), is_object($data) => self::normalize($data), is_array($data) => array_map([self::class, "toArray"], $data), default => [$data], }; } /** * Clonación profunda * * @template T of object * @param T $object * @return T */ public static function deepClone(object $object): object { if (self::$cache->offsetExists($object)) { return self::$cache->offsetGet($object); } $clone = match (true) { extension_loaded("igbinary") => igbinary_unserialize( igbinary_serialize($object) ), method_exists($object, "__clone") => self::cloneWithProperties( $object ), default => unserialize(serialize($object)), }; self::$cache->offsetSet($object, $clone); return $clone; } /** * Clona un objeto y sus propiedades */ private static function cloneWithProperties(object $object): object { $clone = clone $object; foreach (get_object_vars($clone) as $property => $value) { if (is_object($value)) { $clone->$property = self::deepClone($value); } } return $clone; } /** * Limpia la caché */ public static function clearCache(): void { self::$cache = new WeakMap(); } }