Completed
Pull Request — master (#204)
by
unknown
02:26
created

Parser::getClassName()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 5
Bugs 3 Features 0
Metric Value
c 5
b 3
f 0
dl 0
loc 23
ccs 17
cts 17
cp 1
rs 8.5906
cc 6
eloc 13
nc 10
nop 2
crap 6
1
<?php
2
namespace ParaTest\Parser;
3
4
class Parser
5
{
6
    /**
7
     * The path to the source file to parse
8
     *
9
     * @var string
10
     */
11
    private $path;
12
13
    /**
14
     * @var \ReflectionClass
15
     */
16
    private $refl;
17
18
    /**
19
     * Matches a test method beginning with the conventional "test"
20
     * word
21
     *
22
     * @var string
23
     */
24
    private static $testName = '/^test/';
25
26
    /**
27
     * A pattern for matching test methods that use the @test annotation
28
     *
29
     * @var string
30
     */
31
    private static $testAnnotation = '/@test\b/';
32
33 30
    public function __construct($srcPath)
34
    {
35 30
        if (!file_exists($srcPath)) {
36 1
            throw new \InvalidArgumentException("file not found: " . $srcPath);
37
        }
38
39 29
        $this->path = $srcPath;
40 29
        $declaredClasses = get_declared_classes();
41 29
        require_once($this->path);
42 29
        $class = $this->getClassName($this->path, $declaredClasses);
43 29
        if (!$class) {
44 2
            throw new NoClassInFileException();
45
        }
46
        try {
47 27
            $this->refl = new \ReflectionClass($class);
48 27
        } catch (\ReflectionException $e) {
49
            throw new \InvalidArgumentException("Unable to instantiate ReflectionClass. " . $class . " not found in: " . $srcPath);
50
        }
51 27
    }
52
53
    /**
54
     * Returns the fully constructed class
55
     * with methods or null if the class is abstract
56
     *
57
     * @return null|ParsedClass
58
     */
59 27
    public function getClass()
60
    {
61 27
        return ($this->refl->isAbstract())
62 27
            ? null
63 27
            : new ParsedClass(
64 27
                $this->refl->getDocComment(),
65 27
                $this->refl->getName(),
66 27
                $this->refl->getNamespaceName(),
67 27
                $this->getMethods()
68 27
            );
69
    }
70
71
    /**
72
     * Return all test methods present in the file
73
     *
74
     * @return array
75
     */
76 27
    private function getMethods()
77
    {
78 27
        $tests = array();
79 27
        $methods = $this->refl->getMethods(\ReflectionMethod::IS_PUBLIC);
80 27
        foreach ($methods as $method) {
81 25
            $hasTestName = preg_match(self::$testName, $method->getName());
82 25
            $hasTestAnnotation = preg_match(self::$testAnnotation, $method->getDocComment());
83 25
            $isTestMethod = $hasTestName || $hasTestAnnotation;
84 25
            if ($isTestMethod) {
85 25
                $tests[] = new ParsedFunction($method->getDocComment(), 'public', $method->getName());
86 25
            }
87 27
        }
88 27
        return $tests;
89
    }
90
91
    /**
92
     * Return the class name of the class contained
93
     * in the file
94
     *
95
     * @return string
96
     */
97 29
    private function getClassName($filename, $previousDeclaredClasses)
98
    {
99 29
        $filename = realpath($filename);
100 29
        $classes = get_declared_classes();
101 29
        $newClasses = array_values(array_diff($classes, $previousDeclaredClasses));
102
103 29
        foreach ($newClasses as $className) {
104 11
            $class = new \ReflectionClass($className);
105 11
            if ($class->getFileName() == $filename) {
106 11
                if ($this->classNameMatchesFileName($filename, $className)) {
107 9
                    return $className;
108
                }
109 4
            }
110 26
        }
111
112
        // Test class was loaded before somehow (referenced from other test class, or explicitly loaded)
113 24
        foreach ($classes as $className) {
114 24
            $class = new \ReflectionClass($className);
115 24
            if ($class->getFileName() == $filename) {
116 22
                return $className;
117
            }
118 24
        }
119 2
    }
120
121
    /**
122
     * @param $filename
123
     * @param $className
124
     * @return bool
125
     */
126 11
    private function classNameMatchesFileName($filename, $className)
127
    {
128 11
        return strpos($filename, $className) !== false
129 11
            || strpos($filename, $this->invertSlashes($className)) !== false;
130
    }
131
132 4
    private function invertSlashes($className)
133
    {
134 4
        return str_replace('\\', '/', $className);
135
    }
136
}
137