xref: /plugin/struct/meta/NestedValue.php (revision ce44c6393fcd559a7f07942c55824e5b8379912c)
1<?php
2
3namespace dokuwiki\plugin\struct\meta;
4
5use dokuwiki\Utf8\Sort;
6
7/**
8 * Object to create a tree of values
9 *
10 * You should not create these yourself, but use the NestedResult class instead
11 */
12class NestedValue
13{
14
15    /** @var Value */
16    protected $value;
17
18    /** @var NestedValue[] */
19    protected $children = [];
20
21    /** @var Value[][] */
22    protected $resultRows = [];
23
24    /** @var int the nesting depth */
25    protected $depth;
26
27    /**
28     * Create a nested version of the given value
29     *
30     * @param Value|null $value The value to store, null for root node
31     * @param int $depth The depth of this node (avoids collision where the same values are selected on multiple levels)
32     */
33    public function __construct(?Value $value, $depth = 0)
34    {
35        $this->value = $value;
36        $this->depth = $depth;
37    }
38
39    /**
40     * @return int
41     */
42    public function getDepth()
43    {
44        return $this->depth;
45    }
46
47    /**
48     * @param int $depth
49     */
50    public function setDepth($depth)
51    {
52        $this->depth = $depth;
53        foreach ($this->children as $child) {
54            $child->setDepth($depth + 1);
55        }
56    }
57
58    /**
59     * Access the stored value
60     *
61     * @return Value|null the value stored in this node, null for root node
62     */
63    public function getValueObject()
64    {
65        return $this->value;
66    }
67
68    /**
69     * Add a child node
70     *
71     * Nodes with the same key (__toString()) will be overwritten
72     *
73     * @param NestedValue $child
74     * @return void
75     */
76    public function addChild(NestedValue $child)
77    {
78        $this->children[(string)$child] = $child; // ensures uniqueness
79    }
80
81    /**
82     * Get all child nodes
83     *
84     * @return NestedValue[]
85     */
86    public function getChildren()
87    {
88        $children = $this->children;
89        usort($children, [$this, 'sortChildren']);
90        return $children;
91    }
92
93    /**
94     * Add a result row to this node
95     *
96     * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result
97     *
98     * @param Value[] $row
99     * @return void
100     */
101    public function addResultRow($row)
102    {
103        // only add unique rows
104        $ident = md5(array_reduce($row, function ($carry, $value) {
105            return $carry . $value;
106        }, ''));
107
108        $this->resultRows[$ident] = $row;
109    }
110
111    /**
112     * Get all result rows stored in this node
113     *
114     * @return Value[][]
115     */
116    public function getResultRows()
117    {
118        return array_values($this->resultRows);
119    }
120
121    /**
122     * Get a unique key for this node
123     *
124     * @return string
125     */
126    public function __toString()
127    {
128        if ($this->value === null) return ''; // root node
129        return $this->value->__toString() . '-' . $this->depth;
130    }
131
132    /**
133     * Custom comparator to sort the children of this node
134     *
135     * @param NestedValue $a
136     * @param NestedValue $b
137     * @return int
138     */
139    public function sortChildren(NestedValue $a, NestedValue $b)
140    {
141        // note: the way NestedResults build the NestedValues, the value object should
142        // always contain a single value only. But since the associated column is still
143        // a multi-value column, getCompareValue() will still return an array.
144        // So here we treat all returns as array and join them with a dash (even though
145        // there should never be more than one value in there)
146        return Sort::strcmp(
147            join('-', (array)$a->getValueObject()->getCompareValue()),
148            join('-', (array)$b->getValueObject()->getCompareValue())
149        );
150    }
151
152    /**
153     * print the tree for debugging
154     *
155     * @return string
156     */
157    public function dump()
158    {
159        $return = '';
160
161        if ($this->value) {
162            $return .= str_pad('', $this->getDepth() * 4, ' ');
163            $return .= join(', ', (array)$this->value->getDisplayValue());
164            $return .= "\n";
165        } else {
166            $return .= "*\n";
167        }
168
169        foreach ($this->getResultRows() as $row) {
170            $return .= str_pad('', $this->getDepth() * 4, ' ');
171            foreach ($row as $value) {
172                $return .= ' ' . join(', ', (array)$value->getDisplayValue());
173            }
174            $return .= "\n";
175        }
176
177        foreach ($this->getChildren() as $child) {
178            $return .= $child->dump();
179        }
180
181        return $return;
182    }
183}
184