Completed
Push — master ( e7ab98...751609 )
by Shcherbak
02:45
created

ParametersPattern::getArguments()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7.0084

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 17
cts 18
cp 0.9444
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 18
nc 7
nop 1
crap 7.0084
1
<?php
2
3
  declare(strict_types=1);
4
5
  namespace Funivan\PhpTokenizer\Pattern\Patterns;
6
7
  use Funivan\PhpTokenizer\Collection;
8
  use Funivan\PhpTokenizer\QuerySequence\QuerySequence;
9
  use Funivan\PhpTokenizer\Strategy\Section;
10
  use Funivan\PhpTokenizer\Token;
11
12
  /**
13
   *
14
   */
15
  class ParametersPattern implements PatternInterface {
16
17
    /**
18
     * @var array
19
     */
20
    private $argumentCheck = [];
21
22
    /**
23
     * @var int|null
24
     */
25
    private $outputArgument = null;
26
27
    /**
28
     * @var bool|null
29
     */
30
    private $outputPreparedArgument = null;
31
32
33
    /**
34
     *
35
     */
36 51
    public function __construct() {
37 51
      $this->outputFull();
38 51
    }
39
40
41
    /**
42
     * @param QuerySequence $querySequence
43
     * @return Collection|null
44
     * @throws \Exception
45
     */
46 51
    public function __invoke(QuerySequence $querySequence) {
47 51
      $section = $querySequence->section('(', ')');
48 51
      if ($section->count() === 0) {
49 45
        return null;
50
      }
51
52 51
      $section->slice(1, -1);
53
54 51
      if (empty($this->argumentCheck) and $this->outputArgument === null) {
55 15
        return $section;
56
      }
57
58
59
      /** @var Collection[] $arguments */
60 48
      $arguments = $this->getArguments($section);
61
62 48
      foreach ($this->argumentCheck as $index => $check) {
63
64 36
        $argumentTokens = isset($arguments[$index]) ? $arguments[$index] : new Collection();
65 36
        $result = $check($argumentTokens);
66
67 36
        if (!is_bool($result)) {
68 3
          throw new \Exception('Argument check function should return boolean');
69
        }
70
71 33
        if ($result === false) {
72 33
          return null;
73
        }
74
      }
75
76 42
      if ($this->outputArgument !== null) {
77 24
        $argumentCollection = !empty($arguments[$this->outputArgument]) ? $arguments[$this->outputArgument] : null;
78
79
        # Do not remove T_WHITESPACE tokens from the argument output
80 24
        if ($this->outputPreparedArgument === false) {
81 12
          return $argumentCollection;
82
        }
83
84
        // trim first and last whitespace token
85 24
        $first = $argumentCollection->getFirst();
86 24
        $last = $argumentCollection->getLast();
87
88 24
        $from = 0;
89 24
        $to = null;
90 24
        if ($first !== null and $first->getType() == T_WHITESPACE) {
91 15
          $from = 1;
92
        }
93 24
        if ($last !== null and $last->getType() == T_WHITESPACE) {
94 12
          $to = -1;
95
        }
96
97 24
        return $argumentCollection->extractItems($from, $to);
0 ignored issues
show
Bug introduced by
It seems like $to defined by -1 on line 94 can also be of type integer; however, Funivan\PhpTokenizer\Collection::extractItems() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
98
      }
99
100
      # output full
101 18
      return $section;
102
    }
103
104
105
    /**
106
     * @param int $index
107
     * @param callable $check
108
     * @return $this
109
     */
110 36
    public function withArgument(int $index, callable $check = null) : self {
111 36
      if ($check === null) {
112
        $check = function (Collection $argumentTokens) {
113 30
          return $argumentTokens->count() !== 0;
114 30
        };
115
      }
116 36
      $this->argumentCheck[$index] = $check;
117 36
      return $this;
118
    }
119
120
121
    /**
122
     * @param int $index
123
     * @return $this
124
     */
125
    public function withoutArgument(int $index) : self {
126 3
      $check = function (Collection $argumentTokens) {
127 3
        return $argumentTokens->count() === 0;
128 3
      };
129
130 3
      $this->argumentCheck[$index] = $check;
131 3
      return $this;
132
    }
133
134
135
    /**
136
     * @param Collection $section
137
     * @return Collection[]
138
     */
139 48
    protected function getArguments(Collection $section) : array {
140
      /** @var Token $skipToToken */
141 48
      $skipToToken = null;
142 48
      $argumentIndex = 1;
143 48
      $arguments = [];
144 48
      $tokensNum = ($section->count() - 1);
145 48
      for ($tokenIndex = 0; $tokenIndex <= $tokensNum; $tokenIndex++) {
146
147 48
        $token = $section->offsetGet($tokenIndex);
148
149 48
        if ($token === null) {
150
          return null;
151
        }
152
153 48
        if ($skipToToken === null or $token->getIndex() >= $skipToToken->getIndex()) {
154 48
          if ($token->getValue() === ',') {
155 48
            $argumentIndex++;
156 48
            continue;
157
          }
158 48
          $skipToToken = $this->getEndArray($token, $section, $tokenIndex);
159
        }
160
161
162 48
        if (!isset($arguments[$argumentIndex])) {
163 48
          $arguments[$argumentIndex] = new Collection();
164
        }
165 48
        $arguments[$argumentIndex][] = $token;
166
      }
167
168 48
      return $arguments;
169
    }
170
171
172
    /**
173
     * @param Token $token
174
     * @param Collection $section
175
     * @param int $index
176
     * @return Token
177
     */
178 48
    private function getEndArray(Token $token, Collection $section, int $index) {
179
      # check if we have array start
180
181 48
      if ($token->getValue() === '[') {
182 21
        $result = (new Section())->setDelimiters('[', ']')->process($section, $index);
183 21
        return $result->getToken();
184
      }
185
186 48
      if ($token->getValue() === '(') {
187 6
        $result = (new Section())->setDelimiters('(', ')')->process($section, $index);
188 6
        return $result->getToken();
189
      }
190
191 48
      return null;
192
    }
193
194
195
    /**
196
     * @return $this
197
     */
198 51
    public function outputFull() : self {
199 51
      $this->outputArgument = null;
200 51
      $this->outputPreparedArgument = null;
201 51
      return $this;
202
    }
203
204
205
    /**
206
     * @param int $int
207
     * @param bool $prepared
208
     * @return $this
209
     */
210 27
    public function outputArgument(int $int, $prepared = true) : self {
211 27
      $this->outputArgument = $int;
212 27
      $this->outputPreparedArgument = $prepared;
213 27
      return $this;
214
    }
215
216
  }