Completed
Push — di ( f3b698...546a0f )
by Tim
03:40
created

ControllerServlet::initConfiguration()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 21
ccs 8
cts 8
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
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 2
        $this->initRoutes();
113
    }
114 2
115
    /**
116
     * Returns the available routes.
117
     *
118
     * @return array The array with the available routes
119
     */
120
    public function getRoutes()
121
    {
122 1
        return $this->routes;
123
    }
124 1
125
    /**
126
     * Returns the array with request method action -> route mappings.
127
     *
128
     * @return array The request method action -> route mappings
129
     */
130
    public function getActionMappings()
131
    {
132 1
        return $this->actionMappings;
133
    }
134 1
135
    /**
136
     * Returns the naming directoy instance (the application).
137
     *
138
     * @return \AppserverIo\Psr\Naming\NamingDirectoryInterface The naming directory instance
139
     */
140
    public function getNamingDirectory()
141
    {
142 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...
143
    }
144 1
145
    /**
146
     * Returns the object manager instance
147
     *
148
     * @return \AppserverIo\Psr\Di\ObjectManagerInterface The object manager instance
149
     */
150
    public function getObjectManager()
151
    {
152 1
        return $this->getNamingDirectory()->search(ObjectManagerInterface::IDENTIFIER);
153
    }
154 1
155
    /**
156
     * Returns the DI provider instance.
157
     *
158
     * @return \AppserverIo\Psr\Di\ProviderInterface The DI provider instance
159
     */
160
    public function getProvider()
161
    {
162 2
        return $this->getNamingDirectory()->search(ProviderInterface::IDENTIFIER);
163
    }
164 2
165
    /**
166
     * This method returns the default route we'll invoke if the path info doesn't contain one.
167
     *
168
     * @return string The default route
169
     */
170
    public function getDefaultRoute()
171
    {
172 1
        return ControllerServlet::DEFAULT_ROUTE;
173
    }
174 1
175
    /**
176
     * Returns the array with the path descriptors.
177
     *
178
     * @return array The array with the path descriptors
179
     */
180
    public function getPathDescriptors()
181
    {
182
        return $this->paths;
183
    }
184 1
185
    /**
186 1
     * Adds a path descriptor to the controller.
187 1
     *
188
     * @param \AppserverIo\Routlt\Description\PathDescriptorInterface $pathDescriptor The path descriptor to add
189
     *
190
     * @return void
191
     */
192
    public function addPathDescriptor(PathDescriptorInterface $pathDescriptor)
193
    {
194
        $this->paths[$pathDescriptor->getName()] = $pathDescriptor;
195 3
    }
196
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
     *
202 3
     * @return \AppserverIo\Routlt\Description\PathDescriptorInterface The path descriptor instance
203
     * @throws \Exception
204
     */
205 3
    public function getPathDescriptor($name)
206
    {
207 1
208 1
        // query whether or not the path descriptor exists
209
        if (isset($this->paths[$name])) {
210
            return $this->paths[$name];
211 1
        }
212 1
213 1
        // throw an exception if the requested path descriptor ist NOT available
214 1
        throw new \Exception(sprintf('Can\'t find path descriptor with name "%s"', $name));
215 3
    }
216
217
    /**
218
     * Loads the values found in the configuration file and merges
219
     * them with the servlet context initialization parameters.
220
     *
221
     * @return void
222 3
     */
223
    protected function initConfiguration()
