mustache_templates-8.x-1.0-beta4/src/Render/IterableMarkup.php

src/Render/IterableMarkup.php
<?php

namespace Drupal\mustache\Render;

use Drupal\Component\Render\MarkupInterface;

/**
 * Passes multiple safe strings through the render system.
 *
 * This iterable markup allows for both concatenating the contained items,
 * but also define a value that may be used instead of concatenating the items.
 * For nesting iterable markup items, a child markup item may reference to its
 * parent item through its "parent" property. All listed properties below are
 * directly accessible from outside and can be manipulated.
 *
 * This object should only be constructed with known safe strings. If there is
 * any risk that the string contains user-entered data that has not been
 * filtered first, it must not be used.
 *
 * @internal
 *   This class must only be used inside the mustache_templates project.
 */
final class IterableMarkup implements MarkupInterface, \Countable, \IteratorAggregate, \ArrayAccess {

  /**
   * The iterable items.
   *
   * @var array
   */
  private $items = [];

  /**
   * Whether the array of iterable items is primarily associative or not.
   *
   * @var bool
   */
  private $isAssoc = TRUE;

  /**
   * The string value to use instead of concatenating the items.
   *
   * @var mixed
   */
  private $value;

  /**
   * The parent.
   *
   * @var mixed
   */
  private $parent;

  /**
   * The separator to use when concatenating as string.
   *
   * @var string
   */
  private $separator = ', ';

  /**
   * The position of this item within its parent's list of items.
   *
   * Position counting starts at value 1.
   *
   * @var int
   */
  private $position = 1;

  /**
   * Whether this item is the first one within its parent's list of items.
   *
   * @var bool
   */
  private $first = TRUE;

  /**
   * Whether this item is the last one within its parent's list of items.
   *
   * @var bool
   */
  private $last = TRUE;

