Sabre XML v4.1.0

This commit is contained in:
the-djmaze 2026-10-21 15:18:43 +02:00
parent e66f325aa3
commit dfc9420385
13 changed files with 78 additions and 107 deletions

View file

@ -212,11 +212,13 @@ function valueObject(Reader $reader, string $className, string $namespace): obje
$reader->read();
do {
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI === $namespace) {
if (property_exists($valueObject, $reader->localName)) {
if (is_array($defaultProperties[$reader->localName])) {
// @phpstan-ignore property.dynamicName
$valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
} else {
// @phpstan-ignore property.dynamicName
$valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
}
} else {

View file

@ -19,19 +19,13 @@ use Sabre\Xml;
*/
class Base implements Xml\Element
{
/**
* @var mixed PHP value to serialize
*/
protected $value;
/**
* Constructor.
*
* @param mixed $value PHP value to serialize
*/
public function __construct($value = null)
public function __construct(protected $value = null)
{
$this->value = $value;
}
/**

View file

@ -21,17 +21,15 @@ use Sabre\Xml;
*/
class Cdata implements Xml\XmlSerializable
{
/**
* CDATA element value.
*/
protected string $value;
/**
* Constructor.
*/
public function __construct(string $value)
{
$this->value = $value;
public function __construct(
/**
* CDATA element value.
*/
protected string $value,
) {
}
/**
@ -52,6 +50,6 @@ class Cdata implements Xml\XmlSerializable
*/
public function xmlSerialize(Xml\Writer $writer): void
{
$writer->writeCData($this->value);
$writer->writeCdata($this->value);
}
}

View file

@ -37,21 +37,17 @@ use Sabre\Xml\Serializer;
*/
class Elements implements Xml\Element
{
/**
* Value to serialize.
*
* @var array<int, mixed>
*/
protected array $value;
/**
* Constructor.
*
* @param array<int, mixed> $value
*/
public function __construct(array $value = [])
{
$this->value = $value;
public function __construct(
/**
* Value to serialize.
*/
protected array $value = [],
) {
}
/**

View file

@ -37,21 +37,17 @@ use Sabre\Xml\Deserializer;
*/
class KeyValue implements Xml\Element
{
/**
* Value to serialize.
*
* @var array<string, mixed>
*/
protected array $value;
/**
* Constructor.
*
* @param array<string, mixed> $value
*/
public function __construct(array $value = [])
{
$this->value = $value;
public function __construct(
/**
* Value to serialize.
*/
protected array $value = [],
) {
}
/**

View file

@ -26,17 +26,15 @@ use function Sabre\Uri\resolve;
*/
class Uri implements Xml\Element
{
/**
* Uri element value.
*/
protected string $value;
/**
* Constructor.
*/
public function __construct(string $value)
{
$this->value = $value;
public function __construct(
/**
* Uri element value.
*/
protected string $value,
) {
}
/**

View file

@ -24,17 +24,15 @@ use Sabre\Xml\Writer;
*/
class XmlFragment implements Element
{
/**
* The inner XML value.
*/
protected string $xml;
/**
* Constructor.
*/
public function __construct(string $xml)
{
$this->xml = $xml;
public function __construct(
/**
* The inner XML value.
*/
protected string $xml,
) {
}
/**
@ -72,7 +70,7 @@ class XmlFragment implements Element
<xml-fragment xmlns="http://sabre.io/ns">{$this->getXml()}</xml-fragment>
XML;
$reader->xml($xml);
$reader->XML($xml);
while ($reader->read()) {
if ($reader->depth < 1) {

View file

@ -17,13 +17,6 @@ use LibXMLError;
*/
class LibXMLException extends ParseException
{
/**
* The error list.
*
* @var \LibXMLError[]
*/
protected array $errors;
/**
* Creates the exception.
*
@ -31,10 +24,12 @@ class LibXMLException extends ParseException
*
* @param \LibXMLError[] $errors
*/
public function __construct(array $errors, int $code = 0, ?\Throwable $previousException = null)
public function __construct(/**
* The error list.
*/
protected array $errors, int $code = 0, ?\Throwable $previousException = null)
{
$this->errors = $errors;
parent::__construct($errors[0]->message.' on line '.$errors[0]->line.', column '.$errors[0]->column, $code, $previousException);
parent::__construct($this->errors[0]->message.' on line '.$this->errors[0]->line.', column '.$this->errors[0]->column, $code, $previousException);
}
/**

View file

@ -33,7 +33,7 @@ class Reader extends \XMLReader
*/
public function getClark(): ?string
{
if (!$this->localName) {
if ('' === $this->localName) {
return null;
}
@ -67,7 +67,7 @@ class Reader extends \XMLReader
if (!$this->read()) {
$errors = libxml_get_errors();
libxml_clear_errors();
if ($errors) {
if (count($errors) > 0) {
throw new LibXMLException($errors);
}
}
@ -77,7 +77,7 @@ class Reader extends \XMLReader
// last line of defense in case errors did occur above
$errors = libxml_get_errors();
libxml_clear_errors();
if ($errors) {
if (count($errors) > 0) {
throw new LibXMLException($errors);
}
} finally {
@ -153,7 +153,7 @@ class Reader extends \XMLReader
if (!$this->read()) {
$errors = libxml_get_errors();
libxml_clear_errors();
if ($errors) {
if (count($errors) > 0) {
throw new LibXMLException($errors);
}
throw new ParseException('This should never happen (famous last words)');
@ -165,7 +165,7 @@ class Reader extends \XMLReader
if (!$this->isValid()) {
$errors = libxml_get_errors();
if ($errors) {
if (count($errors) > 0) {
libxml_clear_errors();
throw new LibXMLException($errors);
}
@ -199,7 +199,7 @@ class Reader extends \XMLReader
}
}
return $elements ?: $text;
return count($elements) > 0 ? $elements : $text;
}
/**
@ -210,8 +210,8 @@ class Reader extends \XMLReader
$result = '';
$previousDepth = $this->depth;
while ($this->read() && $this->depth != $previousDepth) {
if (in_array($this->nodeType, [\XMLReader::TEXT, \XMLReader::CDATA, \XMLReader::WHITESPACE])) {
while ($this->read() && $this->depth !== $previousDepth) {
if (in_array($this->nodeType, [\XMLReader::TEXT, \XMLReader::CDATA, \XMLReader::WHITESPACE], true)) {
$result .= $this->value;
}
}
@ -266,7 +266,7 @@ class Reader extends \XMLReader
$attributes = [];
while ($this->moveToNextAttribute()) {
if ($this->namespaceURI) {
if ('' !== $this->namespaceURI) {
// Ignoring 'xmlns', it doesn't make any sense.
if ('http://www.w3.org/2000/xmlns/' === $this->namespaceURI) {
continue;
@ -290,10 +290,10 @@ class Reader extends \XMLReader
public function getDeserializerForElementName(string $name): callable
{
if (!array_key_exists($name, $this->elementMap)) {
if ('{}' == substr($name, 0, 2) && array_key_exists(substr($name, 2), $this->elementMap)) {
if (str_starts_with($name, '{}') && array_key_exists(substr($name, 2), $this->elementMap)) {
$name = substr($name, 2);
} else {
return ['Sabre\\Xml\\Element\\Base', 'xmlDeserialize'];
return Element\Base::xmlDeserialize(...);
}
}
@ -302,8 +302,8 @@ class Reader extends \XMLReader
return $deserializer;
}
if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
return [$deserializer, 'xmlDeserialize'];
if (is_subclass_of($deserializer, XmlDeserializable::class)) {
return fn (Reader $reader) => $deserializer::xmlDeserialize($reader);
}
throw new \LogicException('Could not use this type as a deserializer: '.get_debug_type($deserializer).' for element: '.$name);

View file

@ -123,7 +123,7 @@ function valueObject(Writer $writer, object $valueObject, string $namespace): vo
*
* You can even mix the two array syntaxes.
*
* @param string|int|float|bool|array<int|string, mixed>|object $value
* @param string|int|float|bool|array<int|string, mixed>|object|null $value
*/
function standardSerializer(Writer $writer, $value): void
{
@ -133,9 +133,9 @@ function standardSerializer(Writer $writer, $value): void
} elseif ($value instanceof XmlSerializable) {
// XmlSerializable classes or Element classes.
$value->xmlSerialize($writer);
} elseif (is_object($value) && isset($writer->classMap[get_class($value)])) {
} elseif (is_object($value) && isset($writer->classMap[$value::class])) {
// It's an object which class appears in the classmap.
$writer->classMap[get_class($value)]($writer, $value);
$writer->classMap[$value::class]($writer, $value);
} elseif (is_callable($value)) {
// A callback
$value($writer);
@ -144,8 +144,8 @@ function standardSerializer(Writer $writer, $value): void
// describes a 'name' and optionally 'attributes' and 'value'.
$name = $value['name'];
$attributes = isset($value['attributes']) ? $value['attributes'] : [];
$value = isset($value['value']) ? $value['value'] : null;
$attributes = $value['attributes'] ?? [];
$value = $value['value'] ?? null;
$writer->startElement($name);
$writer->writeAttributes($attributes);
@ -157,7 +157,7 @@ function standardSerializer(Writer $writer, $value): void
// This item has a numeric index. We just loop through the
// array and throw it back in the writer.
standardSerializer($writer, $item);
} elseif (is_string($name) && is_array($item) && isset($item['attributes'])) {
} elseif (is_array($item) && isset($item['attributes'])) {
// The key is used for a name, but $item has 'attributes' and
// possibly 'value'
$writer->startElement($name);
@ -176,7 +176,7 @@ function standardSerializer(Writer $writer, $value): void
}
}
} elseif (is_object($value)) {
throw new \InvalidArgumentException('The writer cannot serialize objects of class: '.get_class($value));
throw new \InvalidArgumentException('The writer cannot serialize objects of class: '.$value::class);
} elseif (!is_null($value)) {
throw new \InvalidArgumentException('The writer cannot serialize values of type: '.get_debug_type($value));
}

View file

@ -123,7 +123,7 @@ class Service
}
// If input is empty, then it's safe to throw an exception
if (empty($input)) {
if ('' === $input) {
throw new ParseException('The input element to parse is empty. Do not attempt to parse');
}
@ -174,7 +174,7 @@ class Service
}
// If input is empty, then it's safe to throw an exception
if (empty($input)) {
if ('' === $input) {
throw new ParseException('The input element to parse is empty. Do not attempt to parse');
}
@ -254,14 +254,12 @@ class Service
*/
public function mapValueObject(string $elementName, string $className): void
{
list($namespace) = self::parseClarkNotation($elementName);
[$namespace] = self::parseClarkNotation($elementName);
require_once __DIR__ . '/Deserializer/functions.php';
$this->elementMap[$elementName] = function (Reader $reader) use ($className, $namespace) {
return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace);
};
$this->elementMap[$elementName] = (fn (Reader $reader) => \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace));
require_once __DIR__ . '/Serializer/functions.php';
$this->classMap[$className] = function (Writer $writer, $valueObject) use ($namespace) {
$this->classMap[$className] = function (Writer $writer, $valueObject) use ($namespace): void {
\Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace);
};
$this->valueObjectMap[$className] = $elementName;
@ -280,12 +278,12 @@ class Service
*/
public function writeValueObject(object $object, ?string $contextUri = null): string
{
if (!isset($this->valueObjectMap[get_class($object)])) {
throw new \InvalidArgumentException('"'.get_class($object).'" is not a registered value object class. Register your class with mapValueObject.');
if (!isset($this->valueObjectMap[$object::class])) {
throw new \InvalidArgumentException('"'.$object::class.'" is not a registered value object class. Register your class with mapValueObject.');
}
return $this->write(
$this->valueObjectMap[get_class($object)],
$this->valueObjectMap[$object::class],
$object,
$contextUri
);
@ -306,7 +304,7 @@ class Service
static $cache = [];
if (!isset($cache[$str])) {
if (!preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) {
if (1 !== preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) {
throw new \InvalidArgumentException('\''.$str.'\' is not a valid clark-notation formatted string');
}

View file

@ -16,5 +16,5 @@ class Version
/**
* Full version number.
*/
public const VERSION = '4.0.6';
public const VERSION = '4.1.0';
}

View file

@ -123,11 +123,11 @@ class Writer extends \XMLWriter
public function startElement($name): bool
{
if ('{' === $name[0]) {
list($namespace, $localName) =
[$namespace, $localName] =
Service::parseClarkNotation($name);
if (array_key_exists($namespace, $this->namespaceMap)) {
$result = $this->startElementNS(
$result = $this->startElementNs(
'' === $this->namespaceMap[$namespace] ? null : $this->namespaceMap[$namespace],
$localName,
null
@ -139,10 +139,8 @@ class Writer extends \XMLWriter
$result = $this->startElement($localName);
$this->writeAttribute('xmlns', '');
} else {
if (!isset($this->adhocNamespaces[$namespace])) {
$this->adhocNamespaces[$namespace] = 'x'.(count($this->adhocNamespaces) + 1);
}
$result = $this->startElementNS($this->adhocNamespaces[$namespace], $localName, $namespace);
$this->adhocNamespaces[$namespace] ??= 'x'.(count($this->adhocNamespaces) + 1);
$result = $this->startElementNs($this->adhocNamespaces[$namespace], $localName, $namespace);
}
}
} else {
@ -151,7 +149,7 @@ class Writer extends \XMLWriter
if (!$this->namespacesWritten) {
foreach ($this->namespaceMap as $namespace => $prefix) {
$this->writeAttribute($prefix ? 'xmlns:'.$prefix : 'xmlns', $namespace);
$this->writeAttribute(is_string($prefix) && '' !== $prefix ? 'xmlns:'.$prefix : 'xmlns', $namespace);
}
$this->namespacesWritten = true;
}
@ -248,11 +246,9 @@ class Writer extends \XMLWriter
}
// We don't know the namespace, we must add it in-line
if (!isset($this->adhocNamespaces[$namespace])) {
$this->adhocNamespaces[$namespace] = 'x'.(count($this->adhocNamespaces) + 1);
}
$this->adhocNamespaces[$namespace] ??= 'x'.(count($this->adhocNamespaces) + 1);
return $this->writeAttributeNS(
return $this->writeAttributeNs(
$this->adhocNamespaces[$namespace],
$localName,
$namespace,