224
    {
225
226 3
        // load the relative path to the Routlt configuration file
227
        $configurationFileName = $this->getInitParameter(ControllerServlet::INIT_PARAMETER_ROUTLT_CONFIGURATION_FILE);
228
229 3
        // load the path to the configuration file
230
        $configurationFile = $this->getServletConfig()->getWebappPath() . DIRECTORY_SEPARATOR . ltrim($configurationFileName, '/');
231 2
232
        // if the file is readable
233 2
        if (is_file($configurationFile) && is_readable($configurationFile)) {
234
            // load the  properties from the file
235
            $properties = new Properties();
236 2
            $properties->load($configurationFile);
237
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
     * Initializes the available routes.
247
     *
248 2
     * @return void
249
     */
250
    protected function initRoutes()
251 1
    {
252
253 1
        // 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
        foreach ($this->getObjectManager()->getObjectDescriptors() as $descriptor) {
258 1
            // check if we've found a servlet descriptor
259 1
            if ($descriptor instanceof PathDescriptorInterface) {
260 1
                // register the action's references
261 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...
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
                /** @var \AppserverIo\Routlt\Description\ResultDescriptorInterface $resultDescriptor */
267
                foreach ($descriptor->getResults() as $resultDescriptor) {
268 1
                    // register the result's references
269
                    $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 1
                    $action->addResult($this->initResultInstance($resultDescriptor, $action));
272
                }
273 1
274 1
                // prepare the route, e. g. /index/index
275 1
                $controllerName = str_replace($actionNamespace, '', $descriptor->getName());
276 1
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
                    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 2
                        $actionPath = sprintf('%s%s', $controllerName, $actionDescriptor->getName());
286
287
                        // initialize the action mapping for the actual route
288 2
                        $actionMapping = new ActionMapping();
289 2
                        $actionMapping->setControllerName($controllerName);
290 3
                        $actionMapping->setMethodName($methodName);
291 3
                        $actionMapping->compile(
292
                            $actionPath,
293
                            $actionDescriptor->getRestrictions(),
294
                            $actionDescriptor->getDefaults()
295
                        );
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
                            $actionMapping->setMethodName($methodName);
306
                            $actionMapping->compile(
307
                                $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
                // add the initialized action
318
                $this->routes[$controllerName] = $action;
319
            }
320
        }
321
    }
322
323
    /**
324
     * 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
     *
328
     * @return \AppserverIo\Routlt\ActionInterface The action instance
329
     */
330
    protected function initActionInstance(PathDescriptorInterface $pathDescriptor)
331
    {
332
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
341
        // if the action is descriptor aware
342
        if ($actionInstance instanceof DescriptorAware) {
343
            $actionInstance->setDescriptor($pathDescriptor);
344
        }
345
346
        // return the action instance
347
        return $actionInstance;
348
    }
349
350 3
    /**
351
     * Creates a new instance of the action result the passed descriptor.
352
     *
353
     * @param \AppserverIo\Routlt\Description\ResultDescriptorInterface $resultDescriptor The action result descriptor
354 3
     * @param \AppserverIo\Routlt\ActionInterface                       $action           The action instance the result is bound to
355 3
     *
356 3
     * @return \AppserverIo\Routlt\Results\ResultInterface The result instance
357
     */
358 3
    protected function initResultInstance(ResultDescriptorInterface $resultDescriptor, ActionInterface $action)
359 2
    {
360
361
        // create the result instance
362 3
        $resultInstance = $this->getProvider()->get($resultDescriptor->getType());
363
364
        // if the result is action aware
365 3
        if ($resultInstance instanceof ActionAware) {
366 2
            $resultInstance->setAction($action);
367 2
        }
368 3
369 2
        // if the result is descriptor aware
370
        if ($resultInstance instanceof DescriptorAware) {
371
            $resultInstance->setDescriptor($resultDescriptor);
372 2
        }
373
374
        // if the result is servlet context aware
375
        if ($resultInstance instanceof ServletContextAware) {
376
            $resultInstance->setServletContext($this->getServletContext());
377
        }
378
379
        // initialize the instance from the result descriptor
380
        $resultInstance->init($resultDescriptor);
381
382
        // return the result instance
383 4
        return $resultInstance;
384
    }
385
386 4
    /**
387
     * Checks whether or not an action is generally available for any request method.
388
     * Will return TRUE if so, FALSE otherwise.
389 4
     * 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 4
     * @param string $pathInfo The action path which has been requested
393 3
     *
394
     * @return boolean
395
     */
396
    public function checkGeneralActionAvailability($pathInfo)
397 1
    {
398
399
        // iterate the request methods we have mappings for and check if we can find the requested action
400
        foreach ($this->getActionMappings() as $actionMapping) {
401
            $run = true;
402
            $requestedAction = $pathInfo;
403
            do {
404
                if (isset($actionMapping[$requestedAction])) {
405
                    return true;
406
                }
407
                // strip the last directory
408
                $requestedAction = dirname($requestedAction);
409
410 7
                // query whether we've to stop dispatching
411
                if ($requestedAction === '/' || $requestedAction === false) {
412
                    $run = false;
413
                }
414
            } while ($run === true);
415 7
        }
416
417
        // nothing found? Return false then
418 6
        return false;
419
    }
420
421 6
    /**
422 2
     * Returns the array with request method action -> route mappings
423 2
     * for the passed servlet request.
424
     *
425
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface $servletRequest The request instance
426 6
     *
427
     * @return array The request method action -> route mappings for the passed request method
428
     */
429 6
    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
434
        // load the action mappings
435 5
        $actionMappings = $this->getActionMappings();
436
437
        // query whether we've action mappings for the request method or not
438 5
        if (isset($actionMappings[$requestMethod])) {
439
            return $actionMappings[$requestMethod];
440
        }
441
442 5
        // nothing found? Method must not be allowed then
443
        throw new DispatchException(sprintf('Method %s not allowed', $requestMethod), 405);
444 5
    }
445
446 3
    /**
447 3
     * Delegates to HTTP method specific functions like doPost() for POST e.g.
448 3
     *
449
     * @param \AppserverIo\Psr\Servlet\ServletRequestInterface  $servletRequest  The request instance
450
     * @param \AppserverIo\Psr\Servlet\ServletResponseInterface $servletResponse The response sent back to the client
451 3
     *
452
     * @return void
453
     *
454 3
     * @throws \AppserverIo\Psr\Servlet\ServletException If no action has been found for the requested path
455
     */
456
    public function service(ServletRequestInterface $servletRequest, ServletResponseInterface $servletResponse)
457 3
    {
458
459
        try {
460 3
            // pre-initialize response
461 1
            $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
463
            // load the path info from the servlet request
464
            $pathInfo = $servletRequest->getPathInfo();
465 2
466
            // if the requested action has been found in the path info
467
            if ($pathInfo == null) {
468 2
                $pathInfo = $this->getDefaultRoute();
469 1
            }
470 1
471
            // prepare the path of the requested action
472
            $requestedAction = $pathInfo;
473 2
474
            // load the routes
475 1
            $routes = $this->getRoutes();
476 1
477 1
            // load the DI provider
478
            $provider = $this->getProvider();
479
480 1
            // load the action mappings for the actual servlet request
481 1
            $actionMappings = $this->getActionMappingsForServletRequest($servletRequest);
482
483
            // initialize the parameter map with the values from the request
484 2
            if ($servletRequest->getParameterMap()) {
485
                $parameterMap = $servletRequest->getParameterMap();
486
            } else {
487 2
                $parameterMap = array();
488
            }
489 2
490
            // iterate over the action mappings and try to find a mapping
491
            foreach ($actionMappings as $actionMapping) {
492
                // try to match actual request by the tokenizer
493
                if ($actionMapping->match($requestedAction)) {
494
                    // initialize the request attributes with the values from the action mapping
495 2
                    $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
                        array_merge($parameterMap, $actionMapping->getRequestParameters())
497 1
                    );
498
499
                    // resolve the action with the found mapping
500
                    $action = $routes[$actionMapping->getControllerName()];
501 1
502
                    // query whether or not the action has a descriptor
503 4
                    if ($action instanceof DescriptorAware) {
504
                        $provider->injectDependencies($action->getDescriptor(), $action);
0 ignored issues
show
Unused Code introduced by
The call to ProviderInterface::injectDependencies() has too many arguments starting with $action.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
505 3
                    }
506 1
507
                    // set the method that has to be invoked in the action context
508 1
                    $action->setAttribute(ContextKeys::METHOD_NAME, $actionMapping->getMethodName());
509
510
                    // 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
                    }
517
518
                    // initialize the result with the default value
519
                    $result = ActionInterface::INPUT;
520
521
                    // if not dispatch the action
522
                    if ($newResult = $action->perform($servletRequest, $servletResponse)) {
523
                        $result = $newResult;
524
                    }
525
526
                    // process the result if available
527
                    if (($instance = $action->findResult($result)) instanceof ResultInterface) {
528
                        // query whether or not the result has a descriptor
529
                        if ($instance instanceof DescriptorAware) {
530
                            $provider->injectDependencies($instance->getDescriptor(), $instance);
0 ignored issues
show
Unused Code introduced by
The call to ProviderInterface::injectDependencies() has too many arguments starting with $instance.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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