На дворе 2022 год, а вы по-прежнему предпочитаете использовать XML (расширяемый язык разметки) JSON-ну (нотация объектов JavaScript) для обмена данными? Ну не в этом суть статьи. Все, что вы хотите сказать о компромиссах любого из них, было рассмотрено здесь. Но сегодня все еще существует множество систем, использующих XML, и я могу заверить вас, что это будет так и через X лет.
Проверка XML на соответствие XSD может быть первым шагом, который необходимо предпринять, особенно при создании модуля Reader/Ingester. Для начинающих любой файл, подобный приведенному ниже образцу, представляет собой правильно сформированный XML-файл.
а ниже приведен пример файла XSD (определение схемы XML):
Написать XSD для вашего XML на самом деле легко сделать. На простарах интернета очень много документции и различных статей по описанию XSD-схемы.
Теперь мы готовы проверить наш файл XML на соответствие XSD с помощью DOMDocument
или XMLReader
.
Во-первых, убедитесь, что эти расширения включены при установке PHP.
Проверка с помощью DOMDocument
Сервис валидации XML согласно XSD, использующий DOMDocument
class XmlValidatorService { private string $schema; private DOMDocument $handler; private string $xml; public function __construct() { $this->handler = new DOMDocument('1.0', 'utf-8'); } /** * @return XmlErrorDto[] * @throws Exception */ public function run(): array { $this->checkSchema(); $this->setLibXmlUserInternalErrors(); $this->handler->loadXML($this->xml); if (!$this->handler->schemaValidate($this->schema)) { return $this->getErrors(); } return []; } public function setXml(string $filePath): self { $this->xml = $xml; return $this; } public function setSchema(string $filePath): self { $this->schema = $filePath; return $this; } private function setLibXmlUserInternalErrors(): void { libxml_use_internal_errors(true); } /** * @return void * @throws Exception */ private function checkSchema(): void { if (!file_exists($this->schema)) { throw new Exception('Не найден XSD-файл'); } } /** * @return XmlErrorDto[] */ private function getErrors(): array { $errors = libxml_get_errors(); libxml_clear_errors(); return array_map( fn (LibXMLError $libXMLError) => $this->buildErrorDto($libXMLError), $errors ); } private function buildErrorDto(LibXMLError $libXMLError): XmlErrorDto { return (new XmlErrorDto()) ->setCode($libXMLError->code) ->setColumn($libXMLError->column) ->setLevel($libXMLError->level) ->setLine($libXMLError->line) ->setMessage($libXMLError->message); } }
Класс DTO ошибки
class XmlErrorDto { private int $level; private int $code; private int $column; private string $message; private int $line; public function setLevel(int $level): self { $this->level = $level; return $this; } public function getLevel(): int { return $this->level; } public function setCode(int $code): self { $this->code = $code; return $this; } public function getCode(): int { return $this->code; } public function setColumn(int $column): self { $this->column = $column; return $this; } public function getColumn(): int { return $this->column; } public function setMessage(string $message): self { $this->message = $message; return $this; } public function getMessage(): string { return $this->message; } public function setLine(int $line): self { $this->line = $line; return $this; } public function getLine(): int { return $this->line; } }
Этот XmlValidatorService
можно легко использовать следующим образом:
$errors = (new XmlValidatorService()) ->setSchema('schema.xsd') ->setXml('sample.xml') ->run(); if ($errors === []) { // XML соответствует XSD, ошибок нет } else { // XML не соответствует XSD, ошибки $errors }
Проверка с помощью XMLReader
Преимуществом использования XMLReader вместо DomDocument
является масштабируемость. XMLReader
может обрабатывать очень большие файлы лучше, чем DomDocument
. Наш класс будет очень похож на класс DomDocument
. Также обратите внимание, что ваша версия libxml выше 2.6.
class XmlValidatorService { private string $schema; private DOMDocument $handler; private string $xml; public function __construct() { $this->handler = new XMLReader(); } /** * @return XmlErrorDto[] * @throws Exception */ public function run(): array { $this->fileExists($this->schema); $this->fileExists($this->xml); $this->setLibXmlUserInternalErrors(); $this->handler->open($this->xml); $this->handler->setSchema($this->schema); $errors = []; while ($this->handler->read()) { if ($this->handler->isValid()) { continue; } $errors = array_merge($errors, $this->getErrors()); } return $errors; } /** * @return XmlErrorDto[] */ private function getErrors(): array { $errors = libxml_get_errors(); libxml_clear_errors(); return array_map( fn (LibXMLError $libXMLError) => $this->buildErrorDto($libXMLError), $errors ); } private function setLibXmlUserInternalErrors(): void { libxml_use_internal_errors(true); } /** * @param string $path * @return void * @throws Exception */ private function fileExists(string $path): void { if (!file_exists($path)) { throw new Exception('Не найден файл'); } } private function buildErrorDto(LibXMLError $libXMLError): XmlErrorDto { return (new XmlErrorDto()) ->setCode($libXMLError->code) ->setColumn($libXMLError->column) ->setLevel($libXMLError->level) ->setLine($libXMLError->line) ->setMessage($libXMLError->message); } }