Čtení přes SimpleXML
$xml = simplexml_load_file('objednavka.xml'); // nebo simplexml_load_string($text)
echo $xml['cislo']; // atribut: 2026-117
echo $xml->zakaznik; // text prvku: Jana Nováková
foreach ($xml->polozka as $polozka) {
printf("%s × %d\n", $polozka, (int) $polozka['mnozstvi']);
}
// Pozor: hodnoty jsou objekty SimpleXMLElement, ne řetězce
$kod = (string) $xml->polozka[0]['kod']; // bez přetypování by se porovnávaly objekty
Přístup $xml->polozka vrací první prvek a zároveň se přes něj dá iterovat.
Neexistující prvek nevyhodí chybu, jen vrátí prázdný objekt — ověř ho přes
isset($xml->prvek).
SimpleXML, DOMDocument, nebo XMLReader?
Všechny ukázky pracují s touto objednávkou:
<?xml version="1.0" encoding="UTF-8"?>
<objednavka cislo="2026-117">
<zakaznik>Jana Nováková</zakaznik>
<polozka kod="HR-01" mnozstvi="2">Hrnek</polozka>
<polozka kod="TA-07" mnozstvi="1">Talíř</polozka>
</objednavka> | SimpleXML | DOMDocument | XMLReader | |
|---|---|---|---|
| Styl práce | prvky jako vlastnosti objektu | standardní DOM API (jako v JavaScriptu) | kurzor, čte uzel po uzlu |
| Paměť | celý dokument | celý dokument | jen aktuální uzel |
| XPath | xpath() — jen uzly | DOMXPath — uzly i hodnoty | ne (jen přes převod záznamu) |
| Úpravy | přidání prvků a atributů | cokoli, včetně mazání a přesunů | jen čtení (zápis XMLWriter) |
| Kdy použít | rychlé čtení menších souborů a API odpovědí | úpravy, komentáře, CDATA, validace | soubory ve stovkách MB, feedy |
SimpleXML i DOM stojí na stejné knihovně libxml2 a dají se kombinovat:
dom_import_simplexml() a simplexml_import_dom() převedou prvek z
jednoho API do druhého bez kopírování.
Chyby parsování
libxml_use_internal_errors(true); // místo varování sbírat chyby
$xml = simplexml_load_string($text);
if ($xml === false) {
foreach (libxml_get_errors() as $e) {
printf("Řádek %d, sloupec %d: %s", $e->line, $e->column, trim($e->message));
}
libxml_clear_errors();
} Vytvoření XML: XMLWriter
$w = new XMLWriter();
$w->openUri('export.xml'); // nebo openMemory() a outputMemory()
$w->setIndent(true);
$w->startDocument('1.0', 'UTF-8');
$w->startElement('objednavka');
$w->writeAttribute('cislo', '2026-118');
$w->writeElement('zakaznik', 'Petr & syn'); // & se zapíše jako &
$w->endElement();
$w->endDocument();
$w->flush();
Řetězce neskládej ručně ("<a>$hodnota</a>") — jeden znak
& nebo < v datech udělá z výsledku nevalidní XML.
Serializace XML
Při převodu XML stromu na text (serializaci) knihovna zapíše zvláštní znaky podle pravidel XML, například ampersand jako &. Zápis zpět nemusí zachovat původní prefixy, odsazení, CDATA nebo pořadí atributů; testuj obsah a požadované kódování.
$text = $doc->saveXML();
if ($text === false) { throw new RuntimeException("Serializace selhala"); } Čtení a úpravy přes DOMDocument
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->load('objednavka.xml'); // nebo loadXML($text)
$root = $doc->documentElement;
echo $root->getAttribute('cislo'); // 2026-117
foreach ($doc->getElementsByTagName('polozka') as $polozka) {
echo $polozka->textContent, ' ', $polozka->getAttribute('mnozstvi'), "\n";
}
// Úpravy: přidat prvek a uložit
$nova = $doc->createElement('polozka', 'Miska');
$nova->setAttribute('kod', 'MI-03');
$root->appendChild($nova);
$doc->formatOutput = true;
$doc->save('objednavka-nova.xml'); Velké soubory: XMLReader
// Velký soubor (feed se 100 000 produkty) po jednom záznamu, bez načtení celého do paměti
$reader = XMLReader::open('feed.xml');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'SHOPITEM') {
$item = simplexml_load_string($reader->readOuterXml()); // jeden záznam pohodlně
uloz((string) $item->PRODUCTNAME, (float) $item->PRICE_VAT);
}
}
$reader->close();
Kombinace kurzoru a simplexml_load_string() na jeden záznam drží v paměti jen
malý kus dokumentu, a přitom se se záznamem pracuje pohodlně.
Vyhledávání přes XPath
// SimpleXML: xpath() vrací pole prvků
foreach ($xml->xpath('//polozka[@mnozstvi > 1]') as $p) {
echo $p['kod'], "\n"; // HR-01
}
// DOM: DOMXPath umí i výrazy, které vrací číslo nebo text
$xp = new DOMXPath($doc);
$pocet = $xp->evaluate('count(//polozka)'); // 2.0 (float)
$prvni = $xp->evaluate('string(//polozka[1]/@kod)'); // "HR-01"
foreach ($xp->query('//polozka', $doc) as $uzel) { /* DOMElement */ }
PHP podporuje XPath 1.0. Funkce z novějších verzí (matches(),
string-join()) v něm nejsou — výraz si proto před vložením do kódu vyzkoušej v
testeru s volbou XPath 1.0.
Jmenné prostory
$text = '<f:faktura xmlns:f="http://example.cz/faktura"><f:cislo>FV2026-042</f:cislo></f:faktura>';
// SimpleXML: prefix je třeba zaregistrovat — v dotazu může být i jiný než v dokumentu
$xml = simplexml_load_string($text);
$xml->registerXPathNamespace('fa', 'http://example.cz/faktura');
echo $xml->xpath('//fa:cislo')[0]; // FV2026-042
echo $xml->children('http://example.cz/faktura')->cislo;
// DOM
$xp = new DOMXPath($doc);
$xp->registerNamespace('fa', 'http://example.cz/faktura');
$xp->query('//fa:cislo');
Nejčastější chyba: dokument má výchozí jmenný prostor (xmlns="…" bez prefixu) a
dotaz //cislo nic nenajde. V XPath 1.0 nemá výraz bez prefixu žádný jmenný
prostor, takže je potřeba prefix zaregistrovat a psát //fa:cislo.
Validace proti XSD
$doc = new DOMDocument();
$doc->load('objednavka.xml');
libxml_use_internal_errors(true);
if (!$doc->schemaValidate('objednavka.xsd')) {
foreach (libxml_get_errors() as $e) {
echo "Řádek {$e->line}: ", trim($e->message), "\n";
}
} Bezpečnost: u XML z cizích zdrojů nepoužívej příznaky
LIBXML_NOENT a LIBXML_DTDLOAD — otevírají útok
XXE.
Bezpečné nastavení parseru
Nepovoluj LIBXML_NOENT ani načítání DTD pro cizí data. LIBXML_NONET blokuje síť, nikoli všechny soubory; podle verze PHP/libxml zvaž LIBXML_NO_XXE. Omez vstup a kontroluj chyby. Podrobné varianty jsou v XXE.
Načtení JSON
JSON parser vrací datovou strukturu, ne XML strom. Chyby syntaxe zpracuj jako chyby vstupu; data ověř také strukturálně.
$text = '{"jmeno":"Jana","vek":20}';
$data = json_decode($text, true, 512, JSON_THROW_ON_ERROR);
echo $data['jmeno']; Vytvoření JSON
Vytvoř hodnotu v paměti a nech její textový zápis na knihovně.
$data = ['jmeno' => 'Jana', 'vek' => 20, 'aktivni' => true]; Serializace JSON
Neskládej text ručně. Ověř přesnost čísel, reprezentaci dat a chování nečíselných hodnot.
$text = json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); JSON Schema
Standardní knihovna neposkytuje plnou validaci JSON Schema. Vyber knihovnu s podporou požadované verze pravidel JSON Schema z oficiální dokumentace či katalogu. PHP může použít opis/json-schema. Základy a rozdíly verzí vysvětluje kurz JSON Schema.
Dokumentace API
Příklady JSON vycházejí z oficiální dokumentace jazyka. Překlady typů, maximální hloubka a chování při chybě jsou vlastnosti knihovny, ne samotné syntaxe JSON.
JSON Schema: kompletní příklad Opis 2.x
<?php
require 'vendor/autoload.php';
$schema = json_decode('{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","required":["id"],"properties":{"id":{"type":"string"}},"additionalProperties":false}', false, 512, JSON_THROW_ON_ERROR);
$validator = new Opis\JsonSchema\Validator();
foreach (['{"id":"001"}', '{"id":1}'] as $text) {
$data = json_decode($text, false, 512, JSON_THROW_ON_ERROR);
$result = $validator->validate($data, $schema);
echo $result->isValid() ? "platné\n" : "neplatné\n";
} Spusť php schema.php: první záznam projde a druhý ne. JSON objekty načítáme jako stdClass, protože validátor musí odlišit objekt od PHP pole. Asociativní pole z json_decode s true nejsou stejná reprezentace pro tento postup. Syntax chyba vyvolá JsonException ještě před schématem.
Validátor i schéma znovu používej. Podrobné chyby můžeš formátovat pomocí ErrorFormatter podle oficiální dokumentace Opis. Dále kontroluj limit těla, externí reference a příjemci vracej pouze potřebné diagnostické údaje.
Diakritika a příznaky json_encode
json_encode ve výchozím stavu zapíše každý znak mimo ASCII jako \uXXXX a lomítko jako \/. Výsledek je platný JSON a po dekódování dá stejný řetězec. Diakritika se tedy neztrácí ani nepoškozuje, jen je text delší a hůř čitelný v logu nebo v databázi.
$data = ['nazev' => 'Žluťoučký kůň', 'url' => 'https://example.cz/a/b'];
echo json_encode($data, JSON_THROW_ON_ERROR);
// {"nazev":"Žluťoučký kůň","url":"https://example.cz/a/b"}
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// {"nazev":"Žluťoučký kůň","url":"https://example.cz/a/b"}
var_dump(json_decode('"žluťoučký"') === 'žluťoučký'); // bool(true)
json_encode("Å"); // false, chybu zjistíš jen přes json_last_error()
json_encode("Å", JSON_THROW_ON_ERROR); // JsonException: Malformed UTF-8 characters… JSON_UNESCAPED_UNICODEzapíše znaky v UTF-8 přímo. Vstupní řetězce proto musí být platné UTF-8; data z Windows-1250 nejdřív převeď přesmb_convert_encoding.JSON_UNESCAPED_SLASHESnech vypnutý, pokud JSON vkládáš do HTML elementu<script>. Escapované<\/script>značku neukončí. Pro HTML kontext přidejJSON_HEX_TAG | JSON_HEX_AMP.JSON_THROW_ON_ERRORpoužívej vždy. Bez něj vrátíjson_encodepři neplatném UTF-8 nebo hodnotěNANjenfalsea do databáze se může uložit prázdná hodnota.
Sloupce typu jsonb v PostgreSQL a JSON v MySQL ukládají data v rozložené podobě, takže uložená hodnota je s příznakem i bez něj stejná. Rozdíl uvidíš v textových sloupcích, v souborech a v HTTP odpovědích. Viz také escapování řetězců v JSON.
JSON sloupce v Laravelu a Filamentu
Eloquent převádí mezi JSON sloupcem a PHP hodnotou pomocí castů. array a json vrátí pole, AsArrayObject a AsCollection objekt, ve kterém lze měnit jednotlivé klíče. json:unicode (od Laravelu 12.3) zapisuje jako JSON_UNESCAPED_UNICODE; lomítka dál escapuje.
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Model;
class Produkt extends Model
{
protected $fillable = ['popis', 'vlastnosti', 'vlastnosti->rozmery->v'];
protected function casts(): array
{
return [
'popis' => 'json:unicode', // pole ↔ JSON bez \uXXXX (Laravel 12.3+)
'vlastnosti' => AsArrayObject::class, // jednotlivé klíče lze měnit přímo
];
}
} $p = Produkt::create([
'popis' => ['nazev' => 'Žluťoučký kůň'],
'vlastnosti' => ['barva' => 'zelená', 'rozmery' => ['v' => 10]],
]);
$p->vlastnosti['barva'] = 'modrá'; // s castem 'array' jen Notice: Indirect modification … has no effect
$p->save();
$p->update(['vlastnosti->rozmery->v' => 12]); // klíč musí být ve $fillable
Produkt::where('vlastnosti->barva', 'modrá')->count();
// PostgreSQL: select … where "vlastnosti"->>'barva' = ?
Produkt::whereJsonContains('vlastnosti->rozmery', ['v' => 12])->count();
// PostgreSQL: select … where ("vlastnosti"->'rozmery')::jsonb @> ? popis text {"nazev":"Žluťoučký kůň"} json:unicode
popis text {"nazev":"\u017dlu\u0165ou\u010dk\u00fd k\u016f\u0148"} array
vlastnosti jsonb {"barva": "modrá", "rozmery": {"v": 12}} libovolný cast - Zápis přes
->a $fillable.update(['vlastnosti->rozmery->v' => 12])změní jeden klíč, ale jen když je přesně tento zápis uvedený ve$fillable. Jinak ho Eloquent tiše zahodí a nic neuloží.Model::preventSilentlyDiscardingAttributes()z toho udělá výjimkuMassAssignmentException; zapni ji alespoň ve vývoji. - Dotazy.
where('vlastnosti->barva', …)awhereJsonContainsLaravel přeloží na operátory PostgreSQL->>a@>, v MySQL najson_extractajson_contains. Index nad JSON sloupcem musíš vytvořit v migraci sám. - Cast nekontroluje strukturu. Do sloupce se uloží libovolné pole. Pravidla zajistí validace požadavku (
'vlastnosti.barva' => 'required|string') nebo JSON Schema. - Filament. Pole formuláře
KeyValueaRepeaterukládají pole hodnot. Dokumentace Filamentu doporučuje JSON sloupec a castarrayna modelu.AsArrayObjectnebojson:unicodefungují také, protože vrací a přijímají stejná data.
Ověřeno s illuminate/database 13.34 proti PostgreSQL 18. Zdroje: Laravel: Array and JSON Casting, Mass assignment a JSON sloupce, Filament KeyValue a konstanty JSON v PHP.