limoncello-php-dist /
commands
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 declare(strict_types=1); |
||
| 2 | |||
| 3 | namespace Limoncello\Commands; |
||
| 4 | |||
| 5 | /** |
||
| 6 | * Copyright 2015-2019 [email protected] |
||
| 7 | * |
||
| 8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
| 9 | * you may not use this file except in compliance with the License. |
||
| 10 | * You may obtain a copy of the License at |
||
| 11 | * |
||
| 12 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
| 13 | * |
||
| 14 | * Unless required by applicable law or agreed to in writing, software |
||
| 15 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
| 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
| 17 | * See the License for the specific language governing permissions and |
||
| 18 | * limitations under the License. |
||
| 19 | */ |
||
| 20 | |||
| 21 | use Closure; |
||
| 22 | use Limoncello\Common\Reflection\CheckCallableTrait; |
||
| 23 | use Limoncello\Common\Reflection\ClassIsTrait; |
||
| 24 | use Limoncello\Contracts\Application\ApplicationConfigurationInterface; |
||
| 25 | use Limoncello\Contracts\Application\CacheSettingsProviderInterface; |
||
| 26 | use Limoncello\Contracts\Commands\IoInterface; |
||
| 27 | use Limoncello\Contracts\Commands\RoutesConfiguratorInterface; |
||
| 28 | use Limoncello\Contracts\Commands\RoutesInterface; |
||
| 29 | use Limoncello\Contracts\Container\ContainerInterface as LimoncelloContainerInterface; |
||
| 30 | use Limoncello\Contracts\FileSystem\FileSystemInterface; |
||
| 31 | use Psr\Container\ContainerInterface as PsrContainerInterface; |
||
| 32 | use ReflectionException; |
||
| 33 | use function assert; |
||
| 34 | use function array_merge; |
||
| 35 | use function call_user_func; |
||
| 36 | use function count; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * Code for command execution is separated from the main code to get rid of a dependency from Composer. |
||
| 40 | * This code could be executed independently in tests without composer dependency. |
||
| 41 | * |
||
| 42 | * @package Limoncello\Commands |
||
| 43 | * |
||
| 44 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
||
| 45 | */ |
||
| 46 | trait ExecuteCommandTrait |
||
| 47 | { |
||
| 48 | use ClassIsTrait; |
||
| 49 | |||
| 50 | /** |
||
| 51 | * @param string $name |
||
| 52 | * @param callable $handler |
||
| 53 | * @param IoInterface $inOut |
||
| 54 | * @param LimoncelloContainerInterface $container |
||
| 55 | * |
||
| 56 | 2 | * @return void |
|
| 57 | * |
||
| 58 | * @throws ReflectionException |
||
| 59 | * |
||
| 60 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
| 61 | */ |
||
| 62 | public function executeCommand( |
||
| 63 | string $name, |
||
| 64 | callable $handler, |
||
| 65 | IoInterface $inOut, |
||
| 66 | LimoncelloContainerInterface $container |
||
| 67 | ): void { |
||
| 68 | // This method does bootstrap for every command (e.g. configure containers) |
||
| 69 | // and then calls the actual command handler. |
||
| 70 | |||
| 71 | 2 | // At this point we have probably only partly configured container and we need to read from it |
|
| 72 | 2 | // CLI route setting in order to fully configure it and then run the command with middleware. |
|
| 73 | 2 | // However, when we read anything from it, it changes its state so we are not allowed to add |
|
| 74 | // anything to it (technically we can but in some cases it might cause an exception). |
||
| 75 | // So, what's the solution? We clone the container, read from the clone everything we need, |
||
| 76 | 2 | // and then continue with the original unchanged container. |
|
| 77 | 2 | $routesPath = null; |
|
| 78 | if (true) { |
||
| 79 | 2 | $containerClone = clone $container; |
|
| 80 | 2 | ||
| 81 | /** @var CacheSettingsProviderInterface $provider */ |
||
| 82 | $provider = $container->get(CacheSettingsProviderInterface::class); |
||
| 83 | 2 | $appConfig = $provider->getApplicationConfiguration(); |
|
| 84 | 2 | ||
| 85 | 2 | $routesFolder = $appConfig[ApplicationConfigurationInterface::KEY_ROUTES_FOLDER] ?? ''; |
|
| 86 | 2 | $routesMask = $appConfig[ApplicationConfigurationInterface::KEY_ROUTES_FILE_MASK] ?? ''; |
|
| 87 | 2 | ||
| 88 | /** @var FileSystemInterface $files */ |
||
| 89 | assert( |
||
| 90 | 2 | ($files = $containerClone->get(FileSystemInterface::class)) !== null && |
|
| 91 | empty($routesFolder) === false && empty($routesMask) === false && |
||
| 92 | 2 | $files->exists($routesFolder) === true, |
|
| 93 | 'Routes folder and mask must be defined in application settings.' |
||
| 94 | ); |
||
| 95 | |||
| 96 | 2 | unset($containerClone); |
|
| 97 | |||
| 98 | 2 | $routesPath = $routesFolder . DIRECTORY_SEPARATOR . $routesMask; |
|
| 99 | } |
||
| 100 | 2 | ||
| 101 | [$configurators, $middleware] |
||
|
0 ignored issues
–
show
|
|||
| 102 | = $this->readExtraContainerConfiguratorsAndMiddleware($routesPath, $name); |
||
| 103 | |||
| 104 | 2 | $this->executeContainerConfigurators($configurators, $container); |
|
| 105 | |||
| 106 | $handler = $this->buildExecutionChain($middleware, $handler, $container); |
||
| 107 | |||
| 108 | // finally go through all middleware and execute command handler |
||
| 109 | // (container has to be the same (do not send as param), but middleware my wrap IO (send as param)). |
||
| 110 | call_user_func($handler, $inOut); |
||
| 111 | } |
||
| 112 | |||
| 113 | /** |
||
| 114 | * @param string $routesFolder |
||
| 115 | * @param string $commandName |
||
| 116 | * |
||
| 117 | 2 | * @return array |
|
| 118 | * |
||
| 119 | * @throws ReflectionException |
||
| 120 | * |
||
| 121 | * @SuppressWarnings(PHPMD.ExcessiveMethodLength) |
||
| 122 | */ |
||
| 123 | private function readExtraContainerConfiguratorsAndMiddleware(string $routesFolder, string $commandName): array |
||
| 124 | { |
||
| 125 | $routesFilter = new class ($commandName) implements RoutesInterface |
||
| 126 | { |
||
| 127 | use CheckCallableTrait; |
||
| 128 | |||
| 129 | /** @var array */ |
||
| 130 | private $middleware = []; |
||
| 131 | |||
| 132 | /** @var array */ |
||
| 133 | private $configurators = []; |
||
| 134 | |||
| 135 | /** @var string */ |
||
| 136 | private $commandName; |
||
| 137 | 2 | ||
| 138 | /** |
||
| 139 | * @param string $commandName |
||
| 140 | */ |
||
| 141 | public function __construct(string $commandName) |
||
| 142 | { |
||
| 143 | $this->commandName = $commandName; |
||
| 144 | } |
||
| 145 | 2 | ||
| 146 | /** |
||
| 147 | 2 | * @inheritdoc |
|
| 148 | */ |
||
| 149 | 2 | public function addGlobalMiddleware(array $middleware): RoutesInterface |
|
| 150 | { |
||
| 151 | assert($this->checkMiddlewareCallables($middleware) === true); |
||
| 152 | |||
| 153 | $this->middleware = array_merge($this->middleware, $middleware); |
||
| 154 | |||
| 155 | return $this; |
||
| 156 | } |
||
| 157 | 2 | ||
| 158 | /** |
||
| 159 | 2 | * @inheritdoc |
|
| 160 | */ |
||
| 161 | 2 | public function addGlobalContainerConfigurators(array $configurators): RoutesInterface |
|
| 162 | { |
||
| 163 | assert($this->checkConfiguratorCallables($configurators) === true); |
||
| 164 | |||
| 165 | $this->configurators = array_merge($this->configurators, $configurators); |
||
| 166 | |||
| 167 | return $this; |
||
| 168 | } |
||
| 169 | 2 | ||
| 170 | /** |
||
| 171 | 2 | * @inheritdoc |
|
| 172 | 1 | */ |
|
| 173 | public function addCommandMiddleware(string $name, array $middleware): RoutesInterface |
||
| 174 | { |
||
| 175 | 2 | assert($this->checkMiddlewareCallables($middleware) === true); |
|
| 176 | |||
| 177 | if ($this->commandName === $name) { |
||
| 178 | $this->middleware = array_merge($this->middleware, $middleware); |
||
| 179 | } |
||
| 180 | |||
| 181 | return $this; |
||
| 182 | } |
||
| 183 | 2 | ||
| 184 | /** |
||
| 185 | 2 | * @inheritdoc |
|
| 186 | 1 | */ |
|
| 187 | public function addCommandContainerConfigurators(string $name, array $configurators): RoutesInterface |
||
| 188 | { |
||
| 189 | 2 | assert($this->checkConfiguratorCallables($configurators) === true); |
|
| 190 | |||
| 191 | if ($this->commandName === $name) { |
||
| 192 | $this->configurators = array_merge($this->configurators, $configurators); |
||
| 193 | } |
||
| 194 | |||
| 195 | return $this; |
||
| 196 | } |
||
| 197 | 2 | ||
| 198 | /** |
||
| 199 | * @return array |
||
| 200 | */ |
||
| 201 | public function getMiddleware(): array |
||
| 202 | { |
||
| 203 | return $this->middleware; |
||
| 204 | } |
||
| 205 | 2 | ||
| 206 | /** |
||
| 207 | * @return array |
||
| 208 | */ |
||
| 209 | public function getConfigurators(): array |
||
| 210 | { |
||
| 211 | return $this->configurators; |
||
| 212 | } |
||
| 213 | |||
| 214 | /** |
||
| 215 | 2 | * @param array $mightBeConfigurators |
|
| 216 | * |
||
| 217 | 2 | * @return bool |
|
| 218 | 2 | */ |
|
| 219 | 2 | private function checkConfiguratorCallables(array $mightBeConfigurators): bool |
|
| 220 | 2 | { |
|
| 221 | 2 | $result = true; |
|
| 222 | 2 | ||
| 223 | foreach ($mightBeConfigurators as $mightBeCallable) { |
||
| 224 | $result = $result === true && |
||
| 225 | $this->checkPublicStaticCallable( |
||
| 226 | 2 | $mightBeCallable, |
|
| 227 | [LimoncelloContainerInterface::class], |
||
| 228 | 'void' |
||
| 229 | ); |
||
| 230 | } |
||
| 231 | |||
| 232 | return $result; |
||
| 233 | } |
||
| 234 | |||
| 235 | /** |
||
| 236 | 2 | * @param array $mightBeMiddleware |
|
| 237 | * |
||
| 238 | 2 | * @return bool |
|
| 239 | 2 | */ |
|
| 240 | 2 | private function checkMiddlewareCallables(array $mightBeMiddleware): bool |
|
| 241 | 2 | { |
|
| 242 | 2 | $result = true; |
|
| 243 | |||
| 244 | foreach ($mightBeMiddleware as $mightBeCallable) { |
||
| 245 | $result = $result === true && $this->checkPublicStaticCallable( |
||
| 246 | 2 | $mightBeCallable, |
|
| 247 | [IoInterface::class, Closure::class, PsrContainerInterface::class], |
||
| 248 | 'void' |
||
| 249 | ); |
||
| 250 | 2 | } |
|
| 251 | |||
| 252 | 2 | return $result; |
|
| 253 | } |
||
| 254 | }; |
||
| 255 | 2 | ||
| 256 | foreach (static::selectClasses($routesFolder, RoutesConfiguratorInterface::class) as $class) { |
||
| 257 | /** @var RoutesConfiguratorInterface $class */ |
||
| 258 | $class::configureRoutes($routesFilter); |
||
| 259 | } |
||
| 260 | |||
| 261 | return [$routesFilter->getConfigurators(), $routesFilter->getMiddleware()]; |
||
| 262 | } |
||
| 263 | |||
| 264 | 2 | /** |
|
| 265 | * @param callable[] $configurators |
||
| 266 | 2 | * @param LimoncelloContainerInterface $container |
|
| 267 | 2 | * |
|
| 268 | * @return void |
||
| 269 | */ |
||
| 270 | private function executeContainerConfigurators(array $configurators, LimoncelloContainerInterface $container): void |
||
| 271 | { |
||
| 272 | foreach ($configurators as $configurator) { |
||
| 273 | call_user_func($configurator, $container); |
||
| 274 | } |
||
| 275 | } |
||
| 276 | |||
| 277 | /** |
||
| 278 | 2 | * @param array $middleware |
|
| 279 | * @param callable $command |
||
| 280 | * @param PsrContainerInterface $container |
||
| 281 | * |
||
| 282 | * @return Closure |
||
| 283 | */ |
||
| 284 | 2 | private function buildExecutionChain( |
|
| 285 | 2 | array $middleware, |
|
| 286 | callable $command, |
||
| 287 | 2 | PsrContainerInterface $container |
|
| 288 | 2 | ): Closure { |
|
| 289 | $next = function (IoInterface $inOut) use ($command, $container): void { |
||
| 290 | 2 | call_user_func($command, $container, $inOut); |
|
| 291 | 2 | }; |
|
| 292 | |||
| 293 | for ($index = count($middleware) - 1; $index >= 0; $index--) { |
||
| 294 | 2 | $currentMiddleware = $middleware[$index]; |
|
| 295 | $next = function (IoInterface $inOut) use ($currentMiddleware, $next, $container): void { |
||
| 296 | call_user_func($currentMiddleware, $inOut, $next, $container); |
||
| 297 | }; |
||
| 298 | } |
||
| 299 | |||
| 300 | return $next; |
||
| 301 | } |
||
| 302 | } |
||
| 303 |
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.