Completed
Push — generators ( 74902c...d5c396 )
by Loz
10:24
created

SSViewer_Scope::self()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\View;
4
5
use ArrayIterator;
6
use Countable;
7
use Iterator;
8
9
/**
10
 * This tracks the current scope for an SSViewer instance. It has three goals:
11
 *   - Handle entering & leaving sub-scopes in loops and withs
12
 *   - Track Up and Top
13
 *   - (As a side effect) Inject data that needs to be available globally (used to live in ViewableData)
14
 *
15
 * In order to handle up, rather than tracking it using a tree, which would involve constructing new objects
16
 * for each step, we use indexes into the itemStack (which already has to exist).
17
 *
18
 * Each item has three indexes associated with it
19
 *
20
 *   - Pop. Which item should become the scope once the current scope is popped out of
21
 *   - Up. Which item is up from this item
22
 *   - Current. Which item is the first time this object has appeared in the stack
23
 *
24
 * We also keep the index of the current starting point for lookups. A lookup is a sequence of obj calls -
25
 * when in a loop or with tag the end result becomes the new scope, but for injections, we throw away the lookup
26
 * and revert back to the original scope once we've got the value we're after
27
 */
28
class SSViewer_Scope
29
{
30
31
    const ITEM = 0;
32
    const ITEM_ITERATOR = 1;
33
    const ITEM_ITERATOR_TOTAL = 2;
34
    const POP_INDEX = 3;
35
    const UP_INDEX = 4;
36
    const CURRENT_INDEX = 5;
37
    const ITEM_OVERLAY = 6;
38
39
    // The stack of previous "global" items
40
    // An indexed array of item, item iterator, item iterator total, pop index, up index, current index & parent overlay
41
    private $itemStack = array();
42
43
    /**
44
     * The current "global" item (the one any lookup starts from)
45
     */
46
    protected $item;
47
48
    /**
49
     * If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
50
     *
51
     * @var Iterator
52
     */
53
    protected $itemIterator;
54
55
    //Total number of items in the iterator
56
    protected $itemIteratorTotal;
57
58
    // A pointer into the item stack for which item should be scope on the next pop call
59
    private $popIndex;
60
61
    // A pointer into the item stack for which item is "up" from this one
62
    private $upIndex = null;
63
64
    // A pointer into the item stack for which item is this one (or null if not in stack yet)
65
    private $currentIndex = null;
66
67
    private $localIndex;
68
69
    public function __construct($item, $inheritedScope = null)
70
    {
71
        $this->item = $item;
72
        $this->localIndex = 0;
73
        $this->localStack = array();
0 ignored issues
show
Bug introduced by
The property localStack does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
74
        if ($inheritedScope instanceof SSViewer_Scope) {
75
            $this->itemIterator = $inheritedScope->itemIterator;
76
            $this->itemIteratorTotal = $inheritedScope->itemIteratorTotal;
77
            $this->itemStack[] = array($this->item, $this->itemIterator, $this->itemIteratorTotal, null, null, 0);
78
        } else {
79
            $this->itemStack[] = array($this->item, null, 0, null, null, 0);
80
        }
81
    }
82
83
    public function getItem()
84
    {
85
        return $this->itemIterator ? $this->itemIterator->current() : $this->item;
86
    }
87
88
    /** Called at the start of every lookup chain by SSTemplateParser to indicate a new lookup from local scope */
89
    public function locally()
90
    {
91
        list($this->item, $this->itemIterator, $this->itemIteratorTotal, $this->popIndex, $this->upIndex,
92
            $this->currentIndex) = $this->itemStack[$this->localIndex];
93
94
        // Remember any  un-completed (resetLocalScope hasn't been called) lookup chain. Even if there isn't an
95
        // un-completed chain we need to store an empty item, as resetLocalScope doesn't know the difference later
96
        $this->localStack[] = array_splice($this->itemStack, $this->localIndex + 1);
97
98
        return $this;
99
    }
100
101
    public function resetLocalScope()
102
    {
103
        $previousLocalState = $this->localStack ? array_pop($this->localStack) : null;
104
105
        array_splice($this->itemStack, $this->localIndex + 1, count($this->itemStack), $previousLocalState);
106
107
        list($this->item, $this->itemIterator, $this->itemIteratorTotal, $this->popIndex, $this->upIndex,
108
            $this->currentIndex) = end($this->itemStack);
109
    }
110
111
    public function getObj($name, $arguments = [], $cache = false, $cacheName = null)
112
    {
113
        $on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
114
        return $on->obj($name, $arguments, $cache, $cacheName);
115
    }
116
117
    /**
118
     * @param string $name
119
     * @param array $arguments
120
     * @param bool $cache
121
     * @param string $cacheName
122
     * @return $this
123
     */
124
    public function obj($name, $arguments = [], $cache = false, $cacheName = null)
125
    {
126
        switch ($name) {
127
            case 'Up':
128
                if ($this->upIndex === null) {
129
                    user_error('Up called when we\'re already at the top of the scope', E_USER_ERROR);
130
                }
131
132
                list($this->item, $this->itemIterator, $this->itemIteratorTotal, $unused2, $this->upIndex,
0 ignored issues
show
Unused Code introduced by
The assignment to $unused2 is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
133
                    $this->currentIndex) = $this->itemStack[$this->upIndex];
134
                break;
135
136
            case 'Top':
137
                list($this->item, $this->itemIterator, $this->itemIteratorTotal, $unused2, $this->upIndex,
0 ignored issues
show
Unused Code introduced by
The assignment to $unused2 is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
138
                    $this->currentIndex) = $this->itemStack[0];
139
                break;
140
141
            default:
142
                $this->item = $this->getObj($name, $arguments, $cache, $cacheName);
143
                $this->itemIterator = null;
144
                $this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack) - 1;
145
                $this->currentIndex = count($this->itemStack);
146
                break;
147
        }
148
149
        $this->itemStack[] = array(
150
            $this->item,
151
            $this->itemIterator,
152
            $this->itemIteratorTotal,
153
            null,
154
            $this->upIndex,
155
            $this->currentIndex
156
        );
157
        return $this;
158
    }
