Issues (387)

Branch: develop

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

tests/features/bootstrap/Ast/ApiContext.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 *  For the full copyright and license information, please view the LICENSE
6
 *  file that was distributed with this source code.
7
 *
8
 * @copyright 2010-2018 Mike van Riel<[email protected]>
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\Behat\Contexts\Ast;
14
15
use Behat\Behat\Context\Context;
16
use Behat\Behat\Tester\Exception\PendingException;
17
use Behat\Gherkin\Node\PyStringNode;
18
use phpDocumentor\Descriptor\ArgumentDescriptor;
19
use phpDocumentor\Descriptor\ClassDescriptor;
20
use phpDocumentor\Descriptor\Collection;
21
use phpDocumentor\Descriptor\ConstantDescriptor;
22
use phpDocumentor\Descriptor\DescriptorAbstract;
23
use phpDocumentor\Descriptor\FileDescriptor;
24
use phpDocumentor\Descriptor\FunctionDescriptor;
25
use phpDocumentor\Descriptor\MethodDescriptor;
26
use phpDocumentor\Descriptor\NamespaceDescriptor;
27
use phpDocumentor\Descriptor\PropertyDescriptor;
28
use phpDocumentor\Descriptor\Tag\ParamDescriptor;
29
use phpDocumentor\Descriptor\Tag\ReturnDescriptor;
30
use phpDocumentor\Descriptor\Tag\VersionDescriptor;
31
use phpDocumentor\Descriptor\TraitDescriptor;
32
use phpDocumentor\Reflection\Php\File;
33
use Webmozart\Assert\Assert;
34
35
class ApiContext extends BaseContext implements Context
36
{
37
    /**
38
     * @Then /^the AST has a class named "([^"]*)" in file "([^"]*)"$/
39
     * @throws \Exception
40
     */
41
    public function theASTHasAclassNamedInFile($class, $file)
42
    {
43
        $ast = $this->getAst();
44
45
        $file = $this->processFilePath($file);
46
        /** @var FileDescriptor $fileDescriptor */
47
        $fileDescriptor = $ast->getFiles()->get($file);
48
49
        /** @var ClassDescriptor $classDescriptor */
50
        foreach ($fileDescriptor->getClasses() as $classDescriptor) {
51
            if ($classDescriptor->getName() === $class) {
52
                return;
53
            }
54
        }
55
56
        throw new \Exception(sprintf('Didn\'t find expected class "%s" in "%s"', $class, $file));
57
    }
58
59
    /**
60
     * @Then /^the AST doesn't have a class "([^"]*)"$/
61
     * @throws \Exception
62
     */
63
    public function theASTDoesnTHaveAClass($className)
64
    {
65
        $ast = $this->getAst();
66
        foreach ($ast->getFiles() as $file) {
67
            foreach ($file->getClasses() as $classDescriptor) {
68
                if ($classDescriptor->getName() === $className) {
69
                    throw new \Exception('Found unexpected class');
70
                }
71
            }
72
        }
73
    }
74
75
    /**
76
     * @Then /^the class named "([^"]*)" is in the default package$/
77
     * @throws \Exception
78
     */
79
    public function theASTHasAClassInDefaultPackage($class)
80
    {
81
        $class = $this->findClassByName($class);
82
83
        Assert::eq('Default', $class->getPackage()->getName());
84
    }
85
86
    /**
87
     * @Then /^the AST has a trait named "([^"]*)" in file "([^"]*)"$/
88
     * @throws \Exception
89
     */
90
    public function theASTHasATraitNamedInFile($trait, $file)
91
    {
92
        $ast = $this->getAst();
93
94
        $file = $this->processFilePath($file);
95
        /** @var FileDescriptor $fileDescriptor */
96
        $fileDescriptor = $ast->getFiles()->get($file);
97
98
        /** @var TraitDescriptor $classDescriptor */
99
        foreach ($fileDescriptor->getTraits() as $classDescriptor) {
100
            if ($classDescriptor->getName() === $trait) {
101
                return;
102
            }
103
        }
104
105
        throw new \Exception(sprintf('Didn\'t find expected trait "%s" in "%s"', $trait, $file));
106
    }
107
108
    /**
109
     * @Then the class named ":class" has docblock with content:
110
     */
111
    public function classHasDocblockWithContent($class, PyStringNode $expectedContent)
112
    {
113
        $class = $this->findClassByName($class);
114
115
        Assert::eq($expectedContent->getRaw(), $class->getDescription());
116
    }
117
118
    /**
119
     * @Then class ":classFqsen" has :docElement:
120
     * @throws Exception
121
     */
122
    public function classHasDocblockContent($classFqsen, $docElement, PyStringNode $value)
123
    {
124
        $class = $this->findClassByFqsen($classFqsen);
125
126
        $method = 'get' . $docElement;
127
128
        Assert::eq($value->getRaw(), $class->{$method}());
129
    }
130
131
    /**
132
     * @Then class ":classFqsen" has :elementType :elementName with :docElement:
133
     */
134
    public function classHasElementWithDocblockContent($classFqsen, $elementType, $elementName, $docElement, PyStringNode $value)
135
    {
136
        $class = $this->findClassByFqsen($classFqsen);
137
138
        switch ($elementType) {
139
            case 'method':
140
            case 'constant':
141
                $method = $method = 'get' . $elementType . 's';
142
                break;
143
            case 'property':
144
                $method = 'getProperties';
145
                break;
146
            default:
147
                $method = 'get' . $elementType;
148
                break;
149
        }
150
151
        $element = $class-> {$method}()->get($elementName);
152
        $method = 'get' . $docElement;
153
        $actual = $element->{$method}();
154
155
        Assert::eq($value->getRaw(), $actual, sprintf('"%s" does not match "%s"', $actual, $value->getRaw()));
156
    }
157
158
    /**
159
     * @Then class ":classFqsen" has version :value
160
     */
161
    public function classHasVersion($classFqsen, $value)
162
    {
163
        $class = $this->findClassByFqsen($classFqsen);
164
165
        /** @var VersionDescriptor $tag */
166
        foreach ($class->getVersion() as $tag) {
167
            if ($tag->getVersion() === $value) {
168
                return;
169
            }
170
        }
171
172
        Assert::false(true, sprintf('Didn\'t find expected version "%s"', $value));
173
    }
174
175
    /**
176
     * @Then class ":classFqsen" without tag :tagName
177
     */
178
    public function classWithoutTag($classFqsen, $tagName)
179
    {
180
        $this->classHasTag($classFqsen, $tagName, 0);
181
    }
182
183
    /**
184
     * @param string $classFqsen
185
     * @param string $tagName
186
     * @param int $expectedNumber
187
     * @Then class ":classFqsen" has exactly :expectedNumber tag :tagName
188
     */
189
    public function classHasTag($classFqsen, $tagName, $expectedNumber)
190
    {
191
        $class = $this->findClassByFqsen($classFqsen);
192
        static::AssertTagCount($class, $tagName, $expectedNumber);
0 ignored issues
show
Since AssertTagCount() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of AssertTagCount() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
193
    }
194
195
    /**
196
     * @param string $classFqsen
197
     * @param string $tagName
198
     * @param string $method
199
     * @Then class ":classFqsen" has a method named :method without tag :tagName
200
     */
201
    public function classHasMethodWithoutTag($classFqsen, $tagName, $method)
202
    {
203
        $this->classHasMethodWithExpectedCountTag($classFqsen, $tagName, $method, 0);
204
    }
205
206
    /**
207
     * @param string $classFqsen
208
     * @param string $tagName
209
     * @param string $methodName
210
     * @Then class ":classFqsen" has a method named :method with exactly :expected tag :tagName
211
     */
212
    public function classHasMethodWithExpectedCountTag($classFqsen, $tagName, $methodName, $expectedCount)
213
    {
214
        $class = $this->findClassByFqsen($classFqsen);
215
        $method = $class->getMethods()->get($methodName);
216
217
        static::AssertTagCount($method, $tagName, $expectedCount);
0 ignored issues
show
Since AssertTagCount() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of AssertTagCount() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
218
    }
219
220
    /**
221
     * @param string $classFqsen
222
     * @param string $methodName
223
     * @Then class ":classFqsen" has a method :method with argument ":argument is variadic
224
     */
225
    public function classHasMethodWithArgumentVariadic($classFqsen, $methodName, $argument)
226
    {
227
        $class = $this->findClassByFqsen($classFqsen);
228
        /** @var MethodDescriptor $method */
229
        $method = $class->getMethods()->get($methodName);
230
        Assert::keyExists($method->getArguments()->getAll(), $argument);
231
232
        /** @var ArgumentDescriptor $argumentD */
233
        $argumentD = $method->getArguments()[$argument];
234
        Assert::true($argumentD->isVariadic(), 'Expected argument to be variadic');
235
    }
236
237
    /**
238
     * @param string $classFqsen
239
     * @param string $methodName
240
     * @Then class ":classFqsen" has a method :method
241
     */
242
    public function classHasMethod($classFqsen, $methodName)
243
    {
244
        $class = $this->findClassByFqsen($classFqsen);
245
        /** @var MethodDescriptor $method */
246
        $method = $class->getMethods()->get($methodName, null);
247
        $methodNames = implode(', ', array_keys($class->getMethods()->getAll()));
248
249
        $visibilityLevel = $this->getAst()->getSettings()->getVisibility();
250
        Assert::isInstanceOf(
251
            $method,
252
            MethodDescriptor::class,
253
            "Class $classFqsen does not have a method $methodName, it does have the methods: $methodNames "
254
            . "(visibility level: $visibilityLevel})"
255
        );
256
        Assert::eq($methodName, $method->getName());
257
    }
258
259
    /**
260
     * @param string $classFqsen
261
     * @param string $propertyName
262
     * @Then class ":classFqsen" has a property :property
263
     */
264
    public function classHasProperty($classFqsen, $propertyName)
265
    {
266
        $class = $this->findClassByFqsen($classFqsen);
267
        /** @var PropertyDescriptor $property */
268
        $property = $class->getProperties()->get($propertyName, null);
269
        Assert::isInstanceOf($property, PropertyDescriptor::class);
270
        Assert::eq($propertyName, $property->getName());
271
    }
272
273
    /**
274
     * @param string $classFqsen
275
     * @param string $methodName
276
     * @param string $argument
277
     * @param string $type
278
     * @Then class ":classFqsen" has a method :method with argument :argument of type ":type"
279
     */
280
    public function classHasMethodWithArgumentOfType($classFqsen, $methodName, $argument, $type)
281
    {
282
        $class = $this->findClassByFqsen($classFqsen);
283
        /** @var MethodDescriptor $method */
284
        $method = $class->getMethods()->get($methodName);
285
        Assert::keyExists($method->getArguments()->getAll(), $argument);
286
        /** @var ArgumentDescriptor $argumentDescriptor */
287
        $argumentDescriptor = $method->getArguments()[$argument];
288
289
        Assert::eq($type, (string) $argumentDescriptor->getType());
290
    }
291
292
    /**
293
     * @param string $classFqsen
294
     * @param string $methodName
295
     * @param string $param
296
     * @param string $type
297
     * @Then class ":classFqsen" has a method :method with param :param of type ":type"
298
     */
299
    public function classHasMethodWithParamOfType($classFqsen, $methodName, $param, $type)
300
    {
301
        $class = $this->findClassByFqsen($classFqsen);
302
        /** @var MethodDescriptor $method */
303
        $method = $class->getMethods()->get($methodName);
304
        /** @var ParamDescriptor $paramDescriptor */
305
        foreach ($method->getParam() as $paramDescriptor) {
306
            if ($paramDescriptor->getName() === $param) {
307
                Assert::eq($type, (string) $paramDescriptor->getType());
308
            }
309
        }
310
    }
311
312
    /**
313
     * @param string $classFqsen
314
     * @param string $constantName
315
     * @Then class ":classFqsen" has a constant :constantName
316
     */
317
    public function classHasConstant($classFqsen, $constantName)
318
    {
319
        /** @var ClassDescriptor $class */
320
        $class = $this->findClassByFqsen($classFqsen);
321
        $constant = $class->getConstants()->get($constantName);
322
        Assert::isInstanceOf($constant, ConstantDescriptor::class);
323
    }
324
325
    /**
326
     * @param string $className
327
     * @return ClassDescriptor
328
     * @throws \Exception
329
     */
330
    private function findClassByName($className)
331
    {
332
        $ast = $this->getAst();
333
        foreach ($ast->getFiles() as $file) {
334
            foreach ($file->getClasses() as $classDescriptor) {
335
                if ($classDescriptor->getName() === $className) {
336
                    return $classDescriptor;
337
                }
338
            }
339
        }
340
341
        throw new \Exception(sprintf('Didn\'t find expected class "%s"', $className));
342
    }
343
344
    /**
345
     * @param string $tagName
346
     * @param int $expectedNumber
347
     * @param DescriptorAbstract $element
348
     */
349
    private static function AssertTagCount($element, $tagName, $expectedNumber)
350
    {
351
        /** @var Collection $tagCollection */
352
        $tagCollection = $element->getTags()->get($tagName, new Collection());
353
354
        Assert::eq((int) $expectedNumber, $tagCollection->count());
355
        if ($expectedNumber > 0) {
356
            Assert::eq($tagName, $tagCollection[0]->getName());
357
        }
358
    }
359
360
    /**
361
     * @Then /^the ast has a file named "([^"]*)" with a summary:$/
362
     * @throws \Exception
363
     */
364
    public function theAstHasAFileNamedWithASummary(string $fileName, PyStringNode $string)
365
    {
366
        $ast = $this->getAst();
367
        /** @var FileDescriptor $file */
368
        $file = $ast->getFiles()->get($fileName);
369
370
        Assert::eq($string->getRaw(), $file->getSummary());
371
    }
372
373
    /**
374
     * @param string $classFqsen
375
     * @param string $methodName
376
     * @throws Exception
377
     * @Then class ":classFqsen" has a method :method with returntype :returnType
378
     * @Then class ":classFqsen" has a method :method with returntype :returnType without description
379
     */
380
    public function classHasMethodWithReturnType($classFqsen, $methodName, $returnType)
381
    {
382
        $response = $this->findMethodResponse($classFqsen, $methodName);
383
384
        Assert::eq((string) $response->getType(), $returnType);
385
        Assert::eq((string) $response->getDescription(), '');
386
    }
387
388
    /**
389
     * @param string $classFqsen
390
     * @param string $methodName
391
     * @throws Exception
392
     * @Then class ":classFqsen" has a magic method :method with returntype :returnType
393
     * @Then class ":classFqsen" has a magic method :method with returntype :returnType without description
394
     */
395
    public function classHasMagicMethodWithReturnType($classFqsen, $methodName, $returnType)
396
    {
397
        $response = $this->findMagicMethodResponse($classFqsen, $methodName);
398
399
        Assert::eq((string) $response->getType(), $returnType);
400
        Assert::eq((string) $response->getDescription(), '');
401
    }
402
403
    /**
404
     * @param string $classFqsen
405
     * @param string $methodName
406
     * @throws Exception
407
     * @Then class ":classFqsen" has a method :method with returntype :returnType with description:
408
     */
409
    public function classHasMethodWithReturnTypeAndDescription($classFqsen, $methodName, $returnType, PyStringNode $description)
410
    {
411
        $response = $this->findMethodResponse($classFqsen, $methodName);
412
413
        Assert::eq($returnType, (string) $response->getType());
414
        Assert::eq($description, (string) $response->getDescription());
415
    }
416
417
    /**
418
     * @Then class ":classFqsen" has a method ":method" without returntype
419
     * @throws \Exception
420
     */
421
    public function classReturnTaggetReturnWithoutAnyWithoutReturntype($classFqsen, $methodName)
422
    {
423
        $response = $this->findMethodResponse($classFqsen, $methodName);
424
        Assert::eq('mixed', (string) $response->getType());
425
        Assert::eq('', $response->getDescription());
426
    }
427
428
    /**
429
     * @throws Exception
430
     * @Then has function :fqsen with returntype :returnType
431
     * @Then has function :fqsen with returntype :returnType without description
432
     */
433
    public function functionWithReturnType($fqsen, $returnType)
434
    {
435
        $response = $this->findFunctionResponse($fqsen);
436
437
        Assert::eq($returnType, (string) $response->getType());
438
        Assert::eq('', (string) $response->getDescription());
439
    }
440
441
    /**
442
     * @throws Exception
443
     * @Then has function :fqsen with returntype :returnType with description:
444
     */
445
    public function functionWithReturnTypeAndDescription($fqsen, $returnType, PyStringNode $description)
446
    {
447
        $response = $this->findFunctionResponse($fqsen);
448
449
        Assert::eq($returnType, (string) $response->getType());
450
        Assert::eq($description, (string) $response->getDescription());
451
    }
452
453
    /**
454
     * @Then has function :fqsen without returntype
455
     * @throws \Exception
456
     */
457
    public function functionWithoutReturntype($fqsen)
458
    {
459
        $response = $this->findFunctionResponse($fqsen);
460
        Assert::eq('mixed', (string) $response->getType());
461
        Assert::eq('', $response->getDescription());
462
    }
463
464
    /**
465
     * @throws Exception
466
     */
467
    private function findMethodResponse($classFqsen, $methodName): ReturnDescriptor
468
    {
469
        $class = $this->findClassByFqsen($classFqsen);
470
        /** @var MethodDescriptor $method */
471
        $method = $class->getMethods()->get($methodName, null);
472
        Assert::isInstanceOf($method, MethodDescriptor::class);
473
        Assert::eq($methodName, $method->getName());
474
475
        return $method->getResponse();
476
    }
477
478
    /**
479
     * @throws Exception
480
     */
481
    private function findMagicMethodResponse($classFqsen, $methodName): ReturnDescriptor
482
    {
483
        $class = $this->findClassByFqsen($classFqsen);
484
        $match = null;
485
486
        /** @var MethodDescriptor $method */
487
        foreach ($class->getMagicMethods() as $method) {
488
            if ($method->getName() === $methodName) {
489
                $match = $method;
490
            }
491
        }
492
493
        Assert::isInstanceOf($match, MethodDescriptor::class);
494
        Assert::eq($methodName, $match->getName());
495
496
        return $match->getResponse();
497
    }
498
499
    /**
500
     * @throws Exception
501
     */
502
    private function findFunctionResponse(string $fqsen): ReturnDescriptor
503
    {
504
        $function = $this->findFunctionByFqsen($fqsen);
505
        return $function->getResponse();
506
    }
507
508
    /**
509
     * @Then class ":classFqsen" has a magic method :method with argument ":argument" of type :type
510
     */
511
    public function classHasMagicMethodWithArgument($classFqsen, $methodName, $argument, $type)
0 ignored issues
show
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
512
    {
513
        $class = $this->findClassByFqsen($classFqsen);
514
        $match = null;
515
516
        /** @var MethodDescriptor $method */
517
        foreach ($class->getMagicMethods() as $method) {
518
            if ($method->getName() === $methodName) {
519
                $match = $method;
520
            }
521
        }
522
523
        Assert::isInstanceOf($match, MethodDescriptor::class);
524
        Assert::notNull($match->getArguments()->get($argument));
525
    }
526
527
    /**
528
     * @Then /^(\d+) files should be parsed$/
529
     */
530
    public function filesShouldBeParsed($count)
531
    {
532
        Assert::same((int) $count, $this->getAst()->getFiles()->count());
533
    }
534
535
    /**
536
     * @Then /^the ast has a function named "([^"]*)"$/
537
     */
538
    public function theAstHasAFunctionNamed($functionName)
539
    {
540
        Assert::isInstanceOf(
541
            $this->getAst()->getIndexes()->get('functions')->get($functionName . '()'),
542
            FunctionDescriptor::class
543
        );
544
    }
545
546
    /**
547
     * @Then argument :argument of function ":functionName" has no defined type and description is:
548
     */
549
    public function argumentOfFunctionHasNoTypeAndHasDescripion($argument, $functionName, PyStringNode $description)
550
    {
551
        /** @var FunctionDescriptor $functionDescriptor */
552
        $functionDescriptor = $this->getAst()->getIndexes()->get('functions')->get($functionName . '()');
553
        Assert::isInstanceOf(
554
            $functionDescriptor,
555
            FunctionDescriptor::class
556
        );
557
558
        /** @var ArgumentDescriptor $argumentDescriptor */
559
        $argumentDescriptor = $functionDescriptor->getArguments()->get($argument);
560
561
        Assert::isInstanceOf($argumentDescriptor, ArgumentDescriptor::class);
562
563
        Assert::same($description->getRaw(), (string) $argumentDescriptor->getDescription());
564
    }
565
566
    /**
567
     * @Given the namespace ':namespace' has a function named ':functionName'
568
     */
569
    public function theNamespaceFoo(string $namespace, string $functionName)
570
    {
571
        /** @var NamespaceDescriptor $namespace */
572
        $namespace = $this->getAst()->getIndexes()->get('namespaces')->get($namespace);
573
        Assert::isInstanceOf($namespace, NamespaceDescriptor::class);
574
        $function = $this->findFunctionInNamespace($namespace, $functionName);
575
        Assert::isInstanceOf($function, FunctionDescriptor::class);
576
    }
577
578
    private function findFunctionInNamespace(NamespaceDescriptor $namespace, string $functionName)
579
    {
580
        foreach ($namespace->getFunctions()->getAll() as $key => $function) {
581
            if ($function->getName() === $functionName) {
582
                return $function;
583
            }
584
        }
585
586
        return null;
587
    }
588
589
    /**
590
     * @Then /^file "([^"]*)" must contain a marker$/
591
     */
592
    public function fileMustContainAMarker($filename)
593
    {
594
        $ast = $this->getAst();
595
596
        /** @var FileDescriptor $file */
597
        $file = $ast->getFiles()->get($filename);
598
599
        Assert::count($file->getMarkers(), 1);
600
    }
601
602
    /**
603
     * @Then class ":className" must have magic property ":propertyName" of type :type
604
     */
605
    public function classMustHaveMagicPropertyOfType($className, $propertyName, $type)
606
    {
607
        $classDescriptor = $this->findClassByFqsen($className);
608
        /** @var PropertyDescriptor $propertyDescriptor */
609
        $propertyDescriptor = null;
610
        foreach ($classDescriptor->getMagicProperties() as $property) {
611
            if ($property->getName() === $propertyName) {
612
                $propertyDescriptor = $property;
613
                break;
614
            }
615
        }
616
617
        Assert::isInstanceOf($propertyDescriptor, PropertyDescriptor::class);
618
        Assert::eq($type, (string) $propertyDescriptor->getType());
619
    }
620
}
621