Completed
Push — master ( 42a9f8...195519 )
by Shcherbak
38:40 queued 23:39
created

Collection::append()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 2
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 450
    public function __construct(array $items = []) {
38 450
      if (!empty($items)) {
39 450
        $this->setItems($items);
40 450
      }
41
      $this->storeContentHash();
42
    }
43
44
45
    /**
46
     *
47
     */
48 144
    public function __clone() {
49 144
      $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 435
     *
60 435
     * @return string
61 435
     */
62
    public function __toString() {
63
      return $this->assemble();
64
    }
65
66
67
    /**
68 9
     *
69 9
     * @param string $string
70
     * @return Collection
71
     * @throws Exception
72
     */
73
    public static function createFromString($string) : Collection {
74
      $tokens = Helper::getTokensFromString($string);
75
      return new Collection($tokens);
76 450
    }
77 450
78
79
    /**
80
     * Return number of items in this collection
81
     *
82
     * @return int
83
     */
84
    public function count() {
85
      return count($this->items);
86 441
    }
87 441
88
89
    /**
90
     * Add one item to begin of collection
91
     * This item is accessible via `$collection->getFirst();`
92
     *
93
     * @param $item
94 450
     * @return $this
95 450
     */
96
    public function prepend(Token $item) : self {
97 450
      array_unshift($this->items, $item);
98 435
      return $this;
99 66
    }
100
101 435
102 300
    /**
103
     * Add one item to the end of collection
104 450
     * This item is accessible via `$collection->getLast();`
105
     *
106
     * @param $item
107
     * @return $this
108
     */
109
    public function append(Token $item) : self {
110
      $this->items[] = $item;
111
      return $this;
112
    }
113
114 9
115 9
    /**
116 9
     * @param int $index
117
     * @param array $items
118 9
     * @return $this
119 9
     * @throws \InvalidArgumentException
120
     */
121 9
    public function addAfter($index, $items) : self {
122 9
      if (!is_array($items)) {
123
        throw new \InvalidArgumentException('You can add after only array of items');
124
      }
125
126
      foreach ($items as $item) {
127
        if (!($item instanceof Token)) {
128
          throw new \InvalidArgumentException('Expect array of tokens. Token[]');
129
        }
130 3
      }
131 3
132 3
      if (!is_int($index)) {
133 3
        throw new \InvalidArgumentException('Invalid type of index. Must be integer');
134 2
      }
135 3
136
      $offset = $index + 1;
137
      $firstPart = array_slice($this->items, 0, $offset);
138
      $secondPart = array_slice($this->items, $offset);
139
      $this->items = array_merge($firstPart, $items, $secondPart);
140
      return $this;
141
    }
142
143 6
144 6
    /**
145 6
     * Truncate current list of items and add new
146 6
     *
147 4
     * @param array $items
148 6
     * @return $this
149
     */
150
    public function setItems(array $items) : self {
151
      foreach ($items as $item) {
152
        if (!($item instanceof Token)) {
153
          throw new \InvalidArgumentException('Expect array of tokens. Token[]');
154
        }
155
      }
156
157
      $this->items = $items;
158
      $this->rewind();
159
      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 9
     * @return $this
171 9
     */
172 9
    public function slice(int $offset, int $length = null) : self {
173 9
      $this->items = array_slice($this->items, $offset, $length);
174
      return $this;
175 3
    }
176 6
177
178 9
    /**
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
    public function extractItems(int $offset, $length = null) : Collection {
188 30
      $items = array_slice($this->items, $offset, $length);
189 30
      return new Collection($items);
190 30
    }
191 20
192 30
193
    /**
194
     * Rewind current collection
195
     */
196
    public function rewind() {
197
      $this->position = 0;
198
      $this->items = array_values($this->items);
199
    }
200
201 177
202
    /**
203 177
     * Return last item from collection
204 177
     *
205 177
     * @return Token|null
206
     */
207 177
    public function getLast() {
208 177
      $lastToken = end($this->items);
209 177
      return ($lastToken !== false) ? $lastToken : null;
210 118
    }
211 118
212
213
    /**
214 177
     * Return first item from collection
215
     * @return Token|null
216
     */
217
    public function getFirst() {
218
      $first = reset($this->items);
219
      return $first !== false ? $first : null;
220
    }
221
222 15
223 15
    /**
224 15
     * 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
    public function getNext(int $step = 1) : Token {
231 450
      $position = ($this->position + $step);
232 450
      return $this->items[$position] ?? new Token();
233 450
    }
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
    public function getPrevious(int $step = 1) : Token {
244
      $position = ($this->position - $step);
245
      return ($this->items[$position]) ?? new Token();
246
    }
247
248
249
    /**
250
     * Return current item in collection
251
     *
252
     * @return Token
253
     */
254
    public function current() {
255
      return $this->items[$this->position];
256
    }
257
258
259
    /**
260
     * Return current position
261
     *
262
     * @return int
263
     */
264
    public function key() {
265
      return $this->position;
266
    }
267
268
269
    /**
270
     * Switch to next position
271
     */
272
    public function next() {
273
      ++$this->position;
274
    }
275
276
277
    /**
278
     * Check if item exist in current position
279
     *
280
     * @return bool
281
     */
282
    public function valid() : bool {
283
      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
    public function offsetSet($offset, $item) {
295
      if (!($item instanceof Token)) {
296
        throw new \InvalidArgumentException('Expect Token object');
297
      }
298
299
      if (is_null($offset)) {
300
        $this->append($item);
301
        return $this;
302
      }
303
304
      if (!is_int($offset)) {
305
        throw new \InvalidArgumentException('Invalid type of index. Must be integer');
306
      }
307
      $this->items[$offset] = $item;
308
309
      return $this;
310
    }
311
312
313
    /**
314
     * Check if item with given offset exists
315
     *
316
     * @param int $offset
317
     * @return bool
318
     */
319
    public function offsetExists($offset) {
320
      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
    public function offsetGet($offset) {
341
      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
    public function getTokens() : array {
358
      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
    public function each(callable $callback) : self {
378
379
      if (!is_callable($callback)) {
380
        throw new \InvalidArgumentException('Invalid callback function');
381
      }
382
383
      foreach ($this->getTokens() as $index => $item) {
384
        call_user_func_array($callback, [$item, $index, $this]);
385
      }
386
387
      $this->rewind();
388
389
      return $this;
390
    }
391
392
393
    /**
394
     * Remove all tokens in collection
395
     *
396
     * @return $this
397
     */
398
    public function remove() : self {
399
      foreach ($this as $token) {
400
        $token->remove();
401
      }
402
      return $this;
403
    }
404
405
406
    /**
407
     * @param Query $query
408
     * @return Collection
409
     */
410
    public function find(Query $query) {
411
      $finder = new TokenFinder($this);
412
      return $finder->find($query);
413
    }
414
415
416
    /**
417
     * Remove all invalid tokens in collection
418
     * Refresh index.
419
     *
420
     * @return Collection
421
     */
422
    public function refresh() : self {
423
      $string = $this->assemble();
424
      $this->cleanCollection();
425
426
      $tokens = Helper::getTokensFromString($string);
427
      $this->setItems($tokens);
428
429
      $this->rewind();
430
      return $this;
431
    }
432
433
434
    /**
435
     * @param Token $tokenStart
436
     * @param Token $tokenEnd
437
     * @return Collection
438
     */
439
    public function extractByTokens(Token $tokenStart, Token $tokenEnd) : Collection {
440
441
      $collection = new Collection();
442
      $startIndex = $tokenStart->getIndex();
443
      $endIndex = $tokenEnd->getIndex();
444
445
      foreach ($this->getTokens() as $token) {
446
        if ($token->getIndex() >= $startIndex and $token->getIndex() <= $endIndex) {
447
          $collection->append($token);
448
        }
449
      }
450
451
452
      return $collection;
453
    }
454
455
456
    /**
457
     * @return $this
458
     */
459
    public function storeContentHash() : self {
460
      $this->initialContentHash = $this->getContentHash();
461
      return $this;
462
    }
463
464
465
    /**
466
     * @return bool
467
     */
468
    public function isChanged() : bool {
469
      return ($this->getContentHash() !== $this->initialContentHash);
470
    }
471
472
473
    /**
474
     * @return string
475
     */
476
    private function getContentHash() : string {
477
      return md5($this->assemble());
478
    }
479
480
481
    /**
482
     * @return string
483
     */
484
    public function assemble() : string {
485
      $string = '';
486
      /** @var Token $token */
487
      foreach ($this as $token) {
488
        if (!$token->isValid()) {
489
          continue;
490
        }
491
        $string .= $token->getValue();
492
      }
493
494
      return $string;
495
    }
496
497
498
    /**
499
     * Remove invalid tokens from collection
500
     *
501
     * @return $this
502
     */
503
    protected function cleanCollection() : self {
504
      foreach ($this as $index => $token) {
505
        if ($token->isValid()) {
506
          continue;
507
        }
508
        unset($this->items[$index]);
509
      }
510
511
      return $this;
512
    }
513
514
  }