159
160
    /**
161
     * Gets the current object and resets the scope.
162
     *
163
     * @return object
164
     */
165
    public function self()
166
    {
167
        $result = $this->itemIterator ? $this->itemIterator->current() : $this->item;
168
        $this->resetLocalScope();
169
170
        return $result;
171
    }
172
173
    public function pushScope()
174
    {
175
        $newLocalIndex = count($this->itemStack) - 1;
176
177
        $this->popIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::POP_INDEX] = $this->localIndex;
178
        $this->localIndex = $newLocalIndex;
179
180
        // We normally keep any previous itemIterator around, so local $Up calls reference the right element. But
181
        // once we enter a new global scope, we need to make sure we use a new one
182
        $this->itemIterator = $this->itemStack[$newLocalIndex][SSViewer_Scope::ITEM_ITERATOR] = null;
183
184
        return $this;
185
    }
186
187
    public function popScope()
188
    {
189
        $this->localIndex = $this->popIndex;
190
        $this->resetLocalScope();
191
192
        return $this;
193
    }
194
195
    public function next()
196
    {
197
        if (!$this->item) {
198
            return false;
199
        }
200
201
        if (!$this->itemIterator) {
202
            // TemplateIterator provides methods for extracting the count and iterator directly
203
            if ($this->item instanceof TemplateIterator) {
204
                $this->itemIterator = $this->item->getTemplateIterator();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->item->getTemplateIterator() of type object<SilverStripe\View\Iterator> or object<SilverStripe\View\Generator> is incompatible with the declared type object<Iterator> of property $itemIterator.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
205
                $this->itemIteratorTotal = $this->item->getTemplateIteratorCount();
206
            } else {
207
                // Item may be an array or a regular IteratorAggregate
208
                if (is_array($this->item)) {
209
                    $this->itemIterator = new ArrayIterator($this->item);
210
                } else {
211
                    $this->itemIterator = $this->item->getIterator();
212
                }
213
214
                // If the item implements Countable, use that to fetch the count, otherwise we have to inspect the
215
                // iterator and then rewind it
216
                if ($this->item instanceof Countable) {
217
                    $this->itemIteratorTotal = count($this->item);
218
                } else {
219
                    $this->itemIteratorTotal = iterator_count($this->itemIterator);
220
                    $this->itemIterator->rewind();
221
                }
222
            }
223
224
            $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator;
225
            $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal;
226
        } else {
227
            $this->itemIterator->next();
228
        }
229
230
        $this->resetLocalScope();
231
232
        if (!$this->itemIterator->valid()) {
233
            return false;
234
        }
235
        return $this->itemIterator->key();
236
    }
237
238
    public function __call($name, $arguments)
239
    {
240
        $on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
241
        $retval = $on ? call_user_func_array(array($on, $name), $arguments) : null;
242
243
        $this->resetLocalScope();
244
        return $retval;
245
    }
246
247
    /**
248
     * @return array
249
     */
250
    protected function getItemStack()
251
    {
252
        return $this->itemStack;
253
    }
254
255
    /**
256
     * @param array
257
     */
258
    protected function setItemStack(array $stack)
259
    {
260
        $this->itemStack = $stack;
261
    }
262
263
    /**
264
     * @return int|null
265
     */
266
    protected function getUpIndex()
267
    {
268
        return $this->upIndex;
269
    }
270
}
271