Completed
Push — master ( 688c46...b38950 )
by Tim
8s
created

ControllerServlet::initConfiguration()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 21
ccs 11
cts 11
cp 1
rs 9.0534
c 1
b 0
f 0
cc 4
eloc 8
nc 3
nop 0
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
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
    protected $actionMappings = array();
95
96
    /**
97
     * Initializes the servlet with the passed configuration.
98
     *
99
     * @param \AppserverIo\Psr\Servlet\ServletConfigInterface $config The configuration to initialize the servlet with
100
     *
101
     * @return void
102
     */
103 4
    public function init(ServletConfigInterface $config)
104
    {
105
106
        // call parent method
107 4
        parent::init($config);
108
109
        // load the values from the configuration file
110 4
        $this->initConfiguration();
111
112
        // initialize the routing
113 4
        $this->initRoutes();
114 4
    }
115
116
    /**
117
     * Returns the available routes.
118
     *
119
     * @return array The array with the available routes
120
     */
121 2
    public function getRoutes()
122
    {
123 2
        return $this->routes;
124
    }
125
126
    /**
127
     * Returns the array with request method action -> route mappings.
128
     *
129
     * @return array The request method action -> route mappings
130
     */
131 1
    public function getActionMappings()
132
    {
133 1
        return $this->actionMappings;
134
    }
135
136
    /**
137
     * Returns the naming directoy instance (the application).
138
     *
139
     * @return \AppserverIo\Psr\Naming\NamingDirectoryInterface The naming directory instance
140
     */
141 1
    public function getNamingDirectory()
