Passed
Push — master ( 288a96...964b09 )
by Daniel
10:30
created

src/View/SSViewer_Scope.php (2 issues)

1
<?php
2
3
namespace SilverStripe\View;
4
5
use ArrayIterator;
6
use Iterator;
7
8
/**
9
 * This tracks the current scope for an SSViewer instance. It has three goals:
10
 *   - Handle entering & leaving sub-scopes in loops and withs
11
 *   - Track Up and Top
12
 *   - (As a side effect) Inject data that needs to be available globally (used to live in ViewableData)
13
 *
14
 * In order to handle up, rather than tracking it using a tree, which would involve constructing new objects
15
 * for each step, we use indexes into the itemStack (which already has to exist).
16
 *
17
 * Each item has three indexes associated with it
18
 *
19
 *   - Pop. Which item should become the scope once the current scope is popped out of
20
 *   - Up. Which item is up from this item
21
 *   - Current. Which item is the first time this object has appeared in the stack
22
 *
23
 * We also keep the index of the current starting point for lookups. A lookup is a sequence of obj calls -
24
 * when in a loop or with tag the end result becomes the new scope, but for injections, we throw away the lookup
25
 * and revert back to the original scope once we've got the value we're after
26
 */
27
class SSViewer_Scope
28
{
29
    const ITEM = 0;
30
    const ITEM_ITERATOR = 1;
31
    const ITEM_ITERATOR_TOTAL = 2;
32
    const POP_INDEX = 3;
33
    const UP_INDEX = 4;
34
    const CURRENT_INDEX = 5;
35
    const ITEM_OVERLAY = 6;
36
37
    /**
38
     * The stack of previous items ("scopes") - an indexed array of: item, item iterator, item iterator total,
39
     * pop index, up index, current index & parent overlay
40
     *
41
     * @var array
42
     */
43
    private $itemStack = [];
44
45
    /**
46
     * The current "global" item (the one any lookup starts from)
47
     *
48
     * @var object
49
     */
50
    protected $item;
51
52
    /**
53
     * If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
54
     *
55
     * @var Iterator
56
     */
57
    protected $itemIterator;
58
59
    /**
60
     * Total number of items in the iterator
61
     *
62
     * @var int
63
     */
64
    protected $itemIteratorTotal;
65
66
    /**
67
     * A pointer into the item stack for the item that will become the active scope on the next pop call
68
     *
69
     * @var int
70
     */
71
    private $popIndex;
72
73
    /**
74
     * A pointer into the item stack for which item is "up" from this one
75
     *
76
     * @var int
77
     */
78
    private $upIndex;
79
80
    /**
81
     * A pointer into the item stack for which the active item (or null if not in stack yet)
82
     *
83
     * @var int
84
     */
85
    private $currentIndex;
86
87
    /**
88
     * A store of copies of the main item stack, so it's preserved during a lookup from local scope
89
     * (which may push/pop items to/from the main item stack)
90
     *
91
     * @var array
92
     */
93
    private $localStack = [];
94
95
    /**
96
     * The index of the current item in the main item stack, so we know where to restore the scope
97
     * stored in $localStack.
98
     *
99
     * @var int
100
     */
101
    private $localIndex = 0;
102
103
    /**
104
     * @var object $item
105
     * @var SSViewer_Scope $inheritedScope
106
     */
107
    public function __construct($item, SSViewer_Scope $inheritedScope = null)
108
    {
109
        $this->item = $item;
110
111
        $this->itemIterator = ($inheritedScope) ? $inheritedScope->itemIterator : null;
112
        $this->itemIteratorTotal = ($inheritedScope) ? $inheritedScope->itemIteratorTotal : 0;
113
        $this->itemStack[] = [$this->item, $this->itemIterator, $this->itemIteratorTotal, null, null, 0];
114
    }
115
116
    /**
117
     * Returns the current "active" item
118
     *
119
     * @return object
120
     */
121
    public function getItem()
122
    {
123
        return $this->itemIterator ? $this->itemIterator->current() : $this->item;
124
    }
125
126
    /**
127
     * Called at the start of every lookup chain by SSTemplateParser to indicate a new lookup from local scope
128
     *
129
     * @return self
130
     */
131
    public function locally()
132
    {
133
        list(
134
            $this->item,
135
            $this->itemIterator,
136
            $this->itemIteratorTotal,
137
            $this->popIndex,
138
            $this->upIndex,
139
            $this->currentIndex
140
        ) = $this->itemStack[$this->localIndex];
141
142
        // Remember any  un-completed (resetLocalScope hasn't been called) lookup chain. Even if there isn't an
143
        // un-completed chain we need to store an empty item, as resetLocalScope doesn't know the difference later
144
        $this->localStack[] = array_splice($this->itemStack, $this->localIndex + 1);
145
146
        return $this;
147
    }
148
149
    /**
150
     * Reset the local scope - restores saved state to the "global" item stack. Typically called after
151
     * a lookup chain has been completed
152
     */
153
    public function resetLocalScope()
154
    {
155
        // Restore previous un-completed lookup chain if set
156
        $previousLocalState = $this->localStack ? array_pop($this->localStack) : null;
157
        array_splice($this->itemStack, $this->localIndex + 1, count($this->itemStack), $previousLocalState);
158
159
        list(
160
            $this->item,
161
            $this->itemIterator,
162
            $this->itemIteratorTotal,
163
            $this->popIndex,
164
            $this->upIndex,
165
            $this->currentIndex
166
        ) = end($this->itemStack);
167
    }
168
169
    /**
170
     * @param string $name
171
     * @param array $arguments
172
     * @param bool $cache
173
     * @param string $cacheName
174
     * @return mixed
175
     */
176
    public function getObj($name, $arguments = [], $cache = false, $cacheName = null)
177
    {
178
        $on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
179
        return $on->obj($name, $arguments, $cache, $cacheName);
180
    }
181
182
    /**
183
     * @param string $name
184
     * @param array $arguments
185
     * @param bool $cache
186
     * @param string $cacheName
187
     * @return $this
188
     */
189
    public function obj($name, $arguments = [], $cache = false, $cacheName = null)
190
    {
191
        switch ($name) {
192
            case 'Up':
193
                if ($this->upIndex === null) {
194
                    user_error('Up called when we\'re already at the top of the scope', E_USER_ERROR);
195
                }
196
197
                list(
198
                    $this->item,
199
                    $this->itemIterator,
200
                    $this->itemIteratorTotal,
201
                    /* dud */,
202
                    $this->upIndex,
203
                    $this->currentIndex
204
                ) = $this->itemStack[$this->upIndex];
205
                break;
206
            case 'Top':
207
                list(
208
                    $this->item,
209
                    $this->itemIterator,
210
                    $this->itemIteratorTotal,
211
                    /* dud */,
212
                    $this->upIndex,
213
                    $this->currentIndex
214
                ) = $this->itemStack[0];
215
                break;
216
            default:
217
                $this->item = $this->getObj($name, $arguments, $cache, $cacheName);
218
                $this->itemIterator = null;
219
                $this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack) - 1;
220
                $this->currentIndex = count($this->itemStack);
221
                break;
222
        }
223
224
        $this->itemStack[] = [
225
            $this->item,
226
            $this->itemIterator,
227
            $this->itemIteratorTotal,
228
            null,
229
            $this->upIndex,
230
            $this->currentIndex
231
        ];
232
        return $this;
233
    }
