Completed
Pull Request — master (#8)
by Arnold
02:42
created

RouteAction   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 1
dl 0
loc 170
ccs 42
cts 42
cp 1
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
getRequest() 0 1 ?
getResponse() 0 1 ?
notFound() 0 1 ?
A getActionMethod() 0 4 1
A before() 0 3 1
A after() 0 3 1
A cancel() 0 4 1
A isCancelled() 0 4 1
B getRoute() 0 19 5
B run() 0 20 5
B getFunctionArgs() 0 24 6
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
     * @var boolean
15
     */
16
    protected $actionCancelled = false;
17
    
18
    
19
    /**
20
     * Get request, set for controller
21
     *
22
     * @return ServerRequestInterface
23
     */
24
    abstract public function getRequest();
25
26
    /**
27
     * Get response. set for controller
28
     *
29
     * @return ResponseInterface
30
     */
31
    abstract public function getResponse();
32
33
    /**
34
     * Respond with a server error
35
     *
36
     * @param string $message
37
     * @param int    $code     HTTP status code
38
     */
39
    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...
40
    
41
42
    /**
43
     * Get the route
44
     * 
45
     * @return \stdClass
46
     */
47 13
    protected function getRoute()
48
    {
49 13
        $route = $this->getRequest()->getAttribute('route');
50
        
51 13
        if (!isset($route)) {
52 1
            throw new \LogicException("Route has not been set");
53
        }
54
        
55 12
        if (is_array($route)) {
56 1
            $route = (object)$route;
57
        }
58
        
59 12
        if (!$route instanceof \stdClass) {
60 1
            $type = (is_object($route) ? get_class($route) . ' ' : '') . gettype($route);
61 1
            throw new \UnexpectedValueException("Expected route to be a stdClass object, not a $type");
62
        }
63
        
64 11
        return $route;
65
    }
66
67
    /**
68
     * Get the method name of the action
69
     * 
70
     * @param string $action
71
     * @return string
72
     */
73 11
    protected function getActionMethod($action)
74
    {
75 11
        return \Jasny\camelcase($action) . 'Action';
76
    }
77
    
78
    /**
79
     * Called before executing the action.
80
     * @codeCoverageIgnore
81
     * 
82
     * <code>
83
     * protected function beforeAction()
84
     * {
85
     *    $this->respondWith('json'); // Respond with JSON by default
86
     * 
87
     *    if ($this->auth->getUser()->getCredits() <= 0) {
88
     *        $this->paymentRequired();
89
     *    }
90
     * }
91
     * </code>
92
     */
93
    protected function before()
94
    {
95
    }
96
    
97
    /**
98
     * Called before executing the action.
99
     * @codeCoverageIgnore
100
     */
101
    protected function after()
102
    {
103
    }
104
    
105
    /**
106
     * Cancel the action
107
     */
108 1
    public function cancel()
109
    {
110 1
        $this->actionCancelled = true;
111 1
    }
112
113
    /**
114
     * Check if the action is cancelled
115
     * 
116
     * @return boolean
117
     */
118 10
    public function isCancelled()
119
    {
120 10
        return $this->actionCancelled;
121
    }
122
    
123
    /**
124
     * Run the controller
125
     *
126
     * @return ResponseInterface
127
     */
128 13
    public function run()
129
    {
130 13
        $route = $this->getRoute();
131 11
        $method = $this->getActionMethod(isset($route->action) ? $route->action : 'default');
132
133 11
        if (!method_exists($this, $method)) {
134 1
            return $this->notFound();
135
        }
136
137 10
        $this->before();
138
        
139 10
        if (!$this->isCancelled()) {
140 9
            $args = isset($route->args) ? $route->args
141 9
                : $this->getFunctionArgs($route, new \ReflectionMethod($this, $method)); 
142
143 8
            call_user_func_array([$this, $method], $args);
144
        }
145
        
146 9
        $this->after();
147 9
    }
148
149
    /**
150
     * Get the arguments for a function from a route using reflection
151
     * 
152
     * @param \stdClass                   $route
153
     * @param \ReflectionFunctionAbstract $refl
154
     * @return array
155
     */
156 6
    protected function getFunctionArgs($route, \ReflectionFunctionAbstract $refl)
157
    {
158 6
        $args = [];
159 6
        $params = $refl->getParameters();
160
161 6
        foreach ($params as $param) {
162 6
            $key = $param->name;
163
164 6
            if (property_exists($route, $key)) {
165 5
                $value = $route->$key;
166
            } else {
167 4
                if (!$param->isOptional()) {
168 1
                    $fn = $refl instanceof \ReflectionMethod ? $refl->class . '::' . $refl->name : $refl->name;
169 1
                    throw new \RuntimeException("Missing argument '$key' for {$fn}()");
170
                }
171
                
172 3
                $value = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
173
            }
174
175 5
            $args[$key] = $value;
176
        }
177
        
178 5
        return $args;
179
    }
180
}
181
182