Completed
Push — master ( 1a6b57...4042aa )
by Alex
11:09
created

AnnotationTrait::getAnnotationName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 4
nc 2
nop 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 31 and the first side effect is on line 22.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/**
4
 * Codeburner Framework.
5
 *
6
 * @author Alex Rohleder <[email protected]>
7
 * @copyright 2016 Alex Rohleder
8
 * @license http://opensource.org/licenses/MIT
9
 */
10
11
namespace Codeburner\Annotator\Reflection;
12
13
use Codeburner\Annotator\Annotation;
14
use Codeburner\Annotator\Exceptions\WildcardNotAllowedException;
15
16
/**
17
 * Avoid the autoload by manually including the required files.
18
 * This bust significantly the performance.
19
 */
20
21
if (!class_exists('Codeburner\Annotator\Annotation', false)) {
22
	include __DIR__ . '/../Annotation.php';
23
}
24
25
/**
26
 * Implements the annotation methods into a reflection.
27
 *
28
 * @author Alex Rohleder <[email protected]>
29
 */
30
31
trait AnnotationTrait
32
{
33
34
	abstract public function getDocComment();
35
    abstract public function getNamespaceName();
36
37
    /**
38
     * @var string
39
     */
40
41
    private $useStatementsCache;
42
43
    /**
44
     * Get an annotation by it literal name.
45
     * 
46
     * @param  Annotation|null
47
     * @throws MultipleAnnotationException
48
     * @return string
49
     */
50
51
    public function getAnnotation($name)
52
    {
53
        $annotations = $this->getMatchedAnnotations($name);
54
55
        if (empty($annotations)) {
56
            return null;
57
        }
58
59
        if (count($annotations) > 1) {
60
            throw new WildcardNotAllowedException("getAnnotation");
61
        }
62
63
        return $this->getAnnotationObject($annotations[0]);
64
    }
65
66
    /**
67
     * Get all annotations, or several of then. Note that here annotations can
68
     * have regex on they name.
69
     *
70
     * @param array $names if empty will return all annotations. Can have regex.
71
     * @return array ["AnnotationName" => Annotation]
72
     */
73
74
    public function getAnnotations(array $names = array())
75
    {
76
        if (empty($names)) {
77
               $annotations = $this->getMatchedAnnotations("[\w]+");
78
        } else $annotations = $this->getMatchedAnnotations(implode("|", $names));
79
80
        $bucket = [];
81
82
        foreach ($annotations as $annotation) {
83
            $bucket[$annotation[1]] = $this->getAnnotationObject($annotation);
84
        }
85
86
        return $bucket;
87
    }
88
    
89
    /**
90
     * Check if an annotation exists
91
     *
92
     * @return bool
93
     */
94
95
    public function hasAnnotation($name)
96
    {
97
        return (bool) preg_match("~@" . $this->getAnnotationName($name) . "~", $this->getDocComment());
98
    }
99
100
    /**
101
     * Find all annotations that the name match the $pattern
102
     *
103
     * @return array
104
     */
105
106
    protected function getMatchedAnnotations($pattern)
107
    {
108
        preg_match_all("~@(" . $this->getAnnotationName($pattern) . ")(\s(-f\s?)?(.*))?~i", $this->getDocComment(), $annotations, PREG_SET_ORDER);
109
        return (array) $annotations;
110
    }
111
112
    /**
113
     * Instantiate a new Annotation object, if the annotation has a specific object
114
     * then resolve the name and create it.
115
     *
116
     * @param array $annotation The getMatchedAnnotation annotation return.
117
     * @return Annotation
118
     */
119
120
    protected function getAnnotationObject($annotation)
121
    {
122
        $use = $this->getUseStatements();
0 ignored issues
show
Unused Code introduced by
$use is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
123
        $name = $annotation[1];
124
        $value = $annotation[4];
125
        $flag = $annotation[3];
126
127
        if (trim($flag) === "-f") {
128
            $uses = $this->getUseStatements();
129
130
            if (!isset($uses[$name])) {
131
                   $class = $this->getNamespaceName() . "\\$name";
132
                   return new $class($name, $value);
133
            } else return new $uses[$name]($name, $value);
134
        }
135
136
        return new Annotation($name, $value);
137
    }
138
139
    /**
140
     * Get all use statements from the annotated reflection file.
141
     *
142
     * @return array
143
     */
144
145
    protected function getUseStatements()
146
    {
147
        if ($this->useStatementsCache) {
148
            return $this->useStatementsCache;
149
        }
150
151
        preg_match_all("~use\s([\w\\\\]+)(\sas\s(\w+))?;~i", $this->getClassFileHeader(), $matches, PREG_SET_ORDER);
152
153
        foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
154
            // if an alias exists.
155
            if (isset($match[3])) {
156
                $this->useStatementsCache[$match[3]] = $match[1];
157
            }
158
159
            $this->useStatementsCache[$match[1]] = $match[1];
160
        }
161
162
        return $this->useStatementsCache;
163
    }
164
165
    /**
166
     * Get a fraction of the annotated reflection file.
167
     *
168
     * @return string
169
     */
170
171
    private function getClassFileHeader()
172
    {
173
        $file   = fopen($this->getFileName(), "r");
0 ignored issues
show
Bug introduced by
It seems like getFileName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
174
        $until  = $this->getStartLine();
0 ignored issues
show
Bug introduced by
It seems like getStartLine() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
175
        $line   = 0;
176
        $source = "";
177
178
        while ($line < $until) {
179
            $source .= fgets($file);
180
            ++$line;
181
        }
182
183
        fclose($file);
184
        return $source;
185
    }
186
187
    /**
188
     * Resolve annotation name.
189
     *
190
     * @return string
191
     */
192
193
    private function getAnnotationName($name)
194
    {
195
        // if it is a pattern like [\w]+
196
        if ($name[0] === "[") {
197
            return $name;
198
        }
199
200
        return str_replace("\\", "\\\\", $name);
201
    }
202
203
}
204