234
235
    /**
236
     * Gets the current object and resets the scope.
237
     *
238
     * @return object
239
     */
240
    public function self()
241
    {
242
        $result = $this->itemIterator ? $this->itemIterator->current() : $this->item;
243
        $this->resetLocalScope();
244
245
        return $result;
246
    }
247
248
    /**
249
     * Jump to the last item in the stack, called when a new item is added before a loop/with
250
     *
251
     * @return self
252
     */
253
    public function pushScope()
254
    {
255
        $newLocalIndex = count($this->itemStack) - 1;
256
257
        $this->popIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::POP_INDEX] = $this->localIndex;
258
        $this->localIndex = $newLocalIndex;
259
260
        // We normally keep any previous itemIterator around, so local $Up calls reference the right element. But
261
        // once we enter a new global scope, we need to make sure we use a new one
262
        $this->itemIterator = $this->itemStack[$newLocalIndex][SSViewer_Scope::ITEM_ITERATOR] = null;
263
264
        return $this;
265
    }
266
267
    /**
268
     * Jump back to "previous" item in the stack, called after a loop/with block
269
     *
270
     * @return self
271
     */
272
    public function popScope()
273
    {
274
        $this->localIndex = $this->popIndex;
275
        $this->resetLocalScope();
276
277
        return $this;
278
    }
279
280
    /**
281
     * Fast-forwards the current iterator to the next item
282
     *
283
     * @return mixed
284
     */
285
    public function next()
286
    {
287
        if (!$this->item) {
288
            return false;
289
        }
290
291
        if (!$this->itemIterator) {
292
            if (is_array($this->item)) {
293
                $this->itemIterator = new ArrayIterator($this->item);
294
            } else {
295
                $this->itemIterator = $this->item->getIterator();
296
            }
297
298
            $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator;
0 ignored issues
show
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...
299
            $this->itemIteratorTotal = iterator_count($this->itemIterator); // Count the total number of items
300
            $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal;
0 ignored issues
show
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...
301
            $this->itemIterator->rewind();
302
        } else {
303
            $this->itemIterator->next();
304
        }
305
306
        $this->resetLocalScope();
307
308
        if (!$this->itemIterator->valid()) {
309
            return false;
310
        }
311
312
        return $this->itemIterator->key();
313
    }
314
315
    /**
316
     * @param string $name
317
     * @param array $arguments
318
     * @return mixed
319
     */
320
    public function __call($name, $arguments)
321
    {
322
        $on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
323
        $retval = $on ? $on->$name(...$arguments) : null;
324
325
        $this->resetLocalScope();
326
        return $retval;
327
    }
328
329
    /**
330
     * @return array
331
     */
332
    protected function getItemStack()
333
    {
334
        return $this->itemStack;
335
    }
336
337
    /**
338
     * @param array
339
     */
340
    protected function setItemStack(array $stack)
341
    {
342
        $this->itemStack = $stack;
343
    }
344
345
    /**
346
     * @return int|null
347
     */
348
    protected function getUpIndex()
349
    {
350
        return $this->upIndex;
351
    }
352
}
353