GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( a3eb64...37c075 )
by cao
03:26
created

Application::setUriPrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
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',
50
     *      'App.uriPrefix' => '/',
51
     *
52
     *      'DB.connection' => 'mysql:dbname=default;host=localhost',
53
     *      'DB.username' => 'root',
54
     *      'DB.password' => 'root',
55
     *      'DB.options' => [],
56
     *
57
     *
58
     *      LoggerInterface::class => \DI\object(\Monolog\Logger::class)
59
     *          ->constructor(\DI\get('AppName')),
60
     *      // 注意, 系统缓存, 只使用 apc、文件缓存等本地缓存, 不要使用 redis 等分布式缓存
61
     *      Cache::class => \DI\object(FilesystemCache::class)
62
     *          ->constructorParameter('directory', sys_get_temp_dir())
63
     * ];
64
     * ```
65
     * or just the array
66
     * @return self
67
     */
68 72
    static public function createByDefault($conf = [])
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
69
    {
70 72
        $builder = new DIContainerBuilder();
71
72
        $default = [
73
74 72
            'App.name' => 'App',
75 72
            'App.uriPrefix' => '/',
76
77 72
            'DB.connection' => 'mysql:dbname=default;host=localhost',
78 72
            'DB.username' => 'root',
79 72
            'DB.password' => 'root',
80 72
            'DB.options' => [],
81
82 72
            Application::class => \DI\object()
83 72
                ->method('setUriPrefix', \DI\get('App.uriPrefix')),
84
85 72
            DB::class => \DI\factory([DB::class, 'connect'])
86 72
                ->parameter('dsn', \DI\get('DB.connection'))
87 72
                ->parameter('username', \DI\get('DB.username'))
88 72
                ->parameter('password', \DI\get('DB.password'))
89 72
                ->parameter('options', \DI\get('DB.options')),
90
91 72
            LoggerInterface::class => \DI\object(\Monolog\Logger::class)
92 72
                ->constructor(\DI\get('App.name')),
93
94 72
            Request::class => \DI\factory([Application::class, 'createRequestFromGlobals']),
95 72
        ];
96 72
        if(function_exists('apc_fetch')){
97
            $default += [
98 72
                Cache::class => \DI\object(ApcCache::class)
99 72
            ];
100 72
        }else{
101
            $default += [
102
                Cache::class => \DI\object(FilesystemCache::class)
103
                    ->constructorParameter('directory', sys_get_temp_dir())
104
            ];
105
        }
106
107
108 72
        $builder->addDefinitions($default);
109 72
        $builder->addDefinitions($conf);
110
111 72
        $container = $builder->build();
112
113 72
        Logger::setDefaultLogger($container->get(LoggerInterface::class));
114
115 72
        $app = $container->make(self::class);
116 72
        return $app;
117
    }
118
119
    /**
120
     * @return Cache
121
     */
122 1
    public function getCache()
123
    {
124 1
        return $this->cache;
125
    }
126
127
    /**
128
     * @param Cache $localCache
129
     */
130
    public function setCache(Cache $localCache)
131
    {
132
        $this->cache = $localCache;
133
    }
134
135
    /**
136
     * load routes from class
137
     *
138
     * @param string $className
139
     * @param string[] $hooks hook class names
140
     * @return void
141
     */
142 2
    public function loadRoutesFromClass($className, $hooks=[])
143
    {
144 2
        $cache = new CheckableCache($this->cache);
145
146 2
        $key = 'loadRoutesFromClass:' . md5(__CLASS__ . ':' . $className);
147 2
        $routes = $cache->get($key, $this);
148
149 2
        $controller = null;
150 2
        if ($routes == $this) { //not cached
151 2
            $routes = [];
152 2
            $controller = $this->controllerContainerBuilder->build($className);
153 2
            foreach ($controller->getRoutes() as $actionName => $route) {
154 2
                $routes[] = [$route->getMethod(), $route->getUri(), $actionName];
155 2
            }
156 2
            $cache->set($key, $routes, 0, new ClassModifiedChecker($className));
157 2
        }
158 2
        foreach ($routes as $route) {
159 2
            list($method, $uri, $actionName) = $route;
160 2
            $this->routes[] = [
161 2
                $method,
162 2
                $uri,
163
                function (Application $app, Request $request) use ($cache, $className, $actionName, $controller) {
164
165 1
                    $key = 'loadRoutesFromClass:route:' . md5(__CLASS__ . ':' . $className . ':' . $actionName);
166
167 1
                    $routeInstance = $cache->get($key, $this);
168 1
                    if ($routeInstance == $this) {
169 1
                        if (!$controller) {
170
                            $controller = $app->controllerContainerBuilder->build($className);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $controller, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
171
                        }
172 1
                        $routeInstance = $controller->getRoute($actionName) or
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
173
                        abort(new NotFoundHttpException("action $actionName not found"));
174 1
                        $cache->set($key, $routeInstance, 0, new ClassModifiedChecker($className));
175 1
                    }
176 1
                    return ControllerContainer::dispatch($this, $className, $actionName, $routeInstance, $request);
177 2
                },
178
                $hooks
179 2
            ];
180 2
        }
181 2
        $this->controllers[] = $className;
182 2
    }
183
184
    /**
185
     * load routes from path
186
     *
187
     * 被加载的文件必须以: 类名.php的形式命名
188
     * @param string $fromPath
189
     * @param string $namespace
190
     * @param string[] $hooks
191
     * @return void
192
     */
193
    public function loadRoutesFromPath($fromPath, $namespace = '', $hooks=[])
194
    {
195
        $dir = @dir($fromPath);
196
197
        $getEach = function () use ($dir) {
198
            $name = $dir->read();
199
            if (!$name) {
200
                return $name;
201
            }
202
            return $name;
203
        };
204
205
        while (!!($entry = $getEach())) {
206
            if ($entry == '.' || $entry == '..') {
207
                continue;
208
            }
209
            $path = $fromPath . '/' . str_replace('\\', '/', $entry);
210
            if (is_file($path) && substr_compare($entry, '.php', strlen($entry) - 4, 4, true) == 0) {
211
                $class_name = $namespace . '\\' . substr($entry, 0, strlen($entry) - 4);
212
                $this->loadRoutesFromClass($class_name, $hooks);
213
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
214
                //\Log::debug($path.' ignored');
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
215
            }
216
        }
217
    }
218
219
    /**
220
     * Add route
221
     * @param string $method
222
     * @param string $uri
223
     * @param callable $handler function(Application $app, Request $request):Response
224
     * @param string[] $hooks
225
     */
226 1
    public function addRoute($method, $uri, callable $handler, $hooks=[])
227
    {
228 1
        $this->routes[] = [$method, $uri, $handler, $hooks];
229 1
    }
230
231
    /**
232
     * @return ControllerContainer[]
233
     */
234 1
    public function getControllers()
235
    {
236 1
        $controllers = [];
237 1
        foreach ($this->controllers as $name) {
238 1
            $controllers[] = $this->controllerContainerBuilder->build($name);
239 1
        }
240 1
        return $controllers;
241
    }
242
243
    /**
244
     * @param Request|null $request
245
     * @param bool $send
246
     * @return Response
247
     */
248 3
    public function dispatch(Request $request = null, $send = true)
249
    {
250
        //  TODO 把 Route里的异常处理 ExceptionRenderer 移到这里更妥?
251 2
        $renderer = $this->get(ExceptionRenderer::class);
252
        try{
253 2
            if ($request == null) {
254
                $request = $this->make(Request::class);
255
            }
256 2
            $uri = $request->getRequestUri();
257 2
            if (false !== $pos = strpos($uri, '?')) {
258
                $uri = substr($uri, 0, $pos);
259
            }
260 2
            $uri = rawurldecode($uri);
261
262
            $next = function (Request $request)use($uri){
263 2
                $dispatcher = $this->getDispatcher();
264 3
                $res = $dispatcher->dispatch($request->getMethod(), $uri);
265 2
                if ($res[0] == Dispatcher::FOUND) {
266
267 2
                    if (count($res[2])) {
268
                        $request->attributes->add($res[2]);
269
                    }
270 2
                    list($handler, $hooks) = $res[1];
271
                    $next = function (Request $request)use($handler){
272 2
                        return $handler($this, $request);
273 2
                    };
274 2 View Code Duplication
                    foreach (array_reverse($hooks) as $hookName){
0 ignored issues
show
Duplication introduced by
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.

Loading history...
275
                        $next = function($request)use($hookName, $next){
276 1
                            $hook = $this->get($hookName);
277
                            /**@var $hook HookInterface*/
278 1
                            return $hook->handle($request, $next);
279 1
                        };
280 2
                    }
281 2
                    return $next($request);
282
283
                }elseif ($res[0] == Dispatcher::NOT_FOUND) {
284
                    \PhpBoot\abort(new NotFoundHttpException(), [$request->getMethod(), $uri]);
285
                } elseif ($res[0] == Dispatcher::METHOD_NOT_ALLOWED) {
286
                    \PhpBoot\abort(new MethodNotAllowedHttpException($res[1]), [$request->getMethod(), $uri]);
287
                } else {
288
                    \PhpBoot\abort("unknown dispatch return {$res[0]}");
289
                }
290 2
            };
291
292 2 View Code Duplication
            foreach (array_reverse($this->getGlobalHooks()) as $hookName){
0 ignored issues
show
Duplication introduced by
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.

Loading history...
293
                $next = function($request)use($hookName, $next){
294
                    $hook = $this->get($hookName);
295
                    /**@var $hook HookInterface*/
296
                    return $hook->handle($request, $next);
297
                };
298 2
            }
299 2
            $response = $next($request);
300
301
            /** @var Response $response */
302 2
            if ($send) {
303
                $response->send();
304
            }
305 2
            return $response;
306
307
        }catch (\Exception $e){
308
            $renderer->render($e);
309
        }
310
311
    }
312
313
    /**
314
     * @return GroupCountBasedDispatcher
315
     */
316 2
    private function getDispatcher()
317
    {
318 2
        $routeCollector = new RouteCollector(new Std(), new GroupCountBasedDataGenerator());
319 2
        foreach ($this->routes as $route) {
320 2
            list($method, $uri, $handler, $hooks) = $route;
321 2
            $uri = $this->getFullUri($uri);
322 2
            $routeCollector->addRoute($method, $uri, [$handler, $hooks]);
323 2
        }
324 2
        return new GroupCountBasedDispatcher($routeCollector->getData());
325
    }
326
327
    /**
328
     * Finds an entry of the container by its identifier and returns it.
329
     *
330
     * @param string $id Identifier of the entry to look for.
331
     *
332
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
333
     * @throws ContainerExceptionInterface Error while retrieving the entry.
334
     *
335
     * @return mixed Entry.
336
     */
337 18
    public function get($id)
338
    {
339 18
        return $this->container->get($id);
340
    }
341
342
    /**
343
     * Returns true if the container can return an entry for the given identifier.
344
     * Returns false otherwise.
345
     *
346
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
347
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
348
     *
349
     * @param string $id Identifier of the entry to look for.
350
     *
351
     * @return bool
352
     */
353
    public function has($id)
354
    {
355
        return $this->container->has($id);
356
    }
357
358
    /**
359
     * Call the given function using the given parameters.
360
     *
361
     * @param callable $callable Function to call.
362
     * @param array $parameters Parameters to use.
363
     *
364
     * @return mixed Result of the function.
365
     *
366
     * @throws InvocationException Base exception class for all the sub-exceptions below.
367
     * @throws NotCallableException
368
     * @throws NotEnoughParametersException
369
     */
370
    public function call($callable, array $parameters = array())
371
    {
372
        return $this->container->call($callable, $parameters);
373
    }
374
375 22
    public function make($name, array $parameters = [])
376
    {
377 22
        return $this->container->make($name, $parameters);
378
    }
379
380
    /**
381
     * @param \string[] $globalHooks
382
     */
383
    public function setGlobalHooks($globalHooks)
384
    {
385
        $this->globalHooks = $globalHooks;
0 ignored issues
show
Documentation Bug introduced by
It seems like $globalHooks of type array<integer,object<string>> is incompatible with the declared type array<integer,string> of property $globalHooks.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
386
    }
387
388
    /**
389
     * @return \string[]
390
     */
391 2
    public function getGlobalHooks()
392
    {
393 2
        return $this->globalHooks;
394
    }
395
396
    public static function createRequestFromGlobals()
397
    {
398
        $request = Request::createFromGlobals();
399
        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/json')
400
            && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('POST', 'PUT', 'DELETE', 'PATCH'))
401
        ) {
402
            $data = json_decode($request->getContent(), true);
403
            $request->request = new ParameterBag($data);
404
        }
405
406
        return $request;
407
    }
408
409 3
    public function getFullUri($uri)
410
    {
411 3
        return rtrim($this->getUriPrefix(), '/').'/'.ltrim($uri, '/');
412
    }
413
    /**
414
     * @return string
415
     */
416 3
    public function getUriPrefix()
417
    {
418 3
        return $this->uriPrefix;
419
    }
420
421
    /**
422
     * @param string $uriPrefix
423
     */
424 72
    public function setUriPrefix($uriPrefix)
425
    {
426 72
        $this->uriPrefix = $uriPrefix;
427 72
    }
428
    /**
429
     * @var string
430
     */
431
    protected $uriPrefix = '/';
432
    /**
433
     * @inject
434
     * @var Container
435
     */
436
    protected $container;
437
438
    /**
439
     * @inject
440
     * @var ControllerContainerBuilder
441
     */
442
    protected $controllerContainerBuilder;
443
444
    /**
445
     * @inject
446
     * @var Cache
447
     */
448
    protected $cache;
449
450
    /**
451
     * [
452
     *      [method, uri, handler, hooks]
453
     * ]
454
     * @var array
455
     */
456
    protected $routes = [];
457
458
    /**
459
     * @var string[]
460
     */
461
    protected $controllers = [];
462
463
    /**
464
     * @var string[]
465
     */
466
    protected $globalHooks = [];
467
468
}