xref: /plugin/struct/meta/NestedValue.php (revision ce44c6393fcd559a7f07942c55824e5b8379912c)
15bc00e11SAndreas Gohr<?php
25bc00e11SAndreas Gohr
35bc00e11SAndreas Gohrnamespace dokuwiki\plugin\struct\meta;
45bc00e11SAndreas Gohr
55bc00e11SAndreas Gohruse dokuwiki\Utf8\Sort;
65bc00e11SAndreas Gohr
75bc00e11SAndreas Gohr/**
85bc00e11SAndreas Gohr * Object to create a tree of values
95bc00e11SAndreas Gohr *
105bc00e11SAndreas Gohr * You should not create these yourself, but use the NestedResult class instead
115bc00e11SAndreas Gohr */
125bc00e11SAndreas Gohrclass NestedValue
135bc00e11SAndreas Gohr{
145bc00e11SAndreas Gohr
155bc00e11SAndreas Gohr    /** @var Value */
165bc00e11SAndreas Gohr    protected $value;
175bc00e11SAndreas Gohr
185bc00e11SAndreas Gohr    /** @var NestedValue[] */
195bc00e11SAndreas Gohr    protected $children = [];
205bc00e11SAndreas Gohr
215bc00e11SAndreas Gohr    /** @var Value[][] */
225bc00e11SAndreas Gohr    protected $resultRows = [];
235bc00e11SAndreas Gohr
245bc00e11SAndreas Gohr    /** @var int the nesting depth */
255bc00e11SAndreas Gohr    protected $depth;
265bc00e11SAndreas Gohr
275bc00e11SAndreas Gohr    /**
285bc00e11SAndreas Gohr     * Create a nested version of the given value
295bc00e11SAndreas Gohr     *
305bc00e11SAndreas Gohr     * @param Value|null $value The value to store, null for root node
315bc00e11SAndreas Gohr     * @param int $depth The depth of this node (avoids collision where the same values are selected on multiple levels)
325bc00e11SAndreas Gohr     */
335bc00e11SAndreas Gohr    public function __construct(?Value $value, $depth = 0)
345bc00e11SAndreas Gohr    {
355bc00e11SAndreas Gohr        $this->value = $value;
365bc00e11SAndreas Gohr        $this->depth = $depth;
375bc00e11SAndreas Gohr    }
385bc00e11SAndreas Gohr
395bc00e11SAndreas Gohr    /**
405bc00e11SAndreas Gohr     * @return int
415bc00e11SAndreas Gohr     */
425bc00e11SAndreas Gohr    public function getDepth()
435bc00e11SAndreas Gohr    {
445bc00e11SAndreas Gohr        return $this->depth;
455bc00e11SAndreas Gohr    }
465bc00e11SAndreas Gohr
475bc00e11SAndreas Gohr    /**
48*ce44c639SAndreas Gohr     * @param int $depth
49*ce44c639SAndreas Gohr     */
50*ce44c639SAndreas Gohr    public function setDepth($depth)
51*ce44c639SAndreas Gohr    {
52*ce44c639SAndreas Gohr        $this->depth = $depth;
53*ce44c639SAndreas Gohr        foreach ($this->children as $child) {
54*ce44c639SAndreas Gohr            $child->setDepth($depth + 1);
55*ce44c639SAndreas Gohr        }
56*ce44c639SAndreas Gohr    }
57*ce44c639SAndreas Gohr
58*ce44c639SAndreas Gohr    /**
595bc00e11SAndreas Gohr     * Access the stored value
605bc00e11SAndreas Gohr     *
615bc00e11SAndreas Gohr     * @return Value|null the value stored in this node, null for root node
625bc00e11SAndreas Gohr     */
635bc00e11SAndreas Gohr    public function getValueObject()
645bc00e11SAndreas Gohr    {
655bc00e11SAndreas Gohr        return $this->value;
665bc00e11SAndreas Gohr    }
675bc00e11SAndreas Gohr
685bc00e11SAndreas Gohr    /**
695bc00e11SAndreas Gohr     * Add a child node
705bc00e11SAndreas Gohr     *
715bc00e11SAndreas Gohr     * Nodes with the same key (__toString()) will be overwritten
725bc00e11SAndreas Gohr     *
735bc00e11SAndreas Gohr     * @param NestedValue $child
745bc00e11SAndreas Gohr     * @return void
755bc00e11SAndreas Gohr     */
765bc00e11SAndreas Gohr    public function addChild(NestedValue $child)
775bc00e11SAndreas Gohr    {
785bc00e11SAndreas Gohr        $this->children[(string)$child] = $child; // ensures uniqueness
795bc00e11SAndreas Gohr    }
805bc00e11SAndreas Gohr
815bc00e11SAndreas Gohr    /**
825bc00e11SAndreas Gohr     * Get all child nodes
835bc00e11SAndreas Gohr     *
845bc00e11SAndreas Gohr     * @return NestedValue[]
855bc00e11SAndreas Gohr     */
865bc00e11SAndreas Gohr    public function getChildren()
875bc00e11SAndreas Gohr    {
885bc00e11SAndreas Gohr        $children = $this->children;
895bc00e11SAndreas Gohr        usort($children, [$this, 'sortChildren']);
905bc00e11SAndreas Gohr        return $children;
915bc00e11SAndreas Gohr    }
925bc00e11SAndreas Gohr
935bc00e11SAndreas Gohr    /**
945bc00e11SAndreas Gohr     * Add a result row to this node
955bc00e11SAndreas Gohr     *
961ee0a2dcSAndreas Gohr     * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result
971ee0a2dcSAndreas Gohr     *
985bc00e11SAndreas Gohr     * @param Value[] $row
995bc00e11SAndreas Gohr     * @return void
1005bc00e11SAndreas Gohr     */
1015bc00e11SAndreas Gohr    public function addResultRow($row)
1025bc00e11SAndreas Gohr    {
1031ee0a2dcSAndreas Gohr        // only add unique rows
1041ee0a2dcSAndreas Gohr        $ident = md5(array_reduce($row, function ($carry, $value) {
1051ee0a2dcSAndreas Gohr            return $carry . $value;
1061ee0a2dcSAndreas Gohr        }, ''));
1071ee0a2dcSAndreas Gohr
1081ee0a2dcSAndreas Gohr        $this->resultRows[$ident] = $row;
1095bc00e11SAndreas Gohr    }
1105bc00e11SAndreas Gohr
1115bc00e11SAndreas Gohr    /**
1125bc00e11SAndreas Gohr     * Get all result rows stored in this node
1135bc00e11SAndreas Gohr     *
1145bc00e11SAndreas Gohr     * @return Value[][]
1155bc00e11SAndreas Gohr     */
1165bc00e11SAndreas Gohr    public function getResultRows()
1175bc00e11SAndreas Gohr    {
1181ee0a2dcSAndreas Gohr        return array_values($this->resultRows);
1195bc00e11SAndreas Gohr    }
1205bc00e11SAndreas Gohr
1215bc00e11SAndreas Gohr    /**
1225bc00e11SAndreas Gohr     * Get a unique key for this node
1235bc00e11SAndreas Gohr     *
1245bc00e11SAndreas Gohr     * @return string
1255bc00e11SAndreas Gohr     */
1265bc00e11SAndreas Gohr    public function __toString()
1275bc00e11SAndreas Gohr    {
1285bc00e11SAndreas Gohr        if ($this->value === null) return ''; // root node
1295bc00e11SAndreas Gohr        return $this->value->__toString() . '-' . $this->depth;
1305bc00e11SAndreas Gohr    }
1315bc00e11SAndreas Gohr
1325bc00e11SAndreas Gohr    /**
1335bc00e11SAndreas Gohr     * Custom comparator to sort the children of this node
1345bc00e11SAndreas Gohr     *
1355bc00e11SAndreas Gohr     * @param NestedValue $a
1365bc00e11SAndreas Gohr     * @param NestedValue $b
1375bc00e11SAndreas Gohr     * @return int
1385bc00e11SAndreas Gohr     */
1395bc00e11SAndreas Gohr    public function sortChildren(NestedValue $a, NestedValue $b)
1405bc00e11SAndreas Gohr    {
1415bc00e11SAndreas Gohr        // note: the way NestedResults build the NestedValues, the value object should
1425bc00e11SAndreas Gohr        // always contain a single value only. But since the associated column is still
1435bc00e11SAndreas Gohr        // a multi-value column, getCompareValue() will still return an array.
1445bc00e11SAndreas Gohr        // So here we treat all returns as array and join them with a dash (even though
1455bc00e11SAndreas Gohr        // there should never be more than one value in there)
1465bc00e11SAndreas Gohr        return Sort::strcmp(
1475bc00e11SAndreas Gohr            join('-', (array)$a->getValueObject()->getCompareValue()),
1485bc00e11SAndreas Gohr            join('-', (array)$b->getValueObject()->getCompareValue())
1495bc00e11SAndreas Gohr        );
1505bc00e11SAndreas Gohr    }
1515bc00e11SAndreas Gohr
1521ee0a2dcSAndreas Gohr    /**
1531ee0a2dcSAndreas Gohr     * print the tree for debugging
1541ee0a2dcSAndreas Gohr     *
1551ee0a2dcSAndreas Gohr     * @return string
1561ee0a2dcSAndreas Gohr     */
1571ee0a2dcSAndreas Gohr    public function dump()
1581ee0a2dcSAndreas Gohr    {
1591ee0a2dcSAndreas Gohr        $return = '';
1601ee0a2dcSAndreas Gohr
1611ee0a2dcSAndreas Gohr        if ($this->value) {
1621ee0a2dcSAndreas Gohr            $return .= str_pad('', $this->getDepth() * 4, ' ');
1631ee0a2dcSAndreas Gohr            $return .= join(', ', (array)$this->value->getDisplayValue());
1641ee0a2dcSAndreas Gohr            $return .= "\n";
1651ee0a2dcSAndreas Gohr        } else {
1661ee0a2dcSAndreas Gohr            $return .= "*\n";
1671ee0a2dcSAndreas Gohr        }
1681ee0a2dcSAndreas Gohr
1691ee0a2dcSAndreas Gohr        foreach ($this->getResultRows() as $row) {
1701ee0a2dcSAndreas Gohr            $return .= str_pad('', $this->getDepth() * 4, ' ');
1711ee0a2dcSAndreas Gohr            foreach ($row as $value) {
1721ee0a2dcSAndreas Gohr                $return .= ' ' . join(', ', (array)$value->getDisplayValue());
1731ee0a2dcSAndreas Gohr            }
1741ee0a2dcSAndreas Gohr            $return .= "\n";
1751ee0a2dcSAndreas Gohr        }
1761ee0a2dcSAndreas Gohr
1771ee0a2dcSAndreas Gohr        foreach ($this->getChildren() as $child) {
1781ee0a2dcSAndreas Gohr            $return .= $child->dump();
1791ee0a2dcSAndreas Gohr        }
1801ee0a2dcSAndreas Gohr
1811ee0a2dcSAndreas Gohr        return $return;
1821ee0a2dcSAndreas Gohr    }
1835bc00e11SAndreas Gohr}
184