Completed
Push — master ( ab7dfd...201082 )
by Shcherbak
02:27
created

MethodPattern::isValidBody()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

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