From 23592b8a765381fd5f2ee63bbf808824607bd5cc Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Wed, 13 Sep 2017 09:50:34 +0300 Subject: [PATCH] Major update on Object classes (backwards incompatible) --- .../Object/Access/ArrayAccessTrait.php | 64 +++++ .../Object/Access/NestedArrayAccessTrait.php | 64 +++++ .../Access/NestedPropertyCollectionTrait.php | 124 +++++++++ .../Object/Access/NestedPropertyTrait.php | 179 +++++++++++++ .../Object/Access/OverloadedPropertyTrait.php | 64 +++++ .../src/Grav/Framework/Object/ArrayObject.php | 63 +---- .../{ => Base}/ObjectCollectionTrait.php | 48 +++- .../Framework/Object/Base/ObjectTrait.php | 167 +++++++++++++ .../Interfaces/NestedObjectInterface.php | 57 +++++ .../ObjectCollectionInterface.php | 2 +- .../{ => Interfaces}/ObjectInterface.php | 22 +- .../src/Grav/Framework/Object/LazyObject.php | 26 ++ system/src/Grav/Framework/Object/Object.php | 235 ------------------ .../Framework/Object/ObjectCollection.php | 33 ++- .../src/Grav/Framework/Object/ObjectTrait.php | 86 ------- .../Object/Property/ArrayPropertyTrait.php | 96 +++++++ .../Object/Property/LazyPropertyTrait.php | 92 +++++++ .../Object/Property/MixedPropertyTrait.php | 97 ++++++++ .../Object/Property/ObjectPropertyTrait.php | 151 +++++++++++ .../Grav/Framework/Object/PropertyObject.php | 26 ++ 20 files changed, 1292 insertions(+), 404 deletions(-) create mode 100644 system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php rename system/src/Grav/Framework/Object/{ => Base}/ObjectCollectionTrait.php (71%) create mode 100644 system/src/Grav/Framework/Object/Base/ObjectTrait.php create mode 100644 system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php rename system/src/Grav/Framework/Object/{ => Interfaces}/ObjectCollectionInterface.php (96%) rename system/src/Grav/Framework/Object/{ => Interfaces}/ObjectInterface.php (61%) create mode 100644 system/src/Grav/Framework/Object/LazyObject.php delete mode 100644 system/src/Grav/Framework/Object/Object.php delete mode 100644 system/src/Grav/Framework/Object/ObjectTrait.php create mode 100644 system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php create mode 100644 system/src/Grav/Framework/Object/PropertyObject.php diff --git a/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php new file mode 100644 index 000000000..e22251d88 --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php @@ -0,0 +1,64 @@ +hasProperty($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) + { + $this->unsetProperty($offset); + } + + abstract public function hasProperty($property); + abstract public function getProperty($property, $default = null); + abstract public function setProperty($property, $value); + abstract public function unsetProperty($property); +} diff --git a/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php new file mode 100644 index 000000000..c5e8b1bdd --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php @@ -0,0 +1,64 @@ +hasNestedProperty($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->getNestedProperty($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->setNestedProperty($offset, $value); + } + + /** + * Unsets an offset. + * + * @param mixed $offset The offset to unset. + */ + public function offsetUnset($offset) + { + $this->unsetNestedProperty($offset); + } + + abstract public function hasNestedProperty($property, $separator = null); + abstract public function getNestedProperty($property, $default = null, $separator = null); + abstract public function setNestedProperty($property, $value, $separator = null); + abstract public function unsetNestedProperty($property, $separator = null); +} diff --git a/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php b/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php new file mode 100644 index 000000000..6e1d34ca1 --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php @@ -0,0 +1,124 @@ +getIterator() as $id => $element) { + $list[$id] = $element->hasNestedProperty($property, $separator); + } + + return $list; + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if not set. + * @param string $separator Separator, defaults to '.' + * @return array Key/Value pairs of the properties. + */ + public function getNestedProperty($property, $default = null, $separator = null) + { + $list = []; + + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $id => $element) { + $list[$id] = $element->getNestedProperty($property, $default, $separator); + } + + return $list; + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function setNestedProperty($property, $value, $separator = null) + { + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->setNestedProperty($property, $value, $separator); + } + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function unsetNestedProperty($property, $separator = null) + { + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->unsetNestedProperty($property, $separator); + } + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string $default Default value. + * @param string $separator Separator, defaults to '.' + * @return $this + */ + public function defNestedProperty($property, $default, $separator = null) + { + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->defNestedProperty($property, $default, $separator); + } + + return $this; + } + + /** + * Group items in the collection by a field. + * + * @param string $property Object property to be used to make groups. + * @param string $separator Separator, defaults to '.' + * @return array + */ + public function group($property, $separator = null) + { + $list = []; + + /** @var NestedObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $list[(string) $element->getNestedProperty($property, null, $separator)][] = $element; + } + + return $list; + } + + /** + * @return \Traversable + */ + abstract public function getIterator(); +} diff --git a/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php b/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php new file mode 100644 index 000000000..62f63fd42 --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php @@ -0,0 +1,179 @@ +getNestedProperty($property, $test, $separator) !== $test; + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param string $separator Separator, defaults to '.' + * @return mixed Property value. + */ + public function getNestedProperty($property, $default = null, $separator = null) + { + $path = explode($separator ?: '.', $property); + $offset = array_shift($path); + + if (!$this->hasProperty($offset)) { + return $default; + } + + $current = $this->getProperty($offset); + + while ($path) { + // Get property of nested Object. + if ($current instanceof ObjectInterface) { + if (method_exists($current, 'getNestedProperty')) { + return $current->getNestedProperty(implode($separator, $path), $default, $separator); + } + return $current->getProperty(implode($separator, $path), $default); + } + + $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; + } + }; + + return $current; + } + + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @param string $separator Separator, defaults to '.' + * @return $this + * @throws \RuntimeException + */ + public function setNestedProperty($property, $value, $separator = null) + { + $path = explode($separator ?: '.', $property); + $offset = array_shift($path); + + if (!$path) { + $this->setProperty($offset, $value); + + return $this; + } + + $current = &$this->doGetProperty($offset, null, true); + + while ($path) { + $offset = array_shift($path); + + // Handle arrays and scalars. + if ($current === null) { + $current = [$offset => []]; + } elseif (is_array($current)) { + if (!isset($current[$offset])) { + $current[$offset] = []; + } + } else { + throw new \RuntimeException('Cannot set nested property on non-array value'); + } + + $current = &$current[$offset]; + }; + + $current = $value; + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string $separator Separator, defaults to '.' + * @return $this + * @throws \RuntimeException + */ + public function unsetNestedProperty($property, $separator = null) + { + $path = explode($separator ?: '.', $property); + $offset = array_shift($path); + + if (!$path) { + $this->unsetProperty($offset); + + return $this; + } + + $last = array_pop($path); + $current = &$this->doGetProperty($offset, null, true); + + while ($path) { + $offset = array_shift($path); + + // Handle arrays and scalars. + if ($current === null) { + return $this; + } + if (is_array($current)) { + if (!isset($current[$offset])) { + return $this; + } + } else { + throw new \RuntimeException('Cannot set nested property on non-array value'); + } + + $current = &$current[$offset]; + }; + + unset($current[$last]); + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string $default Default value. + * @param string $separator Separator, defaults to '.' + * @return $this + * @throws \RuntimeException + */ + public function defNestedProperty($property, $default, $separator = null) + { + if (!$this->hasNestedProperty($property, $separator)) { + $this->setNestedProperty($property, $default, $separator); + } + + return $this; + } + + + abstract public function hasProperty($property); + abstract public function getProperty($property, $default = null); + abstract public function setProperty($property, $value); + abstract public function unsetProperty($property); + abstract protected function &doGetProperty($property, $default = null, $doCreate = false); +} diff --git a/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php new file mode 100644 index 000000000..596c799f8 --- /dev/null +++ b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php @@ -0,0 +1,64 @@ +hasProperty($offset); + } + + /** + * Returns the value at specified offset. + * + * @param mixed $offset The offset to retrieve. + * @return mixed Can return all value types. + */ + public function __get($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 __set($offset, $value) + { + $this->setProperty($offset, $value); + } + + /** + * Magic method to unset the attribute + * + * @param mixed $offset The name value to unset + */ + public function __unset($offset) + { + $this->unsetProperty($offset); + } + + abstract public function hasProperty($property); + abstract public function getProperty($property, $default = null); + abstract public function setProperty($property, $value); + abstract public function unsetProperty($property); +} diff --git a/system/src/Grav/Framework/Object/ArrayObject.php b/system/src/Grav/Framework/Object/ArrayObject.php index dae0d00de..583ed3849 100644 --- a/system/src/Grav/Framework/Object/ArrayObject.php +++ b/system/src/Grav/Framework/Object/ArrayObject.php @@ -8,62 +8,19 @@ namespace Grav\Framework\Object; +use Grav\Framework\Object\Access\NestedPropertyTrait; +use Grav\Framework\Object\Access\OverloadedPropertyTrait; +use Grav\Framework\Object\Base\ObjectTrait; +use Grav\Framework\Object\Interfaces\NestedObjectInterface; +use Grav\Framework\Object\Property\ArrayPropertyTrait; +use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess; + /** - * ArrayObject class. + * Array Object class. * * @package Grav\Framework\Object */ -class ArrayObject extends Object implements \ArrayAccess +class ArrayObject implements NestedObjectInterface, \ArrayAccess { - /** - * Whether or not an offset exists. - * - * @param mixed $offset An offset to check for. - * @return bool Returns TRUE on success or FALSE on failure. - */ - public function offsetExists($offset) - { - if (strpos($offset, '.') !== false) { - $test = new \stdClass(); - return $this->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); - } - } + use ObjectTrait, ArrayPropertyTrait, NestedPropertyTrait, OverloadedPropertyTrait, NestedArrayAccess; } diff --git a/system/src/Grav/Framework/Object/ObjectCollectionTrait.php b/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php similarity index 71% rename from system/src/Grav/Framework/Object/ObjectCollectionTrait.php rename to system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php index c2e3df4ed..916502b67 100644 --- a/system/src/Grav/Framework/Object/ObjectCollectionTrait.php +++ b/system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php @@ -6,7 +6,9 @@ * @license MIT License; see LICENSE file for details. */ -namespace Grav\Framework\Object; +namespace Grav\Framework\Object\Base; + +use Grav\Framework\Object\Interfaces\ObjectInterface; /** * ObjectCollection Trait @@ -14,7 +16,9 @@ namespace Grav\Framework\Object; */ trait ObjectCollectionTrait { - use ObjectTrait; + use ObjectTrait { + setKey as public; + } /** * Create a copy from this collection by cloning all objects in the collection. @@ -44,12 +48,28 @@ trait ObjectCollectionTrait return $this->call('getKey'); } + /** + * @param string $property Object property to be matched. + * @return array Key/Value pairs of the properties. + */ + public function doHasProperty($property) + { + $list = []; + + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $id => $element) { + $list[$id] = $element->hasProperty($property); + } + + return $list; + } + /** * @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, $default = null) + public function doGetProperty($property, $default = null) { $list = []; @@ -66,7 +86,7 @@ trait ObjectCollectionTrait * @param string $value New value. * @return $this */ - public function setProperty($property, $value) + public function doSetProperty($property, $value) { /** @var ObjectInterface $element */ foreach ($this->getIterator() as $element) { @@ -78,14 +98,28 @@ trait ObjectCollectionTrait /** * @param string $property Object property to be updated. - * @param string $value New value. * @return $this */ - public function defProperty($property, $value) + public function doUnsetProperty($property) { /** @var ObjectInterface $element */ foreach ($this->getIterator() as $element) { - $element->defProperty($property, $value); + $element->unsetProperty($property); + } + + return $this; + } + + /** + * @param string $property Object property to be updated. + * @param string $default Default value. + * @return $this + */ + public function doDefProperty($property, $default) + { + /** @var ObjectInterface $element */ + foreach ($this->getIterator() as $element) { + $element->defProperty($property, $default); } return $this; diff --git a/system/src/Grav/Framework/Object/Base/ObjectTrait.php b/system/src/Grav/Framework/Object/Base/ObjectTrait.php new file mode 100644 index 000000000..5c2b7b4f7 --- /dev/null +++ b/system/src/Grav/Framework/Object/Base/ObjectTrait.php @@ -0,0 +1,167 @@ +_key ?: $this->getType() . '@' . spl_object_hash($this); + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + public function hasProperty($property) + { + return $this->doHasProperty($property); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @return mixed Property value. + */ + public function getProperty($property, $default = null) + { + return $this->doGetProperty($property, $default); + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @return $this + */ + public function setProperty($property, $value) + { + $this->doSetProperty($property, $value); + + return $this; + } + + /** + * @param string $property Object property to be unset. + * @return $this + */ + public function unsetProperty($property) + { + $this->doUnsetProperty($property); + + return $this; + } + + /** + * @param string $property Object property to be defined. + * @param mixed $default Default value. + * @return $this + */ + public function defProperty($property, $default) + { + if (!$this->hasProperty($property)) { + $this->setProperty($property, $default); + } + + return $this; + } + + /** + * Implements Serializable interface. + * + * @return string + */ + public function serialize() + { + return serialize($this->jsonSerialize()); + } + + /** + * @param string $serialized + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + + $this->doUnserialize($data); + } + + /** + * @param array $serialized + */ + protected function doUnserialize(array $serialized) + { + $this->setKey($serialized['key']); + $this->setElements($serialized['elements']); + } + + /** + * Implements JsonSerializable interface. + * + * @return array + */ + public function jsonSerialize() + { + return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()]; + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + public function __toString() + { + return $this->getKey(); + } + + /** + * @param string $key + */ + protected function setKey($key) + { + $this->_key = (string) $key; + } + + abstract protected function doHasProperty($property); + abstract protected function &doGetProperty($property, $default = null, $doCreate = false); + abstract protected function doSetProperty($property, $value); + abstract protected function doUnsetProperty($property); + abstract protected function getElements(); + abstract protected function setElements(array $elements); +} diff --git a/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php b/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php new file mode 100644 index 000000000..889d35659 --- /dev/null +++ b/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php @@ -0,0 +1,57 @@ +get('this.is.my.nested.variable'); - * - * @param string $property 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($property, $default = null, $separator = '.') - { - $path = explode($separator, $property); - $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 $property Dot separated path to the requested value. - * @param mixed $value New value. - * @param string $separator Separator, defaults to '.' - * @return $this - */ - public function setProperty($property, $value, $separator = '.') - { - $path = explode($separator, $property); - $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 $property Dot separated path to the requested value. - * @param mixed $value New value. - * @param string $separator Separator, defaults to '.' - * @return $this - */ - public function defProperty($property, $value, $separator = '.') - { - $test = new \stdClass; - if ($this->getProperty($property, $test, $separator) === $test) { - $this->setProperty($property, $value, $separator); - } - - return $this; - } - - /** - * Checks whether or not an offset exists with a possibility to load the field by $this->offsetLoad_{$offset}(). - * - * @param mixed $offset An offset to check for. - * @return bool Returns TRUE on success or FALSE on failure. - */ - public function __isset($offset) - { - return array_key_exists($offset, $this->items) || $this->isPropertyDefined($offset); - } - - /** - * Returns the value at specified offset with a possibility to load the field by $this->offsetLoad_{$offset}(). - * - * @param mixed $offset The offset to retrieve. - * @return mixed Can return all value types. - */ - public function __get($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}(). - * - * @param mixed $offset The offset to assign the value to. - * @param mixed $value The value to set. - */ - public function __set($offset, $value) - { - if ($this->isPropertyDefined($offset)) { - $methodName = "offsetPrepare_{$offset}"; - - if (method_exists($this, $methodName)) { - $this->{$offset} = $this->{$methodName}($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; - } - - 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 85ea126aa..1414492d5 100644 --- a/system/src/Grav/Framework/Object/ObjectCollection.php +++ b/system/src/Grav/Framework/Object/ObjectCollection.php @@ -9,16 +9,20 @@ namespace Grav\Framework\Object; use Grav\Framework\Collection\ArrayCollection; +use Grav\Framework\Object\Access\NestedPropertyCollectionTrait; +use Grav\Framework\Object\Base\ObjectCollectionTrait; +use Grav\Framework\Object\Interfaces\NestedObjectInterface; +use Grav\Framework\Object\Interfaces\ObjectCollectionInterface; /** * Object Collection * @package Grav\Framework\Object */ -class ObjectCollection extends ArrayCollection implements ObjectCollectionInterface +class ObjectCollection extends ArrayCollection implements ObjectCollectionInterface, NestedObjectInterface { - use ObjectCollectionTrait; - - static protected $prefix = 'c.'; + use ObjectCollectionTrait, NestedPropertyCollectionTrait { + NestedPropertyCollectionTrait::group insteadof ObjectCollectionTrait; + } /** * @param array $elements @@ -27,23 +31,18 @@ class ObjectCollection extends ArrayCollection implements ObjectCollectionInterf */ public function __construct(array $elements = [], $key = null) { - parent::__construct($elements); + parent::__construct($this->setElements($elements)); - $this->key = $key !== null ? $key : (string) $this; - - if ($this->key === null) { - throw new \InvalidArgumentException('Object cannot be created without assigning a key to it'); - } + $this->setKey($key); } - /** - * @param string $key - * @return $this - */ - public function setKey($key) + protected function getElements() { - $this->key = $key; + return $this->toArray(); + } - return $this; + protected function setElements(array $elements) + { + return $elements; } } diff --git a/system/src/Grav/Framework/Object/ObjectTrait.php b/system/src/Grav/Framework/Object/ObjectTrait.php deleted file mode 100644 index 7292e5158..000000000 --- a/system/src/Grav/Framework/Object/ObjectTrait.php +++ /dev/null @@ -1,86 +0,0 @@ -items = $elements; - $this->key = $key !== null ? $key : (string) $this; - - if ($this->key === null) { - throw new \InvalidArgumentException('Object cannot be created without assigning a key to it'); - } - } - - /** - * @param bool $prefix - * @return string - */ - public function getType($prefix = true) - { - 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 - */ - public function getKey() - { - return $this->key; - } - - /** - * Implements JsonSerializable interface. - * - * @return array - */ - public function jsonSerialize() - { - return ['key' => (string) $this, 'type' => $this->getType(), 'elements' => $this->toArray()]; - } - - /** - * Returns a string representation of this object. - * - * @return string - */ - public function __toString() - { - return $this->getKey() ?: $this->getType() . '@' . spl_object_hash($this); - } -} diff --git a/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php b/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php new file mode 100644 index 000000000..7d1d4f8b5 --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php @@ -0,0 +1,96 @@ +setElements($elements); + $this->setKey($key); + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + protected function doHasProperty($property) + { + return array_key_exists($property, $this->_elements); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param bool $doCreate Set true to create variable. + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if (!$this->doHasProperty($property)) { + if ($doCreate) { + $this->_elements[$property] = null; + } else { + return $default; + } + } + + return $this->_elements[$property]; + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + */ + protected function doSetProperty($property, $value) + { + $this->_elements[$property] = $value; + } + + /** + * @param string $property Object property to be unset. + */ + protected function doUnsetProperty($property) + { + unset($this->_elements[$property]); + } + + /** + * @return array + */ + protected function getElements() + { + return $this->_elements; + } + + /** + * @param array $elements + */ + protected function setElements(array $elements) + { + $this->_elements = $elements; + } + + abstract protected function setKey($key); +} diff --git a/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php new file mode 100644 index 000000000..10e7caa4b --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php @@ -0,0 +1,92 @@ +hasArrayProperty($property) || $this->hasObjectProperty($property); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if ($this->hasObjectProperty($property)) { + return $this->getObjectProperty($property, $default, function ($default = null) use ($property) { + return $this->getArrayProperty($property, $default); + }); + } + + return $this->getArrayProperty($property, $default, $doCreate); + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @return $this + */ + protected function doSetProperty($property, $value) + { + if ($this->hasObjectProperty($property)) { + $this->setObjectProperty($property, $value); + } else { + $this->setArrayProperty($property, $value); + } + + return $this; + } + + /** + * @param string $property Object property to be unset. + * @return $this + */ + protected function doUnsetProperty($property) + { + $this->hasObjectProperty($property) ? + $this->unsetObjectProperty($property) : $this->unsetArrayProperty($property); + + return $this; + } + + /** + * @return array + */ + protected function getElements() + { + return $this->getObjectElements() + $this->getArrayElements(); + } +} diff --git a/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php b/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php new file mode 100644 index 000000000..56b785653 --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php @@ -0,0 +1,97 @@ +hasArrayProperty($property) || $this->hasObjectProperty($property); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if ($this->hasObjectProperty($property)) { + return $this->getObjectProperty($property); + } + + return $this->getArrayProperty($property, $default, $doCreate); + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @return $this + */ + protected function doSetProperty($property, $value) + { + $this->hasObjectProperty($property) + ? $this->setObjectProperty($property, $value) : $this->setArrayProperty($property, $value); + + return $this; + } + + /** + * @param string $property Object property to be unset. + * @return $this + */ + protected function doUnsetProperty($property) + { + $this->hasObjectProperty($property) ? + $this->unsetObjectProperty($property) : $this->unsetArrayProperty($property); + + return $this; + } + + /** + * @return array + */ + protected function getElements() + { + return $this->getObjectElements() + $this->getArrayElements(); + } + + /** + * @param array $elements + */ + protected function setElements(array $elements) + { + $this->setObjectElements(array_intersect_key($elements, $this->_definedProperties)); + $this->setArrayElements(array_diff_key($elements, $this->_definedProperties)); + } +} diff --git a/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php b/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php new file mode 100644 index 000000000..8154c391c --- /dev/null +++ b/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php @@ -0,0 +1,151 @@ +initObjectProperties(); + $this->setElements($elements); + $this->setKey($key); + } + + /** + * @param string $property Object property name. + * @return bool True if property has been defined (can be null). + */ + protected function doHasProperty($property) + { + return array_key_exists($property, $this->_definedProperties); + } + + /** + * @param string $property Object property to be fetched. + * @param mixed $default Default value if property has not been set. + * @param bool $doCreate Set true to create variable. + * @return mixed Property value. + */ + protected function &doGetProperty($property, $default = null, $doCreate = false) + { + if (!array_key_exists($property, $this->_definedProperties)) { + throw new \InvalidArgumentException("Property '{$property}' does not exist in the object!"); + } + + if (empty($this->_definedProperties[$property])) { + if ($doCreate === true) { + $this->_definedProperties[$property] = true; + $this->{$property} = null; + } elseif (is_callable($doCreate)) { + $this->_definedProperties[$property] = true; + $this->{$property} = $this->onPropertyLoad($property, $doCreate()); + } else { + return $default; + } + } + + return $this->{$property}; + } + + /** + * @param string $property Object property to be updated. + * @param string $value New value. + * @throws \InvalidArgumentException + */ + protected function doSetProperty($property, $value) + { + if (!array_key_exists($property, $this->_definedProperties)) { + throw new \InvalidArgumentException("Property '{$property}' does not exist in the object!"); + } + + $this->_definedProperties[$property] = true; + $this->{$property} = $this->onPropertySet($property, $value); + } + + /** + * @param string $property Object property to be unset. + */ + protected function doUnsetProperty($property) + { + if (!array_key_exists($property, $this->_definedProperties)) { + return; + } + + $this->_definedProperties[$property] = false; + unset($this->{$property}); + } + + + protected function onPropertyLoad($offset, $value) + { + $methodName = "offsetLoad_{$offset}"; + + if (method_exists($this, $methodName)) { + return $this->{$methodName}($value); + } + + return $value; + } + + protected function onPropertySet($offset, $value) + { + $methodName = "offsetPrepare_{$offset}"; + + if (method_exists($this, $methodName)) { + return $this->{$methodName}($value); + } + + return $value; + } + + protected function initObjectProperties() + { + $this->_definedProperties = []; + foreach (get_object_vars($this) as $property => $value) { + if ($property[0] !== '_') { + $this->_definedProperties[$property] = ($value !== null); + } + } + } + + /** + * @return array + */ + protected function getElements() + { + return array_intersect_key(get_object_vars($this), array_filter($this->_definedProperties)); + } + + /** + * @param array $elements + */ + protected function setElements(array $elements) + { + foreach ($elements as $property => $value) { + $this->setProperty($property, $value); + } + } + + abstract public function setProperty($property, $value); + abstract protected function setKey($key); +} diff --git a/system/src/Grav/Framework/Object/PropertyObject.php b/system/src/Grav/Framework/Object/PropertyObject.php new file mode 100644 index 000000000..e7b66bc19 --- /dev/null +++ b/system/src/Grav/Framework/Object/PropertyObject.php @@ -0,0 +1,26 @@ +