From ceda9d08dd092c2ce33f3af93d472ce588dd7878 Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Thu, 21 May 2015 18:21:57 +0200 Subject: [PATCH] (mvc) improve API for array type model objects Integrate uuid generation into the core of the model system so items are uniquely identified and can be directly accessed using their uuids. For example: // set recurring pipe item to 1001 $model->setNodeByReference('pipes.pipe.86f19d48-bc91-4c70-a81c-e05cdd3a4372.queue', "1001"); // echo the contents back echo $model->getNodeByReference('pipes.pipe.86f19d48-bc91-4c70-a81c-e05cdd3a4372.queue'); --- .../app/models/OPNsense/Base/BaseModel.php | 17 +++- .../OPNsense/Base/FieldTypes/ArrayField.php | 93 ++++++++----------- .../OPNsense/Base/FieldTypes/BaseField.php | 2 +- 3 files changed, 51 insertions(+), 61 deletions(-) diff --git a/src/opnsense/mvc/app/models/OPNsense/Base/BaseModel.php b/src/opnsense/mvc/app/models/OPNsense/Base/BaseModel.php index 2aa8930aa..d7367c5ce 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Base/BaseModel.php +++ b/src/opnsense/mvc/app/models/OPNsense/Base/BaseModel.php @@ -153,19 +153,26 @@ abstract class BaseModel if ($fieldObject instanceof ArrayField) { // handle Array types, recurring items if ($config_section_data != null) { - $counter = 0 ; foreach ($config_section_data as $conf_section) { + // Array items are identified by a UUID, read from attribute or create a new one + if (isset($conf_section->attributes()->uuid)) { + $tagUUID = $conf_section->attributes()['uuid']->__toString(); + } else { + $tagUUID = $internal_data->generateUUID(); + } + // iterate array items from config data - $child_node = new ContainerField($fieldObject->__reference . "." . ($counter++), $tagName); + $child_node = new ContainerField($fieldObject->__reference . "." . $tagUUID, $tagName); $this->parseXml($xmlNode, $conf_section, $child_node); - $fieldObject->addChildNode(null, $child_node); + $fieldObject->addChildNode($tagUUID, $child_node); } } else { // There's no content in config.xml for this array node. - $child_node = new ContainerField($fieldObject->__reference . ".0", $tagName); + $tagUUID = $internal_data->generateUUID(); + $child_node = new ContainerField($fieldObject->__reference . ".".$tagUUID, $tagName); $child_node->setInternalIsVirtual(); $this->parseXml($xmlNode, $config_section_data, $child_node); - $fieldObject->addChildNode(null, $child_node); + $fieldObject->addChildNode($tagUUID, $child_node); } } else { // All other node types (Text,Email,...) diff --git a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/ArrayField.php b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/ArrayField.php index 9fab5d1fd..a464889c6 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/ArrayField.php +++ b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/ArrayField.php @@ -34,27 +34,11 @@ namespace OPNsense\Base\FieldTypes; */ class ArrayField extends BaseField { - /** - * @var int item index - */ - private $internalArrayCounter = 0 ; - /** * @var null|BaseField node to use for copying */ private $internalTemplateNode = null; - /** - * add Childnode (list), ignore the name of this item - * @param string $name property name - * @param BaseField $node content (must be of type BaseField) - */ - public function addChildNode($name, $node) - { - $this->internalChildnodes[(string)$this->internalArrayCounter] = $node; - $this->internalArrayCounter++; - } - /** * Copy first node pointer as template node to make sure we always have a template to create new nodes from. * If the first node is virtual (no source data), remove that from the list. @@ -63,20 +47,13 @@ class ArrayField extends BaseField { // always make sure there's a node to copy our structure from if ($this->internalTemplateNode ==null) { - $this->internalTemplateNode = $this->internalChildnodes["0"]; + $firstKey = array_keys($this->internalChildnodes)[0]; + $this->internalTemplateNode = $this->internalChildnodes[$firstKey]; /** * if first node is empty, remove reference node. */ - if ($this->internalChildnodes["0"]->getInternalIsVirtual()) { - unset($this->internalChildnodes["0"]); - $this->internalArrayCounter--; - } - } - - // check if all children have a uuid, generate one if missing - foreach ($this->internalChildnodes as $nodeKey => $node) { - if (!array_key_exists('uuid', $node->getAttributes())) { - $node->setAttributeValue("uuid", $this->generateUUID()); + if ($this->internalChildnodes[$firstKey]->getInternalIsVirtual()) { + unset($this->internalChildnodes[$firstKey]); } } } @@ -97,8 +74,9 @@ class ArrayField extends BaseField $new_record[$key] = clone $node ; } + $nodeUUID = $this->generateUUID(); $container_node = new ContainerField( - $this->__reference . "." . $this->internalArrayCounter, + $this->__reference . "." . $nodeUUID, $this->internalXMLTagName ); @@ -110,17 +88,17 @@ class ArrayField extends BaseField } // make sure we have a UUID on repeating child items - $container_node->setAttributeValue("uuid", $this->generateUUID()); + $container_node->setAttributeValue("uuid", $nodeUUID); // add node to this object - $this->addChildNode(null, $container_node); + $this->addChildNode($nodeUUID, $container_node); return $container_node; } /** * remove item by id (number) - * @param $index index number + * @param string $index index number */ public function del($index) { @@ -129,38 +107,43 @@ class ArrayField extends BaseField } } - /** - * search child item by UUID - * @param $uuid item uuid - * @return BaseField|null + * @param string|array $fieldNames sort by fieldname + * @param bool $descending sort descending + * @return array */ - public function findByUUID($uuid) + public function sortedBy($fieldNames, $descending = false) { - foreach ($this->internalChildnodes as $nodeKey => $node) { - $nodeAttr = $node->getAttributes(); - if (array_key_exists('uuid', $nodeAttr) && $nodeAttr['uuid'] == $uuid) { - return $node; - } + // reserve at least X number of characters for every field to improve sorting of multiple fields + $MAX_KEY_LENGTH = 30; + + // fieldnames may be a list or a single item, always convert to a list + if (!is_array($fieldNames)) { + $fieldNames = array($fieldNames); } - return null; - } - /** - * search child item by UUID - * @param $uuid item uuid - * @return bool - */ - public function delByUUID($uuid) - { + // collect sortable data as key/value store + $sortedData=array(); foreach ($this->internalChildnodes as $nodeKey => $node) { - $nodeAttr = $node->getAttributes(); - if (array_key_exists('uuid', $nodeAttr) && $nodeAttr['uuid'] == $uuid) { - unset($this->internalChildnodes[$nodeKey]); - return true; + // populate sort key + $sortKey = ''; + foreach ($fieldNames as $fieldName) { + if (array_key_exists($fieldName, $node->internalChildnodes)) { + $sortKey .= sprintf("%".$MAX_KEY_LENGTH."s ,", $node->$fieldName) ; + } } + $sortKey .= $nodeKey; // prevent overwrite of duplicates + $sortedData[$sortKey] = $node ; } - return false; + + // sort by key on ascending or descending order + if (!$descending) { + ksort($sortedData); + } else { + krsort($sortedData); + } + + return array_values($sortedData); } } diff --git a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/BaseField.php b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/BaseField.php index eb449b73b..348419c4e 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/BaseField.php +++ b/src/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/BaseField.php @@ -93,7 +93,7 @@ abstract class BaseField /** * @return string uuid v4 number */ - protected function generateUUID() + public function generateUUID() { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',