Dispatcher::initResult()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
/**
3
 * Phossa Project
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  Library
8
 * @package   Phossa2\Route
9
 * @copyright Copyright (c) 2016 phossa.com
10
 * @license   http://mit-license.org/ MIT License
11
 * @link      http://www.phossa.com/
12
 */
13
/*# declare(strict_types=1); */
14
15
namespace Phossa2\Route;
16
17
use Phossa2\Route\Collector\Collector;
18
use Phossa2\Route\Traits\AddRouteTrait;
19
use Phossa2\Shared\Debug\DebuggableTrait;
20
use Phossa2\Route\Traits\HandlerAwareTrait;
21
use Phossa2\Route\Traits\ResolverAwareTrait;
22
use Phossa2\Route\Interfaces\RouteInterface;
23
use Phossa2\Route\Interfaces\ResultInterface;
24
use Phossa2\Route\Traits\CollectorAwareTrait;
25
use Phossa2\Shared\Debug\DebuggableInterface;
26
use Phossa2\Route\Interfaces\ResolverInterface;
27
use Phossa2\Route\Interfaces\AddRouteInterface;
28
use Phossa2\Route\Interfaces\CollectorInterface;
29
use Phossa2\Route\Interfaces\DispatcherInterface;
30
use Phossa2\Route\Interfaces\HandlerAwareInterface;
31
use Phossa2\Route\Interfaces\ResolverAwareInterface;
32
use Phossa2\Event\EventableExtensionCapableAbstract;
33
use Phossa2\Route\Interfaces\CollectorAwareInterface;
34
35
/**
36
 * Dispatcher
37
 *
38
 * Matching or dispatching base on the URI PATH and HTTP METHOD
39
 *
40
 * @package Phossa2\Route
41
 * @author  Hong Zhang <[email protected]>
42
 * @see     EventableExtensionCapableAbstract
43
 * @see     DispatcherInterface
44
 * @see     HandlerAwareInterface
45
 * @see     CollectorAwareInterface
46
 * @see     ResolverAwareInterface
47
 * @see     AddRouteInterface
48
 * @see     DebuggableInterface
49
 * @version 2.0.0
50
 * @since   2.0.0 added
51
 */
