TonyBogdanov /
class-config
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) ? |
||
| 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
|
|||
| 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 you suppress an error, we recommend checking for the error condition explicitly: