xref: /plugin/struct/meta/NestedValue.php (revision 7b7a9290f8a0cb5d24bd768166706412896cffe5)
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    /** @var Value */
155bc00e11SAndreas Gohr    protected $value;
165bc00e11SAndreas Gohr
175bc00e11SAndreas Gohr    /** @var NestedValue[] */
185bc00e11SAndreas Gohr    protected $children = [];
195bc00e11SAndreas Gohr
205bc00e11SAndreas Gohr    /** @var Value[][] */
215bc00e11SAndreas Gohr    protected $resultRows = [];
225bc00e11SAndreas Gohr
235bc00e11SAndreas Gohr    /** @var int the nesting depth */
245bc00e11SAndreas Gohr    protected $depth;
255bc00e11SAndreas Gohr
265bc00e11SAndreas Gohr    /**
275bc00e11SAndreas Gohr     * Create a nested version of the given value
285bc00e11SAndreas Gohr     *
295bc00e11SAndreas Gohr     * @param Value|null $value The value to store, null for root node
305bc00e11SAndreas Gohr     * @param int $depth The depth of this node (avoids collision where the same values are selected on multiple levels)
315bc00e11SAndreas Gohr     */
325bc00e11SAndreas Gohr    public function __construct(?Value $value, $depth = 0)
335bc00e11SAndreas Gohr    {
345bc00e11SAndreas Gohr        $this->value = $value;
355bc00e11SAndreas Gohr        $this->depth = $depth;
365bc00e11SAndreas Gohr    }
375bc00e11SAndreas Gohr
385bc00e11SAndreas Gohr    /**
395bc00e11SAndreas Gohr     * @return int
405bc00e11SAndreas Gohr     */
415bc00e11SAndreas Gohr    public function getDepth()
425bc00e11SAndreas Gohr    {
435bc00e11SAndreas Gohr        return $this->depth;
445bc00e11SAndreas Gohr    }
455bc00e11SAndreas Gohr
465bc00e11SAndreas Gohr    /**
47ce44c639SAndreas Gohr     * @param int $depth
48ce44c639SAndreas Gohr     */
49ce44c639SAndreas Gohr    public function setDepth($depth)
50ce44c639SAndreas Gohr    {
51ce44c639SAndreas Gohr        $this->depth = $depth;
52ce44c639SAndreas Gohr        foreach ($this->children as $child) {
53ce44c639SAndreas Gohr            $child->setDepth($depth + 1);
54ce44c639SAndreas Gohr        }
55ce44c639SAndreas Gohr    }
56ce44c639SAndreas Gohr
57ce44c639SAndreas Gohr    /**
585bc00e11SAndreas Gohr     * Access the stored value
595bc00e11SAndreas Gohr     *
605bc00e11SAndreas Gohr     * @return Value|null the value stored in this node, null for root node
615bc00e11SAndreas Gohr     */
625bc00e11SAndreas Gohr    public function getValueObject()
635bc00e11SAndreas Gohr    {
645bc00e11SAndreas Gohr        return $this->value;
655bc00e11SAndreas Gohr    }
665bc00e11SAndreas Gohr
675bc00e11SAndreas Gohr    /**
685bc00e11SAndreas Gohr     * Add a child node
695bc00e11SAndreas Gohr     *
705bc00e11SAndreas Gohr     * Nodes with the same key (__toString()) will be overwritten
715bc00e11SAndreas Gohr     *
725bc00e11SAndreas Gohr     * @param NestedValue $child
735bc00e11SAndreas Gohr     * @return void
745bc00e11SAndreas Gohr     */
755bc00e11SAndreas Gohr    public function addChild(NestedValue $child)
765bc00e11SAndreas Gohr    {
775bc00e11SAndreas Gohr        $this->children[(string)$child] = $child; // ensures uniqueness
785bc00e11SAndreas Gohr    }
795bc00e11SAndreas Gohr
805bc00e11SAndreas Gohr    /**
815bc00e11SAndreas Gohr     * Get all child nodes
825bc00e11SAndreas Gohr     *
835bc00e11SAndreas Gohr     * @return NestedValue[]
845bc00e11SAndreas Gohr     */
855bc00e11SAndreas Gohr    public function getChildren()
865bc00e11SAndreas Gohr    {
875bc00e11SAndreas Gohr        $children = $this->children;
885bc00e11SAndreas Gohr        usort($children, [$this, 'sortChildren']);
895bc00e11SAndreas Gohr        return $children;
905bc00e11SAndreas Gohr    }
915bc00e11SAndreas Gohr
925bc00e11SAndreas Gohr    /**
935bc00e11SAndreas Gohr     * Add a result row to this node
945bc00e11SAndreas Gohr     *
951ee0a2dcSAndreas Gohr     * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result
961ee0a2dcSAndreas Gohr     *
975bc00e11SAndreas Gohr     * @param Value[] $row
985bc00e11SAndreas Gohr     * @return void
995bc00e11SAndreas Gohr     */
1005bc00e11SAndreas Gohr    public function addResultRow($row)
1015bc00e11SAndreas Gohr    {
1021ee0a2dcSAndreas Gohr        // only add unique rows
1031ee0a2dcSAndreas Gohr        $ident = md5(array_reduce($row, function ($carry, $value) {
1041ee0a2dcSAndreas Gohr            return $carry . $value;
1051ee0a2dcSAndreas Gohr        }, ''));
1061ee0a2dcSAndreas Gohr
1071ee0a2dcSAndreas Gohr        $this->resultRows[$ident] = $row;
1085bc00e11SAndreas Gohr    }
1095bc00e11SAndreas Gohr
1105bc00e11SAndreas Gohr    /**
1115bc00e11SAndreas Gohr     * Get all result rows stored in this node
1125bc00e11SAndreas Gohr     *
1135bc00e11SAndreas Gohr     * @return Value[][]
1145bc00e11SAndreas Gohr     */
1155bc00e11SAndreas Gohr    public function getResultRows()
1165bc00e11SAndreas Gohr    {
1171ee0a2dcSAndreas Gohr        return array_values($this->resultRows);
1185bc00e11SAndreas Gohr    }
1195bc00e11SAndreas Gohr
1205bc00e11SAndreas Gohr    /**
1215bc00e11SAndreas Gohr     * Get a unique key for this node
1225bc00e11SAndreas Gohr     *
1235bc00e11SAndreas Gohr     * @return string
1245bc00e11SAndreas Gohr     */
1255bc00e11SAndreas Gohr    public function __toString()
1265bc00e11SAndreas Gohr    {
1275bc00e11SAndreas Gohr        if ($this->value === null) return ''; // root node
1285bc00e11SAndreas Gohr        return $this->value->__toString() . '-' . $this->depth;
1295bc00e11SAndreas Gohr    }
1305bc00e11SAndreas Gohr
1315bc00e11SAndreas Gohr    /**
1325bc00e11SAndreas Gohr     * Custom comparator to sort the children of this node
1335bc00e11SAndreas Gohr     *
1345bc00e11SAndreas Gohr     * @param NestedValue $a
1355bc00e11SAndreas Gohr     * @param NestedValue $b
1365bc00e11SAndreas Gohr     * @return int
1375bc00e11SAndreas Gohr     */
1385bc00e11SAndreas Gohr    public function sortChildren(NestedValue $a, NestedValue $b)
1395bc00e11SAndreas Gohr    {
140*7b7a9290SAndreas Gohr        $compA = join('-', (array)$a->getValueObject()->getCompareValue());
141*7b7a9290SAndreas Gohr        $compB = join('-', (array)$b->getValueObject()->getCompareValue());
142*7b7a9290SAndreas Gohr
143*7b7a9290SAndreas Gohr        // sort empty values to the end
144*7b7a9290SAndreas Gohr        if($compA === $compB) {
145*7b7a9290SAndreas Gohr            return 0;
146*7b7a9290SAndreas Gohr        }
147*7b7a9290SAndreas Gohr        if($compA === '') {
148*7b7a9290SAndreas Gohr            return 1;
149*7b7a9290SAndreas Gohr        }
150*7b7a9290SAndreas Gohr        if($compB === '') {
151*7b7a9290SAndreas Gohr            return -1;
152*7b7a9290SAndreas Gohr        }
153*7b7a9290SAndreas Gohr
1545bc00e11SAndreas Gohr        // note: the way NestedResults build the NestedValues, the value object should
1555bc00e11SAndreas Gohr        // always contain a single value only. But since the associated column is still
1565bc00e11SAndreas Gohr        // a multi-value column, getCompareValue() will still return an array.
1575bc00e11SAndreas Gohr        // So here we treat all returns as array and join them with a dash (even though
1585bc00e11SAndreas Gohr        // there should never be more than one value in there)
159*7b7a9290SAndreas Gohr        return Sort::strcmp($compA, $compB);
1605bc00e11SAndreas Gohr    }
1615bc00e11SAndreas Gohr
1621ee0a2dcSAndreas Gohr    /**
1631ee0a2dcSAndreas Gohr     * print the tree for debugging
1641ee0a2dcSAndreas Gohr     *
1651ee0a2dcSAndreas Gohr     * @return string
1661ee0a2dcSAndreas Gohr     */
1671ee0a2dcSAndreas Gohr    public function dump()
1681ee0a2dcSAndreas Gohr    {
1691ee0a2dcSAndreas Gohr        $return = '';
1701ee0a2dcSAndreas Gohr
1711ee0a2dcSAndreas Gohr        if ($this->value) {
1721ee0a2dcSAndreas Gohr            $return .= str_pad('', $this->getDepth() * 4, ' ');
1731ee0a2dcSAndreas Gohr            $return .= join(', ', (array)$this->value->getDisplayValue());
1741ee0a2dcSAndreas Gohr            $return .= "\n";
1751ee0a2dcSAndreas Gohr        } else {
1761ee0a2dcSAndreas Gohr            $return .= "*\n";
1771ee0a2dcSAndreas Gohr        }
1781ee0a2dcSAndreas Gohr
1791ee0a2dcSAndreas Gohr        foreach ($this->getResultRows() as $row) {
1801ee0a2dcSAndreas Gohr            $return .= str_pad('', $this->getDepth() * 4, ' ');
1811ee0a2dcSAndreas Gohr            foreach ($row as $value) {
1821ee0a2dcSAndreas Gohr                $return .= ' ' . join(', ', (array)$value->getDisplayValue());
1831ee0a2dcSAndreas Gohr            }
1841ee0a2dcSAndreas Gohr            $return .= "\n";
1851ee0a2dcSAndreas Gohr        }
1861ee0a2dcSAndreas Gohr
1871ee0a2dcSAndreas Gohr        foreach ($this->getChildren() as $child) {
1881ee0a2dcSAndreas Gohr            $return .= $child->dump();
1891ee0a2dcSAndreas Gohr        }
1901ee0a2dcSAndreas Gohr
1911ee0a2dcSAndreas Gohr        return $return;
1921ee0a2dcSAndreas Gohr    }
1935bc00e11SAndreas Gohr}
194