Security update for permissions_by_term
[yaffs-website] / vendor / behat / behat / src / Behat / Behat / Context / Reader / AnnotatedContextReader.php
1 <?php
2
3 /*
4  * This file is part of the Behat.
5  * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 namespace Behat\Behat\Context\Reader;
12
13 use Behat\Behat\Context\Annotation\AnnotationReader;
14 use Behat\Behat\Context\Environment\ContextEnvironment;
15 use Behat\Testwork\Call\Callee;
16 use ReflectionClass;
17 use ReflectionException;
18 use ReflectionMethod;
19
20 /**
21  * Reads context callees by annotations using registered annotation readers.
22  *
23  * @author Konstantin Kudryashov <ever.zet@gmail.com>
24  */
25 final class AnnotatedContextReader implements ContextReader
26 {
27     const DOCLINE_TRIMMER_REGEX = '/^\/\*\*\s*|^\s*\*\s*|\s*\*\/$|\s*$/';
28
29     /**
30      * @var string[]
31      */
32     private static $ignoreAnnotations = array(
33         '@param',
34         '@return',
35         '@throws',
36         '@see',
37         '@uses',
38         '@todo'
39     );
40     /**
41      * @var AnnotationReader[]
42      */
43     private $readers = array();
44
45     /**
46      * Registers annotation reader.
47      *
48      * @param AnnotationReader $reader
49      */
50     public function registerAnnotationReader(AnnotationReader $reader)
51     {
52         $this->readers[] = $reader;
53     }
54
55     /**
56      * {@inheritdoc}
57      */
58     public function readContextCallees(ContextEnvironment $environment, $contextClass)
59     {
60         $reflection = new ReflectionClass($contextClass);
61
62         $callees = array();
63         foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
64             foreach ($this->readMethodCallees($reflection->getName(), $method) as $callee) {
65                 $callees[] = $callee;
66             }
67         }
68
69         return $callees;
70     }
71
72     /**
73      * Loads callees associated with specific method.
74      *
75      * @param string           $class
76      * @param ReflectionMethod $method
77      *
78      * @return Callee[]
79      */
80     private function readMethodCallees($class, ReflectionMethod $method)
81     {
82         $callees = array();
83
84         // read parent annotations
85         try {
86             $prototype = $method->getPrototype();
87             // error occurs on every second PHP stable release - getPrototype() returns itself
88             if ($prototype->getDeclaringClass()->getName() !== $method->getDeclaringClass()->getName()) {
89                 $callees = array_merge($callees, $this->readMethodCallees($class, $prototype));
90             }
91         } catch (ReflectionException $e) {
92         }
93
94         if ($docBlock = $method->getDocComment()) {
95             $callees = array_merge($callees, $this->readDocBlockCallees($class, $method, $docBlock));
96         }
97
98         return $callees;
99     }
100
101     /**
102      * Reads callees from the method doc block.
103      *
104      * @param string           $class
105      * @param ReflectionMethod $method
106      * @param string           $docBlock
107      *
108      * @return Callee[]
109      */
110     private function readDocBlockCallees($class, ReflectionMethod $method, $docBlock)
111     {
112         $callees = array();
113         $description = $this->readDescription($docBlock);
114         $docBlock = $this->mergeMultilines($docBlock);
115
116         foreach (explode("\n", $docBlock) as $docLine) {
117             $docLine = preg_replace(self::DOCLINE_TRIMMER_REGEX, '', $docLine);
118
119             if ($this->isEmpty($docLine)) {
120                 continue;
121             }
122
123             if ($this->isNotAnnotation($docLine)) {
124                 continue;
125             }
126
127             if ($callee = $this->readDocLineCallee($class, $method, $docLine, $description)) {
128                 $callees[] = $callee;
129             }
130         }
131
132         return $callees;
133     }
134
135     /**
136      * Merges multiline strings (strings ending with "\")
137      *
138      * @param string $docBlock
139      *
140      * @return string
141      */
142     private function mergeMultilines($docBlock)
143     {
144         return preg_replace("#\\\\$\s*+\*\s*+([^\\\\$]++)#m", '$1', $docBlock);
145     }
146
147     /**
148      * Extracts a description from the provided docblock,
149      * with support for multiline descriptions.
150      *
151      * @param string $docBlock
152      *
153      * @return string
154      */
155     private function readDescription($docBlock)
156     {
157         // Remove indentation
158         $description = preg_replace('/^[\s\t]*/m', '', $docBlock);
159
160         // Remove block comment syntax
161         $description = preg_replace('/^\/\*\*\s*|^\s*\*\s|^\s*\*\/$/m', '', $description);
162
163         // Remove annotations
164         $description = preg_replace('/^@.*$/m', '', $description);
165
166         // Ignore docs after a "--" separator
167         if (preg_match('/^--.*$/m', $description)) {
168             $descriptionParts = preg_split('/^--.*$/m', $description);
169             $description = array_shift($descriptionParts);
170         }
171
172         // Trim leading and trailing newlines
173         $description = trim($description, "\r\n");
174
175         return $description;
176     }
177
178     /**
179      * Checks if provided doc lien is empty.
180      *
181      * @param string $docLine
182      *
183      * @return Boolean
184      */
185     private function isEmpty($docLine)
186     {
187         return '' == $docLine;
188     }
189
190     /**
191      * Checks if provided doc line is not an annotation.
192      *
193      * @param string $docLine
194      *
195      * @return Boolean
196      */
197     private function isNotAnnotation($docLine)
198     {
199         return '@' !== substr($docLine, 0, 1);
200     }
201
202     /**
203      * Reads callee from provided doc line using registered annotation readers.
204      *
205      * @param string           $class
206      * @param ReflectionMethod $method
207      * @param string           $docLine
208      * @param null|string      $description
209      *
210      * @return null|Callee
211      */
212     private function readDocLineCallee($class, ReflectionMethod $method, $docLine, $description = null)
213     {
214         if ($this->isIgnoredAnnotation($docLine)) {
215             return null;
216         }
217
218         foreach ($this->readers as $reader) {
219             if ($callee = $reader->readCallee($class, $method, $docLine, $description)) {
220                 return $callee;
221             }
222         }
223
224         return null;
225     }
226
227     /**
228      * Checks if provided doc line is one of the ignored annotations.
229      *
230      * @param string $docLine
231      *
232      * @return Boolean
233      */
234     private function isIgnoredAnnotation($docLine)
235     {
236         $lowDocLine = strtolower($docLine);
237         foreach (self::$ignoreAnnotations as $ignoredAnnotation) {
238             if ($ignoredAnnotation == substr($lowDocLine, 0, strlen($ignoredAnnotation))) {
239                 return true;
240             }
241         }
242
243         return false;
244     }
245 }