  /**
   * Create an iterable markup object.
   *
   * @param array $items
   *   The items to iterate on.
   * @param mixed $value
   *   (Optional) The string value to use instead of concatenating the items.
   * @param mixed $parent
   *   (Optional) The parent, usually another IterableMarkup instance.
   * @param string $separator
   *   (Optional) The separator to use on string concatenation.
   * @param bool $wrap_scalars
   *   (Optional) Whether scalar values should be wrapped too. Default is TRUE.
   * @param bool $wrap_arrays
   *   (Optional) Whether arrays should be wrapped too. Default is TRUE.
   *
   * @return static
   *   The iterable markup object.
   */
  public static function create(array $items = [], $value = NULL, $parent = NULL, $separator = ', ', $wrap_scalars = TRUE, $wrap_arrays = TRUE) {
    $instance = new static();
    foreach ($items as $key => $item) {
      if (is_int($key)) {
        $instance->isAssoc = FALSE;
      }
      if ($wrap_arrays && is_array($item)) {
        $instance->items[$key] = static::create($item, NULL, $instance, $separator, $wrap_scalars, $wrap_arrays);
      }
      elseif ($wrap_scalars && (is_scalar($item) || is_null($item))) {
        $instance->items[$key] = static::create([], $item, $instance, $separator, $wrap_scalars, $wrap_arrays);
      }
      else {
        $instance->items[$key] = $item;
      }
    }
    $instance->value = $value;
    $instance->parent = $parent;
    $instance->separator = $separator;
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function __toString() {
    if ($this->value !== NULL && $this->value !== $this) {
      return (string) $this->value;
    }
    $items = [];
    foreach ($this as $item) {
      if ($item === $this && !empty($this->items)) {
        // This needs a special handling, to prevent infinite recursion.
        $items = array_merge($items, $this->items);
      }
      else {
        $items[] = $item;
      }
    }
    return implode($this->separator, $items);
  }

  /**
   * {@inheritdoc}
   */
  #[\ReturnTypeWillChange]
  public function jsonSerialize() {
    if ($this->empty()) {
      return [];
    }
    $values = [
      'value' => $this->__toString(),
      'isAssoc' => $this->isAssoc,
      'items' => [],
      'separator' => $this->separator,
      'position' => $this->position,
      'first' => $this->first,
      'last' => $this->last,
    ];
    foreach ($this->items as $i_key => $i_val) {
      if ($i_val instanceof static) {
        $i_val = $i_val->jsonSerialize();
      }
      elseif (is_object($i_val)) {
        if (method_exists($i_val, '__toString')) {
          $i_val = (string) $i_val;
        }
        elseif (method_exists($i_val, 'getString')) {
          $i_val = $i_val->getString();
        }
      }
      $values['items'][$i_key] = $i_val;
    }
    return $values;
  }

  /**
   * {@inheritdoc}
   */
  public function count(): int {
    if ($this->items) {
      $count = 0;
      foreach ($this->items as $item) {
        $count += $item instanceof static ? (int) $item->exists() : (int) ($item !== '');
      }
      if ($count) {
        return $count;
      }
    }
    if ($this->value !== NULL && $this->value !== $this) {
      return is_numeric($this->value) ? (int) $this->value : strlen((string) $this->value);
    }
    return 0;
  }

  /**
   * Whether the whole markup object is empty.
   *
   * @return bool
   *   Returns TRUE when it is empty.
   */
  public function empty() {
    return !$this->count();
  }

  /**
   * Whether the whole markup object contains at least one value.
   *
   * @return bool
   *   Returns TRUE when it exists.
   */
  public function exists() {
    return (($this->value !== NULL) && ($this->value !== $this)) || !$this->empty();
  }

  /**
   * {@inheritdoc}
   */
  public function getIterator(): \Traversable {
    if (!empty($this->items)) {
      if (!$this->isAssoc) {
        // As this item list contains numeric indices, make it a complete
        // numeric list by filtering out any keys that are not numeric.
        $target = [];
        $i = 0;
        while (array_key_exists($i, $this->items)) {
          $target[] = &$this->items[$i];
          $i++;
        }
      }
      elseif (!isset($this->parent)) {
        // Allow looping through the top-level array.
        $target = &$this->items;
      }
      else {
        // For associative arrays and objects, behave accordingly like
        // Mustache.php handles associative arrays and objects.
        // @see https://github.com/bobthecow/mustache.php/wiki/Variable-Resolution
        return $this->exists() ? new \ArrayIterator([$this]) : new \ArrayIterator([]);
      }
      return new \CallbackFilterIterator(new \ArrayIterator($target), function ($current, $key, $iterator) {
        return $this->stringIsEmpty($current);
      });
    }
    return $this->exists() ? new \ArrayIterator([$this]) : new \ArrayIterator($this->items);
  }

  /**
   * {@inheritdoc}
   */
  public function offsetSet($offset, $value): void {
    if (is_null($offset)) {
      $this->items[] = $value;
    }
    else {
      $this->items[$offset] = $value;
    }
    $this->updatePositions();
  }

  /**
   * Sets an offset by reference.
   *
   * This extra method is needed to support setting by reference, as it is not
   * officially supported by \ArrayAccess.
   *
   * @param mixed $offset
   *   The offset to assign the reference to.
   * @param mixed &$value
   *   The value, passed by reference.
   */
  public function offsetSetReference($offset, &$value) {
    if (is_null($offset)) {
      $this->items[] = &$value;
    }
    else {
      $this->items[$offset] = &$value;
    }
    $this->updatePositions();
  }

  /**
   * {@inheritdoc}
   */
  public function offsetExists($offset): bool {
    if (!isset($this->items[$offset])) {
      return FALSE;
    }
    if ($this->items[$offset] instanceof static) {
      return $this->items[$offset]->exists();
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function offsetUnset($offset): void {
    unset($this->items[$offset]);
    $this->updatePositions();
  }

  /**
   * {@inheritdoc}
   */
  #[\ReturnTypeWillChange]
  public function &offsetGet($offset) {
    if (isset($this->items[$offset])) {
      return $this->items[$offset];
    }
    $null = static::create([], NULL, $this);
    return $null;
  }

  /**
   * Implements the magic __isset() method.
   *
   * @return bool
   *   Whether the property is set.
   */
  public function __isset($name): bool {
    if (is_null($name) || !is_scalar($name)) {
      return FALSE;
    }
    if (isset($this->items[$name]) && $this->items[$name] instanceof static) {
      return $this->items[$name]->exists();
    }
    if (isset($this->items[$name]) || (property_exists($this, $name) && !is_null($this->$name))) {
      return TRUE;
    }
    if ($name === 'value') {
      return isset($this->value) || !empty($this->items);
    }
    if ($name === 'string') {
      return isset($this->value);
    }
    return FALSE;
  }

  /**
   * Implements the magic __set() method.
   */
  public function __set($name, $value): void {
    $this->$name = &$value;
  }

  /**
   * Implements the magic __get() method.
   *
   * @return mixed
   *   The value for the given name.
   */
  public function &__get($name) {
    if (isset($this->items[$name])) {
      return $this->items[$name];
    }
    if ($name === 'value') {
      if (empty($this->items)) {
        $value = &$this->value;
        return $value;
      }
      return $this;
    }
    if ($name === 'string') {
      $return = $this->value !== $this ? (string) $this->value : '';
      return $return;
    }
    if (property_exists($this, $name)) {
      return $this->$name;
    }
    $return = static::create([], NULL, $this);
    return $return;
  }

  /**
   * Get the items list.
   *
   * @return array
   *   The items list.
   */
  public function &getItems(): array {
    return $this->items;
  }

  /**
   * Updates the positions on the child items.
   */
  public function updatePositions() {
    reset($this->items);
    $first = TRUE;
    $i = 0;
    $this->isAssoc = TRUE;
    foreach ($this->items as $key => &$item) {
      if (is_int($key)) {
        $this->isAssoc = FALSE;
      }
      $i++;
      if ($item instanceof static) {
        $item->position = $i;
        $item->first = $first;
        $item->last = FALSE;
        $item->parent = $this;
        $item->updatePositions();
      }
      elseif (is_array($item)) {
        $item['position'] = $i;
        $item['first'] = $first;
        $item['last'] = FALSE;
        $item['parent'] = $this;
      }
      $first = FALSE;
    }
    if (isset($item)) {
      if ($item instanceof static) {
        $item->last = TRUE;
      }
      elseif (is_array($item)) {
        $item['last'] = TRUE;
      }
    }
  }

  /**
   * Get an item value that belongs to the given key.
   *
   * @param mixed &$item
   *   The item.
   * @param mixed $key
   *   The key.
   *
   * @return mixed
   *   The item value. Might return NULL when no value was found for the given
   *   key, but also when the value of the given key is actually NULL.
   */
  public function getItemValue(&$item, $key) {
    if ($item instanceof static) {
      if (isset($item->items[$key])) {
        return $item->items[$key];
      }
      if ($key === 'value' && isset($item->value)) {
        return $item->value;
      }
      if ($key === 'string') {
        return $item->string;
      }
    }
    elseif ((($item instanceof \ArrayAccess)|| is_array($item)) && !empty($item) && isset($item[$key])) {
      return $item[$key];
    }
    elseif (is_object($item)) {
      if (method_exists($item, 'isEmpty') && $item->isEmpty()) {
        return NULL;
      }
      if (method_exists($item, 'empty') && $item->empty()) {
        return NULL;
      }
      if (isset($item->$key)) {
        return $item->$key;
      }
    }
    return NULL;
  }

  /**
   * Whether the string representation of the given item is empty.
   *
   * @param mixed &$item
   *   The item to check the emptyness of the string representation for. Skip
   *   the argument to check for the iterable object itself.
   *
   * @return bool
   *   Returns TRUE if empty, FALSE otherwise.
   */
  public function stringIsEmpty(&$item = NULL) {
    if ($item === NULL) {
      unset($item);
      $item = $this;
    }
    if ($item instanceof static) {
      return $item->exists();
    }
    if (is_object($item)) {
      if (!method_exists($item, '__toString')) {
        return FALSE;
      }
      if (method_exists($item, 'isEmpty')) {
        return !$item->isEmpty();
      }
      if (method_exists($item, 'empty')) {
        return !$item->empty();
      }
      // @todo Consider not to rely on this conversion,
      // as it could be too expensive in some cases.
      return !empty((string) $item);
    }
    if (is_scalar($item)) {
      return trim((string) $item) !== '';
    }
    return NULL;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc