Passed
Push — master ( 3f5943...604c83 )
by Damian
07:59
created

SSViewer_Scope::next()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 43
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 21
nc 11
nop 0
dl 0
loc 43
rs 8.439
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
    const ITEM = 0;
31
    const ITEM_ITERATOR = 1;
32
    const ITEM_ITERATOR_TOTAL = 2;
33
    const POP_INDEX = 3;
34
    const UP_INDEX = 4;
35
    const CURRENT_INDEX = 5;
36
    const ITEM_OVERLAY = 6;
37
38
    /**
39
     * The stack of previous items ("scopes") - an indexed array of: item, item iterator, item iterator total,
40
     * pop index, up index, current index & parent overlay
41
     *
42
     * @var array
43
     */
44
    private $itemStack = [];
45
46
    /**
47
     * The current "global" item (the one any lookup starts from)
48
     *
49
     * @var object
50
     */
51
    protected $item;
52
53
    /**
54
     * If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
55
     *
56
     * @var Iterator
57
     */
58
    protected $itemIterator;
59
60
    /**
61
     * Total number of items in the iterator
62
     *
63
     * @var int
64
     */
65
    protected $itemIteratorTotal;
66
67
    /**
68
     * A pointer into the item stack for the item that will become the active scope on the next pop call
69
     *
70
     * @var int
71
     */
72
    private $popIndex;
73
74
    /**
75
     * A pointer into the item stack for which item is "up" from this one
76
     *
77
     * @var int
78
     */
79
    private $upIndex;
80
81
    /**
82
     * A pointer into the item stack for which the active item (or null if not in stack yet)
83
     *
84
     * @var int
85
     */
86
    private $currentIndex;
87
88
    /**
89
     * A store of copies of the main item stack, so it's preserved during a lookup from local scope
90
     * (which may push/pop items to/from the main item stack)
91
     *
92
     * @var array
93
     */
94
    private $localStack = [];
95
96
    /**
97
     * The index of the current item in the main item stack, so we know where to restore the scope
98
     * stored in $localStack.
99
     *
100
     * @var int
101
     */
102
    private $localIndex = 0;
103
104
    /**
105
     * @var object $item
106
     * @var SSViewer_Scope $inheritedScope
107
     */
108
    public function __construct($item, SSViewer_Scope $inheritedScope = null)
109
    {
110
        $this->item = $item;
111
112
        $this->itemIterator = ($inheritedScope) ? $inheritedScope->itemIterator : null;
113
        $this->itemIteratorTotal = ($inheritedScope) ? $inheritedScope->itemIteratorTotal : 0;
114
        $this->itemStack[] = [$this->item, $this->itemIterator, $this->itemIteratorTotal, null, null, 0];
115
    }
116
117
    /**
118
     * Returns the current "active" item
119
     *
120
     * @return object
121
     */
122
    public function getItem()
123
    {
124
        return $this->itemIterator ? $this->itemIterator->current() : $this->item;
125
    }
126
127
    /**
128
     * Called at the start of every lookup chain by SSTemplateParser to indicate a new lookup from local scope
129
     *
130
     * @return self
131
     */
132
    public function locally()
133
    {
134
        list(
135
            $this->item,
136
            $this->itemIterator,
137
            $this->itemIteratorTotal,
138
            $this->popIndex,
139
            $this->upIndex,
140
            $this->currentIndex
141
        ) = $this->itemStack[$this->localIndex];
142
143
        // Remember any  un-completed (resetLocalScope hasn't been called) lookup chain. Even if there isn't an
144
        // un-completed chain we need to store an empty item, as resetLocalScope doesn't know the difference later
145
        $this->localStack[] = array_splice($this->itemStack, $this->localIndex + 1);
146
147
        return $this;
148
    }
149
150
    /**
151
     * Reset the local scope - restores saved state to the "global" item stack. Typically called after
152
     * a lookup chain has been completed
153
     */
154
    public function resetLocalScope()
155
    {
156
        // Restore previous un-completed lookup chain if set
157
        $previousLocalState = $this->localStack ? array_pop($this->localStack) : null;
158
        array_splice($this->itemStack, $this->localIndex + 1, count($this->itemStack), $previousLocalState);
159
160
        list(
161
            $this->item,
162
            $this->itemIterator,
163
            $this->itemIteratorTotal,
164
            $this->popIndex,
165
            $this->upIndex,
166
            $this->currentIndex
167
        ) = end($this->itemStack);
168
    }
169
170
    /**
171
     * @param string $name
172
     * @param array $arguments
173
     * @param bool $cache
174
     * @param string $cacheName
175
     * @return mixed
176
     */
177
    public function getObj($name, $arguments = [], $cache = false, $cacheName = null)
178
    {
179
        $on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
180
        return $on->obj($name, $arguments, $cache, $cacheName);
181
    }
182
183
    /**
184
     * @param string $name
185
     * @param array $arguments
186
     * @param bool $cache
187
     * @param string $cacheName
188
     * @return $this
189
     */
190
    public function obj($name, $arguments = [], $cache = false, $cacheName = null)
191
    {
192
        switch ($name) {
193
            case 'Up':
194
                if ($this->upIndex === null) {
195
                    throw new \LogicException('Up called when we\'re already at the top of the scope');
196
                }
197
198
                list(
199
                    $this->item,
200
                    $this->itemIterator,
201
                    $this->itemIteratorTotal,
202
                    /* dud */,
203
                    $this->upIndex,
204
                    $this->currentIndex
205
                ) = $this->itemStack[$this->upIndex];
206
                break;
207
            case 'Top':
208
                list(
209
                    $this->item,
210
                    $this->itemIterator,
211
                    $this->itemIteratorTotal,
212
                    /* dud */,
213
                    $this->upIndex,
214
                    $this->currentIndex
215
                ) = $this->itemStack[0];
216
                break;
217
            default:
218
                $this->item = $this->getObj($name, $arguments, $cache, $cacheName);
219
                $this->itemIterator = null;
220
                $this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack) - 1;
221
                $this->currentIndex = count($this->itemStack);
222
                break;
223
        }
224
225
        $this->itemStack[] = [
226
            $this->item,
227
            $this->itemIterator,
228
            $this->itemIteratorTotal,
229
            null,
230
            $this->upIndex,
231
            $this->currentIndex
232
        ];
233
        return $this;
234
    }
235
236
    /**
237
     * Gets the current object and resets the scope.
238
     *
239
     * @return object
240
     */
241
    public function self()
242
    {
243
        $result = $this->itemIterator ? $this->itemIterator->current() : $this->item;
244
        $this->resetLocalScope();
245
246
        return $result;
247
    }
248
249
    /**
250
     * Jump to the last item in the stack, called when a new item is added before a loop/with
251
     *
252
     * @return self
253
     */
254
    public function pushScope()
255
    {
256
        $newLocalIndex = count($this->itemStack) - 1;
257
258
        $this->popIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::POP_INDEX] = $this->localIndex;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
259
        $this->localIndex = $newLocalIndex;
260
261
        // $Up now becomes the parent scope - the parent of the current <% loop %> or <% with %>
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected EOF on line 261 at column 82
Loading history...
262
        $this->upIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::UP_INDEX] = $this->popIndex;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
263
264
        // We normally keep any previous itemIterator around, so local $Up calls reference the right element. But
265
        // once we enter a new global scope, we need to make sure we use a new one
266
        $this->itemIterator = $this->itemStack[$newLocalIndex][SSViewer_Scope::ITEM_ITERATOR] = null;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
267
268
        return $this;
269
    }
270
271
    /**
272
     * Jump back to "previous" item in the stack, called after a loop/with block
273
     *
274
     * @return self
275
     */
276
    public function popScope()
277
    {
278
        $this->localIndex = $this->popIndex;
279
        $this->resetLocalScope();
280
281
        return $this;
282
    }
283
284
    /**
285
     * Fast-forwards the current iterator to the next item
286
     *
287
     * @return mixed
288
     */
289
    public function next()
290
    {
291
        if (!$this->item) {
292
            return false;
293
        }
294
295
        if (!$this->itemIterator) {
296
            // Note: it is important that getIterator() is called before count() as implemenations may rely on
297
            // this to efficiently get both the number of records and an iterator (e.g. DataList does this)
298
299
            // Item may be an array or a regular IteratorAggregate
300
            if (is_array($this->item)) {
301
                $this->itemIterator = new ArrayIterator($this->item);
302
            } else {
303
                $this->itemIterator = $this->item->getIterator();
304
305
                // This will execute code in a generator up to the first yield. For example, this ensures that
306
                // DataList::getIterator() is called before Datalist::count()
307
                $this->itemIterator->rewind();
308
            }
309
310
            // If the item implements Countable, use that to fetch the count, otherwise we have to inspect the
311
            // iterator and then rewind it.
312
            if ($this->item instanceof Countable) {
313
                $this->itemIteratorTotal = count($this->item);
314
            } else {
315
                $this->itemIteratorTotal = iterator_count($this->itemIterator);
316
                $this->itemIterator->rewind();
317
            }
318
319
            $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
320
            $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
321
        } else {
322
            $this->itemIterator->next();
323
        }
324
325
        $this->resetLocalScope();
326
327
        if (!$this->itemIterator->valid()) {
328
            return false;
329
        }
330
331
        return $this->itemIterator->key();
332
    }
333
334
    /**
335
     * @param string $name
336
     * @param array $arguments
337
     * @return mixed
338
     */
339
    public function __call($name, $arguments)
340
    {
341
        $on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
342
        $retval = $on ? $on->$name(...$arguments) : null;
343
344
        $this->resetLocalScope();
345
        return $retval;
346
    }
347
348
    /**
349
     * @return array
350
     */
351
    protected function getItemStack()
352
    {
353
        return $this->itemStack;
354
    }
355
356
    /**
357
     * @param array
358
     */
359
    protected function setItemStack(array $stack)
360
    {
361
        $this->itemStack = $stack;
362
    }
363
364
    /**
365
     * @return int|null
366
     */
367
    protected function getUpIndex()
368
    {
369
        return $this->upIndex;
370
    }
371
}
372