Failed Conditions
Push — master ( c04808...f5c6c8 )
by Arnold
04:54
created

RouteAction::getRoute()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 19
Code Lines 10

Duplication

Lines 4
Ratio 21.05 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 0
Metric Value
dl 4
loc 19
ccs 11
cts 11
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 7
nop 0
crap 5
1
<?php
2
3
namespace Jasny\Controller;
4
5
use Psr\Http\Message\ServerRequestInterface;
6
use Psr\Http\Message\ResponseInterface;
7
8
/**
9
 * Execute controller on given route
10
 */
11
trait RouteAction
12
{
13
    /**
14
     * Get request, set for controller
15
     *
16
     * @return ServerRequestInterface
17
     */
18
    abstract public function getRequest();
19
20
    /**
21
     * Get response. set for controller
22
     *
23
     * @return ResponseInterface
24
     */
25
    abstract public function getResponse();
26
27
    /**
28
     * Respond with a server error
29
     *
30
     * @param string $message
31
     * @param int    $code     HTTP status code
32
     */
33
    abstract public function notFound($message = '', $code = 404);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
34
35
    /**
36
     * Check if response is 2xx succesful, or empty
37
     * 
38
     * @return boolean
39
     */
40
    abstract public function isSuccessful();
41
    
42
    
43
    /**
44
     * Get the route
45
     * 
46
     * @return \stdClass
47
     */
48 13
    protected function getRoute()
49
    {
50 13
        $route = $this->getRequest()->getAttribute('route');
51
        
52 13
        if (!isset($route)) {
53 1
            throw new \LogicException("Route has not been set");
54
        }
55
        
56 12
        if (is_array($route)) {
57 1
            $route = (object)$route;
58 1
        }
59
        
60 12 View Code Duplication
        if (!$route instanceof \stdClass) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
61 1
            $type = (is_object($route) ? get_class($route) . ' ' : '') . gettype($route);
62 1
            throw new \UnexpectedValueException("Expected route to be a stdClass object, not a $type");
63
        }
64
        
65 11
        return $route;
66
    }
67
68
    /**
69
     * Get the method name of the action
70
     * 
71
     * @param string $action
72
     * @return string
73
     */
74 11
    protected function getActionMethod($action)
75
    {
76 11
        return \Jasny\camelcase($action) . 'Action';
77
    }
78
    
79
    /**
80
     * Called before executing the action.
81
     * If the response is no longer a success statuc (>= 300), the action will not be executed.
82
     * 
83
     * <code>
84
     * protected function beforeAction()
85
     * {
86
     *    $this->respondWith('json'); // Respond with JSON by default
87
     * 
88
     *    if ($this->auth->getUser()->getCredits() <= 0) {
89
     *        $this->paymentRequired();
90
     *    }
91
     * }
92
     * </code>
93
     */
94 10
    protected function beforeAction()
95
    {
96 10
    }
97
    
98
    /**
99
     * Run the controller
100
     *
101
     * @return ResponseInterface
102
     */
103 13
    public function run()
104
    {
105 13
        $route = $this->getRoute();
106 11
        $method = $this->getActionMethod(isset($route->action) ? $route->action : 'default');
107
108 11
        if (!method_exists($this, $method)) {
109 1
            return $this->notFound();
110
        }
111
112 10
        $this->beforeAction();
113
        
114 10
        if ($this->isSuccessful()) {
115 9
            $args = isset($route->args) ? $route->args
116 9
                : $this->getFunctionArgs($route, new \ReflectionMethod($this, $method)); 
117
118 8
            call_user_func_array([$this, $method], $args);
119 8
        }
120 9
    }
121
122
    /**
123
     * Get the arguments for a function from a route using reflection
124
     * 
125
     * @param object $route
126
     * @param \ReflectionFunctionAbstract $refl
127
     * @return array
128
     */
129 6
    protected function getFunctionArgs($route, \ReflectionFunctionAbstract $refl)
130
    {
131 6
        $args = [];
132 6
        $params = $refl->getParameters();
133
134 6
        foreach ($params as $param) {
135 6
            $key = $param->name;
136
137 6
            if (property_exists($route, $key)) {
138 5
                $value = $route->$key;
139 5
            } else {
140 4
                if (!$param->isOptional()) {
141 1
                    $fn = $refl instanceof \ReflectionMethod ? $refl->class . '::' . $refl->name : $refl->name;
142 1
                    throw new \RuntimeException("Missing argument '$key' for {$fn}()");
143
                }
144
                
145 3
                $value = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
146
            }
147
148 5
            $args[$key] = $value;
149 5
        }
150
        
151 5
        return $args;
152
    }
153
}
154