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 | namespace PhpBoot; |
||
3 | |||
4 | use DI\Container; |
||
5 | use DI\FactoryInterface; |
||
6 | use Doctrine\Common\Cache\ApcCache; |
||
7 | use Doctrine\Common\Cache\Cache; |
||
8 | use Doctrine\Common\Cache\FilesystemCache; |
||
9 | use FastRoute\DataGenerator\GroupCountBased as GroupCountBasedDataGenerator; |
||
10 | use FastRoute\Dispatcher\GroupCountBased as GroupCountBasedDispatcher; |
||
11 | use FastRoute\Dispatcher; |
||
12 | use FastRoute\RouteCollector; |
||
13 | use FastRoute\RouteParser\Std; |
||
14 | use Invoker\Exception\InvocationException; |
||
15 | use Invoker\Exception\NotCallableException; |
||
16 | use Invoker\Exception\NotEnoughParametersException; |
||
17 | use PhpBoot\Controller\ControllerContainerBuilder; |
||
18 | use PhpBoot\Cache\CheckableCache; |
||
19 | use PhpBoot\Cache\ClassModifiedChecker; |
||
20 | use PhpBoot\Controller\ControllerContainer; |
||
21 | use PhpBoot\Controller\ExceptionRenderer; |
||
22 | use PhpBoot\Controller\HookInterface; |
||
23 | use PhpBoot\Controller\Route; |
||
24 | use PhpBoot\DB\DB; |
||
25 | use PhpBoot\DI\DIContainerBuilder; |
||
26 | use PhpBoot\DI\Traits\EnableDIAnnotations; |
||
27 | use PhpBoot\Lock\LocalAutoLock; |
||
28 | use PhpBoot\Utils\Logger; |
||
29 | use Psr\Container\ContainerExceptionInterface; |
||
30 | use Psr\Container\ContainerInterface; |
||
31 | use Psr\Container\NotFoundExceptionInterface; |
||
32 | use Psr\Log\LoggerInterface; |
||
33 | use Symfony\Component\HttpFoundation\ParameterBag; |
||
34 | use Symfony\Component\HttpFoundation\Request; |
||
35 | use Symfony\Component\HttpFoundation\Response; |
||
36 | use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; |
||
37 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
||
38 | |||
39 | class Application implements ContainerInterface, FactoryInterface, \DI\InvokerInterface |
||
40 | { |
||
41 | use EnableDIAnnotations; |
||
42 | |||
43 | /** |
||
44 | * @param string|array |
||
45 | * .php file |
||
46 | * ``` |
||
47 | * return |
||
48 | * [ |
||
49 | * 'App.name' => 'App', //The App's name, default is "App", use by \Monolog\Logger as the logging channel |
||
50 | * 'App.uriPrefix' => '/', // The prefix of api uri path, default is "/" |
||
51 | * |
||
52 | * //DB.* are the params for PDO::__construct, @see http://php.net/manual/en/pdo.construct.php |
||
53 | * 'DB.connection' => 'mysql:dbname=default;host=localhost', |
||
54 | * 'DB.username' => 'root', |
||
55 | * 'DB.password' => 'root', |
||
56 | * 'DB.options' => [], |
||
57 | * |
||
58 | * |
||
59 | * LoggerInterface::class => \DI\object(\Monolog\Logger::class) |
||
60 | * ->constructor(\DI\get('App.name')), |
||
61 | * // 注意, 系统缓存, 只使用 apc、文件缓存等本地缓存, 不要使用 redis 等分布式缓存 |
||
62 | * Cache::class => \DI\object(FilesystemCache::class) |
||
63 | * ->constructorParameter('directory', sys_get_temp_dir()) |
||
64 | * ]; |
||
65 | * ``` |
||
66 | * or just the array |
||
67 | * @return self |
||
68 | */ |
||
69 | 89 | static public function createByDefault($conf = []) |
|
70 | { |
||
71 | 89 | $builder = new DIContainerBuilder(); |
|
72 | |||
73 | $default = [ |
||
74 | |||
75 | 89 | 'App.name' => 'App', |
|
76 | 89 | 'App.uriPrefix' => '/', |
|
77 | |||
78 | 89 | 'DB.connection' => 'mysql:dbname=default;host=localhost', |
|
79 | 89 | 'DB.username' => 'root', |
|
80 | 89 | 'DB.password' => 'root', |
|
81 | 89 | 'DB.options' => [], |
|
82 | |||
83 | 89 | Application::class => \DI\object() |
|
84 | 89 | ->method('setUriPrefix', \DI\get('App.uriPrefix')), |
|
85 | |||
86 | 89 | DB::class => \DI\factory([DB::class, 'connect']) |
|
87 | 89 | ->parameter('dsn', \DI\get('DB.connection')) |
|
88 | 89 | ->parameter('username', \DI\get('DB.username')) |
|
89 | 89 | ->parameter('password', \DI\get('DB.password')) |
|
90 | 89 | ->parameter('options', \DI\get('DB.options')), |
|
91 | |||
92 | 89 | LoggerInterface::class => \DI\object(\Monolog\Logger::class) |
|
93 | 89 | ->constructor(\DI\get('App.name')), |
|
94 | |||
95 | 89 | Request::class => \DI\factory([Application::class, 'createRequestFromGlobals']), |
|
96 | 89 | ]; |
|
97 | 89 | if(function_exists('apc_fetch')){ |
|
98 | $default += [ |
||
99 | 89 | Cache::class => \DI\object(ApcCache::class) |
|
100 | 89 | ]; |
|
101 | 89 | }else{ |
|
102 | $default += [ |
||
103 | Cache::class => \DI\object(FilesystemCache::class) |
||
104 | ->constructorParameter('directory', sys_get_temp_dir()) |
||
105 | ]; |
||
106 | } |
||
107 | |||
108 | |||
109 | 89 | $builder->addDefinitions($default); |
|
110 | 89 | $builder->addDefinitions($conf); |
|
111 | |||
112 | 89 | $container = $builder->build(); |
|
113 | |||
114 | 89 | Logger::setDefaultLogger($container->get(LoggerInterface::class)); |
|
115 | |||
116 | 89 | $app = $container->make(self::class); |
|
117 | 89 | return $app; |
|
118 | } |
||
119 | |||
120 | /** |
||
121 | * @return Cache |
||
122 | */ |
||
123 | 1 | public function getCache() |
|
124 | { |
||
125 | 1 | return $this->cache; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * @param Cache $localCache |
||
130 | */ |
||
131 | public function setCache(Cache $localCache) |
||
132 | { |
||
133 | $this->cache = $localCache; |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * load routes from class |
||
138 | * |
||
139 | * @param string $className |
||
140 | * @param string[] $hooks hook class names |
||
141 | * @return void |
||
142 | */ |
||
143 | 2 | public function loadRoutesFromClass($className, $hooks=[]) |
|
144 | { |
||
145 | 2 | $cache = new CheckableCache($this->cache); |
|
146 | |||
147 | 2 | $key = 'loadRoutesFromClass:' . md5(__CLASS__ . ':' . $className); |
|
148 | 2 | $routes = $cache->get($key, $this); |
|
149 | |||
150 | 2 | $controller = null; |
|
151 | 2 | if ($routes == $this) { //not cached |
|
152 | 2 | $routes = []; |
|
153 | 2 | $controller = $this->controllerContainerBuilder->build($className); |
|
154 | 2 | foreach ($controller->getRoutes() as $actionName => $route) { |
|
155 | 2 | $routes[] = [$route->getMethod(), $route->getUri(), $actionName]; |
|
156 | 2 | } |
|
157 | 2 | $cache->set($key, $routes, 0, new ClassModifiedChecker($className)); |
|
158 | 2 | } |
|
159 | 2 | foreach ($routes as $route) { |
|
160 | 2 | list($method, $uri, $actionName) = $route; |
|
161 | 2 | $this->routes[] = [ |
|
162 | 2 | $method, |
|
163 | 2 | $uri, |
|
164 | function (Application $app, Request $request) use ($cache, $className, $actionName, $controller) { |
||
165 | |||
166 | 1 | $key = 'loadRoutesFromClass:route:' . md5(__CLASS__ . ':' . $className . ':' . $actionName); |
|
167 | |||
168 | 1 | $routeInstance = $cache->get($key, $this); |
|
169 | 1 | if ($routeInstance == $this) { |
|
170 | 1 | if (!$controller) { |
|
171 | $controller = $app->controllerContainerBuilder->build($className); |
||
172 | } |
||
173 | 1 | $routeInstance = $controller->getRoute($actionName) or |
|
174 | abort(new NotFoundHttpException("action $actionName not found")); |
||
175 | 1 | $cache->set($key, $routeInstance, 0, new ClassModifiedChecker($className)); |
|
176 | 1 | } |
|
177 | 1 | return ControllerContainer::dispatch($this, $className, $actionName, $routeInstance, $request); |
|
178 | 2 | }, |
|
179 | $hooks |
||
180 | 2 | ]; |
|
181 | 2 | } |
|
182 | 2 | $this->controllers[] = $className; |
|
183 | 2 | } |
|
184 | |||
185 | /** |
||
186 | * load routes from path |
||
187 | * |
||
188 | * 被加载的文件必须以: 类名.php的形式命名 |
||
189 | * @param string $fromPath |
||
190 | * @param string $namespace |
||
191 | * @param string[] $hooks |
||
192 | * @return void |
||
193 | */ |
||
194 | View Code Duplication | public function loadRoutesFromPath($fromPath, $namespace = '', $hooks=[]) |
|
0 ignored issues
–
show
|
|||
195 | { |
||
196 | $dir = @dir($fromPath) or abort("dir $fromPath not exist"); |
||
197 | |||
198 | $getEach = function () use ($dir) { |
||
199 | $name = $dir->read(); |
||
200 | if (!$name) { |
||
201 | return $name; |
||
202 | } |
||
203 | return $name; |
||
204 | }; |
||
205 | |||
206 | while (!!($entry = $getEach())) { |
||
207 | if ($entry == '.' || $entry == '..') { |
||
208 | continue; |
||
209 | } |
||
210 | $path = $fromPath . '/' . str_replace('\\', '/', $entry); |
||
211 | if (is_file($path) && substr_compare($entry, '.php', strlen($entry) - 4, 4, true) == 0) { |
||
212 | $class_name = $namespace . '\\' . substr($entry, 0, strlen($entry) - 4); |
||
213 | $this->loadRoutesFromClass($class_name, $hooks); |
||
214 | } else { |
||
215 | //\Log::debug($path.' ignored'); |
||
216 | } |
||
217 | } |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Add route |
||
222 | * @param string $method |
||
223 | * @param string $uri |
||
224 | * @param callable $handler function(Application $app, Request $request):Response |
||
225 | * @param string[] $hooks |
||
226 | */ |
||
227 | 1 | public function addRoute($method, $uri, callable $handler, $hooks=[]) |
|
228 | { |
||
229 | 1 | $this->routes[] = [$method, $uri, $handler, $hooks]; |
|
230 | 1 | } |
|
231 | |||
232 | /** |
||
233 | * @return ControllerContainer[] |
||
234 | */ |
||
235 | 1 | public function getControllers() |
|
236 | { |
||
237 | 1 | $controllers = []; |
|
238 | 1 | foreach ($this->controllers as $name) { |
|
239 | 1 | $controllers[] = $this->controllerContainerBuilder->build($name); |
|
240 | 1 | } |
|
241 | 1 | return $controllers; |
|
242 | } |
||
243 | |||
244 | /** |
||
245 | * @param Request|null $request |
||
246 | * @param bool $send |
||
247 | * @return Response |
||
248 | */ |
||
249 | 3 | public function dispatch(Request $request = null, $send = true) |
|
250 | { |
||
251 | // TODO 把 Route里的异常处理 ExceptionRenderer 移到这里更妥? |
||
252 | 2 | $renderer = $this->get(ExceptionRenderer::class); |
|
253 | try{ |
||
254 | 2 | if ($request == null) { |
|
255 | $request = $this->make(Request::class); |
||
256 | } |
||
257 | 2 | $uri = $request->getRequestUri(); |
|
258 | 2 | if (false !== $pos = strpos($uri, '?')) { |
|
259 | $uri = substr($uri, 0, $pos); |
||
260 | } |
||
261 | 2 | $uri = rawurldecode($uri); |
|
262 | |||
263 | $next = function (Request $request)use($uri){ |
||
264 | 3 | $dispatcher = $this->getDispatcher(); |
|
265 | 2 | $res = $dispatcher->dispatch($request->getMethod(), $uri); |
|
266 | 2 | if ($res[0] == Dispatcher::FOUND) { |
|
267 | |||
268 | 2 | if (count($res[2])) { |
|
269 | $request->attributes->add($res[2]); |
||
270 | } |
||
271 | 2 | list($handler, $hooks) = $res[1]; |
|
272 | $next = function (Request $request)use($handler){ |
||
273 | 2 | return $handler($this, $request); |
|
274 | 2 | }; |
|
275 | 2 | View Code Duplication | foreach (array_reverse($hooks) as $hookName){ |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
276 | $next = function($request)use($hookName, $next){ |
||
277 | 1 | $hook = $this->get($hookName); |
|
278 | /**@var $hook HookInterface*/ |
||
279 | 1 | return $hook->handle($request, $next); |
|
280 | 1 | }; |
|
281 | 2 | } |
|
282 | 2 | return $next($request); |
|
283 | |||
284 | }elseif ($res[0] == Dispatcher::NOT_FOUND) { |
||
285 | \PhpBoot\abort(new NotFoundHttpException(), [$request->getMethod(), $uri]); |
||
286 | } elseif ($res[0] == Dispatcher::METHOD_NOT_ALLOWED) { |
||
287 | \PhpBoot\abort(new MethodNotAllowedHttpException($res[1]), [$request->getMethod(), $uri]); |
||
288 | } else { |
||
289 | \PhpBoot\abort("unknown dispatch return {$res[0]}"); |
||
290 | } |
||
291 | 2 | }; |
|
292 | |||
293 | 2 | View Code Duplication | foreach (array_reverse($this->getGlobalHooks()) as $hookName){ |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
294 | $next = function($request)use($hookName, $next){ |
||
295 | $hook = $this->get($hookName); |
||
296 | /**@var $hook HookInterface*/ |
||
297 | return $hook->handle($request, $next); |
||
298 | }; |
||
299 | 2 | } |
|
300 | 2 | $response = $next($request); |
|
301 | |||
302 | /** @var Response $response */ |
||
303 | 2 | if ($send) { |
|
304 | $response->send(); |
||
305 | } |
||
306 | 2 | return $response; |
|
307 | |||
308 | }catch (\Exception $e){ |
||
309 | $renderer->render($e); |
||
310 | } |
||
311 | |||
312 | } |
||
313 | |||
314 | /** |
||
315 | * @return GroupCountBasedDispatcher |
||
316 | */ |
||
317 | 2 | private function getDispatcher() |
|
318 | { |
||
319 | 2 | $routeCollector = new RouteCollector(new Std(), new GroupCountBasedDataGenerator()); |
|
320 | 2 | foreach ($this->routes as $route) { |
|
321 | 2 | list($method, $uri, $handler, $hooks) = $route; |
|
322 | 2 | $uri = $this->getFullUri($uri); |
|
323 | 2 | $routeCollector->addRoute($method, $uri, [$handler, $hooks]); |
|
324 | 2 | } |
|
325 | 2 | return new GroupCountBasedDispatcher($routeCollector->getData()); |
|
326 | } |
||
327 | |||
328 | /** |
||
329 | * Finds an entry of the container by its identifier and returns it. |
||
330 | * |
||
331 | * @param string $id Identifier of the entry to look for. |
||
332 | * |
||
333 | * @throws NotFoundExceptionInterface No entry was found for **this** identifier. |
||
334 | * @throws ContainerExceptionInterface Error while retrieving the entry. |
||
335 | * |
||
336 | * @return mixed Entry. |
||
337 | */ |
||
338 | 18 | public function get($id) |
|
339 | { |
||
340 | 18 | return $this->container->get($id); |
|
341 | } |
||
342 | |||
343 | /** |
||
344 | * Returns true if the container can return an entry for the given identifier. |
||
345 | * Returns false otherwise. |
||
346 | * |
||
347 | * `has($id)` returning true does not mean that `get($id)` will not throw an exception. |
||
348 | * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`. |
||
349 | * |
||
350 | * @param string $id Identifier of the entry to look for. |
||
351 | * |
||
352 | * @return bool |
||
353 | */ |
||
354 | public function has($id) |
||
355 | { |
||
356 | return $this->container->has($id); |
||
357 | } |
||
358 | |||
359 | /** |
||
360 | * Call the given function using the given parameters. |
||
361 | * |
||
362 | * @param callable $callable Function to call. |
||
363 | * @param array $parameters Parameters to use. |
||
364 | * |
||
365 | * @return mixed Result of the function. |
||
366 | * |
||
367 | * @throws InvocationException Base exception class for all the sub-exceptions below. |
||
368 | * @throws NotCallableException |
||
369 | * @throws NotEnoughParametersException |
||
370 | */ |
||
371 | public function call($callable, array $parameters = array()) |
||
372 | { |
||
373 | return $this->container->call($callable, $parameters); |
||
374 | } |
||
375 | |||
376 | 23 | public function make($name, array $parameters = []) |
|
377 | { |
||
378 | 23 | return $this->container->make($name, $parameters); |
|
379 | } |
||
380 | |||
381 | /** |
||
382 | * @param \string[] $globalHooks |
||
383 | */ |
||
384 | public function setGlobalHooks($globalHooks) |
||
385 | { |
||
386 | $this->globalHooks = $globalHooks; |
||
387 | } |
||
388 | |||
389 | /** |
||
390 | * @return \string[] |
||
391 | */ |
||
392 | 2 | public function getGlobalHooks() |
|
393 | { |
||
394 | 2 | return $this->globalHooks; |
|
395 | } |
||
396 | |||
397 | public static function createRequestFromGlobals() |
||
398 | { |
||
399 | $request = Request::createFromGlobals(); |
||
400 | if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/json') |
||
401 | && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('POST', 'PUT', 'DELETE', 'PATCH')) |
||
402 | ) { |
||
403 | $data = json_decode($request->getContent(), true); |
||
404 | $request->request = new ParameterBag($data); |
||
405 | } |
||
406 | |||
407 | return $request; |
||
408 | } |
||
409 | |||
410 | 3 | public function getFullUri($uri) |
|
411 | { |
||
412 | 3 | return rtrim($this->getUriPrefix(), '/').'/'.ltrim($uri, '/'); |
|
413 | } |
||
414 | /** |
||
415 | * @return string |
||
416 | */ |
||
417 | 3 | public function getUriPrefix() |
|
418 | { |
||
419 | 3 | return $this->uriPrefix; |
|
420 | } |
||
421 | |||
422 | /** |
||
423 | * @param string $uriPrefix |
||
424 | */ |
||
425 | 89 | public function setUriPrefix($uriPrefix) |
|
426 | { |
||
427 | 89 | $this->uriPrefix = $uriPrefix; |
|
428 | 89 | } |
|
429 | /** |
||
430 | * @var string |
||
431 | */ |
||
432 | protected $uriPrefix = '/'; |
||
433 | /** |
||
434 | * @inject |
||
435 | * @var Container |
||
436 | */ |
||
437 | protected $container; |
||
438 | |||
439 | /** |
||
440 | * @inject |
||
441 | * @var ControllerContainerBuilder |
||
442 | */ |
||
443 | protected $controllerContainerBuilder; |
||
444 | |||
445 | /** |
||
446 | * @inject |
||
447 | * @var Cache |
||
448 | */ |
||
449 | protected $cache; |
||
450 | |||
451 | /** |
||
452 | * [ |
||
453 | * [method, uri, handler, hooks] |
||
454 | * ] |
||
455 | * @var array |
||
456 | */ |
||
457 | protected $routes = []; |
||
458 | |||
459 | /** |
||
460 | * @var string[] |
||
461 | */ |
||
462 | protected $controllers = []; |
||
463 | |||
464 | /** |
||
465 | * @var string[] |
||
466 | */ |
||
467 | protected $globalHooks = []; |
||
468 | |||
469 | } |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.