Completed
Push — master ( c280e5...b534db )
by Tim
11s
created

ControllerServlet::initResultInstance()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 27
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 12
cts 12
cp 1
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 10
nc 8
nop 2
crap 4
1
<?php
2
3
/**
4
 * AppserverIo\Routlt\ControllerServlet
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2015 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      http://github.com/appserver-io/routlt
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Routlt;
22
23
use AppserverIo\Http\HttpProtocol;
24
use AppserverIo\Properties\Properties;
25
use AppserverIo\Psr\Di\ProviderInterface;
26
use AppserverIo\Psr\Di\ObjectManagerInterface;
27
use AppserverIo\Psr\Servlet\Http\HttpServlet;
28
use AppserverIo\Psr\Servlet\ServletException;
29
use AppserverIo\Psr\Servlet\ServletConfigInterface;
30
use AppserverIo\Psr\Servlet\ServletRequestInterface;
31
use AppserverIo\Psr\Servlet\ServletResponseInterface;
32
use AppserverIo\Routlt\Util\ContextKeys;
33
use AppserverIo\Routlt\Util\ActionAware;
34
use AppserverIo\Routlt\Util\ServletContextAware;
35
use AppserverIo\Routlt\Results\ResultInterface;
36
use AppserverIo\Routlt\Description\PathDescriptorInterface;
37
use AppserverIo\Routlt\Description\ResultDescriptorInterface;
38
use AppserverIo\Routlt\Util\DescriptorAware;
39
40
/**
41
 * Abstract example implementation that provides some kind of basic MVC functionality
42
 * to handle requests by subclasses action methods.
43
 *
44
 * @author    Tim Wagner <[email protected]>
45
 * @copyright 2015 TechDivision GmbH <[email protected]>
46
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
47
 * @link      http://github.com/appserver-io/routlt
48
 * @link      http://www.appserver.io
49
 */
50
class ControllerServlet extends HttpServlet implements ControllerInterface
51
{
52
53
    /**
54
     * The key for the init parameter with the action namespace.
55
     *
56
     * @var string
57
     */
58
    const INIT_PARAMETER_ACTION_NAMESPACE = 'action.namespace';
59
60
    /**
61
     * The key for the init parameter with the path to the configuration file.
62
     *
63
     * @var string
64
     */
65
    const INIT_PARAMETER_ROUTLT_CONFIGURATION_FILE = 'routlt.configuration.file';
66
67
    /**
68
     * The default action if no valid action name was found in the path info.
69
     *
70
     * @var string
71
     */
72
    const DEFAULT_ROUTE = '/index';
73
74
    /**
75
     * The array with the initialized routes.
76
     *
77
     * @var array
78
     */
79
    protected $routes = array();
80
81
    /**
82
     * The array with the path descriptors.
83
     *
84
     * @var array
85
     */
86
    protected $paths = array();
87
88
    /**
89
     * The array with request method action -> route mappings.
90
     *
91
     * @var array
92
     */
93
    protected $actionMappings = array();
94 4
95
    /**
96
     * Initializes the servlet with the passed configuration.
97
     *
98 4
     * @param \AppserverIo\Psr\Servlet\ServletConfigInterface $config The configuration to initialize the servlet with
99
     *
100
     * @return void
101 4
     */
102
    public function init(ServletConfigInterface $config)
103
    {
104 4
105 4
        // call parent method
106
        parent::init($config);
107
108
        // load the values from the configuration file
109
        $this->initConfiguration();
110
111
        // initialize the routing
112
        $this->initRoutes();
113
    }
114 1
115
    /**
116 1
     * Returns the available routes.
117
     *
118
     * @return array The array with the available routes
119
     */
120
    public function getRoutes()
121
    {
122
        return $this->routes;
123
    }
124
125
    /**
126 1
     * Returns the array with request method action -> route mappings.
127
     *
128 1
     * @return array The request method action -> route mappings
129
     */
130
    public function getActionMappings()
131
    {
132
        return $this->actionMappings;
133
    }
134
135
    /**
136 1
     * Returns the naming directoy instance (the application).
137
     *
138 1
     * @return \AppserverIo\Psr\Naming\NamingDirectoryInterface The naming directory instance
139
     */
140
    public function getNamingDirectory()
141
    {
142
        return $this->getServletContext()->getApplication();
0 ignored issues
show
Bug introduced by
The method getApplication() does not seem to exist on object<AppserverIo\Psr\S...ervletContextInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
143
    }
144
145
    /**
146 1
     * Returns the object manager instance
147
     *
148 1
     * @return \AppserverIo\Psr\Di\ObjectManagerInterface The object manager instance
149
     */
150
    public function getObjectManager()
151
    {
152
        return $this->getNamingDirectory()->search(ObjectManagerInterface::IDENTIFIER);
153
    }
154
155
    /**
156 2
     * Returns the DI provider instance.
157
     *
158 2
     * @return \AppserverIo\Psr\Di\ProviderInterface The DI provider instance
159
     */
160
    public function getProvider()
161
    {
162
        return $this->getNamingDirectory()->search(ProviderInterface::IDENTIFIER);
163
    }
164
165
    /**
166 1
     * This method returns the default route we'll invoke if the path info doesn't contain one.
167
     *
168 1
     * @return string The default route
169
     */
170
    public function getDefaultRoute()
171
    {
172
        return ControllerServlet::DEFAULT_ROUTE;
173
    }
174
175
    /**
176
     * Returns the array with the path descriptors.
177
     *
178 1
     * @return array The array with the path descriptors
179
     */
180 1
    public function getPathDescriptors()
181 1
    {
182
        return $this->paths;
183
    }
184
185
    /**
186
     * Adds a path descriptor to the controller.
187
     *
188
     * @param \AppserverIo\Routlt\Description\PathDescriptorInterface $pathDescriptor The path descriptor to add
189 3
     *
190
     * @return void
191
     */
192
    public function addPathDescriptor(PathDescriptorInterface $pathDescriptor)
193 3
    {
194
        $this->paths[$pathDescriptor->getName()] = $pathDescriptor;
195
    }
196 3
197
    /**
198
     * Returns the path descriptor with the passed name.
199 3
     *
200
     * @param string $name The name of the path descriptor to return
201 1
     *
202 1
     * @return \AppserverIo\Routlt\Description\PathDescriptorInterface The path descriptor instance
203
     * @throws \Exception
204
     */
205 1
    public function getPathDescriptor($name)
206 1
    {
207 1
208 1
        // query whether or not the path descriptor exists
209 3
        if (isset($this->paths[$name])) {
210
            return $this->paths[$name];
211
        }
212
213
        // throw an exception if the requested path descriptor ist NOT available
214
        throw new \Exception(sprintf('Can\'t find path descriptor with name "%s"', $name));
215
    }
216 3
217
    /**
218
     * Loads the values found in the configuration file and merges
219
     * them with the servlet context initialization parameters.
220 3
     *
221
     * @return void
222
     */
223 3
    protected function initConfiguration()
224
    {
225 2
226
        // load the relative path to the Routlt configuration file
227 2
        $configurationFileName = $this->getInitParameter(ControllerServlet::INIT_PARAMETER_ROUTLT_CONFIGURATION_FILE);
228
229
        // load the path to the configuration file
230 2
        $configurationFile = $this->getServletConfig()->getWebappPath() . DIRECTORY_SEPARATOR . ltrim($configurationFileName, '/');
231 1
232 1
        // if the file is readable
233
        if (is_file($configurationFile) && is_readable($configurationFile)) {
234
            // load the  properties from the file
235 2
            $properties = new Properties();
236 1
            $properties->load($configurationFile);
237 2
238
            // append the properties to the servlet context
239
            foreach ($properties as $paramName => $paramValue) {
240 2
                $this->getServletContext()->addInitParameter($paramName, $paramValue);
241 1
            }
242 2
        }
243
    }
244
245 2
    /**
246 1
     * Initializes the available routes.
247 2
     *
248
     * @return void
249
     */
250 2
    protected function initRoutes()
251
    {
252
253 2
        // load the action namespace
254
        $actionNamespace = strtolower($this->getInitParameter(ControllerServlet::INIT_PARAMETER_ACTION_NAMESPACE));
255 1
256
        // register the beans located by annotations and the XML configuration
257 1
        foreach ($this->getObjectManager()->getObjectDescriptors() as $descriptor) {
258
            // check if we've found a servlet descriptor
259 1
            if ($descriptor instanceof PathDescriptorInterface) {
260
                // register the action's references
261
                $this->getServletContext()->registerReferences($descriptor);
0 ignored issues
show
Bug introduced by
The method registerReferences() does not seem to exist on object<AppserverIo\Psr\S...ervletContextInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
262 1
                // initialize a new action instance
263 1
                $action = $this->initActionInstance($descriptor);
264 1
265 1
                // prepare the action's the result descriptors
266 1
                /** @var \AppserverIo\Routlt\Description\ResultDescriptorInterface $resultDescriptor */
267 1
                foreach ($descriptor->getResults() as $resultDescriptor) {
268 1
                    // register the result's references
269 1
                    $this->getServletContext()->registerReferences($resultDescriptor);
0 ignored issues
show
Bug introduced by
The method registerReferences() does not seem to exist on object<AppserverIo\Psr\S...ervletContextInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
270
                    // initialize a new result instance and add it to the action
271
                    $action->addResult($this->initResultInstance($resultDescriptor, $action));
272 1
                }
273
274
                // prepare the route, e. g. /index/index
275 1
                $controllerName = str_replace($actionNamespace, '', $descriptor->getName());
276
277 1
                // initialize the action mappings
278 1
                foreach ($descriptor->getActions() as $actionDescriptors) {
279 1
                    // iterate over all request methods
280 1
                    /** @var \AppserverIo\Routlt\Description\ActionDescriptorInterface $actionDescriptor */
281 1
                    foreach ($actionDescriptors as $requestMethod => $actionDescriptor) {
282 1
                        // prepare the real action method name
283 1
                        $methodName = $actionDescriptor->getMethodName();
284 1
                        // prepare the action path -> concatenate route + action name
285
                        $actionPath = sprintf('%s%s', $controllerName, $actionDescriptor->getName());
286 1
287 1
                        // initialize the action mapping for the actual route
288 1
                        $actionMapping = new ActionMapping();
289 2
                        $actionMapping->setControllerName($controllerName);
290
                        $actionMapping->setMethodName($methodName);
291
                        $actionMapping->compile(
292 2
                            $actionPath,
293 2
                            $actionDescriptor->getRestrictions(),
294 3
                            $actionDescriptor->getDefaults()
295 3
                        );
296
297
                        // add the action path -> route mapping for the request method
298
                        $this->actionMappings[$requestMethod][$actionPath] = $actionMapping;
299
300
                        // add an alias for the route for the action's default method
301
                        if ($actionDescriptor->getMethodName() === $action->getDefaultMethod()) {
302
                            // initialize the action mapping for the default route
303
                            $actionMapping = new ActionMapping();
304
                            $actionMapping->setControllerName($controllerName);
305 1
                            $actionMapping->setMethodName($methodName);
306
                            $actionMapping->compile(
307 1
                                $controllerName,
308
                                $actionDescriptor->getRestrictions(),
309
                                $actionDescriptor->getDefaults()
310
                            );
311
                            // add the action mapping for the default route
312
                            $this->actionMappings[$requestMethod][$controllerName] = $actionMapping;
313
                        }
314
                    }
315
                }
316
317 1
                // add the initialized action
318
                $this->routes[$controllerName] = $action;
319
            }
320
        }
321 1
    }
322
323
    /**
324 1
     * Creates a new instance of the action from the passed path descriptor instance.
325
     *
326
     * @param \AppserverIo\Routlt\Description\PathDescriptorInterface $pathDescriptor The path descriptor to create the action from
327 1
     *
328 1
     * @return \AppserverIo\Routlt\ActionInterface The action instance
329 1
     */
330
    protected function initActionInstance(PathDescriptorInterface $pathDescriptor)
331
    {
332 1
333
        // create a new action instance
334
        $actionInstance = $this->getProvider()->get($pathDescriptor->getName());
335
336
        // if the action is servlet context aware
337
        if ($actionInstance instanceof ServletContextAware) {
338
            $actionInstance->setServletContext($this->getServletContext());
339
        }
340 2
341
        // if the action is descriptor aware
342 2
        if ($actionInstance instanceof DescriptorAware) {
343
            $actionInstance->setDescriptor($pathDescriptor);
344
        }
345
346
        // return the action instance
347
        return $actionInstance;
348
    }
349
350 1
    /**
351
     * Creates a new instance of the action result the passed descriptor.
352 1
     *
353
     * @param \AppserverIo\Routlt\Description\ResultDescriptorInterface $resultDescriptor The action result descriptor
354
     * @param \AppserverIo\Routlt\ActionInterface                       $action           The action instance the result is bound to
355
     *
356
     * @return \AppserverIo\Routlt\Results\ResultInterface The result instance
357
     */
358
    protected function initResultInstance(ResultDescriptorInterface $resultDescriptor, ActionInterface $action)
359
    {
360
361
        // create the result instance
362
        $resultInstance = $this->getProvider()->get($resultDescriptor->getType());
363
364
        // if the result is action aware
365 3
        if ($resultInstance instanceof ActionAware) {
366
            $resultInstance->setAction($action);
367
        }
368 3
369 3
        // if the result is descriptor aware
370 3
        if ($resultInstance instanceof DescriptorAware) {
371
            $resultInstance->setDescriptor($resultDescriptor);
372 3
        }
373 2
374
        // if the result is servlet context aware
375
        if ($resultInstance instanceof ServletContextAware) {
376 3
            $resultInstance->setServletContext($this->getServletContext());
377
        }
378
379 3
        // initialize the instance from the result descriptor
380 2
        $resultInstance->init($resultDescriptor);
381 2
382 3
        // return the result instance
383 2
        return $resultInstance;
384
    }
385
386 2
    /**
387
     * Checks whether or not an action is generally available for any request method.
388
     * Will return TRUE if so, FALSE otherwise.
389
     * This method replicates a lot of the checks generally necessary but omits the request method check.
390
     * Still best called in exception- or edge-cases
391
     *
392
     * @param string $pathInfo The action path which has been requested
393
     *
394
     * @return boolean
395
     */
396
    public function checkGeneralActionAvailability($pathInfo)
397 4
    {
398
399
        // iterate the request methods we have mappings for and check if we can find the requested action
400 4
        foreach ($this->getActionMappings() as $actionMapping) {
401
            $run = true;
402
            $requestedAction = $pathInfo;
403 4
            do {
404
                if (isset($actionMapping[$requestedAction])) {
405
                    return true;
406 4
                }
407 3
                // strip the last directory
408
                $requestedAction = dirname($requestedAction);
409
410
                // query whether we've to stop dispatching
411 1
                if ($requestedAction === '/' || $requestedAction === false) {
412
                    $run = false;
413
                }
414
            } while ($run === true);
415
        }
416
417
        // nothing found? Return false then
418
        return false;
419
    }
420
421
    /**
422
     * Returns the array with request method action -> route mappings
423
     * for the passed servlet request.
424 7
     *
425
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface $servletRequest The request instance
426
     *
427
     * @return array The request method action -> route mappings for the passed request method
428
     */
429 7
    public function getActionMappingsForServletRequest(ServletRequestInterface $servletRequest)
430
    {
431
        // load the servlet request method
432 6
        $requestMethod = $servletRequest->getMethod();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AppserverIo\Psr\Servlet\ServletRequestInterface as the method getMethod() does only exist in the following implementations of said interface: AppserverIo\Psr\Servlet\...tpServletRequestWrapper.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
433 6
434
        // load the action mappings
435
        $actionMappings = $this->getActionMappings();
436 6
437
        // query whether we've action mappings for the request method or not
438
        if (isset($actionMappings[$requestMethod])) {
439 6
            return $actionMappings[$requestMethod];
440 2
        }
441 2
442
        // nothing found? Method must not be allowed then
443
        throw new DispatchException(sprintf('Method %s not allowed', $requestMethod), 405);
444 6
    }
445
446
    /**
447 6
     * Delegates to HTTP method specific functions like doPost() for POST e.g.
448
     *
449
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface  $servletRequest  The request instance
450 6
     * @param \AppserverIo\Psr\Servlet\ServletResponseInterface $servletResponse The response sent back to the client
451
     *
452
     * @return void
453 5
     *
454
     * @throws \AppserverIo\Psr\Servlet\ServletException If no action has been found for the requested path
455
     */
456 5
    public function service(ServletRequestInterface $servletRequest, ServletResponseInterface $servletResponse)
457
    {
458
459
        try {
460 5
            // pre-initialize response
461
            $servletResponse->addHeader(HttpProtocol::HEADER_X_POWERED_BY, get_class($this));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AppserverIo\Psr\Servlet\ServletResponseInterface as the method addHeader() does only exist in the following implementations of said interface: AppserverIo\Psr\Servlet\...pServletResponseWrapper.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
462 5
463
            // load the path info from the servlet request
464 3
            $pathInfo = $servletRequest->getPathInfo();
465 3
466 3
            // if the requested action has been found in the path info
467
            if ($pathInfo == null) {
468
                $pathInfo = $this->getDefaultRoute();
469 3
            }
470
471
            // prepare the path of the requested action
472 3
            $requestedAction = $pathInfo;
473
474
            // load the routes
475 3
            $routes = $this->getRoutes();
476
477
            // load the DI provider
478 3
            $provider = $this->getProvider();
479
480
            // load the action mappings for the actual servlet request
481 3
            $actionMappings = $this->getActionMappingsForServletRequest($servletRequest);
482 1
483
            // initialize the parameter map with the values from the request
484
            if ($servletRequest->getParameterMap()) {
485
                $parameterMap = $servletRequest->getParameterMap();
486 2
            } else {
487
                $parameterMap = array();
488
            }
489 2
490 1
            // iterate over the action mappings and try to find a mapping
491 1
            foreach ($actionMappings as $actionMapping) {
492
                // try to match actual request by the tokenizer
493
                if ($actionMapping->match($requestedAction)) {
494 2
                    // initialize the request attributes with the values from the action mapping
495
                    $servletRequest->setParameterMap(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AppserverIo\Psr\Servlet\ServletRequestInterface as the method setParameterMap() does only exist in the following implementations of said interface: AppserverIo\Psr\Servlet\...tpServletRequestWrapper.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
496 1
                        array_merge($parameterMap, $actionMapping->getRequestParameters())
497 1
                    );
498 1
499
                    // resolve the action with the found mapping
500
                    $action = $routes[$actionMapping->getControllerName()];
501 1
502 1
                    // query whether or not the action has a descriptor
503
                    if ($action instanceof DescriptorAware) {
504
                        $provider->injectDependencies($action->getDescriptor(), $action);
505 2
                    }
506
507
                    // set the method that has to be invoked in the action context
508 2
                    $action->setAttribute(ContextKeys::METHOD_NAME, $actionMapping->getMethodName());
509
510 2
                    // pre-dispatch the action
511
                    $action->preDispatch($servletRequest, $servletResponse);
512
513
                    // if the action has been dispatched, we're done
514
                    if ($servletRequest->isDispatched()) {
515
                        return;
516 2
                    }
517
518 1
                    // initialize the result with the default value
519
                    $result = ActionInterface::INPUT;
520
521
                    // if not dispatch the action
522 1
                    if ($newResult = $action->perform($servletRequest, $servletResponse)) {
523
                        $result = $newResult;
524 4
                    }
525
526 3
                    // process the result if available
527 1
                    if (($instance = $action->findResult($result)) instanceof ResultInterface) {
528
                        // query whether or not the result has a descriptor
529 1
                        if ($instance instanceof DescriptorAware) {
530
                            $provider->injectDependencies($instance->getDescriptor(), $instance);
531
                        }
532
533
                        // query whether or not the result is action aware
534
                        if ($instance instanceof ActionAware) {
535
                            $instance->setAction($action);
536
                        }
537
538
                        // process the result
539
                        $instance->process($servletRequest, $servletResponse);
540
                    }
541
542
                    // post-dispatch the action instance
543
                    $action->postDispatch($servletRequest, $servletResponse);
544
545
                    // stop processing
546
                    return;
547
                }
548
            }
549
550
            // We did not find anything for this method/URI connection. We have to evaluate if there simply
551
            // is a method restriction. This replicates a lot of the checks we did before but omits extra
552
            // iterations in a positive dispatch event, 4xx's should be the exception and can handle that
553
            // penalty therefore
554
            if ($this->checkGeneralActionAvailability($pathInfo)) {
555
                // nothing found? Method must not be allowed then
556
                throw new DispatchException(sprintf('Method %s not allowed', $servletRequest->getMethod()), 405);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface AppserverIo\Psr\Servlet\ServletRequestInterface as the method getMethod() does only exist in the following implementations of said interface: AppserverIo\Psr\Servlet\...tpServletRequestWrapper.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
557
            }
558
559
            // throw an action, because we can't find an action mapping
560
            throw new DispatchException(sprintf('Can\'t find action to dispatch path info %s', $pathInfo), 404);
561
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
562
        } catch (DispatchException $de) {
563
            // results in a 4xx error
564
            throw new ServletException($de->__toString(), $de->getCode());
565
        } catch (\Exception $e) {
566
            // results in a 500 error page
567
            throw new ServletException($e->__toString(), 500);
568
        }
569
    }
570
}
571