Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  /**
   3   * LICENSE
   4   *
   5   * This file is part of CFPropertyList.
   6   *
   7   * The PHP implementation of Apple's PropertyList can handle XML PropertyLists
   8   * as well as binary PropertyLists. It offers functionality to easily convert
   9   * data between worlds, e.g. recalculating timestamps from unix epoch to apple
  10   * epoch and vice versa. A feature to automagically create (guess) the plist
  11   * structure from a normal PHP data structure will help you dump your data to
  12   * plist in no time.
  13   *
  14   * Copyright (c) 2018 Teclib'
  15   *
  16   * Permission is hereby granted, free of charge, to any person obtaining a copy
  17   * of this software and associated documentation files (the "Software"), to deal
  18   * in the Software without restriction, including without limitation the rights
  19   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  20   * copies of the Software, and to permit persons to whom the Software is
  21   * furnished to do so, subject to the following conditions:
  22   *
  23   * The above copyright notice and this permission notice shall be included in all
  24   * copies or substantial portions of the Software.
  25   *
  26   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  27   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  28   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  29   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  30   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  31   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  32   * SOFTWARE.
  33   *
  34   * ------------------------------------------------------------------------------
  35   * @author    Rodney Rehm <rodney.rehm@medialize.de>
  36   * @author    Christian Kruse <cjk@wwwtech.de>
  37   * @copyright Copyright © 2018 Teclib
  38   * @package   plist
  39   * @license   MIT
  40   * @link      https://github.com/TECLIB/CFPropertyList/
  41   * @link      http://developer.apple.com/documentation/Darwin/Reference/ManPages/man5/plist.5.html Property Lists
  42   */
  43  
  44  namespace CFPropertyList;
  45  
  46  use Iterator;
  47  use DOMDocument;
  48  use DOMException;
  49  use DOMImplementation;
  50  use DOMNode;
  51  
  52  /**
  53   * Property List
  54   * Interface for handling reading, editing and saving Property Lists as defined by Apple.
  55   * @example   example-read-01.php Read an XML PropertyList
  56   * @example   example-read-02.php Read a Binary PropertyList
  57   * @example   example-read-03.php Read a PropertyList without knowing the type
  58   * @example   example-create-01.php Using the CFPropertyList API
  59   * @example   example-create-02.php Using CFTypeDetector
  60   * @example   example-create-03.php Using CFTypeDetector with CFDate and CFData
  61   * @example   example-modify-01.php Read, modify and save a PropertyList
  62   * ------------------------------------------------------------------------------
  63   */
  64  class CFPropertyList extends CFBinaryPropertyList implements Iterator
  65  {
  66    /**
  67     * Format constant for binary format
  68     * @var integer
  69     */
  70      const FORMAT_BINARY = 1;
  71  
  72    /**
  73     * Format constant for xml format
  74     * @var integer
  75     */
  76      const FORMAT_XML = 2;
  77  
  78    /**
  79     * Format constant for automatic format recognizing
  80     * @var integer
  81     */
  82      const FORMAT_AUTO = 0;
  83  
  84    /**
  85     * Path of PropertyList
  86     * @var string
  87     */
  88      protected $file = null;
  89  
  90    /**
  91     * Detected format of PropertyList
  92     * @var integer
  93     */
  94      protected $detectedFormat = null;
  95  
  96    /**
  97     * Path of PropertyList
  98     * @var integer
  99     */
 100      protected $format = null;
 101  
 102    /**
 103     * CFType nodes
 104     * @var array
 105     */
 106      protected $value = array();
 107  
 108    /**
 109     * Position of iterator {@link http://php.net/manual/en/class.iterator.php}
 110     * @var integer
 111     */
 112      protected $iteratorPosition = 0;
 113  
 114    /**
 115     * List of Keys for numerical iterator access {@link http://php.net/manual/en/class.iterator.php}
 116     * @var array
 117     */
 118      protected $iteratorKeys = null;
 119  
 120    /**
 121     * List of NodeNames to ClassNames for resolving plist-files
 122     * @var array
 123     */
 124      protected static $types = array(
 125      'string'  => 'CFString',
 126      'real'    => 'CFNumber',
 127      'integer' => 'CFNumber',
 128      'date'    => 'CFDate',
 129      'true'    => 'CFBoolean',
 130      'false'   => 'CFBoolean',
 131      'data'    => 'CFData',
 132      'array'   => 'CFArray',
 133      'dict'    => 'CFDictionary'
 134      );
 135  
 136  
 137    /**
 138     * Create new CFPropertyList.
 139     * If a path to a PropertyList is specified, it is loaded automatically.
 140     * @param string $file Path of PropertyList
 141     * @param integer $format he format of the property list, see {@link FORMAT_XML}, {@link FORMAT_BINARY} and {@link FORMAT_AUTO}, defaults to {@link FORMAT_AUTO}
 142     * @throws IOException if file could not be read by {@link load()}
 143     * @uses $file for storing the current file, if specified
 144     * @uses load() for loading the plist-file
 145     */
 146      public function __construct($file = null, $format = self::FORMAT_AUTO)
 147      {
 148          $this->file = $file;
 149          $this->format = $format;
 150          $this->detectedFormat = $format;
 151          if ($this->file) {
 152              $this->load();
 153          }
 154      }
 155  
 156    /**
 157     * Load an XML PropertyList.
 158     * @param string $file Path of PropertyList, defaults to {@link $file}
 159     * @return void
 160     * @throws IOException if file could not be read
 161     * @throws DOMException if XML-file could not be read properly
 162     * @uses load() to actually load the file
 163     */
 164      public function loadXML($file = null)
 165      {
 166          $this->load($file, CFPropertyList::FORMAT_XML);
 167      }
 168  
 169    /**
 170     * Load an XML PropertyList.
 171     * @param resource $stream A stream containing the xml document.
 172     * @return void
 173     * @throws IOException if stream could not be read
 174     * @throws DOMException if XML-stream could not be read properly
 175     */
 176      public function loadXMLStream($stream)
 177      {
 178          if (($contents = stream_get_contents($stream)) === false) {
 179              throw IOException::notReadable('<stream>');
 180          }
 181          $this->parse($contents, CFPropertyList::FORMAT_XML);
 182      }
 183  
 184    /**
 185     * Load an binary PropertyList.
 186     * @param string $file Path of PropertyList, defaults to {@link $file}
 187     * @return void
 188     * @throws IOException if file could not be read
 189     * @throws PListException if binary plist-file could not be read properly
 190     * @uses load() to actually load the file
 191     */
 192      public function loadBinary($file = null)
 193      {
 194          $this->load($file, CFPropertyList::FORMAT_BINARY);
 195      }
 196  
 197    /**
 198     * Load an binary PropertyList.
 199     * @param stream $stream Stream containing the PropertyList
 200     * @return void
 201     * @throws IOException if file could not be read
 202     * @throws PListException if binary plist-file could not be read properly
 203     * @uses parse() to actually load the file
 204     */
 205      public function loadBinaryStream($stream)
 206      {
 207          if (($contents = stream_get_contents($stream)) === false) {
 208              throw IOException::notReadable('<stream>');
 209          }
 210          $this->parse($contents, CFPropertyList::FORMAT_BINARY);
 211      }
 212  
 213    /**
 214     * Load a plist file.
 215     * Load and import a plist file.
 216     * @param string $file Path of PropertyList, defaults to {@link $file}
 217     * @param integer $format The format of the property list, see {@link FORMAT_XML}, {@link FORMAT_BINARY} and {@link FORMAT_AUTO}, defaults to {@link $format}
 218     * @return void
 219     * @throws PListException if file format version is not 00
 220     * @throws IOException if file could not be read
 221     * @throws DOMException if plist file could not be parsed properly
 222     * @uses $file if argument $file was not specified
 223     * @uses $value reset to empty array
 224     * @uses import() for importing the values
 225     */
 226      public function load($file = null, $format = null)
 227      {
 228          $file = $file ? $file : $this->file;
 229          $format = $format !== null ? $format : $this->format;
 230          $this->value = array();
 231  
 232          if (!is_readable($file)) {
 233              throw IOException::notReadable($file);
 234          }
 235  
 236          switch ($format) {
 237              case CFPropertyList::FORMAT_BINARY:
 238                  $this->readBinary($file);
 239                  break;
 240              case CFPropertyList::FORMAT_AUTO: // what we now do is ugly, but neccessary to recognize the file format
 241                  $fd = fopen($file, "rb");
 242                  if (($magic_number = fread($fd, 8)) === false) {
 243                      throw IOException::notReadable($file);
 244                  }
 245                  fclose($fd);
 246  
 247                  $filetype = substr($magic_number, 0, 6);
 248                  $version  = substr($magic_number, -2);
 249  
 250                  if ($filetype == "bplist") {
 251                      if ($version != "00") {
 252                          throw new PListException("Wrong file format version! Expected 00, got $version!");
 253                      }
 254                      $this->detectedFormat = CFPropertyList::FORMAT_BINARY;
 255                      $this->readBinary($file);
 256                      break;
 257                  }
 258                  $this->detectedFormat = CFPropertyList::FORMAT_XML;
 259                // else: xml format, break not neccessary
 260              case CFPropertyList::FORMAT_XML:
 261                  $doc = new DOMDocument();
 262                  $prevXmlErrors = libxml_use_internal_errors(true);
 263                  libxml_clear_errors();
 264                  if (!$doc->load($file)) {
 265                      $message = $this->getLibxmlErrors();
 266                      libxml_clear_errors();
 267                      libxml_use_internal_errors($prevXmlErrors);
 268                      throw new DOMException($message);
 269                  }
 270                  libxml_use_internal_errors($prevXmlErrors);
 271                  $this->import($doc->documentElement, $this);
 272                  break;
 273          }
 274      }
 275  
 276    /**
 277     * Parse a plist string.
 278     * Parse and import a plist string.
 279     * @param string $str String containing the PropertyList, defaults to {@link $content}
 280     * @param integer $format The format of the property list, see {@link FORMAT_XML}, {@link FORMAT_BINARY} and {@link FORMAT_AUTO}, defaults to {@link $format}
 281     * @return void
 282     * @throws PListException if file format version is not 00
 283     * @throws IOException if file could not be read
 284     * @throws DOMException if plist file could not be parsed properly
 285     * @uses $content if argument $str was not specified
 286     * @uses $value reset to empty array
 287     * @uses import() for importing the values
 288     */
 289      public function parse($str = null, $format = null)
 290      {
 291          $format = $format !== null ? $format : $this->format;
 292          $str = $str !== null ? $str : $this->content;
 293          if ($str === null || strlen($str) === 0) {
 294              throw IOException::readError('');
 295          }
 296          $this->value = array();
 297  
 298          switch ($format) {
 299              case CFPropertyList::FORMAT_BINARY:
 300                  $this->parseBinary($str);
 301                  break;
 302              case CFPropertyList::FORMAT_AUTO: // what we now do is ugly, but neccessary to recognize the file format
 303                  if (($magic_number = substr($str, 0, 8)) === false) {
 304                      throw IOException::notReadable("<string>");
 305                  }
 306  
 307                  $filetype = substr($magic_number, 0, 6);
 308                  $version  = substr($magic_number, -2);
 309  
 310                  if ($filetype == "bplist") {
 311                      if ($version != "00") {
 312                          throw new PListException("Wrong file format version! Expected 00, got $version!");
 313                      }
 314                      $this->detectedFormat = CFPropertyList::FORMAT_BINARY;
 315                      $this->parseBinary($str);
 316                      break;
 317                  }
 318                  $this->detectedFormat = CFPropertyList::FORMAT_XML;
 319                // else: xml format, break not neccessary
 320              case CFPropertyList::FORMAT_XML:
 321                  $doc = new DOMDocument();
 322                  $prevXmlErrors = libxml_use_internal_errors(true);
 323                  libxml_clear_errors();
 324                  if (!$doc->loadXML($str)) {
 325                      $message = $this->getLibxmlErrors();
 326                      libxml_clear_errors();
 327                      libxml_use_internal_errors($prevXmlErrors);
 328                      throw new DOMException($message);
 329                  }
 330                  libxml_use_internal_errors($prevXmlErrors);
 331                  $this->import($doc->documentElement, $this);
 332                  break;
 333          }
 334      }
 335  
 336      protected function getLibxmlErrors()
 337      {
 338          return implode(', ', array_map(function (\LibXMLError $error) {
 339              return trim("{$error->line}:{$error->column} [$error->code] $error->message");
 340          }, libxml_get_errors()));
 341      }
 342  
 343    /**
 344     * Convert a DOMNode into a CFType.
 345     * @param DOMNode $node Node to import children of
 346     * @param CFDictionary|CFArray|CFPropertyList $parent
 347     * @return void
 348     */
 349      protected function import(DOMNode $node, $parent)
 350      {
 351        // abort if there are no children
 352          if (!$node->childNodes->length) {
 353              return;
 354          }
 355  
 356          foreach ($node->childNodes as $n) {
 357            // skip if we can't handle the element
 358              if (!isset(self::$types[$n->nodeName])) {
 359                  continue;
 360              }
 361  
 362              $class = __NAMESPACE__ . '\\'.self::$types[$n->nodeName];
 363              $key = null;
 364  
 365            // find previous <key> if possible
 366              $ps = $n->previousSibling;
 367              while ($ps && $ps->nodeName == '#text' && $ps->previousSibling) {
 368                  $ps = $ps->previousSibling;
 369              }
 370  
 371            // read <key> if possible
 372              if ($ps && $ps->nodeName == 'key') {
 373                  $key = $ps->firstChild->nodeValue;
 374              }
 375  
 376              switch ($n->nodeName) {
 377                  case 'date':
 378                      $value = new $class(CFDate::dateValue($n->nodeValue));
 379                      break;
 380                  case 'data':
 381                      $value = new $class($n->nodeValue, true);
 382                      break;
 383                  case 'string':
 384                      $value = new $class($n->nodeValue);
 385                      break;
 386  
 387                  case 'real':
 388                  case 'integer':
 389                      $value = new $class($n->nodeName == 'real' ? floatval($n->nodeValue) : intval($n->nodeValue));
 390                      break;
 391  
 392                  case 'true':
 393                  case 'false':
 394                      $value = new $class($n->nodeName == 'true');
 395                      break;
 396  
 397                  case 'array':
 398                  case 'dict':
 399                      $value = new $class();
 400                      $this->import($n, $value);
 401  
 402                      if ($value instanceof CFDictionary) {
 403                          $hsh = $value->getValue();
 404                          if (isset($hsh['CF$UID']) && count($hsh) == 1) {
 405                              $value = new CFUid($hsh['CF$UID']->getValue());
 406                          }
 407                      }
 408  
 409                      break;
 410              }
 411  
 412              if ($parent instanceof CFDictionary) {
 413                  // Dictionaries need a key
 414                  $parent->add($key, $value);
 415              } else {
 416                  // others don't
 417                  $parent->add($value);
 418              }
 419          }
 420      }
 421  
 422    /**
 423     * Convert CFPropertyList to XML and save to file.
 424     * @param string $file Path of PropertyList, defaults to {@link $file}
 425     * @param bool $formatted Print plist formatted (i.e. with newlines and whitespace indention) if true; defaults to false
 426     * @return void
 427     * @throws IOException if file could not be read
 428     * @uses $file if $file was not specified
 429     */
 430      public function saveXML($file, $formatted = false)
 431      {
 432          $this->save($file, CFPropertyList::FORMAT_XML, $formatted);
 433      }
 434  
 435    /**
 436     * Convert CFPropertyList to binary format (bplist00) and save to file.
 437     * @param string $file Path of PropertyList, defaults to {@link $file}
 438     * @return void
 439     * @throws IOException if file could not be read
 440     * @uses $file if $file was not specified
 441     */
 442      public function saveBinary($file)
 443      {
 444          $this->save($file, CFPropertyList::FORMAT_BINARY);
 445      }
 446  
 447    /**
 448     * Convert CFPropertyList to XML or binary and save to file.
 449     * @param string $file Path of PropertyList, defaults to {@link $file}
 450     * @param string $format Format of PropertyList, defaults to {@link $format}
 451     * @param bool $formatted_xml Print XML plist formatted (i.e. with newlines and whitespace indention) if true; defaults to false
 452     * @return void
 453     * @throws IOException if file could not be read
 454     * @throws PListException if evaluated $format is neither {@link FORMAT_XML} nor {@link FORMAL_BINARY}
 455     * @uses $file if $file was not specified
 456     * @uses $format if $format was not specified
 457     */
 458      public function save($file = null, $format = null, $formatted_xml = false)
 459      {
 460          $file = $file ? $file : $this->file;
 461          $format = $format ? $format : $this->format;
 462          if ($format == self::FORMAT_AUTO) {
 463              $format = $this->detectedFormat;
 464          }
 465  
 466          if (!in_array($format, array( self::FORMAT_BINARY, self::FORMAT_XML ))) {
 467              throw new PListException("format {$format} is not supported, use CFPropertyList::FORMAT_BINARY or CFPropertyList::FORMAT_XML");
 468          }
 469  
 470          if (!file_exists($file)) {
 471            // dirname("file.xml") == "" and is treated as the current working directory
 472              if (!is_writable(dirname($file))) {
 473                  throw IOException::notWritable($file);
 474              }
 475          } elseif (!is_writable($file)) {
 476              throw IOException::notWritable($file);
 477          }
 478  
 479          $content = $format == self::FORMAT_BINARY ? $this->toBinary() : $this->toXML($formatted_xml);
 480  
 481          $fh = fopen($file, 'wb');
 482          fwrite($fh, $content);
 483          fclose($fh);
 484      }
 485  
 486    /**
 487     * Convert CFPropertyList to XML
 488     * @param bool $formatted Print plist formatted (i.e. with newlines and whitespace indention) if true; defaults to false
 489     * @return string The XML content
 490     */
 491      public function toXML($formatted = false)
 492      {
 493          $domimpl = new DOMImplementation();
 494        // <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 495          $dtd = $domimpl->createDocumentType('plist', '-//Apple//DTD PLIST 1.0//EN', 'http://www.apple.com/DTDs/PropertyList-1.0.dtd');
 496          $doc = $domimpl->createDocument(null, "plist", $dtd);
 497          $doc->encoding = "UTF-8";
 498  
 499        // format output
 500          if ($formatted) {
 501              $doc->formatOutput = true;
 502              $doc->preserveWhiteSpace = true;
 503          }
 504  
 505        // get documentElement and set attribs
 506          $plist = $doc->documentElement;
 507          $plist->setAttribute('version', '1.0');
 508  
 509        // add PropertyList's children
 510          $plist->appendChild($this->getValue(true)->toXML($doc));
 511  
 512          return $doc->saveXML();
 513      }
 514  
 515  
 516    /************************************************************************************************
 517     *    M A N I P U L A T I O N
 518     ************************************************************************************************/
 519  
 520    /**
 521     * Add CFType to collection.
 522     * @param CFType $value CFType to add to collection
 523     * @return void
 524     * @uses $value for adding $value
 525     */
 526      public function add(CFType $value = null)
 527      {
 528        // anything but CFType is null, null is an empty string - sad but true
 529          if (!$value) {
 530              $value = new CFString();
 531          }
 532  
 533          $this->value[] = $value;
 534      }
 535  
 536    /**
 537     * Get CFType from collection.
 538     * @param integer $key Key of CFType to retrieve from collection
 539     * @return CFType CFType found at $key, null else
 540     * @uses $value for retrieving CFType of $key
 541     */
 542      public function get($key)
 543      {
 544          if (isset($this->value[$key])) {
 545              return $this->value[$key];
 546          }
 547          return null;
 548      }
 549  
 550    /**
 551     * Generic getter (magic)
 552     *
 553     * @param integer $key Key of CFType to retrieve from collection
 554     * @return CFType CFType found at $key, null else
 555     * @author Sean Coates <sean@php.net>
 556     * @link http://php.net/oop5.overloading
 557     */
 558      public function __get($key)
 559      {
 560          return $this->get($key);
 561      }
 562  
 563    /**
 564     * Remove CFType from collection.
 565     * @param integer $key Key of CFType to removes from collection
 566     * @return CFType removed CFType, null else
 567     * @uses $value for removing CFType of $key
 568     */
 569      public function del($key)
 570      {
 571          if (isset($this->value[$key])) {
 572              $t = $this->value[$key];
 573              unset($this->value[$key]);
 574              return $t;
 575          }
 576  
 577          return null;
 578      }
 579  
 580    /**
 581     * Empty the collection
 582     * @return array the removed CFTypes
 583     * @uses $value for removing CFType of $key
 584     */
 585      public function purge()
 586      {
 587          $t = $this->value;
 588          $this->value = array();
 589          return $t;
 590      }
 591  
 592    /**
 593     * Get first (and only) child, or complete collection.
 594     * @param boolean $cftype if set to true returned value will be CFArray instead of an array in case of a collection
 595     * @return CFType|array CFType or list of CFTypes known to the PropertyList
 596     * @uses $value for retrieving CFTypes
 597     */
 598      public function getValue($cftype = false)
 599      {
 600          if (count($this->value) === 1) {
 601              $t = array_values($this->value);
 602              return $t[0];
 603          }
 604          if ($cftype) {
 605              $t = new CFArray();
 606              foreach ($this->value as $value) {
 607                  if ($value instanceof CFType) {
 608                      $t->add($value);
 609                  }
 610              }
 611              return $t;
 612          }
 613          return $this->value;
 614      }
 615  
 616    /**
 617     * Create CFType-structure from guessing the data-types.
 618     * The functionality has been moved to the more flexible {@link CFTypeDetector} facility.
 619     * @param mixed $value Value to convert to CFType
 620     * @param array $options Configuration for casting values [autoDictionary, suppressExceptions, objectToArrayMethod, castNumericStrings]
 621     * @return CFType CFType based on guessed type
 622     * @uses CFTypeDetector for actual type detection
 623     * @deprecated
 624     */
 625      public static function guess($value, $options = array())
 626      {
 627          static $t = null;
 628          if ($t === null) {
 629              $t = new CFTypeDetector($options);
 630          }
 631  
 632          return $t->toCFType($value);
 633      }
 634  
 635  
 636    /************************************************************************************************
 637     *    S E R I A L I Z I N G
 638     ************************************************************************************************/
 639  
 640    /**
 641     * Get PropertyList as array.
 642     * @return mixed primitive value of first (and only) CFType, or array of primitive values of collection
 643     * @uses $value for retrieving CFTypes
 644     */
 645      public function toArray()
 646      {
 647          $a = array();
 648          foreach ($this->value as $value) {
 649              $a[] = $value->toArray();
 650          }
 651          if (count($a) === 1) {
 652              return $a[0];
 653          }
 654  
 655          return $a;
 656      }
 657  
 658  
 659    /************************************************************************************************
 660     *    I T E R A T O R   I N T E R F A C E
 661     ************************************************************************************************/
 662  
 663    /**
 664     * Rewind {@link $iteratorPosition} to first position (being 0)
 665     * @link http://php.net/manual/en/iterator.rewind.php
 666     * @return void
 667     * @uses $iteratorPosition set to 0
 668     * @uses $iteratorKeys store keys of {@link $value}
 669     */
 670      public function rewind(): void
 671      {
 672          $this->iteratorPosition = 0;
 673          $this->iteratorKeys = array_keys($this->value);
 674      }
 675  
 676    /**
 677     * Get Iterator's current {@link CFType} identified by {@link $iteratorPosition}
 678     * @link http://php.net/manual/en/iterator.current.php
 679     * @return mixed current Item
 680     * @uses $iteratorPosition identify current key
 681     * @uses $iteratorKeys identify current value
 682     */
 683      #[\ReturnTypeWillChange]
 684      public function current()
 685      {
 686          return $this->value[$this->iteratorKeys[$this->iteratorPosition]];
 687      }
 688  
 689    /**
 690     * Get Iterator's current key identified by {@link $iteratorPosition}
 691     * @link http://php.net/manual/en/iterator.key.php
 692     * @return mixed key of the current Item
 693     * @uses $iteratorPosition identify current key
 694     * @uses $iteratorKeys identify current value
 695     */
 696      #[\ReturnTypeWillChange]
 697      public function key()
 698      {
 699          return $this->iteratorKeys[$this->iteratorPosition];
 700      }
 701  
 702    /**
 703     * Increment {@link $iteratorPosition} to address next {@see CFType}
 704     * @link http://php.net/manual/en/iterator.next.php
 705     * @return void
 706     * @uses $iteratorPosition increment by 1
 707     */
 708      public function next(): void
 709      {
 710          $this->iteratorPosition++;
 711      }
 712  
 713    /**
 714     * Test if {@link $iteratorPosition} addresses a valid element of {@link $value}
 715     * @link http://php.net/manual/en/iterator.valid.php
 716     * @return boolean true if current position is valid, false else
 717     * @uses $iteratorPosition test if within {@link $iteratorKeys}
 718     * @uses $iteratorPosition test if within {@link $value}
 719     */
 720      public function valid(): bool
 721      {
 722          return isset($this->iteratorKeys[$this->iteratorPosition]) && isset($this->value[$this->iteratorKeys[$this->iteratorPosition]]);
 723      }
 724  }
 725  
 726  # eof