Maslosoft /
Addendum
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 | /** |
||
| 4 | * This software package is licensed under AGPL, Commercial license. |
||
| 5 | * |
||
| 6 | * @package maslosoft/addendum |
||
| 7 | * @licence AGPL, Commercial |
||
| 8 | * @copyright Copyright (c) Piotr Masełkowski <[email protected]> (Meta container, further improvements, bugfixes) |
||
| 9 | * @copyright Copyright (c) Maslosoft (Meta container, further improvements, bugfixes) |
||
| 10 | * @copyright Copyright (c) Jan Suchal (Original version, builder, parser) |
||
| 11 | * @link https://maslosoft.com/addendum/ - maslosoft addendum |
||
| 12 | * @link https://code.google.com/p/addendum/ - original addendum project |
||
| 13 | */ |
||
| 14 | |||
| 15 | namespace Maslosoft\Addendum\Builder; |
||
| 16 | |||
| 17 | use Exception; |
||
| 18 | use Maslosoft\Addendum\Addendum; |
||
| 19 | use Maslosoft\Addendum\Annotations\TargetAnnotation; |
||
| 20 | use Maslosoft\Addendum\Cache\BuildOneCache; |
||
| 21 | use Maslosoft\Addendum\Collections\AnnotationsCollection; |
||
| 22 | use Maslosoft\Addendum\Collections\MatcherConfig; |
||
| 23 | use Maslosoft\Addendum\Helpers\CoarseChecker; |
||
| 24 | use Maslosoft\Addendum\Interfaces\AnnotationInterface; |
||
| 25 | use Maslosoft\Addendum\Matcher\AnnotationsMatcher; |
||
| 26 | use Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass; |
||
| 27 | use Maslosoft\Addendum\Reflection\ReflectionAnnotatedMethod; |
||
| 28 | use Maslosoft\Addendum\Reflection\ReflectionAnnotatedProperty; |
||
| 29 | use Maslosoft\Addendum\Utilities\Blacklister; |
||
| 30 | use Maslosoft\Addendum\Utilities\ClassChecker; |
||
| 31 | use Maslosoft\Addendum\Utilities\NameNormalizer; |
||
| 32 | use Maslosoft\Addendum\Utilities\ReflectionName; |
||
| 33 | use ReflectionClass; |
||
| 34 | use ReflectionMethod; |
||
| 35 | use ReflectionProperty; |
||
| 36 | use Reflector; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * @Label("Annotations builder") |
||
| 40 | */ |
||
| 41 | class Builder |
||
| 42 | { |
||
| 43 | |||
| 44 | /** |
||
| 45 | * Cached values of parsing |
||
| 46 | * @var string[][][] |
||
| 47 | */ |
||
| 48 | private static $cache = []; |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Addendum instance |
||
| 52 | * @var Addendum |
||
| 53 | */ |
||
| 54 | private $addendum = null; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * One entity build cache |
||
| 58 | * @var BuildOneCache |
||
| 59 | */ |
||
| 60 | private $buildCache = null; |
||
| 61 | |||
| 62 | 58 | public function __construct(Addendum $addendum = null) |
|
| 63 | { |
||
| 64 | 58 | $this->addendum = $addendum ?: new Addendum(); |
|
| 65 | 58 | $this->buildCache = new BuildOneCache(static::class, null, $this->addendum); |
|
| 66 | 58 | } |
|
| 67 | |||
| 68 | /** |
||
| 69 | * Build annotations collection |
||
| 70 | * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $targetReflection |
||
| 71 | * @return AnnotationsCollection |
||
| 72 | */ |
||
| 73 | 58 | public function build($targetReflection) |
|
| 74 | { |
||
| 75 | 58 | $annotations = []; |
|
| 76 | |||
| 77 | 58 | $data = $this->buildOne($targetReflection); |
|
| 78 | |||
| 79 | // Get annotations from current entity |
||
| 80 | 58 | foreach ($data as $class => $parameters) |
|
| 81 | { |
||
| 82 | 55 | foreach ($parameters as $params) |
|
| 83 | { |
||
| 84 | 55 | $annotation = $this->instantiateAnnotation($class, $params, $targetReflection); |
|
| 85 | 55 | if ($annotation !== false) |
|
| 86 | { |
||
| 87 | 55 | $annotations[$class][] = $annotation; |
|
| 88 | } |
||
| 89 | } |
||
| 90 | } |
||
| 91 | |||
| 92 | 58 | return new AnnotationsCollection($annotations); |
|
| 93 | } |
||
| 94 | |||
| 95 | 58 | private function buildOne($targetReflection) |
|
| 96 | { |
||
| 97 | 58 | if (empty($targetReflection)) |
|
| 98 | { |
||
| 99 | 23 | return []; |
|
| 100 | } |
||
| 101 | // Decide where from take traits and base classes. |
||
| 102 | // Either from class if it's being processed reflection class |
||
| 103 | // or from declaring class if it's being processed for properties and |
||
| 104 | // methods |
||
| 105 | 58 | if ($targetReflection instanceof ReflectionClass) |
|
| 106 | { |
||
| 107 | 58 | $targetClass = $targetReflection; |
|
| 108 | } |
||
| 109 | else |
||
| 110 | { |
||
| 111 | 30 | assert($targetReflection instanceof ReflectionMethod || $targetReflection instanceof ReflectionProperty); |
|
| 112 | 30 | $targetClass = $targetReflection->getDeclaringClass(); |
|
| 113 | } |
||
| 114 | |||
| 115 | // Check if currently reflected component is cached |
||
| 116 | 58 | $this->buildCache->setComponent(ReflectionName::createName($targetReflection)); |
|
| 117 | 58 | $cached = $this->buildCache->get(); |
|
| 118 | |||
| 119 | // Check if currently reflected component *class* is cached |
||
| 120 | 58 | $this->buildCache->setComponent(ReflectionName::createName($targetClass)); |
|
| 121 | 58 | $cachedClass = $this->buildCache->get(); |
|
| 122 | |||
| 123 | // Cache is valid only if class cache is valid, |
||
| 124 | // this is required for Conflicts and Target checks |
||
| 125 | 58 | if (false !== $cached && false !== $cachedClass) |
|
| 126 | { |
||
| 127 | 58 | return $cached; |
|
| 128 | } |
||
| 129 | |||
| 130 | // Partials annotation data |
||
| 131 | 53 | $partialsData = []; |
|
| 132 | |||
| 133 | // Extract annotations from all partials |
||
| 134 | 53 | foreach ($this->getPartials($targetClass) as $partial) |
|
| 135 | { |
||
| 136 | // Need to recurse here, as: |
||
| 137 | // |
||
| 138 | // * Interface might extend from other interfaces |
||
| 139 | // * Parent class, might have traits or interfaces |
||
| 140 | // * Trait might have traits |
||
| 141 | 51 | $partData = $this->buildOne($this->getRelated($targetReflection, $partial)); |
|
| 142 | |||
| 143 | // Check if data is present and proper |
||
| 144 | 51 | if (false === $partData || empty($partData) || !is_array($partData)) |
|
| 145 | { |
||
| 146 | 46 | continue; |
|
| 147 | } |
||
| 148 | |||
| 149 | 17 | $partialsData = array_merge_recursive($partialsData, $partData); |
|
| 150 | } |
||
| 151 | |||
| 152 | // Merge data from traits etc. |
||
| 153 | // with data from class |
||
| 154 | 53 | $data = array_merge_recursive($partialsData, $this->parse($targetReflection)); |
|
|
0 ignored issues
–
show
|
|||
| 155 | |||
| 156 | 53 | $this->buildCache->setComponent(ReflectionName::createName($targetReflection)); |
|
| 157 | 53 | $this->buildCache->set($data); |
|
| 158 | 53 | return $data; |
|
| 159 | } |
||
| 160 | |||
| 161 | /** |
||
| 162 | * Get reflection for related target reflection. |
||
| 163 | * Will return class, property or method reflection from related reflection, |
||
| 164 | * based on type of target reflection. |
||
| 165 | * |
||
| 166 | * Will return false target reflection id for method or property, and |
||
| 167 | * method or property does not exists on related reflection. |
||
| 168 | * |
||
| 169 | * @param Reflector $targetReflection |
||
| 170 | * @param ReflectionClass $relatedReflection |
||
| 171 | * @return ReflectionClass|ReflectionProperty|ReflectionMethod|bool |
||
| 172 | */ |
||
| 173 | 51 | private function getRelated(Reflector $targetReflection, ReflectionClass $relatedReflection) |
|
| 174 | { |
||
| 175 | switch (true) |
||
| 176 | { |
||
| 177 | 51 | case $targetReflection instanceof ReflectionClass: |
|
| 178 | 43 | return $relatedReflection; |
|
| 179 | 26 | case $targetReflection instanceof ReflectionProperty && $relatedReflection->hasProperty($targetReflection->name): |
|
| 180 | 9 | return $relatedReflection->getProperty($targetReflection->name); |
|
| 181 | 25 | case $targetReflection instanceof ReflectionMethod && $relatedReflection->hasMethod($targetReflection->name): |
|
| 182 | 6 | return $relatedReflection->getMethod($targetReflection->name); |
|
| 183 | } |
||
| 184 | 23 | return false; |
|
| 185 | } |
||
| 186 | |||
| 187 | /** |
||
| 188 | * Get partials of class, this includes: |
||
| 189 | * |
||
| 190 | * * Parent class |
||
| 191 | * * Interfaces |
||
| 192 | * * Traits |
||
| 193 | * |
||
| 194 | * @param ReflectionClass $targetClass |
||
| 195 | * @return ReflectionAnnotatedClass[] |
||
| 196 | */ |
||
| 197 | 53 | private function getPartials(ReflectionClass $targetClass) |
|
| 198 | { |
||
| 199 | // Partial reflections |
||
| 200 | /* @var ReflectionAnnotatedClass[] */ |
||
| 201 | 53 | $partials = []; |
|
| 202 | |||
| 203 | // Collect current class interfaces |
||
| 204 | 53 | foreach ($targetClass->getInterfaces() as $targetInterface) |
|
| 205 | { |
||
| 206 | /* @var $targetInterface ReflectionAnnotatedClass */ |
||
| 207 | 48 | $partials[] = $targetInterface; |
|
| 208 | } |
||
| 209 | |||
| 210 | // Collect current class parent class |
||
| 211 | 53 | $targetParent = $targetClass->getParentClass(); |
|
| 212 | 53 | if (!empty($targetParent)) |
|
| 213 | { |
||
| 214 | 27 | $partials[] = $targetParent; |
|
| 215 | } |
||
| 216 | |||
| 217 | // Collect current class traits |
||
| 218 | 53 | foreach ($targetClass->getTraits() as $trait) |
|
| 219 | { |
||
| 220 | /* @var $trait ReflectionAnnotatedClass */ |
||
| 221 | 10 | $partials[] = $trait; |
|
| 222 | } |
||
| 223 | 53 | return $partials; |
|
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Create new instance of annotation |
||
| 228 | * @param string $class |
||
| 229 | * @param mixed[] $parameters |
||
| 230 | * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty|bool $targetReflection |
||
| 231 | * @return boolean|object |
||
| 232 | */ |
||
| 233 | 55 | public function instantiateAnnotation($class, $parameters, $targetReflection = false) |
|
| 234 | { |
||
| 235 | 55 | $class = ucfirst($class) . "Annotation"; |
|
| 236 | |||
| 237 | // If namespaces are empty assume global namespace |
||
| 238 | 55 | $fqn = $this->normalizeFqn('\\', $class); |
|
| 239 | 55 | $mandatoryNs = TargetAnnotation::Ns; |
|
| 240 | 55 | $namespaces = $this->addendum->namespaces; |
|
| 241 | 55 | if (!in_array($mandatoryNs, $namespaces)) |
|
| 242 | { |
||
| 243 | $namespaces[] = $mandatoryNs; |
||
| 244 | } |
||
| 245 | 55 | foreach ($namespaces as $ns) |
|
| 246 | { |
||
| 247 | 55 | $fqn = $this->normalizeFqn($ns, $class); |
|
| 248 | 55 | if (Blacklister::ignores($fqn)) |
|
| 249 | { |
||
| 250 | 38 | continue; |
|
| 251 | } |
||
| 252 | try |
||
| 253 | { |
||
| 254 | 55 | if (!ClassChecker::exists($fqn)) |
|
| 255 | { |
||
| 256 | 52 | $this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]); |
|
| 257 | 52 | Blacklister::ignore($fqn); |
|
| 258 | } |
||
| 259 | else |
||
| 260 | { |
||
| 261 | // Class exists, exit loop |
||
| 262 | 55 | break; |
|
| 263 | } |
||
| 264 | } |
||
| 265 | catch (Exception $e) |
||
| 266 | 52 | { |
|
| 267 | // Ignore class autoloading errors |
||
| 268 | } |
||
| 269 | } |
||
| 270 | 55 | if (Blacklister::ignores($fqn)) |
|
| 271 | { |
||
| 272 | 1 | return false; |
|
| 273 | } |
||
| 274 | try |
||
| 275 | { |
||
| 276 | // NOTE: was class_exists here, however should be safe to use ClassChecker::exists here |
||
| 277 | 54 | if (!ClassChecker::exists($fqn)) |
|
| 278 | { |
||
| 279 | $this->addendum->getLogger()->debug('Annotation class `{fqn}` not found, ignoring', ['fqn' => $fqn]); |
||
| 280 | Blacklister::ignore($fqn); |
||
| 281 | 54 | return false; |
|
| 282 | } |
||
| 283 | } |
||
| 284 | catch (Exception $e) |
||
| 285 | { |
||
| 286 | // Ignore autoload errors and return false |
||
| 287 | Blacklister::ignore($fqn); |
||
| 288 | return false; |
||
| 289 | } |
||
| 290 | 54 | $resolvedClass = Addendum::resolveClassName($fqn); |
|
| 291 | 54 | if ((new ReflectionClass($resolvedClass))->implementsInterface(AnnotationInterface::class) || $resolvedClass == AnnotationInterface::class) |
|
| 292 | { |
||
| 293 | 54 | return new $resolvedClass($parameters, $targetReflection); |
|
| 294 | } |
||
| 295 | return false; |
||
| 296 | } |
||
| 297 | |||
| 298 | /** |
||
| 299 | * Normalize class name and namespace to proper fully qualified name |
||
| 300 | * @param string $ns |
||
| 301 | * @param string $class |
||
| 302 | * @return string |
||
| 303 | */ |
||
| 304 | 55 | private function normalizeFqn($ns, $class) |
|
| 305 | { |
||
| 306 | 55 | $fqn = "\\$ns\\$class"; |
|
| 307 | 55 | NameNormalizer::normalize($fqn); |
|
| 308 | 55 | return $fqn; |
|
| 309 | } |
||
| 310 | |||
| 311 | /** |
||
| 312 | * Get doc comment |
||
| 313 | * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection |
||
| 314 | * @return array |
||
| 315 | */ |
||
| 316 | 53 | private function parse($reflection) |
|
| 317 | { |
||
| 318 | 53 | $key = sprintf('%s@%s', $this->addendum->getInstanceId(), ReflectionName::createName($reflection)); |
|
| 319 | 53 | if (!isset(self::$cache[$key])) |
|
| 320 | { |
||
| 321 | // |
||
| 322 | 53 | if (!CoarseChecker::mightHaveAnnotations($reflection)) |
|
| 323 | { |
||
| 324 | 27 | self::$cache[$key] = []; |
|
| 325 | 27 | return self::$cache[$key]; |
|
| 326 | } |
||
| 327 | 50 | $parser = new AnnotationsMatcher; |
|
| 328 | 50 | $data = []; |
|
| 329 | 50 | $parser->setPlugins(new MatcherConfig([ |
|
| 330 | 50 | 'addendum' => $this->addendum, |
|
| 331 | 50 | 'reflection' => $reflection |
|
| 332 | ])); |
||
| 333 | 50 | $parser->matches($this->getDocComment($reflection), $data); |
|
| 334 | 50 | self::$cache[$key] = $data; |
|
| 335 | } |
||
| 336 | 50 | return self::$cache[$key]; |
|
| 337 | } |
||
| 338 | |||
| 339 | /** |
||
| 340 | * Get doc comment |
||
| 341 | * @param ReflectionAnnotatedClass|ReflectionAnnotatedMethod|ReflectionAnnotatedProperty $reflection |
||
| 342 | * @return mixed[] |
||
| 343 | */ |
||
| 344 | 50 | protected function getDocComment($reflection) |
|
| 345 | { |
||
| 346 | 50 | return Addendum::getDocComment($reflection); |
|
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * Clear local parsing cache |
||
| 351 | */ |
||
| 352 | 3 | public static function clearCache() |
|
| 353 | { |
||
| 354 | 3 | self::$cache = []; |
|
| 355 | 3 | } |
|
| 356 | |||
| 357 | } |
||
| 358 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: