Completed
Push — master ( b534db...688c46 )
by Tim
40s
created

ControllerServlet::initActionInstance()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 19
c 0
b 0
f 0
ccs 5
cts 5
cp 1
rs 9.4285
cc 3
eloc 7
nc 4
nop 1
crap 3
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
use AppserverIo\Routlt\Description\ResultConfigurationDescriptorInterface;
40
41
/**
42
 * Abstract example implementation that provides some kind of basic MVC functionality
43
 * to handle requests by subclasses action methods.
44
 *
45
 * @author    Tim Wagner <[email protected]>
46
 * @copyright 2015 TechDivision GmbH <[email protected]>
47
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
48
 * @link      http://github.com/appserver-io/routlt
49
 * @link      http://www.appserver.io
50
 */
51
class ControllerServlet extends HttpServlet implements ControllerInterface
52
{
53
54
    /**
55
     * The key for the init parameter with the action namespace.
56
     *
57
     * @var string
58
     */
59
    const INIT_PARAMETER_ACTION_NAMESPACE = 'action.namespace';
60
61
    /**
62
     * The key for the init parameter with the path to the configuration file.
63
     *
64
     * @var string
65
     */
66
    const INIT_PARAMETER_ROUTLT_CONFIGURATION_FILE = 'routlt.configuration.file';
67
68
    /**
69
     * The default action if no valid action name was found in the path info.
70
     *
71
     * @var string
72
     */
73
    const DEFAULT_ROUTE = '/index';
74
75
    /**
76
     * The array with the initialized routes.
77
     *
78
     * @var array
79
     */
80
    protected $routes = array();
81
82
    /**
83
     * The array with the path descriptors.
84
     *
85
     * @var array
86
     */
87
    protected $paths = array();
88
89
    /**
90
     * The array with request method action -> route mappings.
91
     *
92
     * @var array
93
     */
94 4
    protected $actionMappings = array();
95
96
    /**
97
     * Initializes the servlet with the passed configuration.
98 4
     *
99
     * @param \AppserverIo\Psr\Servlet\ServletConfigInterface $config The configuration to initialize the servlet with
100
     *
101 4
     * @return void
102
     */
103
    public function init(ServletConfigInterface $config)
104 4
    {
105 4
106
        // call parent method
107
        parent::init($config);
108
109
        // load the values from the configuration file
110
        $this->initConfiguration();
111
112
        // initialize the routing
113
        $this->initRoutes();
114 1
    }
115
116 1
    /**
117
     * Returns the available routes.
118
     *
119
     * @return array The array with the available routes
120
     */
121
    public function getRoutes()
122
    {
123
        return $this->routes;
124
    }
125
126 1
    /**
127
     * Returns the array with request method action -> route mappings.
128 1
     *
129
     * @return array The request method action -> route mappings
130
     */
131
    public function getActionMappings()
132
    {
133
        return $this->actionMappings;
134
    }
135
136 1
    /**
137
     * Returns the naming directoy instance (the application).
138 1
     *
139
     * @return \AppserverIo\Psr\Naming\NamingDirectoryInterface The naming directory instance
140
     */
141
    public function getNamingDirectory()
142
    {
143
        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...
144
    }
145
146 1
    /**
147
     * Returns the object manager instance
148 1
     *
149
     * @return \AppserverIo\Psr\Di\ObjectManagerInterface The object manager instance
150
     */
151
    public function getObjectManager()
152
    {
153
        return $this->getNamingDirectory()->search(ObjectManagerInterface::IDENTIFIER);
154
    }
155
156 2
    /**
157
     * Returns the DI provider instance.
158 2
     *
159
     * @return \AppserverIo\Psr\Di\ProviderInterface The DI provider instance
160
     */
161
    public function getProvider()
162
    {
163
        return $this->getNamingDirectory()->search(ProviderInterface::IDENTIFIER);
164
    }
165
166 1
    /**
167
     * This method returns the default route we'll invoke if the path info doesn't contain one.
168 1
     *
169
     * @return string The default route
170
     */
171
    public function getDefaultRoute()
172
    {
173
        return ControllerServlet::DEFAULT_ROUTE;
174
    }
175
176
    /**
177
     * Returns the array with the path descriptors.
178 1
     *
179
     * @return array The array with the path descriptors
180 1
     */
181 1
    public function getPathDescriptors()
182
    {
183
        return $this->paths;
184
    }
185
186
    /**
187
     * Adds a path descriptor to the controller.
188
     *
189 3
     * @param \AppserverIo\Routlt\Description\PathDescriptorInterface $pathDescriptor The path descriptor to add
190
     *
191
     * @return void
192
     */
193 3
    public function addPathDescriptor(PathDescriptorInterface $pathDescriptor)
194
    {
195
        $this->paths[$pathDescriptor->getName()] = $pathDescriptor;
196 3
    }
197
198
    /**
199 3
     * Returns the path descriptor with the passed name.
200
     *
201 1
     * @param string $name The name of the path descriptor to return
202 1
     *
203
     * @return \AppserverIo\Routlt\Description\PathDescriptorInterface The path descriptor instance
204
     * @throws \Exception
205 1
     */
206 1
    public function getPathDescriptor($name)
207 1
    {
208 1
209 3
        // query whether or not the path descriptor exists
210
        if (isset($this->paths[$name])) {
211
            return $this->paths[$name];
212
        }
213
214
        // throw an exception if the requested path descriptor ist NOT available
215
        throw new \Exception(sprintf('Can\'t find path descriptor with name "%s"', $name));
216 3
    }
217
218
    /**
219
     * Loads the values found in the configuration file and merges
220 3
     * them with the servlet context initialization parameters.
221
     *
222
     * @return void
223 3
     */
224
    protected function initConfiguration()
225 2
    {
226
227 2
        // load the relative path to the Routlt configuration file
228
        $configurationFileName = $this->getInitParameter(ControllerServlet::INIT_PARAMETER_ROUTLT_CONFIGURATION_FILE);
229
230 2
        // load the path to the configuration file
231 1
        $configurationFile = $this->getServletConfig()->getWebappPath() . DIRECTORY_SEPARATOR . ltrim($configurationFileName, '/');
232 1
233
        // if the file is readable
234
        if (is_file($configurationFile) && is_readable($configurationFile)) {
235 2
            // load the  properties from the file
236 1
            $properties = new Properties();
237 2
            $properties->load($configurationFile);
238
239
            // append the properties to the servlet context
240 2
            foreach ($properties as $paramName => $paramValue) {
241 1
                $this->getServletContext()->addInitParameter($paramName, $paramValue);
242 2
            }
243
        }
244
    }
245 2
246 1
    /**
247 2
     * Initializes the available routes.
248
     *
249
     * @return void
250 2
     */
251
    protected function initRoutes()
252
    {
253 2
254
        // load the action namespace
255 1
        $actionNamespace = strtolower($this->getInitParameter(ControllerServlet::INIT_PARAMETER_ACTION_NAMESPACE));
256
257 1
        // register the actions located by annotations and the XML configuration
258
        foreach ($this->getObjectManager()->getObjectDescriptors() as $descriptor) {
259 1
            // check if we've found a servlet descriptor
260
            if ($descriptor instanceof PathDescriptorInterface) {
261
                // register the action's references
262 1
                $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...
263 1
                // initialize a new action instance
264 1
                $action = $this->initActionInstance($descriptor);
265 1
266 1
                // prepare the action's the result configuration descriptors
267 1
                /** @var \AppserverIo\Routlt\Description\ResultConfigurationDescriptorInterface $resultConfigurationDescriptor */
268 1
                foreach ($descriptor->getResults() as $resultConfigurationDescriptor) {
269 1
                    $action->addResult($this->initResultInstance($resultConfigurationDescriptor, $action));
270
                }
271
272 1
                // prepare the route, e. g. /index/index
273
                $controllerName = str_replace($actionNamespace, '', $descriptor->getName());
274
275 1
                // initialize the action mappings
276
                foreach ($descriptor->getActions() as $actionDescriptors) {
277 1
                    // iterate over all request methods
278 1
                    /** @var \AppserverIo\Routlt\Description\ActionDescriptorInterface $actionDescriptor */
279 1
                    foreach ($actionDescriptors as $requestMethod => $actionDescriptor) {
280 1
                        // prepare the real action method name
281 1
                        $methodName = $actionDescriptor->getMethodName();
282 1
                        // prepare the action path -> concatenate route + action name
283 1
                        $actionPath = sprintf('%s%s', $controllerName, $actionDescriptor->getName());
284 1
285
                        // initialize the action mapping for the actual route
286 1
                        $actionMapping = new ActionMapping();
287 1
                        $actionMapping->setControllerName($controllerName);
288 1
                        $actionMapping->setMethodName($methodName);
289 2
                        $actionMapping->compile(
290
                            $actionPath,
291
                            $actionDescriptor->getRestrictions(),
292 2
                            $actionDescriptor->getDefaults()
293 2
                        );
294 3
295 3
                        // add the action path -> route mapping for the request method
296
                        $this->actionMappings[$requestMethod][$actionPath] = $actionMapping;
297
298
                        // add an alias for the route for the action's default method
299
                        if ($actionDescriptor->getMethodName() === $action->getDefaultMethod()) {
300
                            // initialize the action mapping for the default route
301
                            $actionMapping = new ActionMapping();
302
                            $actionMapping->setControllerName($controllerName);
303
                            $actionMapping->setMethodName($methodName);
304
                            $actionMapping->compile(
305 1
                                $controllerName,
306
                                $actionDescriptor->getRestrictions(),
307 1
                                $actionDescriptor->getDefaults()
308
                            );
309
                            // add the action mapping for the default route
310
                            $this->actionMappings[$requestMethod][$controllerName] = $actionMapping;
311
                        }
312
                    }
313
                }
314
315
                // add the initialized action
316
                $this->routes[$controllerName] = $action;
317 1
            }
318
        }
319
    }
320
321 1
    /**
322
     * Creates a new instance of the action from the passed path descriptor instance.
323
     *
324 1
     * @param \AppserverIo\Routlt\Description\PathDescriptorInterface $pathDescriptor The path descriptor to create the action from
325
     *
326
     * @return \AppserverIo\Routlt\ActionInterface The action instance
327 1
     */
328 1
    protected function initActionInstance(PathDescriptorInterface $pathDescriptor)
329 1
    {
330
331
        // create a new action instance
332 1
        $actionInstance = $this->getProvider()->get($pathDescriptor->getName());
333
334
        // if the action is servlet context aware
335
        if ($actionInstance instanceof ServletContextAware) {
336
            $actionInstance->setServletContext($this->getServletContext());
337
        }
338
339
        // if the action is descriptor aware
340 2
        if ($actionInstance instanceof DescriptorAware) {
341
            $actionInstance->setDescriptor($pathDescriptor);
342 2
        }
343
344
        // return the action instance
345
        return $actionInstance;
346
    }
347
348
    /**
349
     * Creates a new instance of the action result the passed descriptor.
350 1
     *
351
     * @param \AppserverIo\Routlt\Description\ResultConfigurationDescriptorInterface $resultConfigurationDescriptor The action result configuration descriptor
352 1
     * @param \AppserverIo\Routlt\ActionInterface                                    $action                        The action instance the result is bound to
353
     *
354
     * @return \AppserverIo\Routlt\Results\ResultInterface The result instance
355
     */
356
    protected function initResultInstance(ResultConfigurationDescriptorInterface $resultConfigurationDescriptor, ActionInterface $action)
357
    {
358
359
        // load the object manager instance
360
        $objectManager = $this->getObjectManager();
361
362
        // query whether or not we've a real deployment descriptor or not
363
        if ($objectManager->hasObjectDescriptor($lookupName = $resultConfigurationDescriptor->getType())) {
364
            // if not replqce it with the real one
365 3
            $objectDescriptor = $objectManager->getObjectDescriptor($lookupName);
366
            // register the result's references
367
            $this->getServletContext()->registerReferences($objectDescriptor);
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...
368 3
            // now use the result descriptors name for lookup
369 3
            $lookupName = $objectDescriptor->getName();
370 3
        }
371
372 3
        // initialize the result instance by the lookup name
373 2
        $resultInstance = $this->getProvider()->get($lookupName);
374
375
        // if the result is action aware
376 3
        if ($resultInstance instanceof ActionAware) {
377
            $resultInstance->setAction($action);
378
        }
379 3
380 2
        // if the result is descriptor aware
381 2
        if ($resultInstance instanceof DescriptorAware && $objectDescriptor instanceof ResultDescriptorInterface) {
382 3
            $resultInstance->setDescriptor($objectDescriptor);
0 ignored issues
show
Bug introduced by
The variable $objectDescriptor does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
383 2
        }
384
385
        // if the result is servlet context aware
386 2
        if ($resultInstance instanceof ServletContextAware) {
387
            $resultInstance->setServletContext($this->getServletContext());
388
        }
389
390
        // initialize the instance from the result descriptor
391
        $resultInstance->init($resultConfigurationDescriptor);
392
393
        // return the result instance
394
        return $resultInstance;
395
    }
396
397 4
    /**
398
     * Checks whether or not an action is generally available for any request method.
399
     * Will return TRUE if so, FALSE otherwise.
400 4
     * This method replicates a lot of the checks generally necessary but omits the request method check.
401
     * Still best called in exception- or edge-cases
402
     *
403 4
     * @param string $pathInfo The action path which has been requested
404
     *
405
     * @return boolean
406 4
     */
407 3
    public function checkGeneralActionAvailability($pathInfo)
408
    {
409
410
        // iterate the request methods we have mappings for and check if we can find the requested action
411 1
        foreach ($this->getActionMappings() as $actionMapping) {
412
            $run = true;
413
            $requestedAction = $pathInfo;
414
            do {
415
                if (isset($actionMapping[$requestedAction])) {
416
                    return true;
417
                }
418
                // strip the last directory
419
                $requestedAction = dirname($requestedAction);
420
421
                // query whether we've to stop dispatching
422
                if ($requestedAction === '/' || $requestedAction === false) {
423
                    $run = false;
424 7
                }
425
            } while ($run === true);
426
        }
427
428
        // nothing found? Return false then
429 7
        return false;
430
    }
431
432 6
    /**
433 6
     * Returns the array with request method action -> route mappings
434
     * for the passed servlet request.
435
     *
436 6
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface $servletRequest The request instance
437
     *
438
     * @return array The request method action -> route mappings for the passed request method
439 6
     */
440 2
    public function getActionMappingsForServletRequest(ServletRequestInterface $servletRequest)
441 2
    {
442
        // load the servlet request method
443
        $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...
444 6
445
        // load the action mappings
446
        $actionMappings = $this->getActionMappings();
447 6
448
        // query whether we've action mappings for the request method or not
449
        if (isset($actionMappings[$requestMethod])) {
450 6
            return $actionMappings[$requestMethod];
451
        }
452
453 5
        // nothing found? Method must not be allowed then
454
        throw new DispatchException(sprintf('Method %s not allowed', $requestMethod), 405);
455
    }
456 5
457
    /**
458
     * Delegates to HTTP method specific functions like doPost() for POST e.g.
459
     *
460 5
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface  $servletRequest  The request instance
461
     * @param \AppserverIo\Psr\Servlet\ServletResponseInterface $servletResponse The response sent back to the client
462 5
     *
463
     * @return void
464 3
     *
465 3
     * @throws \AppserverIo\Psr\Servlet\ServletException If no action has been found for the requested path
466 3
     */
467
    public function service(ServletRequestInterface $servletRequest, ServletResponseInterface $servletResponse)
468
    {
469 3
470
        try {
471
            // pre-initialize response
472 3
            $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...
473
474
            // load the path info from the servlet request
475 3
            $pathInfo = $servletRequest->getPathInfo();
476
477
            // if the requested action has been found in the path info
478 3
            if ($pathInfo == null) {
479
                $pathInfo = $this->getDefaultRoute();
480
            }
481 3
482 1
            // prepare the path of the requested action
483
            $requestedAction = $pathInfo;
484
485
            // load the routes
486 2
            $routes = $this->getRoutes();
487
488
            // load the DI provider
489 2
            $provider = $this->getProvider();
490 1
491 1
            // load the action mappings for the actual servlet request
492
            $actionMappings = $this->getActionMappingsForServletRequest($servletRequest);
493
494 2
            // initialize the parameter map with the values from the request
495
            if ($servletRequest->getParameterMap()) {
496 1
                $parameterMap = $servletRequest->getParameterMap();
497 1
            } else {
498 1
                $parameterMap = array();
499
            }
500
501 1
            // iterate over the action mappings and try to find a mapping
502 1
            foreach ($actionMappings as $actionMapping) {
503
                // try to match actual request by the tokenizer
504
                if ($actionMapping->match($requestedAction)) {
505 2
                    // initialize the request attributes with the values from the action mapping
506
                    $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...
507
                        array_merge($parameterMap, $actionMapping->getRequestParameters())
508 2
                    );
509
510 2
                    // resolve the action with the found mapping
511
                    $action = $routes[$actionMapping->getControllerName()];
512
513
                    // query whether or not the action has a descriptor
514
                    if ($action instanceof DescriptorAware) {
515
                        $provider->injectDependencies($action->getDescriptor(), $action);
516 2
                    }
517
518 1
                    // set the method that has to be invoked in the action context
519
                    $action->setAttribute(ContextKeys::METHOD_NAME, $actionMapping->getMethodName());
520
521
                    // pre-dispatch the action
522 1
                    $action->preDispatch($servletRequest, $servletResponse);
523
524 4
                    // if the action has been dispatched, we're done
525
                    if ($servletRequest->isDispatched()) {
526 3
                        return;
527 1
                    }
528
529 1
                    // initialize the result with the default value
530
                    $result = ActionInterface::INPUT;
531
532
                    // if not dispatch the action
533
                    if ($newResult = $action->perform($servletRequest, $servletResponse)) {
534
                        $result = $newResult;
535
                    }
536
537
                    // process the result if available
538
                    if (($instance = $action->findResult($result)) instanceof ResultInterface) {
539
                        // query whether or not the result has a descriptor
540
                        if ($instance instanceof DescriptorAware && $descriptor = $instance->getDescriptor()) {
541
                            $provider->injectDependencies($descriptor, $instance);
542
                        }
543
544
                        // query whether or not the result is action aware
545
                        if ($instance instanceof ActionAware) {
546
                            $instance->setAction($action);
547
                        }
548
549
                        // process the result
550
                        $instance->process($servletRequest, $servletResponse);
551
                    }
552
553
                    // post-dispatch the action instance
554
                    $action->postDispatch($servletRequest, $servletResponse);
555
556
                    // stop processing
557
                    return;
558
                }
559
            }
560
561
            // We did not find anything for this method/URI connection. We have to evaluate if there simply
562
            // is a method restriction. This replicates a lot of the checks we did before but omits extra
563
            // iterations in a positive dispatch event, 4xx's should be the exception and can handle that
564
            // penalty therefore
565
            if ($this->checkGeneralActionAvailability($pathInfo)) {
566
                // nothing found? Method must not be allowed then
567
                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...
568
            }
569
570
            // throw an action, because we can't find an action mapping
571
            throw new DispatchException(sprintf('Can\'t find action to dispatch path info %s', $pathInfo), 404);
572
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
573
        } catch (DispatchException $de) {
574
            // results in a 4xx error
575
            throw new ServletException($de->__toString(), $de->getCode());
576
        } catch (\Exception $e) {
577
            // results in a 500 error page
578
            throw new ServletException($e->__toString(), 500);
579
        }
580
    }
581
}
582