xref: /plugin/struct/meta/NestedValue.php (revision 1ee0a2dc290e607b00cb9d6d919f4f60a00fcd2d)
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     * Access the stored value
49     *
50     * @return Value|null the value stored in this node, null for root node
51     */
52    public function getValueObject()
53    {
54        return $this->value;
55    }
56
57    /**
58     * Add a child node
59     *
60     * Nodes with the same key (__toString()) will be overwritten
61     *
62     * @param NestedValue $child
63     * @return void
64     */
65    public function addChild(NestedValue $child)
66    {
67        $this->children[(string)$child] = $child; // ensures uniqueness
68    }
69
70    /**
71     * Get all child nodes
72     *
73     * @return NestedValue[]
74     */
75    public function getChildren()
76    {
77        $children = $this->children;
78        usort($children, [$this, 'sortChildren']);
79        return $children;
80    }
81
82    /**
83     * Add a result row to this node
84     *
85     * Only unique rows will be stored, duplicates are detected by hashing the row values' toString result
86     *
87     * @param Value[] $row
88     * @return void
89     */
90    public function addResultRow($row)
91    {
92        // only add unique rows
93        $ident = md5(array_reduce($row, function ($carry, $value) {
94            return $carry . $value;
95        }, ''));
96
97        $this->resultRows[$ident] = $row;
98    }
99
100    /**
101     * Get all result rows stored in this node
102     *
103     * @return Value[][]
104     */
105    public function getResultRows()
106    {
107        return array_values($this->resultRows);
108    }
109
110    /**
111     * Get a unique key for this node
112     *
113     * @return string
114     */
115    public function __toString()
116    {
117        if ($this->value === null) return ''; // root node
118        return $this->value->__toString() . '-' . $this->depth;
119    }
120
121    /**
122     * Custom comparator to sort the children of this node
123     *
124     * @param NestedValue $a
125     * @param NestedValue $b
126     * @return int
127     */
128    public function sortChildren(NestedValue $a, NestedValue $b)
129    {
130        // note: the way NestedResults build the NestedValues, the value object should
131        // always contain a single value only. But since the associated column is still
132        // a multi-value column, getCompareValue() will still return an array.
133        // So here we treat all returns as array and join them with a dash (even though
134        // there should never be more than one value in there)
135        return Sort::strcmp(
136            join('-', (array)$a->getValueObject()->getCompareValue()),
137            join('-', (array)$b->getValueObject()->getCompareValue())
138        );
139    }
140
141    /**
142     * print the tree for debugging
143     *
144     * @return string
145     */
146    public function dump()
147    {
148        $return = '';
149
150        if ($this->value) {
151            $return .= str_pad('', $this->getDepth() * 4, ' ');
152            $return .= join(', ', (array)$this->value->getDisplayValue());
153            $return .= "\n";
154        } else {
155            $return .= "*\n";
156        }
157
158        foreach ($this->getResultRows() as $row) {
159            $return .= str_pad('', $this->getDepth() * 4, ' ');
160            foreach ($row as $value) {
161                $return .= ' ' . join(', ', (array)$value->getDisplayValue());
162            }
163            $return .= "\n";
164        }
165
166        foreach ($this->getChildren() as $child) {
167            $return .= $child->dump();
168        }
169
170        return $return;
171    }
172}
173