Collection::append()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
  declare(strict_types=1);
4
5
  namespace Funivan\PhpTokenizer;
6
7
  use Funivan\PhpTokenizer\Exception\Exception;
8
  use Funivan\PhpTokenizer\Query\Query;
9
10
  /**
11
   *
12
   */
13
  class Collection implements \Iterator, \ArrayAccess, \Countable {
14
15
    /**
16
     * @var int
17
     */
18
    protected $position = 0;
19
20
    /**
21
     * Array of objects
22
     *
23
     * @var array
24
     */
25
    protected $items = [];
26
27
28
    /**
29
     * @var string
30
     */
31
    protected $initialContentHash;
32
33
34
    /**
35
     * @param array $items
36
     */
37 480
    public function __construct(array $items = []) {
38 480
      if (!empty($items)) {
39 465
        $this->setItems($items);
40
      }
41 480
      $this->storeContentHash();
42 480
    }
43
44
45
    /**
46
     *
47
     */
48
    public function __clone() {
49
      $items = [];
50
      foreach ($this->items as $item) {
51
        $items[] = $item;
52
      }
53
      $this->setItems($items);
54
    }
55
56
57
    /**
58
     * Extract each value from token
59
     *
60
     * @return string
61
     */
62 168
    public function __toString() {
63 168
      return $this->assemble();
64
    }
65
66
67
    /**
68
     *
69
     * @param string $string
70
     * @return Collection
71
     * @throws Exception
72
     */
73 465
    public static function createFromString($string) : Collection {
74 465
      $tokens = Helper::getTokensFromString($string);
75 465
      return new Collection($tokens);
76
    }
77
78
79
    /**
80
     * Return number of items in this collection
81
     *
82
     * @return int
83
     */
84 87
    public function count() {
85 87
      return count($this->items);
86
    }
87
88
89
    /**
90
     * Add one item to begin of collection
91
     * This item is accessible via `$collection->getFirst();`
92
     *
93
     * @param $item
94
     * @return $this
95
     */
96
    public function prepend(Token $item) : self {
97
      array_unshift($this->items, $item);
98
      return $this;
99
    }
100
101
102
    /**
103
     * Add one item to the end of collection
104
     * This item is accessible via `$collection->getLast();`
105
     *
106
     * @param $item
107
     * @return $this
108
     */
109 255
    public function append(Token $item) : self {
110 255
      $this->items[] = $item;
111 255
      return $this;
112
    }
113
114
115
    /**
116
     * @param int $index
117
     * @param array $items
118
     * @return $this
119
     * @throws \InvalidArgumentException
120
     */
121 6
    public function addAfter($index, $items) : self {
122 6
      if (!is_array($items)) {
123
        throw new \InvalidArgumentException('You can add after only array of items');
124
      }
125
126 6
      foreach ($items as $item) {
127 6
        if (!($item instanceof Token)) {
128 6
          throw new \InvalidArgumentException('Expect array of tokens. Token[]');
129
        }
130
      }
131
132 6
      if (!is_int($index)) {
133
        throw new \InvalidArgumentException('Invalid type of index. Must be integer');
134
      }
135
136 6
      $offset = $index + 1;
137 6
      $firstPart = array_slice($this->items, 0, $offset);
138 6
      $secondPart = array_slice($this->items, $offset);
139 6
      $this->items = array_merge($firstPart, $items, $secondPart);
140 6
      return $this;
141
    }
142
143
144
    /**
145
     * Truncate current list of items and add new
146
     *
147
     * @param array $items
148
     * @return $this
149
     */
150 468
    public function setItems(array $items) : self {
151 468
      foreach ($items as $item) {
152 468
        if (!($item instanceof Token)) {
153 468
          throw new \InvalidArgumentException('Expect array of tokens. Token[]');
154
        }
155
      }
156
157 465
      $this->items = $items;
158 465
      $this->rewind();
159 465
      return $this;
160
    }
161
162
163
    /**
164
     * Remove part of items from collection
165
     * Works as array_slice
166
     *
167
     *
168
     * @param int $offset
169
     * @param int|null $length
170
     * @return $this
171
     */
172 75
    public function slice(int $offset, int $length = null) : self {
173 75
      $this->items = array_slice($this->items, $offset, $length);
174 75
      return $this;
175
    }
176
177
178
    /**
179
     * Take part of items and return new collection
180
     * Works as array_slice
181
     * At this point items in 2 collection is same
182
     *
183
     * @param int $offset
184
     * @param null $length
185
     * @return Collection
186
     */
187 30
    public function extractItems(int $offset, $length = null) : Collection {
188 30
      $items = array_slice($this->items, $offset, $length);
189 30
      return new Collection($items);
190
    }
191
192
193
    /**
194
     * Rewind current collection
195
     */
196 480
    public function rewind() {
197 480
      $this->position = 0;
198 480
      $this->items = array_values($this->items);
199 480
    }
200
201
202
    /**
203
     * Return last item from collection
204
     *
205
     * @return Token|null
206
     */
207 159
    public function getLast() {
208 159
      $lastToken = end($this->items);
209 159
      return ($lastToken !== false) ? $lastToken : null;
210
    }
211
212
213
    /**
214
     * Return first item from collection
215
     * @return Token|null
216
     */
217 78
    public function getFirst() {
218 78
      $first = reset($this->items);
219 78
      return $first !== false ? $first : null;
220
    }
221
222
223
    /**
224
     * Return next item from current
225
     * Also can return item with position from current + $step
226
     *
227
     * @param int $step
228
     * @return Token
229
     */
230 6
    public function getNext(int $step = 1) : Token {
231 6
      $position = ($this->position + $step);
232 6
      return $this->items[$position] ?? new Token();
233
    }
234
235
236
    /**
237
     * Return previous item
238
     * Also can return previous from current position + $step
239
     *
240
     * @param int $step
241
     * @return Token
242
     */
243 3
    public function getPrevious(int $step = 1) : Token {
244 3
      $position = ($this->position - $step);
245 3
      return ($this->items[$position]) ?? new Token();
246
    }
247
248
249
    /**
250
     * Return current item in collection
251
     *
252
     * @return Token
253
     */
254 465
    public function current() {
255 465
      return $this->items[$this->position];
256
    }
257
258
259
    /**
260
     * Return current position
261
     *
262
     * @return int
263
     */
264 321
    public function key() {
265 321
      return $this->position;
266
    }
267
268
269
    /**
270
     * Switch to next position
271
     */
272 465
    public function next() {
273 465
      ++$this->position;
274 465
    }
275
276
277
    /**
278
     * Check if item exist in current position
279
     *
280
     * @return bool
281
     */
282 480
    public function valid() : bool {
283 480
      return isset($this->items[$this->position]);
284
    }
285
286
287
    /**
288
     * Add item to the end or modify item with given key
289
     *
290
     * @param int|null $offset
291
     * @param Token $item
292
     * @return $this
293
     */
294 144
    public function offsetSet($offset, $item) {
295 144
      if (!($item instanceof Token)) {
296 3
        throw new \InvalidArgumentException('Expect Token object');
297
      }
298
299 144
      if (null === $offset) {
300 141
        $this->append($item);
301 141
        return $this;
302
      }
303
304 21
      if (!is_int($offset)) {
305
        throw new \InvalidArgumentException('Invalid type of index. Must be integer');
306
      }
307 21
      $this->items[$offset] = $item;
308
309 21
      return $this;
310
    }
311
312
313
    /**
314
     * Check if item with given offset exists
315
     *
316
     * @param int $offset
317
     * @return bool
318
     */
319 57
    public function offsetExists($offset) {
320 57
      return isset($this->items[$offset]);
321
    }
322
323
324
    /**
325
     * Remove item from collection
326
     *
327
     * @param int $offset
328
     */
329
    public function offsetUnset($offset) {
330
      unset($this->items[$offset]);
331
    }
332
333
334
    /**
335
     * Get item from collection
336
     *
337
     * @param int $offset
338
     * @return Token|null
339
     */
340 360
    public function offsetGet($offset) {
341 360
      return isset($this->items[$offset]) ? $this->items[$offset] : null;
342
    }
343
344
345
    /**
346
     * Return array of items connected to this collection
347
     *
348
     * Rewrite this method in you class
349
     *
350
     * <code>
351
     * foreach($collection->getTokens() as $item){
352
     *  echo get_class($item)."\n;
353
     * }
354
     * </code>
355
     * @return Token[]
356
     */
357 183
    public function getTokens() : array {
358 183
      return $this->items;
359
    }
360
361
362
    /**
363
     * Iterate over objects in collection
364
     *
365
     * <code>
366
     * $collection->each(function($item, $index, $collection){
367
     *    if ( $index > 0 ) {
368
     *      $item->remove();
369
     *    }
370
     * })
371
     * </code>
372
     *
373
     * @param callable $callback
374
     * @return $this
375
     * @throws \InvalidArgumentException
376
     */
377 3
    public function each(callable $callback) : self {
378
379 3
      if (!is_callable($callback)) {
380
        throw new \InvalidArgumentException('Invalid callback function');
381
      }
382
383 3
      foreach ($this->getTokens() as $index => $item) {
384 3
        call_user_func($callback, $item, $index, $this);
385
      }
386
387 3
      $this->rewind();
388
389 3
      return $this;
390
    }
391
392
393
    /**
394
     * Remove all tokens in collection
395
     *
396
     * @return $this
397
     */
398 30
    public function remove() : self {
399 30
      foreach ($this as $token) {
400 30
        $token->remove();
401
      }
402 30
      return $this;
403
    }
404
405
406
    /**
407
     * @param Query $query
408
     * @return Collection
409
     */
410 15
    public function find(Query $query) {
411 15
      $finder = new TokenFinder($this);
412 15
      return $finder->find($query);
413
    }
414
415
416
    /**
417
     * Remove all invalid tokens in collection
418
     * Refresh index.
419
     *
420
     * @return Collection
421
     */
422 33
    public function refresh() : self {
423 33
      $string = $this->assemble();
424 33
      $this->cleanCollection();
425
426 33
      $tokens = Helper::getTokensFromString($string);
427 33
      $this->setItems($tokens);
428
429 33
      $this->rewind();
430 33
      return $this;
431
    }
432
433
434
    /**
435
     * @param Token $tokenStart
436
     * @param Token $tokenEnd
437
     * @return Collection
438
     */
439 177
    public function extractByTokens(Token $tokenStart, Token $tokenEnd) : Collection {
440
441 177
      $collection = new Collection();
442 177
      $startIndex = $tokenStart->getIndex();
443 177
      $endIndex = $tokenEnd->getIndex();
444
445 177
      foreach ($this->getTokens() as $token) {
446 177
        if ($token->getIndex() >= $startIndex and $token->getIndex() <= $endIndex) {
447 177
          $collection->append($token);
448
        }
449
      }
450
451
452 177
      return $collection;
453
    }
454
455
456
    /**
457
     * @return $this
458
     */
459 480
    public function storeContentHash() : self {
460 480
      $this->initialContentHash = $this->getContentHash();
461 480
      return $this;
462
    }
463
464
465
    /**
466
     * @return bool
467
     */
468 9
    public function isChanged() : bool {
469 9
      return ($this->getContentHash() !== $this->initialContentHash);
470
    }
471
472
473
    /**
474
     * @return string
475
     */
476 480
    private function getContentHash() : string {
477 480
      return md5($this->assemble());
478
    }
479
480
481
    /**
482
     * @return string
483
     */
484 480
    public function assemble() : string {
485 480
      $string = '';
486
      /** @var Token $token */
487 480
      foreach ($this as $token) {
488 465
        if (!$token->isValid()) {
489 90
          continue;
490
        }
491 465
        $string .= $token->getValue();
492
      }
493
494 480
      return $string;
495
    }
496
497
498
    /**
499
     * Remove invalid tokens from collection
500
     *
501
     * @return $this
502
     */
503 33
    protected function cleanCollection() : self {
504 33
      foreach ($this as $index => $token) {
505 33
        if ($token->isValid()) {
506 33
          continue;
507
        }
508 3
        unset($this->items[$index]);
509
      }
510
511 33
      return $this;
512
    }
513
514
  }