Completed
Push — master ( 5844c1...ab7dfd )
by Shcherbak
02:28
created

MethodPattern::isValidModifiers()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
ccs 9
cts 10
cp 0.9
rs 9.2
cc 4
eloc 8
nc 4
nop 1
crap 4.016
1
<?php
2
3
  namespace Funivan\PhpTokenizer\Pattern\Patterns;
4
5
  use Funivan\PhpTokenizer\Collection;
6
  use Funivan\PhpTokenizer\Pattern\PatternMatcher;
7
  use Funivan\PhpTokenizer\QuerySequence\QuerySequence;
8
  use Funivan\PhpTokenizer\Strategy\QueryStrategy;
9
  use Funivan\PhpTokenizer\Strategy\Strict;
10
  use Funivan\PhpTokenizer\Token;
11
12
  /**
13
   * Find method in code
14
   */
15
  class MethodPattern implements PatternInterface {
16
17
    const OUTPUT_BODY = 0;
18
19
    const OUTPUT_FULL = 1;
20
21
    const OUTPUT_DOC_COMMENT = 2;
22
23
24
    /**
25
     * @var QueryStrategy
26
     */
27
    private $nameQuery;
28
29
    /**
30
     * @var callable[]
31
     */
32
    private $modifierChecker = [];
33
34
    /**
35
     * @var callable
36
     */
37
    private $bodyChecker;
38
39
    /**
40
     * @var callable
41
     */
42
    private $docCommentChecker;
43
44
    /**
45
     * @var int
46
     */
47
    private $outputType = self::OUTPUT_BODY;
48
49
    /**
50
     * @var ArgumentsPattern
51
     */
52
    private $argumentsPattern;
53
54
55
    /**
56
     *
57
     */
58 21
    public function __construct() {
59 18
      $this->withName(Strict::create()->valueLike('!.+!'));
60 18
      $this->withAnyModifier();
61
62
      /** @noinspection PhpUnusedParameterInspection */
63
      $this->withBody(function (Collection $body) {
64 17
        return true;
65 18
      });
66
67
      /** @noinspection PhpUnusedParameterInspection */
68
      $this->withDocComment(function (Token $token) {
69 17
        return true;
70 18
      });
71
72 21
    }
73
74
75
    /**
76
     * @return $this
77
     */
78 3
    public function outputFull() {
79 3
      $this->outputType = self::OUTPUT_FULL;
80 3
      return $this;
81
    }
82
83
84
    /**
85
     * @return $this
86
     */
87 3
    public function outputBody() {
88 3
      $this->outputType = self::OUTPUT_BODY;
89 3
      return $this;
90
    }
91
92
93
    /**
94
     * @return $this
95
     */
96 3
    public function outputDocComment() {
97 3
      $this->outputType = self::OUTPUT_DOC_COMMENT;
98 3
      return $this;
99
    }
100
101
102
    /**
103
     * @param string|QueryStrategy $name
104
     * @return $this
105
     */
106 21
    public function withName($name) {
107 18
      if (is_string($name)) {
108 2
        $this->nameQuery = Strict::create()->valueIs($name);
109 20
      } elseif ($name instanceof QueryStrategy) {
110 21
        $this->nameQuery = $name;
111 18
      } else {
112 2
        throw new \InvalidArgumentException('Invalid name format. Expect string or Query');
113 3
      }
114
115 21
      return $this;
116
    }
117
118
119
    /**
120
     * @param callable $check
121
     * @return $this
122
     */
123 21
    public function withBody(callable $check) {
124 21
      $this->bodyChecker = $check;
125 21
      return $this;
126 3
    }
127
128
129
    /**
130
     * @param Collection $body
131
     * @return bool
132
     * @throws \Exception
133
     */
134 17
    private function isValidBody(Collection $body) {
135 17
      $checker = $this->bodyChecker;
136 16
      $result = $checker($body);
137 16
      if (!is_bool($result)) {
138
        throw new \Exception('Body checker should return boolean result');
139
      }
140
141 17
      return $result;
142 1
    }
143
144
145
    /**
146
     * @param callable $check
147
     * @return $this
148
     */
149 21
    public function withDocComment(callable $check = null) {
150
151 21
      if ($check === null) {
152
        $check = function (Token $token) {
153 2
          return $token->getType() === T_DOC_COMMENT;
154 2
        };
155 2
      }
156 21
      $this->docCommentChecker = $check;
157
158 21
      return $this;
159 3
    }
160
161
162
    /**
163
     * Find functions without doc comments
164
     */
165 3
    public function withoutDocComment() {
166
      $this->docCommentChecker = function (Token $token) {
167 2
        return $token->isValid() === false;
168
      };
169
170 3
      return $this;
171
    }
172
173
174
    /**
175
     * @param ArgumentsPattern $pattern
176
     * @return $this
177
     */
178 2
    public function withParameters(ArgumentsPattern $pattern) {
179 2
      $this->argumentsPattern = $pattern;
180 2
      return $this;
181
    }
182
183
184
    /**
185
     * @return $this
186
     */
187 21
    public function withAnyModifier() {
188 21
      $this->modifierChecker = [];
189
      $this->modifierChecker[] = function () {
190 17
        return true;
191 3
      };
192 21
      return $this;
193
    }
194
195
196
    /**
197
     * @param string $modifier
198
     * @return $this
199
     */
200 2
    public function withModifier($modifier) {
201
      $this->modifierChecker[] = function ($allModifiers) use ($modifier) {
202 2
        return in_array($modifier, $allModifiers);
203
      };
204
205 2
      return $this;
206
    }
207
208
209
    /**
210
     * @param string $modifier
211
     * @return $this
212
     */
213
    public function withoutModifier($modifier) {
214
215 3
      $this->modifierChecker[] = function ($allModifiers) use ($modifier) {
216 2
        return !in_array($modifier, $allModifiers);
217 1
      };
218
219 3
      return $this;
220
    }
221
222
223
    /**
224
     * @param array $modifiers Array<String>
225
     * @return bool
226
     * @throws \Exception
227
     */
228 17
    private function isValidModifiers(array $modifiers) {
229 17
      foreach ($this->modifierChecker as $checkModifier) {
230 16
        $result = $checkModifier($modifiers);
231
232 16
        if (!is_bool($result)) {
233
          throw new \Exception('Modifier checker should return boolean result');
234
        }
235 17
        if ($result === false) {
236 2
          return false;
237
        }
238 16
      }
239
240 17
      return true;
241 1
    }
242
243
244
    /**
245
     * @param QuerySequence $querySequence
246
     * @return Collection|null
247
     */
248 18
    public function __invoke(QuerySequence $querySequence) {
249
      static $availableModifiers = [
250 1
        T_STATIC,
251 1
        T_PRIVATE,
252 1
        T_PUBLIC,
253 1
        T_ABSTRACT,
254 1
        T_FINAL,
255 16
      ];
256
257
258
      # detect function
259 16
      $functionKeyword = $querySequence->strict('function');
260 16
      $querySequence->strict(T_WHITESPACE);
261 16
      $querySequence->process($this->nameQuery);
262 16
      $arguments = $querySequence->section('(', ')');
263 16
      $querySequence->possible(T_WHITESPACE);
264 16
      $body = $querySequence->section('{', '}');
265
266 16
      if (!$querySequence->isValid()) {
267 16
        return null;
268
      }
269
270 16
      $collection = $querySequence->getCollection();
271 16
      $start = $collection->extractByTokens($collection->getFirst(), $functionKeyword);
272 16
      $start->slice(0, -1);  // remove last function keyword
273
274
      # start reverse search
275 16
      $items = array_reverse($start->getItems());
276 17
      $startFrom = null;
277
278 16
      $docComment = new Token();
279
280 17
      $modifiers = [];
281
282
283
      /** @var Token[] $items */
284 16
      foreach ($items as $item) {
285
286 16
        if ($item->getType() === T_WHITESPACE) {
287 14
          $startFrom = $item;
288 14
          continue;
289
        }
290
291 16
        if ($item->getType() === T_DOC_COMMENT and $docComment->isValid() === false) {
292
          # Detect only first doc comment
293 4
          $startFrom = $item;
294 4
          $docComment = $item;
295 4
          continue;
296
        }
297
298
299 16
        if (in_array($item->getType(), $availableModifiers)) {
300 10
          $startFrom = $item;
301 10
          $modifiers[] = $item->getValue();
302 10
          continue;
303
        }
304
305 18
        break;
306 17
      }
307
308 16
      if ($this->isValidModifiers($modifiers) === false) {
309 2
        return null;
310
      }
311
312 16
      if ($this->isValidBody($body) === false) {
313 2
        return null;
314
      }
315
316 16
      if ($this->isValidDocComment($docComment) === false) {
317 2
        return null;
318
      }
319
320 16
      if ($this->isValidArguments($arguments) === false) {
321 3
        return null;
322
      }
323
324 17
      if ($startFrom === null) {
325 3
        $startFrom = $functionKeyword;
326 2
      }
327
328
329 17
      if ($this->outputType === self::OUTPUT_FULL) {
330
        # all conditions are ok, so extract full function
331 2
        $fullFunction = $collection->extractByTokens($startFrom, $body->getLast());
332 2
        if ($fullFunction->getFirst()->getType() === T_WHITESPACE) {
333 2
          $fullFunction->slice(1);
334 2
        }
335 3
        return $fullFunction;
336
337 17
      } elseif ($this->outputType === self::OUTPUT_DOC_COMMENT) {
338 2
        return new Collection([$docComment]);
339
      }
340
341
      # body by default
342 16
      $body->slice(0, -1);
343 17
      return $body;
344
    }
345
346
347
    /**
348
     * @param Token $token
349
     * @return mixed
350
     * @throws \Exception
351
     */
352 17
    private function isValidDocComment(Token $token) {
353 17
      $checker = $this->docCommentChecker;
354 16
      $result = $checker($token);
355 16
      if (!is_bool($result)) {
356
        throw new \Exception('DocComment checker should return boolean result');
357
      }
358
359 17
      return $result;
360 1
    }
361
362
363
    /**
364
     * @param Collection $parameters
365
     * @return bool
366
     */
367 17
    private function isValidArguments(Collection $parameters) {
368 17
      if ($this->argumentsPattern === null) {
369 15
        return true;
370
      }
371
372 2
      $pattern = (new PatternMatcher($parameters))->apply($this->argumentsPattern);
373
374 2
      return (count($pattern->getCollections()) !== 0);
375 1
    }
376
377
378
  }