52
class Dispatcher extends EventableExtensionCapableAbstract implements DispatcherInterface, HandlerAwareInterface, CollectorAwareInterface, ResolverAwareInterface, AddRouteInterface, DebuggableInterface
53
{
54
    use HandlerAwareTrait, CollectorAwareTrait, ResolverAwareTrait, AddRouteTrait, DebuggableTrait;
55
56
    /**#@+
57
     * Dispatcher level events
58
     *
59
     * @const
60
     */
61
62
    // before any matching starts
63
    const EVENT_BEFORE_MATCH = 'dispatcher.match.before';
64
65
    // after a successful matching
66
    const EVENT_AFTER_MATCH = 'dispatcher.match.after';
67
68
    // after a successful matching, before execute handler
69
    const EVENT_BEFORE_DISPATCH = 'dispatcher.dispatch.before';
70
71
    // after handler executed successfully
72
    const EVENT_AFTER_DISPATCH = 'dispatcher.dispatch.after';
73
74
    // before execute dispatcher's default handler
75
    const EVENT_BEFORE_HANDLER = 'dispatcher.handler.before';
76
77
    // after dispatcher's default handler executed
78
    const EVENT_AFTER_HANDLER = 'dispatcher.handler.after';
79
    /**#@-*/
80
81
    /**
82
     * The matching result
83
     *
84
     * @var    ResultInterface
85
     * @access protected
86
     */
87
    protected $result;
88
89
    /**
90
     * @param  CollectorInterface $collector
91
     * @param  ResolverInterface $resolver
92
     * @access public
93
     */
94
    public function __construct(
95
        CollectorInterface $collector = null,
96
        ResolverInterface $resolver = null
97
    ) {
98
        // inject first collector
99
        if ($collector) {
100
            $this->addCollector($collector);
101
        }
102
103
        // inject handler resolver
104
        if ($resolver) {
105
            $this->setResolver($resolver);
106
        }
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     *
112
     * @since  2.0.1 added $parameters
113
     */
114
    public function match(
115
        /*# string */ $httpMethod,
116
        /*# string */ $uriPath,
117
        array $parameters = []
118
    )/*# :  bool */ {
119
        $this->initResult($httpMethod, $uriPath);
120
        $this->getResult()->setParameters($parameters);
121
122
        $param = ['result' => $this->result];
123
        if ($this->trigger(self::EVENT_BEFORE_MATCH, $param) &&
124
            $this->matchWithCollectors() &&
125
            $this->trigger(self::EVENT_AFTER_MATCH, $param)
126
        ) {
127
            return true;
128
        }
129
        return false;
130
    }
131
132
    /**
133
     * {@inheritDoc}
134
     *
135
     * @since  2.0.1 added $parameters
136
     */
137
    public function dispatch(
138
        /*# string */ $httpMethod,
139
        /*# string */ $uriPath,
140
        array $parameters = []
141
    )/*# : bool */ {
142
        // match & dispatch
143
        if ($this->match($httpMethod, $uriPath, $parameters) &&
144
            $this->isDispatched()
145
        ) {
146
            return true;
147
        }
148
149
        // failed, execute default handler if any
150
        return $this->defaultHandler();
151
    }
152
153
    /**
154
     * {@inheritDoc}
155
     */
156
    public function getResult()/*# : ResultInterface */
157
    {
158
        return $this->result;
159
    }
160
161
    /**
162
     * {@inheritDoc}
163
     */
164
    public function addRoute(RouteInterface $route)
165
    {
166
        // add a default collector if nothing found
167
        if (0 === count($this->getCollectors())) {
168
            $this->addCollector(new Collector());
169
        }
170
171
        // add route to the first collector
172
        $this->getCollectors()[0]->addRoute($route);
173
174
        return $this;
175
    }
176
177
    /**
178
     * Initialize the result
179
     *
180
     * @param  string $httpMethod
181
     * @param  string $uriPath
182
     * @access protected
183
     */
184
    protected function initResult($httpMethod, $uriPath)
185
    {
186
        $this->result = new Result($httpMethod, $uriPath);
187
    }
188
189
    /**
190
     * Match with all the route collectors of this dispatcher
191
     *
192
     * @return boolean
193
     * @access protected
194
     */
195
    protected function matchWithCollectors()/*# : bool */
196
    {
197
        foreach ($this->getCollectors() as $coll) {
198
            if ($coll->matchRoute($this->result)) {
199
                return true;
200
            }
201
        }
202
        return false;
203
    }
204
205
    /**
206
     * Real dispatching process
207
     *
208
     * @return bool
209
     * @access protected
210
     */
211
    protected function isDispatched()/*# : bool */
212
    {
213
        $param = ['result' => $this->result];
214
        if ($this->trigger(self::EVENT_BEFORE_DISPATCH, $param) &&
215
            $this->executeHandler() &&
216
            $this->trigger(self::EVENT_AFTER_DISPATCH, $param)
217
        ) {
218
            return true;
219
        }
220
        return false;
221
    }
222
223
    /**
224
     * Execute handler of the result
225
     *
226
     * IF HANDLER NOT EXECUTED, REMOVE IT !!
227
     *
228
     * @return bool true if handler executed
229
     * @access protected
230
     */
231
    protected function executeHandler()/*# : bool */
232
    {
233
        try {
234
            $handler = $this->result->getHandler();
235
            $callable = $this->getResolver()->resolve($handler);
236
237
            if ($this->result->getRoute()) {
238
                return $this->callableWithRoute($callable);
239
            } else {
240
                call_user_func($callable, $this->result);
241
                return true;
242
            }
243
        } catch (\Exception $e) {
244
            $this->result->setHandler(null);
245
            return false;
246
        }
247
    }
248
249
    /**
250
     * Execute the callable with route events
251
     *
252
     * IF HANDLER NOT EXECUTED, REMOVE IT !!
253
     *
254
     * @param  callable $callable
255
     * @return bool true if callable executed
256
     * @access protected
257
     */
258
    protected function callableWithRoute(callable $callable)/*# : bool */
259
    {
260
        /* @var EventCapableAbstract $route */
261
        $route = $this->result->getRoute();
262
        $param = ['result' => $this->result];
263
        if ($route->trigger(Route::EVENT_BEFORE_HANDLER, $param)) {
264
            call_user_func($callable, $this->result);
265
            $route->trigger(Route::EVENT_AFTER_HANDLER, $param);
266
            return true;
267
        }
268
        $this->result->setHandler(null);
269
        return false;
270
    }
271
272
    /**
273
     * Execute dispatcher level handler
274
     *
275
     * @return bool
276
     * @access protected
277
     */
278
    protected function defaultHandler()/*# : bool */
279
    {
280
        $status = $this->result->getStatus();
281
        $handler = $this->result->getHandler() ?: $this->getHandler($status);
282
283
        if ($handler) {
284
            $param = ['result' => $this->result];
285
            $callable = $this->getResolver()->resolve($handler);
286
            if ($this->trigger(self::EVENT_BEFORE_HANDLER, $param)) {
287
                call_user_func($callable, $this->result);
288
                $this->trigger(self::EVENT_AFTER_HANDLER, $param);
289
            }
290
        }
291
        return false;
292
    }
293
}
294