142
    {
143 1
        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
    /**
147
     * Returns the object manager instance
148
     *
149
     * @return \AppserverIo\Psr\Di\ObjectManagerInterface The object manager instance
150
     */
151 1
    public function getObjectManager()
152
    {
153 1
        return $this->getNamingDirectory()->search(ObjectManagerInterface::IDENTIFIER);
154
    }
155
156
    /**
157
     * Returns the DI provider instance.
158
     *
159
     * @return \AppserverIo\Psr\Di\ProviderInterface The DI provider instance
160
     */
161 1
    public function getProvider()
162
    {
163 1
        return $this->getNamingDirectory()->search(ProviderInterface::IDENTIFIER);
164
    }
165
166
    /**
167
     * This method returns the default route we'll invoke if the path info doesn't contain one.
168
     *
169
     * @return string The default route
170
     */
171 2
    public function getDefaultRoute()
172
    {
173 2
        return ControllerServlet::DEFAULT_ROUTE;
174
    }
175
176
    /**
177
     * Returns the array with the path descriptors.
178
     *
179
     * @return array The array with the path descriptors
180
     */
181 1
    public function getPathDescriptors()
182
    {
183 1
        return $this->paths;
184
    }
185
186
    /**
187
     * Adds a path descriptor to the controller.
188
     *
189
     * @param \AppserverIo\Routlt\Description\PathDescriptorInterface $pathDescriptor The path descriptor to add
190
     *
191
     * @return void
192
     */
193 1
    public function addPathDescriptor(PathDescriptorInterface $pathDescriptor)
194
    {
195 1
        $this->paths[$pathDescriptor->getName()] = $pathDescriptor;
196 1
    }
197
198
    /**
199
     * Returns the path descriptor with the passed name.
200
     *
201
     * @param string $name The name of the path descriptor to return
202
     *
203
     * @return \AppserverIo\Routlt\Description\PathDescriptorInterface The path descriptor instance
204
     * @throws \Exception
205
     */
206
    public function getPathDescriptor($name)
207
    {
208
209
        // 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
    }
217
218
    /**
219
     * Loads the values found in the configuration file and merges
220
     * them with the servlet context initialization parameters.
221
     *
222
     * @return void
223
     */
224 3
    protected function initConfiguration()
225
    {
226
227
        // load the relative path to the Routlt configuration file
228 3
        $configurationFileName = $this->getInitParameter(ControllerServlet::INIT_PARAMETER_ROUTLT_CONFIGURATION_FILE);
229
230
        // load the path to the configuration file
231 3
        $configurationFile = $this->getServletConfig()->getWebappPath() . DIRECTORY_SEPARATOR . ltrim($configurationFileName, '/');
232
233
        // if the file is readable
234 3
        if (is_file($configurationFile) && is_readable($configurationFile)) {
235
            // load the  properties from the file
236 1
            $properties = new Properties();
237 1
            $properties->load($configurationFile);
238
239
            // append the properties to the servlet context
240 1
            foreach ($properties as $paramName => $paramValue) {
241 1
                $this->getServletContext()->addInitParameter($paramName, $paramValue);
242 1
            }
243 1
        }
244 3
    }
245
246
    /**
247
     * Initializes the available routes.
248
     *
249
     * @return void
250
     */
251 3
    protected function initRoutes()
252
    {
253
254
        // load the action namespace
255 3
        $actionNamespace = strtolower($this->getInitParameter(ControllerServlet::INIT_PARAMETER_ACTION_NAMESPACE));
256
257
        // register the actions located by annotations and the XML configuration
258 3
        foreach ($this->getObjectManager()->getObjectDescriptors() as $descriptor) {
259
            // check if we've found a servlet descriptor
260 2
            if ($descriptor instanceof PathDescriptorInterface) {
261
                // register the action's references
262 2
                $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
                // initialize a new action instance
264 2
                $action = $this->initActionInstance($descriptor);
265
266
                // prepare the action's the result configuration descriptors
267
                /** @var \AppserverIo\Routlt\Description\ResultConfigurationDescriptorInterface $resultConfigurationDescriptor */
268 2
                foreach ($descriptor->getResults() as $resultConfigurationDescriptor) {
269 1
                    $action->addResult($this->initResultInstance($resultConfigurationDescriptor, $action));
270 2
                }
271
272
                // prepare the route, e. g. /index/index
273 2
                $controllerName = str_replace($actionNamespace, '', $descriptor->getName());
274
275
                // initialize the action mappings
276 2
                foreach ($descriptor->getActions() as $actionDescriptors) {
277
                    // iterate over all request methods
278
                    /** @var \AppserverIo\Routlt\Description\ActionDescriptorInterface $actionDescriptor */
279 1
                    foreach ($actionDescriptors as $requestMethod => $actionDescriptor) {
280
                        // prepare the real action method name
281 1
                        $methodName = $actionDescriptor->getMethodName();
282
                        // prepare the action path -> concatenate route + action name
283 1
                        $actionPath = sprintf('%s%s', $controllerName, $actionDescriptor->getName());
284
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 1
                        $actionMapping->compile(
290 1
                            $actionPath,
291 1
                            $actionDescriptor->getRestrictions(),
292 1
                            $actionDescriptor->getDefaults()
293 1
                        );
294
295
                        // add the action path -> route mapping for the request method
296 1
                        $this->actionMappings[$requestMethod][$actionPath] = $actionMapping;
297
298
                        // add an alias for the route for the action's default method
299 1
                        if ($actionDescriptor->getMethodName() === $action->getDefaultMethod()) {
300
                            // initialize the action mapping for the default route
301 1
                            $actionMapping = new ActionMapping();
302 1
                            $actionMapping->setControllerName($controllerName);
303 1
                            $actionMapping->setMethodName($methodName);
304 1
                            $actionMapping->compile(
305 1
                                $controllerName,
306 1
                                $actionDescriptor->getRestrictions(),
307 1
                                $actionDescriptor->getDefaults()
308 1
                            );
309
                            // add the action mapping for the default route
310 1
                            $this->actionMappings[$requestMethod][$controllerName] = $actionMapping;
311 1
                        }
312 1
                    }
313 2
                }
314
315
                // add the initialized action
316 2
                $this->routes[$controllerName] = $action;
317 2
            }
318 3
        }
319 3
    }
320
321
    /**
322
     * Creates a new instance of the action from the passed path descriptor instance.
323
     *
324
     * @param \AppserverIo\Routlt\Description\PathDescriptorInterface $pathDescriptor The path descriptor to create the action from
325
     *
326
     * @return \AppserverIo\Routlt\ActionInterface The action instance
327
     */
328
    protected function initActionInstance(PathDescriptorInterface $pathDescriptor)
329
    {
330
331
        // create a new action instance
332
        $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
        if ($actionInstance instanceof DescriptorAware) {
341
            $actionInstance->setDescriptor($pathDescriptor);
342
        }
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
     *
351
     * @param \AppserverIo\Routlt\Description\ResultConfigurationDescriptorInterface $resultConfigurationDescriptor The action result configuration descriptor
352
     * @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
            $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
            // now use the result descriptors name for lookup
369
            $lookupName = $objectDescriptor->getName();
370
        }
371
372
        // initialize the result instance by the lookup name
373
        $resultInstance = $this->getProvider()->get($lookupName);
374
375
        // if the result is action aware
376
        if ($resultInstance instanceof ActionAware) {
377
            $resultInstance->setAction($action);
378
        }
379
380
        // if the result is descriptor aware
381
        if ($resultInstance instanceof DescriptorAware && $objectDescriptor instanceof ResultDescriptorInterface) {
382
            $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
        }
384
385
        // if the result is servlet context aware
386
        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
    /**
398
     * Checks whether or not an action is generally available for any request method.
399
     * Will return TRUE if so, FALSE otherwise.
400
     * 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
     * @param string $pathInfo The action path which has been requested
404
     *
405
     * @return boolean
406
     */
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 3
        foreach ($this->getActionMappings() as $actionMapping) {
412 3
            $run = true;
413 3
            $requestedAction = $pathInfo;
414
            do {
415 3
                if (isset($actionMapping[$requestedAction])) {
416 2
                    return true;
417
                }
418
                // strip the last directory
419 3
                $requestedAction = dirname($requestedAction);
420
421
                // query whether we've to stop dispatching
422 3
                if ($requestedAction === '/' || $requestedAction === false) {
423 2
                    $run = false;
424 2
                }
425 3
            } while ($run === true);
426 2
        }
427
428
        // nothing found? Return false then
429 2
        return false;
430
    }
