This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace ClassConfig; |
||
4 | |||
5 | use ClassConfig\Annotation\Config; |
||
6 | use ClassConfig\Annotation\ConfigList; |
||
7 | use ClassConfig\Annotation\ConfigBoolean; |
||
8 | use ClassConfig\Annotation\ConfigEntryInterface; |
||
9 | use ClassConfig\Annotation\ConfigFloat; |
||
10 | use ClassConfig\Annotation\ConfigInteger; |
||
11 | use ClassConfig\Annotation\ConfigMap; |
||
12 | use ClassConfig\Annotation\ConfigObject; |
||
13 | use ClassConfig\Annotation\ConfigString; |
||
14 | use ClassConfig\Exceptions\ClassConfigAlreadyRegisteredException; |
||
15 | use ClassConfig\Exceptions\ClassConfigNotRegisteredException; |
||
16 | use Doctrine\Common\Annotations\AnnotationReader; |
||
17 | |||
18 | /** |
||
19 | * Class ClassConfig |
||
20 | * @package ClassConfig |
||
21 | */ |
||
22 | class ClassConfig |
||
23 | { |
||
24 | /** |
||
25 | * Config files are always re-generated when requested. |
||
26 | */ |
||
27 | const CACHE_NEVER = 0; |
||
28 | |||
29 | /** |
||
30 | * Config files are re-generated if older than the source (filemtime). |
||
31 | */ |
||
32 | const CACHE_VALIDATE = 1; |
||
33 | |||
34 | /** |
||
35 | * Config files are only generated once (or after being manually deleted). |
||
36 | */ |
||
37 | const CACHE_ALWAYS = 2; |
||
38 | |||
39 | /** |
||
40 | * Flag to determine whether the register() method has been called. |
||
41 | * |
||
42 | * @var bool |
||
43 | */ |
||
44 | protected static $registered = false; |
||
45 | |||
46 | /** |
||
47 | * In-memory cache for the annotation reader. |
||
48 | * |
||
49 | * @var AnnotationReader |
||
50 | */ |
||
51 | protected static $annotationReader; |
||
52 | |||
53 | /** |
||
54 | * The registered path to a cache folder. |
||
55 | * |
||
56 | * @var string |
||
57 | */ |
||
58 | protected static $cachePath; |
||
59 | |||
60 | /** |
||
61 | * The registered caching strategy. |
||
62 | * |
||
63 | * @var int |
||
64 | */ |
||
65 | protected static $cacheStrategy; |
||
66 | |||
67 | /** |
||
68 | * The registered class namespace for config classes. |
||
69 | * This will be used as prefix to source classes. |
||
70 | * |
||
71 | * @var string |
||
72 | */ |
||
73 | protected static $classNamespace; |
||
74 | |||
75 | /** |
||
76 | * @param string $path |
||
77 | */ |
||
78 | protected static function createDirectories(string $path) |
||
79 | { |
||
80 | if (!is_dir($path)) { |
||
81 | static::createDirectories(dirname($path)); |
||
82 | mkdir($path); |
||
83 | } |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Lazy getter for the annotation reader. |
||
88 | * |
||
89 | * @return AnnotationReader |
||
90 | * @throws \Doctrine\Common\Annotations\AnnotationException |
||
91 | */ |
||
92 | protected static function getAnnotationReader(): AnnotationReader |
||
93 | { |
||
94 | if (!isset(static::$annotationReader)) { |
||
95 | static::$annotationReader = new AnnotationReader(); |
||
96 | } |
||
97 | return static::$annotationReader; |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Getter for the registered cache path. |
||
102 | * Throws a ClassConfigNotRegisteredException if register() wasn't called prior. |
||
103 | * |
||
104 | * @return string |
||
105 | * @throws ClassConfigNotRegisteredException |
||
106 | */ |
||
107 | protected static function getCachePath(): string |
||
108 | { |
||
109 | if (!static::$registered) { |
||
110 | throw new ClassConfigNotRegisteredException(); |
||
111 | } |
||
112 | return static::$cachePath; |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Getter for the registered class namespace. |
||
117 | * Throws a ClassConfigNotRegisteredException if register() wasn't called prior. |
||
118 | * |
||
119 | * @return string |
||
120 | * @throws ClassConfigNotRegisteredException |
||
121 | */ |
||
122 | protected static function getClassNamespace(): string |
||
123 | { |
||
124 | if (!static::$registered) { |
||
125 | throw new ClassConfigNotRegisteredException(); |
||
126 | } |
||
127 | return self::$classNamespace; |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * @param Config $annotation |
||
132 | * @param string $className |
||
133 | * @param string $classNamespace |
||
134 | * @param string $canonicalClassName |
||
135 | * @param string $targetClassNamespace |
||
136 | * @param string $targetCanonicalClassName |
||
137 | * @param int $time |
||
138 | * @param int $subClassIteration |
||
139 | * @return string |
||
140 | */ |
||
141 | protected static function generate( |
||
142 | Config $annotation, |
||
143 | string $className, |
||
144 | string $classNamespace, |
||
145 | string $canonicalClassName, |
||
146 | string $targetClassNamespace, |
||
147 | string $targetCanonicalClassName, |
||
148 | int $time, |
||
149 | int &$subClassIteration = 0 |
||
150 | ): string { |
||
151 | // a suffix of _0, _1, _2 etc. is added to generated sub-classes |
||
152 | $suffix = 0 < $subClassIteration ? '_' . $subClassIteration : ''; |
||
153 | |||
154 | $effectiveClassName = $className . $suffix; |
||
155 | $effectiveTargetCanonicalClassName = $targetCanonicalClassName . $suffix; |
||
156 | |||
157 | $generator = new ClassGenerator($annotation, $effectiveClassName, $targetClassNamespace, $canonicalClassName); |
||
158 | |||
159 | /** |
||
160 | * @var string $key |
||
161 | * @var ConfigEntryInterface $entry |
||
162 | */ |
||
163 | foreach ($annotation->value as $key => $entry) { |
||
164 | switch (true) { |
||
165 | case $entry instanceof ConfigString: |
||
166 | case $entry instanceof ConfigInteger: |
||
167 | case $entry instanceof ConfigFloat: |
||
168 | case $entry instanceof ConfigBoolean: |
||
169 | case $entry instanceof ConfigObject: |
||
170 | $type = $entry->getType(); |
||
171 | $generator |
||
172 | ->generateProperty($key, $type, isset($entry->default) ? $entry->default : null) |
||
173 | ->generateGet($key, $type) |
||
174 | ->generateSet($key, $type) |
||
175 | ->generateIsset($key) |
||
176 | ->generateUnset($key); |
||
177 | break; |
||
178 | |||
179 | case $entry instanceof ConfigList: |
||
180 | $type = isset($entry->value) ? $entry->value->getType() : 'mixed'; |
||
181 | $generator |
||
182 | ->generateProperty($key, $type . '[]', isset($entry->default) ? |
||
0 ignored issues
–
show
|
|||
183 | array_values($entry->default) : null) |
||
184 | ->generateGet($key, $type . '[]') |
||
185 | ->generateListSet($key, $type . '[]') |
||
186 | ->generateListGetAt($key, $type) |
||
187 | ->generateListSetAt($key, $type) |
||
188 | ->generateListPush($key, $type) |
||
189 | ->generateListUnshift($key, $type) |
||
190 | ->generateArrayPop($key, $type) |
||
191 | ->generateArrayShift($key, $type) |
||
192 | ->generateArrayClear($key) |
||
193 | ->generateIsset($key) |
||
194 | ->generateUnset($key); |
||
195 | break; |
||
196 | |||
197 | case $entry instanceof ConfigMap: |
||
198 | $type = isset($entry->value) ? $entry->value->getType() : 'mixed'; |
||
199 | $generator |
||
200 | ->generateProperty($key, $type . '[]', $entry->default) |
||
201 | ->generateGet($key, $type . '[]') |
||
202 | ->generateMapSet($key, $type . '[]') |
||
203 | ->generateMapGetAt($key, $type) |
||
204 | ->generateMapSetAt($key, $type) |
||
205 | ->generateArrayPop($key, $type) |
||
206 | ->generateArrayShift($key, $type) |
||
207 | ->generateArrayClear($key) |
||
208 | ->generateIsset($key) |
||
209 | ->generateUnset($key); |
||
210 | break; |
||
211 | |||
212 | case $entry instanceof Config: |
||
213 | $subClassIteration++; |
||
214 | $entryCanonicalClassName = static::generate( |
||
215 | $entry, |
||
216 | $className, |
||
217 | $classNamespace, |
||
218 | $canonicalClassName, |
||
219 | $targetClassNamespace, |
||
220 | $targetCanonicalClassName, |
||
221 | $time, |
||
222 | $subClassIteration |
||
223 | ); |
||
224 | $generator |
||
225 | ->generateProperty($key, $entryCanonicalClassName) |
||
226 | ->generateConfigGet($key, $entryCanonicalClassName) |
||
227 | ->generateConfigSet($key) |
||
228 | ->generateConfigIsset($key) |
||
229 | ->generateConfigUnset($key); |
||
230 | break; |
||
231 | |||
232 | default: |
||
233 | throw new \RuntimeException(sprintf( |
||
234 | 'Invalid or unsupported configuration entry type: "%s".', |
||
235 | get_class($entry) |
||
236 | )); |
||
237 | } |
||
238 | } |
||
239 | |||
240 | $generator |
||
241 | ->generateMagicGet() |
||
242 | ->generateMagicSet() |
||
243 | ->generateMagicIsset() |
||
244 | ->generateMagicUnset(); |
||
245 | |||
246 | $targetDir = static::getCachePath() . '/' . str_replace('\\', '/', $classNamespace); |
||
247 | $targetPath = $targetDir . '/' . $effectiveClassName . '.php'; |
||
248 | |||
249 | static::createDirectories($targetDir); |
||
250 | |||
251 | file_put_contents($targetPath, (string) $generator); |
||
252 | @touch($targetPath, $time); |
||
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||
253 | clearstatcache(); |
||
254 | |||
255 | // as optimization measure composer's autoloader remembers that a class does not exist on the first requested |
||
256 | // it will refuse to autoload the class even if it subsequently becomes available |
||
257 | // for this reason we need to manually load the newly generated class |
||
258 | include_once $targetPath; |
||
259 | |||
260 | return $effectiveTargetCanonicalClassName; |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Register the environment. |
||
265 | * This must be called once and only once (on each request) before working with the library. |
||
266 | * |
||
267 | * @param string $cachePath |
||
268 | * @param int $cacheStrategy |
||
269 | * @param string $classNamespace |
||
270 | */ |
||
271 | public static function register( |
||
272 | string $cachePath, |
||
273 | int $cacheStrategy = self::CACHE_VALIDATE, |
||
274 | string $classNamespace = 'ClassConfig\Cache' |
||
275 | ) { |
||
276 | if (static::$registered) { |
||
277 | throw new ClassConfigAlreadyRegisteredException(); |
||
278 | } |
||
279 | |||
280 | // ensure the cache folder exists |
||
281 | static::createDirectories($cachePath); |
||
282 | |||
283 | static::$registered = true; |
||
284 | static::$cachePath = $cachePath; |
||
285 | static::$cacheStrategy = $cacheStrategy; |
||
286 | static::$classNamespace = $classNamespace; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * @param string $canonicalClassName |
||
291 | * @return string |
||
292 | * @throws \Doctrine\Common\Annotations\AnnotationException |
||
293 | * @throws \ReflectionException |
||
294 | * @throws ClassConfigNotRegisteredException |
||
295 | */ |
||
296 | public static function createClass(string $canonicalClassName): string |
||
297 | { |
||
298 | $parts = explode('\\', $canonicalClassName); |
||
299 | |||
300 | $className = $parts[count($parts) - 1]; |
||
301 | $classNamespace = implode('\\', array_slice($parts, 0, -1)); |
||
302 | |||
303 | $targetClassNamespace = static::getClassNamespace() . '\\' . $classNamespace; |
||
304 | $targetCanonicalClassName = $targetClassNamespace . '\\' . $className; |
||
305 | |||
306 | switch (static::$cacheStrategy) { |
||
307 | case static::CACHE_NEVER: |
||
308 | // always regenerate |
||
309 | $time = time(); |
||
310 | break; |
||
311 | |||
312 | case static::CACHE_ALWAYS: |
||
313 | // only generate if class does not exist |
||
314 | if (class_exists($targetCanonicalClassName)) { |
||
315 | return $targetCanonicalClassName; |
||
316 | } |
||
317 | $time = time(); |
||
318 | break; |
||
319 | |||
320 | case static::CACHE_VALIDATE: |
||
321 | default: |
||
322 | // validate by last modified time |
||
323 | $time = filemtime((new \ReflectionClass($canonicalClassName))->getFileName()); |
||
324 | if ( |
||
325 | class_exists($targetCanonicalClassName) && |
||
326 | filemtime((new \ReflectionClass($canonicalClassName))->getFileName()) === |
||
327 | filemtime((new \ReflectionClass($targetCanonicalClassName))->getFileName()) |
||
328 | ) { |
||
329 | return $targetCanonicalClassName; |
||
330 | } |
||
331 | break; |
||
332 | } |
||
333 | |||
334 | /** @var Config $annotation */ |
||
335 | $annotation = static::getAnnotationReader()->getClassAnnotation( |
||
336 | new \ReflectionClass($canonicalClassName), |
||
337 | Config::class |
||
338 | ); |
||
339 | |||
340 | return static::generate( |
||
341 | $annotation, |
||
342 | $className, |
||
343 | $classNamespace, |
||
344 | $canonicalClassName, |
||
345 | $targetClassNamespace, |
||
346 | $targetCanonicalClassName, |
||
347 | $time |
||
348 | ); |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * @param string $class |
||
353 | * @param object $owner |
||
354 | * @return AbstractConfig |
||
355 | * @throws \Doctrine\Common\Annotations\AnnotationException |
||
356 | * @throws \ReflectionException |
||
357 | * @throws ClassConfigNotRegisteredException |
||
358 | */ |
||
359 | public static function createInstance(string $class, $owner): AbstractConfig |
||
360 | { |
||
361 | $canonicalClassName = static::createClass($class); |
||
362 | return new $canonicalClassName($owner); |
||
363 | } |
||
364 | } |
||
365 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.