Sabre: VObject to v4.5.0 and Xml to v3.0.0

This commit is contained in:
the-djmaze 2022-09-05 11:05:38 +02:00
parent fe20741409
commit 5a9540b9c3
24 changed files with 189 additions and 127 deletions

View file

@ -456,7 +456,7 @@ HELP
*/ */
protected function color($vObj) protected function color($vObj)
{ {
fwrite($this->stdout, $this->serializeComponent($vObj)); $this->serializeComponent($vObj);
} }
/** /**

View file

@ -28,7 +28,7 @@ class Component extends Node
/** /**
* A list of properties and/or sub-components. * A list of properties and/or sub-components.
* *
* @var array * @var array<string, Component|Property>
*/ */
protected $children = []; protected $children = [];
@ -43,12 +43,12 @@ class Component extends Node
* an iCalendar object, this may be something like CALSCALE:GREGORIAN. To * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
* ensure that this does not happen, set $defaults to false. * ensure that this does not happen, set $defaults to false.
* *
* @param string $name such as VCALENDAR, VEVENT * @param string|null $name such as VCALENDAR, VEVENT
* @param bool $defaults * @param bool $defaults
*/ */
public function __construct(Document $root, $name, array $children = [], $defaults = true) public function __construct(Document $root, $name, array $children = [], $defaults = true)
{ {
$this->name = strtoupper($name); $this->name = isset($name) ? strtoupper($name) : '';
$this->root = $root; $this->root = $root;
if ($defaults) { if ($defaults) {
@ -238,7 +238,7 @@ class Component extends Node
return array_filter( return array_filter(
$result, $result,
function ($child) use ($group) { function ($child) use ($group) {
return $child instanceof Property && strtoupper($child->group) === $group; return $child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group;
} }
); );
} }
@ -249,7 +249,7 @@ class Component extends Node
$result = []; $result = [];
foreach ($this->children as $childGroup) { foreach ($this->children as $childGroup) {
foreach ($childGroup as $child) { foreach ($childGroup as $child) {
if ($child instanceof Property && $child->group && strtoupper($child->group) === $group) { if ($child instanceof Property && (null !== $child->group ? strtoupper($child->group) : '') === $group) {
$result[] = $child; $result[] = $child;
} }
} }

View file

@ -52,11 +52,12 @@ class Parameter extends Node
*/ */
public function __construct(Document $root, $name, $value = null) public function __construct(Document $root, $name, $value = null)
{ {
$this->name = strtoupper($name);
$this->root = $root; $this->root = $root;
if (is_null($name)) { if (is_null($name)) {
$this->noName = true; $this->noName = true;
$this->name = static::guessParameterNameByValue($value); $this->name = static::guessParameterNameByValue($value);
} else {
$this->name = strtoupper($name);
} }
// If guessParameterNameByValue() returns an empty string // If guessParameterNameByValue() returns an empty string

View file

@ -167,7 +167,11 @@ class MimeDir extends Parser
while (true) { while (true) {
// Reading until we hit END: // Reading until we hit END:
$line = $this->readLine(); try {
$line = $this->readLine();
} catch (EofException $oEx) {
$line = 'END:'.$this->root->name;
}
if ('END:' === strtoupper(substr($line, 0, 4))) { if ('END:' === strtoupper(substr($line, 0, 4))) {
break; break;
} }
@ -372,12 +376,22 @@ class MimeDir extends Parser
$value = $this->unescapeParam($value); $value = $this->unescapeParam($value);
if (is_null($lastParam)) { if (is_null($lastParam)) {
if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
// When the property can't be matched and the configuration
// option is set to ignore invalid lines, we ignore this line
// This can happen when servers provide faulty data as iCloud
// frequently does with X-APPLE-STRUCTURED-LOCATION
continue;
}
throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions'); throw new ParseException('Invalid Mimedir file. Line starting at '.$this->startLine.' did not follow iCalendar/vCard conventions');
} }
if (is_null($property['parameters'][$lastParam])) { if (is_null($property['parameters'][$lastParam])) {
$property['parameters'][$lastParam] = $value; $property['parameters'][$lastParam] = $value;
} elseif (is_array($property['parameters'][$lastParam])) { } elseif (is_array($property['parameters'][$lastParam])) {
$property['parameters'][$lastParam][] = $value; $property['parameters'][$lastParam][] = $value;
} elseif ($property['parameters'][$lastParam] === $value) {
// When the current value of the parameter is the same as the
// new one, then we can leave the current parameter as it is.
} else { } else {
$property['parameters'][$lastParam] = [ $property['parameters'][$lastParam] = [
$property['parameters'][$lastParam], $property['parameters'][$lastParam],

View file

@ -30,7 +30,7 @@ abstract class Property extends Node
* *
* This is only used in vcards * This is only used in vcards
* *
* @var string * @var string|null
*/ */
public $group; public $group;

View file

@ -136,16 +136,18 @@ class Text extends Property
} }
foreach ($item as &$subItem) { foreach ($item as &$subItem) {
$subItem = strtr( if (!is_null($subItem)) {
$subItem, $subItem = strtr(
[ $subItem,
'\\' => '\\\\', [
';' => '\;', '\\' => '\\\\',
',' => '\,', ';' => '\;',
"\n" => '\n', ',' => '\,',
"\r" => '', "\n" => '\n',
] "\r" => '',
); ]
);
}
} }
$item = implode(',', $item); $item = implode(',', $item);
} }

View file

@ -24,6 +24,13 @@ use Sabre\VObject\Property;
*/ */
class RRuleIterator implements Iterator class RRuleIterator implements Iterator
{ {
/**
* Constant denoting the upper limit on how long into the future
* we want to iterate. The value is a unix timestamp and currently
* corresponds to the datetime 9999-12-31 11:59:59 UTC.
*/
const dateUpperLimit = 253402300799;
/** /**
* Creates the Iterator. * Creates the Iterator.
* *
@ -366,6 +373,12 @@ class RRuleIterator implements Iterator
// Current hour of the day // Current hour of the day
$currentHour = $this->currentDate->format('G'); $currentHour = $this->currentDate->format('G');
if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
$this->currentDate = null;
return;
}
} while ( } while (
($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
($this->byHour && !in_array($currentHour, $recurrenceHours)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
@ -486,7 +499,7 @@ class RRuleIterator implements Iterator
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php .... // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
if ($this->currentDate->getTimestamp() > 253402300799) { if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
$this->currentDate = null; $this->currentDate = null;
return; return;
@ -589,11 +602,12 @@ class RRuleIterator implements Iterator
// loop through all YearDay and Days to check all the combinations // loop through all YearDay and Days to check all the combinations
foreach ($this->byYearDay as $byYearDay) { foreach ($this->byYearDay as $byYearDay) {
$date = clone $this->currentDate; $date = clone $this->currentDate;
$date = $date->setDate($currentYear, 1, 1);
if ($byYearDay > 0) { if ($byYearDay > 0) {
$date = $date->add(new \DateInterval('P'.$byYearDay.'D')); $date = $date->setDate($currentYear, 1, 1);
$date = $date->add(new \DateInterval('P'.($byYearDay - 1).'D'));
} else { } else {
$date = $date->sub(new \DateInterval('P'.abs($byYearDay).'D')); $date = $date->setDate($currentYear, 12, 31);
$date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
} }
if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) { if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
@ -658,6 +672,14 @@ class RRuleIterator implements Iterator
(int) $currentMonth, (int) $currentMonth,
(int) $currentDayOfMonth (int) $currentDayOfMonth
); );
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
$this->currentDate = null;
return;
}
} }
// If we made it here, it means we got a valid occurrence // If we made it here, it means we got a valid occurrence

View file

@ -140,6 +140,8 @@ class VCardConverter
$newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP'); $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP');
break; break;
} }
} elseif ('MEMBER' === $property->name) {
$newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-MEMBER', $property->getValue());
} }
} elseif (Document::VCARD40 === $targetVersion) { } elseif (Document::VCARD40 === $targetVersion) {
// These properties were removed in vCard 4.0 // These properties were removed in vCard 4.0
@ -173,6 +175,9 @@ class VCardConverter
$newProperty = $output->createProperty('KIND', 'GROUP'); $newProperty = $output->createProperty('KIND', 'GROUP');
} }
break; break;
case 'X-ADDRESSBOOKSERVER-MEMBER':
$newProperty = $output->createProperty('MEMBER', $property->getValue());
break;
case 'X-ANNIVERSARY': case 'X-ANNIVERSARY':
$newProperty->name = 'ANNIVERSARY'; $newProperty->name = 'ANNIVERSARY';
// If we already have an anniversary property with the same // If we already have an anniversary property with the same

View file

@ -14,5 +14,5 @@ class Version
/** /**
* Full version number. * Full version number.
*/ */
const VERSION = '4.4.2'; const VERSION = '4.5.0';
} }

View file

@ -32,9 +32,9 @@ trait ContextStackTrait
* Values may also be a callable. In that case the function will be called * Values may also be a callable. In that case the function will be called
* directly. * directly.
* *
* @var array * @phpstan-var array<string, class-string|callable|object>
*/ */
public $elementMap = []; public array $elementMap = [];
/** /**
* A contextUri pointing to the document being parsed / written. * A contextUri pointing to the document being parsed / written.
@ -44,10 +44,8 @@ trait ContextStackTrait
* The reader and writer don't use this property, but as it's an extremely * The reader and writer don't use this property, but as it's an extremely
* common use-case for parsing XML documents, it's added here as a * common use-case for parsing XML documents, it's added here as a
* convenience. * convenience.
*
* @var string|null
*/ */
public $contextUri; public ?string $contextUri = null;
/** /**
* This is a list of namespaces that you want to give default prefixes. * This is a list of namespaces that you want to give default prefixes.
@ -55,9 +53,9 @@ trait ContextStackTrait
* You must make sure you create this entire list before starting to write. * You must make sure you create this entire list before starting to write.
* They should be registered on the root element. * They should be registered on the root element.
* *
* @var array * @phpstan-var array<string, class-string|string|null>
*/ */
public $namespaceMap = []; public array $namespaceMap = [];
/** /**
* This is a list of custom serializers for specific classes. * This is a list of custom serializers for specific classes.
@ -75,16 +73,16 @@ trait ContextStackTrait
* *
* function (Writer $writer, object $value) * function (Writer $writer, object $value)
* *
* @var array * @phpstan-var array<class-string, callable(Writer, object):mixed>
*/ */
public $classMap = []; public array $classMap = [];
/** /**
* Backups of previous contexts. * Backups of previous contexts.
* *
* @var array * @var list<mixed>
*/ */
protected $contextStack = []; protected array $contextStack = [];
/** /**
* Create a new "context". * Create a new "context".
@ -93,7 +91,7 @@ trait ContextStackTrait
* namespaceMap. After you're done, you can restore the old data again * namespaceMap. After you're done, you can restore the old data again
* with popContext. * with popContext.
*/ */
public function pushContext() public function pushContext(): void
{ {
$this->contextStack[] = [ $this->contextStack[] = [
$this->elementMap, $this->elementMap,
@ -106,7 +104,7 @@ trait ContextStackTrait
/** /**
* Restore the previous "context". * Restore the previous "context".
*/ */
public function popContext() public function popContext(): void
{ {
list( list(
$this->elementMap, $this->elementMap,

View file

@ -54,6 +54,8 @@ use Sabre\Xml\Reader;
* *
* Attributes will be removed from the top-level elements. If elements with * Attributes will be removed from the top-level elements. If elements with
* the same name appear twice in the list, only the last one will be kept. * the same name appear twice in the list, only the last one will be kept.
*
* @phpstan-return array<string, mixed>
*/ */
function keyValue(Reader $reader, string $namespace = null): array function keyValue(Reader $reader, string $namespace = null): array
{ {
@ -143,6 +145,7 @@ function keyValue(Reader $reader, string $namespace = null): array
* ]; * ];
* *
* @return string[] * @return string[]
* @phpstan-return list<string>
*/ */
function enum(Reader $reader, string $namespace = null): array function enum(Reader $reader, string $namespace = null): array
{ {
@ -189,9 +192,12 @@ function enum(Reader $reader, string $namespace = null): array
* This is primarily used by the mapValueObject function from the Service * This is primarily used by the mapValueObject function from the Service
* class, but it can also easily be used for more specific situations. * class, but it can also easily be used for more specific situations.
* *
* @return object * @template C of object
*
* @param class-string<C> $className
* @phpstan-return C
*/ */
function valueObject(Reader $reader, string $className, string $namespace) function valueObject(Reader $reader, string $className, string $namespace): object
{ {
$valueObject = new $className(); $valueObject = new $className();
if ($reader->isEmptyElement) { if ($reader->isEmptyElement) {
@ -250,6 +256,8 @@ function valueObject(Reader $reader, string $className, string $namespace)
* *
* $childElementName must either be a a clark-notation element name, or if no * $childElementName must either be a a clark-notation element name, or if no
* namespace is used, the bare element name. * namespace is used, the bare element name.
*
* @phpstan-return list<mixed>
*/ */
function repeatingElements(Reader $reader, string $childElementName): array function repeatingElements(Reader $reader, string $childElementName): array
{ {
@ -268,7 +276,7 @@ function repeatingElements(Reader $reader, string $childElementName): array
} }
/** /**
* This deserializer helps you to deserialize structures which contain mixed content like this:. * This deserializer helps you to deserialize structures which contain mixed content.
* *
* <p>some text <extref>and a inline tag</extref>and even more text</p> * <p>some text <extref>and a inline tag</extref>and even more text</p>
* *
@ -285,6 +293,8 @@ function repeatingElements(Reader $reader, string $childElementName): array
* ] * ]
* *
* In strict XML documents you wont find this kind of markup but in html this is a quite common pattern. * In strict XML documents you wont find this kind of markup but in html this is a quite common pattern.
*
* @return array<mixed>
*/ */
function mixedContent(Reader $reader): array function mixedContent(Reader $reader): array
{ {

View file

@ -28,6 +28,8 @@ class Base implements Xml\Element
/** /**
* Constructor. * Constructor.
*
* @param mixed $value
*/ */
public function __construct($value = null) public function __construct($value = null)
{ {
@ -50,7 +52,7 @@ class Base implements Xml\Element
* *
* If you are opening new elements, you must also close them again. * If you are opening new elements, you must also close them again.
*/ */
public function xmlSerialize(Xml\Writer $writer) public function xmlSerialize(Xml\Writer $writer): void
{ {
$writer->write($this->value); $writer->write($this->value);
} }

View file

@ -23,10 +23,8 @@ class Cdata implements Xml\XmlSerializable
{ {
/** /**
* CDATA element value. * CDATA element value.
*
* @var string
*/ */
protected $value; protected string $value;
/** /**
* Constructor. * Constructor.
@ -52,7 +50,7 @@ class Cdata implements Xml\XmlSerializable
* *
* If you are opening new elements, you must also close them again. * If you are opening new elements, you must also close them again.
*/ */
public function xmlSerialize(Xml\Writer $writer) public function xmlSerialize(Xml\Writer $writer): void
{ {
$writer->writeCData($this->value); $writer->writeCData($this->value);
} }

View file

@ -40,12 +40,14 @@ class Elements implements Xml\Element
/** /**
* Value to serialize. * Value to serialize.
* *
* @var array * @var array<int, mixed>
*/ */
protected $value; protected array $value;
/** /**
* Constructor. * Constructor.
*
* @param array<int, mixed> $value
*/ */
public function __construct(array $value = []) public function __construct(array $value = [])
{ {
@ -68,7 +70,7 @@ class Elements implements Xml\Element
* *
* If you are opening new elements, you must also close them again. * If you are opening new elements, you must also close them again.
*/ */
public function xmlSerialize(Xml\Writer $writer) public function xmlSerialize(Xml\Writer $writer): void
{ {
Serializer\enum($writer, $this->value); Serializer\enum($writer, $this->value);
} }
@ -91,9 +93,9 @@ class Elements implements Xml\Element
* $reader->parseSubTree() will parse the entire sub-tree, and advance to * $reader->parseSubTree() will parse the entire sub-tree, and advance to
* the next element. * the next element.
* *
* @return mixed * @return string[]
*/ */
public static function xmlDeserialize(Xml\Reader $reader) public static function xmlDeserialize(Xml\Reader $reader): array
{ {
return Deserializer\enum($reader); return Deserializer\enum($reader);
} }

View file

@ -40,12 +40,14 @@ class KeyValue implements Xml\Element
/** /**
* Value to serialize. * Value to serialize.
* *
* @var array * @var array<string, mixed>
*/ */
protected $value; protected array $value;
/** /**
* Constructor. * Constructor.
*
* @param array<string, mixed> $value
*/ */
public function __construct(array $value = []) public function __construct(array $value = [])
{ {
@ -68,7 +70,7 @@ class KeyValue implements Xml\Element
* *
* If you are opening new elements, you must also close them again. * If you are opening new elements, you must also close them again.
*/ */
public function xmlSerialize(Xml\Writer $writer) public function xmlSerialize(Xml\Writer $writer): void
{ {
$writer->write($this->value); $writer->write($this->value);
} }
@ -91,9 +93,9 @@ class KeyValue implements Xml\Element
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element. * the next element.
* *
* @return mixed * @return array<string, mixed>
*/ */
public static function xmlDeserialize(Xml\Reader $reader) public static function xmlDeserialize(Xml\Reader $reader): array
{ {
return Deserializer\keyValue($reader); return Deserializer\keyValue($reader);
} }

View file

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Sabre\Xml\Element; namespace Sabre\Xml\Element;
use function Sabre\Uri\resolve;
use Sabre\Xml; use Sabre\Xml;
/** /**
@ -26,17 +28,13 @@ class Uri implements Xml\Element
{ {
/** /**
* Uri element value. * Uri element value.
*
* @var string
*/ */
protected $value; protected string $value;
/** /**
* Constructor. * Constructor.
*
* @param string $value
*/ */
public function __construct($value) public function __construct(string $value)
{ {
$this->value = $value; $this->value = $value;
} }
@ -57,10 +55,10 @@ class Uri implements Xml\Element
* *
* If you are opening new elements, you must also close them again. * If you are opening new elements, you must also close them again.
*/ */
public function xmlSerialize(Xml\Writer $writer) public function xmlSerialize(Xml\Writer $writer): void
{ {
$writer->text( $writer->text(
\Sabre\Uri\resolve( resolve(
$writer->contextUri, $writer->contextUri,
$this->value $this->value
) )
@ -84,13 +82,11 @@ class Uri implements Xml\Element
* *
* $reader->parseSubTree() will parse the entire sub-tree, and advance to * $reader->parseSubTree() will parse the entire sub-tree, and advance to
* the next element. * the next element.
*
* @return mixed
*/ */
public static function xmlDeserialize(Xml\Reader $reader) public static function xmlDeserialize(Xml\Reader $reader): Uri
{ {
return new self( return new self(
\Sabre\Uri\resolve( resolve(
(string) $reader->contextUri, (string) $reader->contextUri,
$reader->readText() $reader->readText()
) )

View file

@ -26,10 +26,8 @@ class XmlFragment implements Element
{ {
/** /**
* The inner XML value. * The inner XML value.
*
* @var string
*/ */
protected $xml; protected string $xml;
/** /**
* Constructor. * Constructor.
@ -63,7 +61,7 @@ class XmlFragment implements Element
* *
* If you are opening new elements, you must also close them again. * If you are opening new elements, you must also close them again.
*/ */
public function xmlSerialize(Writer $writer) public function xmlSerialize(Writer $writer): void
{ {
$reader = new Reader(); $reader = new Reader();
@ -135,10 +133,8 @@ XML;
* *
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element. * the next element.
*
* @return mixed
*/ */
public static function xmlDeserialize(Reader $reader) public static function xmlDeserialize(Reader $reader): XmlFragment
{ {
$result = new self($reader->readInnerXml()); $result = new self($reader->readInnerXml());
$reader->next(); $reader->next();

View file

@ -23,7 +23,7 @@ class LibXMLException extends ParseException
* *
* @var \LibXMLError[] * @var \LibXMLError[]
*/ */
protected $errors; protected array $errors;
/** /**
* Creates the exception. * Creates the exception.
@ -31,7 +31,6 @@ class LibXMLException extends ParseException
* You should pass a list of LibXMLError objects in its constructor. * You should pass a list of LibXMLError objects in its constructor.
* *
* @param LibXMLError[] $errors * @param LibXMLError[] $errors
* @param Throwable $previousException
*/ */
public function __construct(array $errors, int $code = 0, Throwable $previousException = null) public function __construct(array $errors, int $code = 0, Throwable $previousException = null)
{ {
@ -41,6 +40,8 @@ class LibXMLException extends ParseException
/** /**
* Returns the LibXML errors. * Returns the LibXML errors.
*
* @return LibXMLError[]
*/ */
public function getErrors(): array public function getErrors(): array
{ {

View file

@ -30,10 +30,8 @@ class Reader extends XMLReader
* Or if no namespace is defined: "{}feed". * Or if no namespace is defined: "{}feed".
* *
* This method returns null if we're not currently on an element. * This method returns null if we're not currently on an element.
*
* @return string|null
*/ */
public function getClark() public function getClark(): ?string
{ {
if (!$this->localName) { if (!$this->localName) {
return null; return null;
@ -52,6 +50,8 @@ class Reader extends XMLReader
* *
* This function will also disable the standard libxml error handler (which * This function will also disable the standard libxml error handler (which
* usually just results in PHP errors), and throw exceptions instead. * usually just results in PHP errors), and throw exceptions instead.
*
* @return array<string, mixed>
*/ */
public function parse(): array public function parse(): array
{ {
@ -102,6 +102,10 @@ class Reader extends XMLReader
* *
* If the $elementMap argument is specified, the existing elementMap will * If the $elementMap argument is specified, the existing elementMap will
* be overridden while parsing the tree, and restored after this process. * be overridden while parsing the tree, and restored after this process.
*
* @param array<string, mixed>|null $elementMap
*
* @return array<string, mixed>
*/ */
public function parseGetElements(array $elementMap = null): array public function parseGetElements(array $elementMap = null): array
{ {
@ -124,7 +128,9 @@ class Reader extends XMLReader
* If the $elementMap argument is specified, the existing elementMap will * If the $elementMap argument is specified, the existing elementMap will
* be overridden while parsing the tree, and restored after this process. * be overridden while parsing the tree, and restored after this process.
* *
* @return array|string|null * @param array<string, mixed>|null $elementMap
*
* @return array<string, mixed>|string|null
*/ */
public function parseInnerTree(array $elementMap = null) public function parseInnerTree(array $elementMap = null)
{ {
@ -193,7 +199,7 @@ class Reader extends XMLReader
} }
} }
return $elements ? $elements : $text; return $elements ?: $text;
} }
/** /**
@ -220,6 +226,8 @@ class Reader extends XMLReader
* * name - A clark-notation XML element name. * * name - A clark-notation XML element name.
* * value - The parsed value. * * value - The parsed value.
* * attributes - A key-value list of attributes. * * attributes - A key-value list of attributes.
*
* @return array <string, mixed>
*/ */
public function parseCurrentElement(): array public function parseCurrentElement(): array
{ {
@ -250,6 +258,8 @@ class Reader extends XMLReader
* If the attributes are part of the same namespace, they will simply be * If the attributes are part of the same namespace, they will simply be
* short keys. If they are defined on a different namespace, the attribute * short keys. If they are defined on a different namespace, the attribute
* name will be returned in clark-notation. * name will be returned in clark-notation.
*
* @return array<string, mixed>
*/ */
public function parseAttributes(): array public function parseAttributes(): array
{ {
@ -297,9 +307,9 @@ class Reader extends XMLReader
} }
$type = gettype($deserializer); $type = gettype($deserializer);
if ('string' === $type) { if (is_string($deserializer)) {
$type .= ' ('.$deserializer.')'; $type .= ' ('.$deserializer.')';
} elseif ('object' === $type) { } elseif (is_object($deserializer)) {
$type .= ' ('.get_class($deserializer).')'; $type .= ' ('.get_class($deserializer).')';
} }
throw new \LogicException('Could not use this type as a deserializer: '.$type.' for element: '.$name); throw new \LogicException('Could not use this type as a deserializer: '.$type.' for element: '.$name);

View file

@ -38,7 +38,7 @@ use Sabre\Xml\XmlSerializable;
* *
* @param string[] $values * @param string[] $values
*/ */
function enum(Writer $writer, array $values) function enum(Writer $writer, array $values): void
{ {
foreach ($values as $value) { foreach ($values as $value) {
$writer->writeElement($value); $writer->writeElement($value);
@ -53,10 +53,8 @@ function enum(Writer $writer, array $values)
* *
* Values that are set to null or an empty array are not serialized. To * Values that are set to null or an empty array are not serialized. To
* serialize empty properties, you must specify them as an empty string. * serialize empty properties, you must specify them as an empty string.
*
* @param object $valueObject
*/ */
function valueObject(Writer $writer, $valueObject, string $namespace) function valueObject(Writer $writer, object $valueObject, string $namespace): void
{ {
foreach (get_object_vars($valueObject) as $key => $val) { foreach (get_object_vars($valueObject) as $key => $val) {
if (is_array($val)) { if (is_array($val)) {
@ -85,8 +83,10 @@ function valueObject(Writer $writer, $valueObject, string $namespace)
* and this could be called like this: * and this could be called like this:
* *
* repeatingElements($writer, $items, '{}item'); * repeatingElements($writer, $items, '{}item');
*
* @param array<int,mixed> $items
*/ */
function repeatingElements(Writer $writer, array $items, string $childElementName) function repeatingElements(Writer $writer, array $items, string $childElementName): void
{ {
foreach ($items as $item) { foreach ($items as $item) {
$writer->writeElement($childElementName, $item); $writer->writeElement($childElementName, $item);
@ -104,7 +104,7 @@ function repeatingElements(Writer $writer, array $items, string $childElementNam
* calls it's xmlSerialize() method. * calls it's xmlSerialize() method.
* $value may be a PHP callback/function/closure, in case we call the callback * $value may be a PHP callback/function/closure, in case we call the callback
* and give it the Writer as an argument. * and give it the Writer as an argument.
* $value may be a an object, and if it's in the classMap we automatically call * $value may be an object, and if it's in the classMap we automatically call
* the correct serializer for it. * the correct serializer for it.
* $value may be null, in which case we do nothing. * $value may be null, in which case we do nothing.
* *
@ -148,9 +148,9 @@ function repeatingElements(Writer $writer, array $items, string $childElementNam
* *
* You can even mix the two array syntaxes. * You can even mix the two array syntaxes.
* *
* @param string|int|float|bool|array|object $value * @param string|int|float|bool|array<int|string, mixed>|object $value
*/ */
function standardSerializer(Writer $writer, $value) function standardSerializer(Writer $writer, $value): void
{ {
if (is_scalar($value)) { if (is_scalar($value)) {
// String, integer, float, boolean // String, integer, float, boolean

View file

@ -26,9 +26,9 @@ class Service
* Values may also be a callable. In that case the function will be called * Values may also be a callable. In that case the function will be called
* directly. * directly.
* *
* @var array * @phpstan-var array<string, class-string|callable|object>
*/ */
public $elementMap = []; public array $elementMap = [];
/** /**
* This is a list of namespaces that you want to give default prefixes. * This is a list of namespaces that you want to give default prefixes.
@ -36,9 +36,9 @@ class Service
* You must make sure you create this entire list before starting to write. * You must make sure you create this entire list before starting to write.
* They should be registered on the root element. * They should be registered on the root element.
* *
* @var array * @phpstan-var array<string, class-string|string|null>
*/ */
public $namespaceMap = []; public array $namespaceMap = [];
/** /**
* This is a list of custom serializers for specific classes. * This is a list of custom serializers for specific classes.
@ -56,16 +56,14 @@ class Service
* *
* function (Writer $writer, object $value) * function (Writer $writer, object $value)
* *
* @var array * @phpstan-var array<class-string, callable(Writer, object):mixed>
*/ */
public $classMap = []; public array $classMap = [];
/** /**
* A bitmask of the LIBXML_* constants. * A bitmask of the LIBXML_* constants.
*
* @var int
*/ */
public $options = 0; public int $options = 0;
/** /**
* Returns a fresh XML Reader. * Returns a fresh XML Reader.
@ -107,7 +105,7 @@ class Service
* *
* @throws ParseException * @throws ParseException
* *
* @return array|object|string * @return array<string, mixed>|object|string
*/ */
public function parse($input, string $contextUri = null, string &$rootElementName = null) public function parse($input, string $contextUri = null, string &$rootElementName = null)
{ {
@ -151,7 +149,7 @@ class Service
* *
* @throws ParseException * @throws ParseException
* *
* @return array|object|string * @return array<string, mixed>|object|string
*/ */
public function expect($rootElementName, $input, string $contextUri = null) public function expect($rootElementName, $input, string $contextUri = null)
{ {
@ -200,11 +198,9 @@ class Service
* This allows an implementor to easily create URI's relative to the root * This allows an implementor to easily create URI's relative to the root
* of the domain. * of the domain.
* *
* @param string|array|object|XmlSerializable $value * @param string|array<string, mixed>|object|XmlSerializable $value
*
* @return string
*/ */
public function write(string $rootElementName, $value, string $contextUri = null) public function write(string $rootElementName, $value, string $contextUri = null): string
{ {
$w = $this->getWriter(); $w = $this->getWriter();
$w->openMemory(); $w->openMemory();
@ -239,8 +235,10 @@ class Service
* These can easily be mapped by calling: * These can easily be mapped by calling:
* *
* $service->mapValueObject('{http://example.org}author', 'Author'); * $service->mapValueObject('{http://example.org}author', 'Author');
*
* @param class-string $className
*/ */
public function mapValueObject(string $elementName, string $className) public function mapValueObject(string $elementName, string $className): void
{ {
list($namespace) = self::parseClarkNotation($elementName); list($namespace) = self::parseClarkNotation($elementName);
@ -248,7 +246,7 @@ class Service
return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace); return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace);
}; };
$this->classMap[$className] = function (Writer $writer, $valueObject) use ($namespace) { $this->classMap[$className] = function (Writer $writer, $valueObject) use ($namespace) {
return \Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace); \Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace);
}; };
$this->valueObjectMap[$className] = $elementName; $this->valueObjectMap[$className] = $elementName;
} }
@ -262,11 +260,9 @@ class Service
* The ValueObject must have been previously registered using * The ValueObject must have been previously registered using
* mapValueObject(). * mapValueObject().
* *
* @param object $object *@throws \InvalidArgumentException
*
* @throws \InvalidArgumentException
*/ */
public function writeValueObject($object, string $contextUri = null) public function writeValueObject(object $object, string $contextUri = null): string
{ {
if (!isset($this->valueObjectMap[get_class($object)])) { 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.'); throw new \InvalidArgumentException('"'.get_class($object).'" is not a registered value object class. Register your class with mapValueObject.');
@ -286,6 +282,8 @@ class Service
* If the string was invalid, it will throw an InvalidArgumentException. * If the string was invalid, it will throw an InvalidArgumentException.
* *
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*
* @return array{string|null, string}
*/ */
public static function parseClarkNotation(string $str): array public static function parseClarkNotation(string $str): array
{ {
@ -307,6 +305,8 @@ class Service
/** /**
* A list of classes and which XML elements they map to. * A list of classes and which XML elements they map to.
*
* @var array<class-string, string>
*/ */
protected $valueObjectMap = []; protected array $valueObjectMap = [];
} }

View file

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

View file

@ -41,19 +41,17 @@ class Writer extends XMLWriter
* time* they are used, but this array allows the writer to make sure that * time* they are used, but this array allows the writer to make sure that
* the prefixes are consistent anyway. * the prefixes are consistent anyway.
* *
* @var array * @var array<string, string>
*/ */
protected $adhocNamespaces = []; protected array $adhocNamespaces = [];
/** /**
* When the first element is written, this flag is set to true. * When the first element is written, this flag is set to true.
* *
* This ensures that the namespaces in the namespaces map are only written * This ensures that the namespaces in the namespaces map are only written
* once. * once.
*
* @var bool
*/ */
protected $namespacesWritten = false; protected bool $namespacesWritten = false;
/** /**
* Writes a value to the output stream. * Writes a value to the output stream.
@ -96,16 +94,15 @@ class Writer extends XMLWriter
* *
* @param mixed $value * @param mixed $value
*/ */
public function write($value) public function write($value): void
{ {
require_once __DIR__ . '/Serializer/functions.php';
Serializer\standardSerializer($this, $value); Serializer\standardSerializer($this, $value);
} }
/** /**
* Opens a new element. * Opens a new element.
* *
* You can either just use a local elementname, or you can use clark- * You can either just use a local element name, or you can use clark-
* notation to start a new element. * notation to start a new element.
* *
* Example: * Example:
@ -118,6 +115,7 @@ class Writer extends XMLWriter
* *
* Note: this function doesn't have the string typehint, because PHP's * Note: this function doesn't have the string typehint, because PHP's
* XMLWriter::startElement doesn't either. * XMLWriter::startElement doesn't either.
* From PHP 8.0 the typehint exists, so it can be added here after PHP 7.4 is dropped.
* *
* @param string $name * @param string $name
*/ */
@ -152,7 +150,7 @@ class Writer extends XMLWriter
if (!$this->namespacesWritten) { if (!$this->namespacesWritten) {
foreach ($this->namespaceMap as $namespace => $prefix) { foreach ($this->namespaceMap as $namespace => $prefix) {
$this->writeAttribute(($prefix ? 'xmlns:'.$prefix : 'xmlns'), $namespace); $this->writeAttribute($prefix ? 'xmlns:'.$prefix : 'xmlns', $namespace);
} }
$this->namespacesWritten = true; $this->namespacesWritten = true;
} }
@ -181,8 +179,10 @@ class Writer extends XMLWriter
* *
* Note: this function doesn't have the string typehint, because PHP's * Note: this function doesn't have the string typehint, because PHP's
* XMLWriter::startElement doesn't either. * XMLWriter::startElement doesn't either.
* From PHP 8.0 the typehint exists, so it can be added here after PHP 7.4 is dropped.
* *
* @param array|string|object|null $content * @param string $name
* @param array<int|string, mixed>|string|object|null $content
*/ */
public function writeElement($name, $content = null): bool public function writeElement($name, $content = null): bool
{ {
@ -203,8 +203,10 @@ class Writer extends XMLWriter
* The key is an attribute name. If the key is a 'localName', the current * The key is an attribute name. If the key is a 'localName', the current
* xml namespace is assumed. If it's a 'clark notation key', this namespace * xml namespace is assumed. If it's a 'clark notation key', this namespace
* will be used instead. * will be used instead.
*
* @param array<string, string> $attributes
*/ */
public function writeAttributes(array $attributes) public function writeAttributes(array $attributes): void
{ {
foreach ($attributes as $name => $value) { foreach ($attributes as $name => $value) {
$this->writeAttribute($name, $value); $this->writeAttribute($name, $value);
@ -220,6 +222,7 @@ class Writer extends XMLWriter
* *
* Note: this function doesn't have typehints, because for some reason * Note: this function doesn't have typehints, because for some reason
* PHP's XMLWriter::writeAttribute doesn't either. * PHP's XMLWriter::writeAttribute doesn't either.
* From PHP 8.0 the typehint exists, so it can be added here after PHP 7.4 is dropped.
* *
* @param string $name * @param string $name
* @param string $value * @param string $value

View file

@ -30,5 +30,5 @@ interface XmlSerializable
* *
* If you are opening new elements, you must also close them again. * If you are opening new elements, you must also close them again.
*/ */
public function xmlSerialize(Writer $writer); public function xmlSerialize(Writer $writer): void;
} }