431
432
    /**
433
     * Returns the array with request method action -> route mappings
434
     * for the passed servlet request.
435
     *
436
     * @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
     */
440 4
    public function getActionMappingsForServletRequest(ServletRequestInterface $servletRequest)
441
    {
442
        // load the servlet request method
443 4
        $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
445
        // load the action mappings
446 4
        $actionMappings = $this->getActionMappings();
447
448
        // query whether we've action mappings for the request method or not
449 4
        if (isset($actionMappings[$requestMethod])) {
450 3
            return $actionMappings[$requestMethod];
451
        }
452
453
        // nothing found? Method must not be allowed then
454 1
        throw new DispatchException(sprintf('Method %s not allowed', $requestMethod), 405);
455
    }
456
457
    /**
458
     * Delegates to HTTP method specific functions like doPost() for POST e.g.
459
     *
460
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface  $servletRequest  The request instance
461
     * @param \AppserverIo\Psr\Servlet\ServletResponseInterface $servletResponse The response sent back to the client
462
     *
463
     * @return void
464
     *
465
     * @throws \AppserverIo\Psr\Servlet\ServletException If no action has been found for the requested path
466
     */
467 7
    public function service(ServletRequestInterface $servletRequest, ServletResponseInterface $servletResponse)
468
    {
469
470
        try {
471
            // pre-initialize response
472 7
            $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 6
            $pathInfo = $servletRequest->getPathInfo();
476
477
            // if the requested action has been found in the path info
478 6
            if ($pathInfo == null) {
479 2
                $pathInfo = $this->getDefaultRoute();
480 2
            }
481
482
            // prepare the path of the requested action
483 6
            $requestedAction = $pathInfo;
484
485
            // load the routes
486 6
            $routes = $this->getRoutes();
487
488
            // load the DI provider
489 6
            $provider = $this->getProvider();
490
491
            // load the action mappings for the actual servlet request
492 6
            $actionMappings = $this->getActionMappingsForServletRequest($servletRequest);
493
494
            // initialize the parameter map with the values from the request
495 5
            if ($servletRequest->getParameterMap()) {
496
                $parameterMap = $servletRequest->getParameterMap();
497
            } else {
498 5
                $parameterMap = array();
499
            }
500
501
            // iterate over the action mappings and try to find a mapping
502 5
            foreach ($actionMappings as $actionMapping) {
503
                // try to match actual request by the tokenizer
504 5
                if ($actionMapping->match($requestedAction)) {
505
                    // initialize the request attributes with the values from the action mapping
506 3
                    $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 3
                        array_merge($parameterMap, $actionMapping->getRequestParameters())
508 3
                    );
509
510
                    // resolve the action with the found mapping
511 3
                    $action = $routes[$actionMapping->getControllerName()];
512
513
                    // query whether or not the action has a descriptor
514 3
                    if ($action instanceof DescriptorAware) {
515 3
                        $provider->injectDependencies($action->getDescriptor(), $action);
516 3
                    }
517
518
                    // set the method that has to be invoked in the action context
519 3
                    $action->setAttribute(ContextKeys::METHOD_NAME, $actionMapping->getMethodName());
520
521
                    // pre-dispatch the action
522 3
                    $action->preDispatch($servletRequest, $servletResponse);
523
524
                    // if the action has been dispatched, we're done
525 3
                    if ($servletRequest->isDispatched()) {
526 1
                        return;
527
                    }
528
529
                    // initialize the result with the default value
530 2
                    $result = ActionInterface::INPUT;
531
532
                    // if not dispatch the action
533 2
                    if ($newResult = $action->perform($servletRequest, $servletResponse)) {
534 1
                        $result = $newResult;
535 1
                    }
536
537
                    // process the result if available
538 2
                    if (($instance = $action->findResult($result)) instanceof ResultInterface) {
539
                        // query whether or not the result has a descriptor
540 1
                        if ($instance instanceof DescriptorAware && $descriptor = $instance->getDescriptor()) {
541 1
                            $provider->injectDependencies($descriptor, $instance);
542 1
                        }
543
544
                        // query whether or not the result is action aware
545 1
                        if ($instance instanceof ActionAware) {
546 1
                            $instance->setAction($action);
547 1
                        }
548
549
                        // process the result
550 1
                        $instance->process($servletRequest, $servletResponse);
551 1
                    }
552
553
                    // post-dispatch the action instance
554 2
                    $action->postDispatch($servletRequest, $servletResponse);
555
556
                    // stop processing
557 2
                    return;
558
                }
559 2
            }
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 2
            if ($this->checkGeneralActionAvailability($pathInfo)) {
566
                // nothing found? Method must not be allowed then
567 1
                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 1
            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 4
        } catch (DispatchException $de) {
574
            // results in a 4xx error
575 3
            throw new ServletException($de->__toString(), $de->getCode());
576 1
        } catch (\Exception $e) {
577
            // results in a 500 error page
578 1
            throw new ServletException($e->__toString(), 500);
579
        }
580
    }
581
}
582