From 8c3210332e8bfd32db0e12e1aabddb6ff170291d Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 8 Sep 2017 14:44:37 +0300 Subject: [PATCH] Add `ArrayObject` and make `Object` not to implement `ArrayAccess` interface. Remove Toolbox dependency. --- .../src/Grav/Framework/Object/ArrayObject.php | 69 ++++++ system/src/Grav/Framework/Object/Object.php | 216 +++++++++++++++--- .../Framework/Object/ObjectCollection.php | 8 +- .../Object/ObjectCollectionInterface.php | 6 +- .../Object/ObjectCollectionTrait.php | 15 +- .../Grav/Framework/Object/ObjectInterface.php | 20 +- .../src/Grav/Framework/Object/ObjectTrait.php | 20 +- 7 files changed, 316 insertions(+), 38 deletions(-) create mode 100644 system/src/Grav/Framework/Object/ArrayObject.php diff --git a/system/src/Grav/Framework/Object/ArrayObject.php b/system/src/Grav/Framework/Object/ArrayObject.php new file mode 100644 index 000000000..d7dfbb881 --- /dev/null +++ b/system/src/Grav/Framework/Object/ArrayObject.php @@ -0,0 +1,69 @@ +getProperty($offset, $test) !== $test; + } + + return $this->__isset($offset); + } + + /** + * Returns the value at specified offset. + * + * @param mixed $offset The offset to retrieve. + * @return mixed Can return all value types. + */ + public function offsetGet($offset) + { + return $this->getProperty($offset); + } + + /** + * Assigns a value to the specified offset. + * + * @param mixed $offset The offset to assign the value to. + * @param mixed $value The value to set. + */ + public function offsetSet($offset, $value) + { + $this->setProperty($offset, $value); + } + + /** + * Unsets an offset. + * + * @param mixed $offset The offset to unset. + */ + public function offsetUnset($offset) + { + if (strpos($offset, '.') !== false) { + $this->setProperty($offset, null); + } else { + $this->__unset($offset); + } + } +} diff --git a/system/src/Grav/Framework/Object/Object.php b/system/src/Grav/Framework/Object/Object.php index fbbdab919..cec2dfdbe 100644 --- a/system/src/Grav/Framework/Object/Object.php +++ b/system/src/Grav/Framework/Object/Object.php @@ -8,9 +8,6 @@ namespace Grav\Framework\Object; -use RocketTheme\Toolbox\ArrayTraits\Export; -use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters; - /** * Object class. * @@ -18,10 +15,126 @@ use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters; */ class Object implements ObjectInterface { - use ObjectTrait, NestedArrayAccessWithGetters, Export { - NestedArrayAccessWithGetters::offsetExists as private parentOffsetExists; - NestedArrayAccessWithGetters::offsetGet as private parentOffsetGet; - NestedArrayAccessWithGetters::offsetSet as private parentOffsetSet; + use ObjectTrait; + + static protected $prefix = 'o.'; + static protected $type; + + /** + * Get value by using dot notation for nested arrays/objects. + * + * @example $value = $this->get('this.is.my.nested.variable'); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $default Default value (or null). + * @param string $separator Separator, defaults to '.' + * @return mixed Value. + */ + public function getProperty($name, $default = null, $separator = '.') + { + $path = explode($separator, $name); + $offset = array_shift($path); + $current = $this->__get($offset); + + do { + // We are done: return current variable. + if (empty($path)) { + return $current; + } + + // Get property of nested Object. + if ($current instanceof Object) { + return $current->getProperty(implode($separator, $path), $default, $separator); + } + + $offset = array_shift($path); + + if ((is_array($current) || is_a($current, 'ArrayAccess')) && isset($current[$offset])) { + $current = $current[$offset]; + } elseif (is_object($current) && isset($current->{$offset})) { + $current = $current->{$offset}; + } else { + return $default; + } + } while ($path); + + return $current; + } + + /** + * Set value by using dot notation for nested arrays/objects. + * + * @example $data->set('this.is.my.nested.variable', $value); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function setProperty($name, $value, $separator = '.') + { + $path = explode($separator, $name); + $offset = array_shift($path); + + // Set simple variable. + if (empty($path)) { + $this->__set($offset, $value); + + return $this; + } + + $current = &$this->getRef($offset, true); + + do { + // Set property of nested Object. + if ($current instanceof Object) { + $current->setProperty(implode($separator, $path), $value, $separator); + + return $this; + } + + $offset = array_shift($path); + + if (is_object($current)) { + // Handle objects. + if (!isset($current->{$offset})) { + $current->{$offset} = []; + } + $current = &$current->{$offset}; + } else { + // Handle arrays and scalars. + if (!is_array($current)) { + $current = [$offset => []]; + } elseif (!isset($current[$offset])) { + $current[$offset] = []; + } + $current = &$current[$offset]; + } + } while ($path); + + $current = $value; + + return $this; + } + + /** + * Define value by using dot notation for nested arrays/objects. + * + * @example $data->defProperty('this.is.my.nested.variable', $value); + * + * @param string $name Dot separated path to the requested value. + * @param mixed $value New value. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function defProperty($name, $value, $separator = '.') + { + $test = new \stdClass; + if ($this->getProperty($name, $test, $separator) === $test) { + $this->setProperty($name, $value, $separator); + } + + return $this; } /** @@ -30,11 +143,9 @@ class Object implements ObjectInterface * @param mixed $offset An offset to check for. * @return bool Returns TRUE on success or FALSE on failure. */ - public function offsetExists($offset) + public function __isset($offset) { - $methodName = "offsetLoad_{$offset}"; - - return $this->parentOffsetExists($offset) || method_exists($this, $methodName); + return array_key_exists($offset, $this->items) || $this->isPropertyDefined($offset); } /** @@ -43,18 +154,11 @@ class Object implements ObjectInterface * @param mixed $offset The offset to retrieve. * @return mixed Can return all value types. */ - public function offsetGet($offset) + public function __get($offset) { - $methodName = "offsetLoad_{$offset}"; - - if (!$this->parentOffsetExists($offset) && method_exists($this, $methodName)) { - $this->offsetSet($offset, $this->{$methodName}()); - } - - return $this->parentOffsetGet($offset); + return $this->getRef($offset); } - /** * Assigns a value to the specified offset with a possibility to check or alter the value by * $this->offsetPrepare_{$offset}(). @@ -62,15 +166,41 @@ class Object implements ObjectInterface * @param mixed $offset The offset to assign the value to. * @param mixed $value The value to set. */ - public function offsetSet($offset, $value) + public function __set($offset, $value) { - $methodName = "offsetPrepare_{$offset}"; + if ($this->isPropertyDefined($offset)) { + $methodName = "offsetPrepare_{$offset}"; - if (method_exists($this, $methodName)) { - $value = $this->{$methodName}($value); + if (method_exists($this, $methodName)) { + $this->{$offset} = $this->{$methodName}($value); + } } - $this->parentOffsetSet($offset, $value); + $this->items[$offset] = $value; + } + + /** + * Magic method to unset the attribute + * + * @param mixed $offset The name value to unset + */ + public function __unset($offset) + { + if ($this->isPropertyDefined($offset)) { + $this->{$offset} = null; + } else { + unset($this->items[$offset]); + } + } + + /** + * Convert object into an array. + * + * @return array + */ + protected function toArray() + { + return $this->items; } /** @@ -80,6 +210,40 @@ class Object implements ObjectInterface */ public function jsonSerialize() { - return ['key' => $this->getKey(), 'object' => $this->toArray()]; + return [ + 'key' => $this->getKey(), + 'type' => $this->getType(true), + 'object' => $this->toArray() + ]; + } + + protected function &getRef($offset, $new = false) + { + if ($this->isPropertyDefined($offset)) { + if ($this->{$offset} === null) { + $methodName = "offsetLoad_{$offset}"; + + if (method_exists($this, $methodName)) { + $this->{$offset} = $this->{$methodName}(); + } + } + + return $this->{$offset}; + } + + if (!isset($this->items[$offset])) { + if (!$new) { + $null = null; + return $null; + } + $this->items[$offset] = []; + } + + return $this->items[$offset]; + } + + protected function isPropertyDefined($offset) + { + return array_key_exists($offset, get_object_vars($this)); } } diff --git a/system/src/Grav/Framework/Object/ObjectCollection.php b/system/src/Grav/Framework/Object/ObjectCollection.php index 81fa198a1..7ac1e1e53 100644 --- a/system/src/Grav/Framework/Object/ObjectCollection.php +++ b/system/src/Grav/Framework/Object/ObjectCollection.php @@ -18,6 +18,8 @@ class ObjectCollection extends ArrayCollection implements ObjectCollectionInterf { use ObjectCollectionTrait; + static protected $prefix = 'c.'; + /** * @param array $elements * @param string $key @@ -27,7 +29,11 @@ class ObjectCollection extends ArrayCollection implements ObjectCollectionInterf { parent::__construct($elements); - $this->key = $key ?: $this->getKey() ?: __CLASS__ . '@' . spl_object_hash($this); + $this->key = $key !== null ? $key : (string) $this; + + if ($this->key === null) { + throw new \InvalidArgumentException('Object cannot be created without assigning a key to it'); + } } /** diff --git a/system/src/Grav/Framework/Object/ObjectCollectionInterface.php b/system/src/Grav/Framework/Object/ObjectCollectionInterface.php index c4764ea1a..94aa36c0c 100644 --- a/system/src/Grav/Framework/Object/ObjectCollectionInterface.php +++ b/system/src/Grav/Framework/Object/ObjectCollectionInterface.php @@ -36,14 +36,14 @@ interface ObjectCollectionInterface extends CollectionInterface, ObjectInterface /** * @param string $property Object property to be fetched. - * @return array Values of the property. + * @param mixed $default Default value if not set. + * @return array Property value. */ - public function getProperty($property); + public function getProperty($property, $default = null); /** * @param string $property Object property to be updated. * @param string $value New value. - * @return $this */ public function setProperty($property, $value); diff --git a/system/src/Grav/Framework/Object/ObjectCollectionTrait.php b/system/src/Grav/Framework/Object/ObjectCollectionTrait.php index 52d8e592c..8c9f05edc 100644 --- a/system/src/Grav/Framework/Object/ObjectCollectionTrait.php +++ b/system/src/Grav/Framework/Object/ObjectCollectionTrait.php @@ -46,14 +46,16 @@ trait ObjectCollectionTrait /** * @param string $property Object property to be fetched. + * @param mixed $default Default value if not set. * @return array Key/Value pairs of the properties. */ - public function getProperty($property) + public function getProperty($property, $default = null) { $list = []; + /** @var ObjectInterface $element */ foreach ($this as $id => $element) { - $list[$id] = isset($element[$property]) ? $element[$property] : null; + $list[$id] = $element->getProperty($property, $default); } return $list; @@ -62,12 +64,16 @@ trait ObjectCollectionTrait /** * @param string $property Object property to be updated. * @param string $value New value. + * @return $this */ public function setProperty($property, $value) { + /** @var ObjectInterface $element */ foreach ($this as $element) { - $element[$property] = $value; + $element->setProperty($property, $value); } + + return $this; } /** @@ -98,8 +104,9 @@ trait ObjectCollectionTrait { $list = []; + /** @var ObjectInterface $element */ foreach ($this as $element) { - $list[$element[$property]][] = $element; + $list[(string) $element->getProperty($property)][] = $element; } return $list; diff --git a/system/src/Grav/Framework/Object/ObjectInterface.php b/system/src/Grav/Framework/Object/ObjectInterface.php index 26c7a27b7..d67c33339 100644 --- a/system/src/Grav/Framework/Object/ObjectInterface.php +++ b/system/src/Grav/Framework/Object/ObjectInterface.php @@ -12,7 +12,7 @@ namespace Grav\Framework\Object; * Object Interface * @package Grav\Framework\Object */ -interface ObjectInterface extends \ArrayAccess, \JsonSerializable +interface ObjectInterface extends \JsonSerializable { /** * @param array $elements @@ -20,8 +20,26 @@ interface ObjectInterface extends \ArrayAccess, \JsonSerializable */ public function __construct(array $elements = [], $key = null); + /** + * @return string + */ + public function getType(); + /** * @return string */ public function getKey(); + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if not set. + * @return mixed Property value. + */ + public function getProperty($property, $default = null); + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + */ + public function setProperty($property, $value); } diff --git a/system/src/Grav/Framework/Object/ObjectTrait.php b/system/src/Grav/Framework/Object/ObjectTrait.php index 1c353eb58..001d53ffa 100644 --- a/system/src/Grav/Framework/Object/ObjectTrait.php +++ b/system/src/Grav/Framework/Object/ObjectTrait.php @@ -35,13 +35,27 @@ trait ObjectTrait { $this->items = $elements; - $this->key = $key !== null ? $key : $this->getKey(); + $this->key = $key !== null ? $key : (string) $this; if ($this->key === null) { - throw new \InvalidArgumentException('Object cannot be created without assigning a key'); + throw new \InvalidArgumentException('Object cannot be created without assigning a key to it'); } } + /** + * @param bool $prefix + * @return string + */ + public function getType($prefix = false) + { + if (static::$type) { + return ($prefix ? static::$prefix : '') . static::$type; + } + + $class = get_class($this); + return ($prefix ? static::$prefix : '') . strtolower(substr($class, strrpos($class, '\\') + 1)); + } + /** * @return string */ @@ -57,6 +71,6 @@ trait ObjectTrait */ public function __toString() { - return __CLASS__ . '@' . spl_object_hash($this); + return $this->getKey() ?: $this->getType() . '@' . spl_object_hash($this); } }