Application::offsetExists()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 2
eloc 2
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * The Proton Micro Framework.
4
 *
5
 * @author  Alex Bilbie <[email protected]>
6
 * @license MIT
7
 */
8
9
namespace Proton;
10
11
use League\Container\ContainerAwareInterface;
12
use League\Container\ContainerAwareTrait;
13
use League\Container\ContainerInterface;
14
use League\Event\EmitterTrait;
15
use League\Event\ListenerAcceptorInterface;
16
use Symfony\Component\HttpKernel\HttpKernelInterface;
17
use Symfony\Component\HttpKernel\TerminableInterface;
18
use League\Container\Container;
19
use League\Route\RouteCollection;
20
use Monolog\Logger;
21
use Symfony\Component\HttpFoundation\Request;
22
use Symfony\Component\HttpFoundation\Response;
23
24
/**
25
 * Proton Application Class.
26
 */
27
class Application implements HttpKernelInterface, TerminableInterface, ContainerAwareInterface, ListenerAcceptorInterface, \ArrayAccess
28
{
29
    use EmitterTrait;
30
    use ContainerAwareTrait;
31
32
    /**
33
     * @var \League\Route\RouteCollection
34
     */
35
    protected $router;
36
37
    /**
38
     * @var \callable
39
     */
40
    protected $exceptionDecorator;
41
42
    /**
43
     * @var array
44
     */
45
    protected $config = [];
46
    
47
    /**
48
     * @var array
49
     */
50
    protected $loggers = [];
51
52
    /**
53
     * New Application.
54
     *
55
     * @param bool $debug Enable debug mode
56
     */
57 33
    public function __construct($debug = true)
58
    {
59 33
        $this->setConfig('debug', $debug);
60
61 33
        $this->setExceptionDecorator(function (\Exception $e) {
62 9
            $response = new Response;
63 9
            $response->setStatusCode(method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getStatusCode() does only exist in the following sub-classes of Exception: League\Route\Http\Exception, League\Route\Http\Exception\BadRequestException, League\Route\Http\Exception\ConflictException, League\Route\Http\Except...ectationFailedException, League\Route\Http\Exception\ForbiddenException, League\Route\Http\Exception\GoneException, League\Route\Http\Exception\ImATeapotException, League\Route\Http\Except...LengthRequiredException, League\Route\Http\Except...thodNotAllowedException, League\Route\Http\Exception\NotAcceptableException, League\Route\Http\Exception\NotFoundException, League\Route\Http\Except...onditionFailedException, League\Route\Http\Except...ditionRequiredException, League\Route\Http\Except...ooManyRequestsException, League\Route\Http\Exception\UnauthorizedException, League\Route\Http\Except...cessableEntityException, League\Route\Http\Except...supportedMediaException, Symfony\Component\HttpKe...cessDeniedHttpException, Symfony\Component\HttpKe...BadRequestHttpException, Symfony\Component\HttpKe...n\ConflictHttpException, Symfony\Component\HttpKe...ption\GoneHttpException, Symfony\Component\HttpKe...Exception\HttpException, Symfony\Component\HttpKe...thRequiredHttpException, Symfony\Component\HttpKe...NotAllowedHttpException, Symfony\Component\HttpKe...AcceptableHttpException, Symfony\Component\HttpKe...n\NotFoundHttpException, Symfony\Component\HttpKe...tionFailedHttpException, Symfony\Component\HttpKe...onRequiredHttpException, Symfony\Component\HttpKe...navailableHttpException, Symfony\Component\HttpKe...nyRequestsHttpException, Symfony\Component\HttpKe...authorizedHttpException, Symfony\Component\HttpKe...ableEntityHttpException, Symfony\Component\HttpKe...dMediaTypeHttpException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
64 9
            $response->headers->add(['Content-Type' => 'application/json']);
65
66
            $return = [
67
                'error' => [
68 9
                    'message' => $e->getMessage()
69 6
                ]
70 6
            ];
71
72 9
            if ($this->getConfig('debug', true) === true) {
73 9
                $return['error']['trace'] = explode(PHP_EOL, $e->getTraceAsString());
74 6
            }
75
76 9
            $response->setContent(json_encode($return));
77
78 9
            return $response;
79 33
        });
80 33
    }
81
82
    /**
83
     * Set a container.
84
     *
85
     * @param \League\Container\ContainerInterface $container
86
     */
87 30
    public function setContainer(ContainerInterface $container)
88
    {
89 30
        $this->container = $container;
90 30
        $this->container->singleton('app', $this);
91 30
        $this->router = null;
92 30
    }
93
94
    /**
95
     * Get the container.
96
     *
97
     * @return \League\Container\ContainerInterface
98
     */
99 30
    public function getContainer()
100
    {
101 30
        if (!isset($this->container)) {
102 30
            $this->setContainer(new Container);
103 20
        }
104
105 30
        return $this->container;
106
    }
107
108
    /**
109
     * Return the router.
110
     *
111
     * @return \League\Route\RouteCollection
112
     */
113 18
    public function getRouter()
114
    {
115 18
        if (!isset($this->router)) {
116 18
            $this->router = new RouteCollection($this->getContainer());
117 12
        }
118
119 18
        return $this->router;
120
    }
121
122
    /**
123
     * Return the event emitter.
124
     *
125
     * @return \League\Event\Emitter
126
     */
127 6
    public function getEventEmitter()
128
    {
129 6
        return $this->getEmitter();
130
    }
131
    
132
    /**
133
     * Return a logger
134
     *
135
     * @param string $name
136
     * @return \Psr\Log\LoggerInterface
137
     */
138 3
    public function getLogger($name = 'default')
139
    {
140 3
        if (isset($this->loggers[$name])) {
141 3
            return $this->loggers[$name];
142
        }
143
144 3
        $logger = new Logger($name);
145 3
        $this->loggers[$name] = $logger;
146 3
        return $logger;
147
    }
148
149
    /**
150
     * Set the exception decorator.
151
     *
152
     * @param callable $func
153
     *
154
     * @return void
155
     */
156 33
    public function setExceptionDecorator(callable $func)
157
    {
158 33
        $this->exceptionDecorator = $func;
0 ignored issues
show
Documentation Bug introduced by
It seems like $func of type callable is incompatible with the declared type object<callable> of property $exceptionDecorator.

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...
159 33
    }
160
161
    /**
162
     * Add a GET route.
163
     *
164
     * @param string $route
165
     * @param mixed  $action
166
     *
167
     * @return void
168
     */
169 6
    public function get($route, $action)
170
    {
171 6
        $this->getRouter()->addRoute('GET', $route, $action);
172 6
    }
173
174
    /**
175
     * Add a POST route.
176
     *
177
     * @param string $route
178
     * @param mixed  $action
179
     *
180
     * @return void
181
     */
182 3
    public function post($route, $action)
183
    {
184 3
        $this->getRouter()->addRoute('POST', $route, $action);
185 3
    }
186
187
    /**
188
     * Add a PUT route.
189
     *
190
     * @param string $route
191
     * @param mixed  $action
192
     *
193
     * @return void
194
     */
195 3
    public function put($route, $action)
196 2
    {
197 3
        $this->getRouter()->addRoute('PUT', $route, $action);
198 3
    }
199
200
    /**
201
     * Add a DELETE route.
202
     *
203
     * @param string $route
204
     * @param mixed  $action
205
     *
206
     * @return void
207
     */
208 3
    public function delete($route, $action)
209
    {
210 3
        $this->getRouter()->addRoute('DELETE', $route, $action);
211 3
    }
212
213
    /**
214
     * Add a PATCH route.
215
     *
216
     * @param string $route
217
     * @param mixed  $action
218
     *
219
     * @return void
220
     */
221 3
    public function patch($route, $action)
222
    {
223 3
        $this->getRouter()->addRoute('PATCH', $route, $action);
224 3
    }
225
226
    /**
227
     * Handle the request.
228
     *
229
     * @param \Symfony\Component\HttpFoundation\Request $request
230
     * @param int                                       $type
231
     * @param bool                                      $catch
232
     *
233
     * @throws \Exception
234
     * @throws \LogicException
235
     *
236
     * @return \Symfony\Component\HttpFoundation\Response
237
     */
238 24
    public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
239
    {
240
        // Passes the request to the container
241 24
        $this->getContainer()->add('Symfony\Component\HttpFoundation\Request', $request);
242
243
        try {
244
245 24
            $this->emit('request.received', $request);
246
247 15
            $dispatcher = $this->getRouter()->getDispatcher();
248 15
            $response = $dispatcher->dispatch(
249 15
                $request->getMethod(),
250 15
                $request->getPathInfo()
251 10
            );
252
253 6
            $this->emit('response.created', $request, $response);
254
255 6
            return $response;
256
257 18
        } catch (\Exception $e) {
258
259 18
            if (!$catch) {
260 3
                throw $e;
261
            }
262
263 15
            $response = call_user_func($this->exceptionDecorator, $e);
264 15
            if (!$response instanceof Response) {
265 3
                throw new \LogicException('Exception decorator did not return an instance of Symfony\Component\HttpFoundation\Response');
266
            }
267
268 12
            $this->emit('response.created', $request, $response);
269
270 12
            return $response;
271
        }
272
    }
273
274
    /**
275
     * Terminates a request/response cycle.
276
     *
277
     * @param \Symfony\Component\HttpFoundation\Request  $request
278
     * @param \Symfony\Component\HttpFoundation\Response $response
279
     *
280
     * @return void
281
     */
282 6
    public function terminate(Request $request, Response $response)
283
    {
284 6
        $this->emit('response.sent', $request, $response);
285 6
    }
286
287
    /**
288
     * Run the application.
289
     *
290
     * @param \Symfony\Component\HttpFoundation\Request|null $request
291
     *
292
     * @return void
293
     */
294 3
    public function run(Request $request = null)
295
    {
296 3
        if (null === $request) {
297 3
            $request = Request::createFromGlobals();
298 2
        }
299
300 3
        $response = $this->handle($request);
301 3
        $response->send();
302
303 3
        $this->terminate($request, $response);
304 3
    }
305
306
    /**
307
     * Subscribe to an event.
308
     *
309
     * @param string   $event
310
     * @param callable $listener
311
     * @param int      $priority
312
     */
313 21
    public function subscribe($event, $listener, $priority = ListenerAcceptorInterface::P_NORMAL)
314
    {
315 21
        $this->addListener($event, $listener, $priority);
316 21
    }
317
318
    /**
319
     * Array Access get.
320
     *
321
     * @param string $key
322
     *
323
     * @return mixed
324
     */
325 3
    public function offsetGet($key)
326
    {
327 3
        return $this->getContainer()->get($key);
328
    }
329
330
    /**
331
     * Array Access set.
332
     *
333
     * @param string $key
334
     * @param mixed  $value
335
     *
336
     * @return void
337
     */
338 9
    public function offsetSet($key, $value)
339
    {
340 9
        $this->getContainer()->singleton($key, $value);
341 9
    }
342
343
    /**
344
     * Array Access unset.
345
     *
346
     * @param string $key
347
     *
348
     * @return void
349
     */
350 3
    public function offsetUnset($key)
351
    {
352 3
        $this->getContainer()->offsetUnset($key);
353 3
    }
354
355
    /**
356
     * Array Access isset.
357
     *
358
     * @param string $key
359
     *
360
     * @return bool
361
     */
362 3
    public function offsetExists($key)
363
    {
364 3
        return $this->getContainer()->isRegistered($key) || $this->getContainer()->isSingleton($key);
365
    }
366
367
    /**
368
     * Register a new service provider
369
     *
370
     * @param $serviceProvider
371
     */
372
    public function register($serviceProvider)
373
    {
374
        $this->getContainer()->addServiceProvider($serviceProvider);
375
    }
376
377
    /**
378
     * Set a config item
379
     *
380
     * @param string $key
381
     * @param mixed  $value
382
     */
383 33
    public function setConfig($key, $value)
384
    {
385 33
        $this->config[$key] = $value;
386 33
    }
387
388
    /**
389
     * Get a config key's value
390
     *
391
     * @param string $key
392
     * @param mixed  $default
393
     *
394
     * @return mixed
395
     */
396 9
    public function getConfig($key, $default = null)
397
    {
398 9
        return isset($this->config[$key]) ? $this->config[$key] : $default;
399
    }
400
}
401