RouteMetaData::getOriginLocation()   B
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.0729

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 12
cts 14
cp 0.8571
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 11
nc 5
nop 3
crap 5.0729
1
<?php
2
/**
3
 * This file is part of the Drest package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author Lee Davis
9
 * @copyright Copyright (c) Lee Davis <@leedavis81>
10
 * @link https://github.com/leedavis81/drest/blob/master/LICENSE
11
 * @license http://opensource.org/licenses/MIT The MIT X License (MIT)
12
 */
13
namespace Drest\Mapping;
14
15
use Doctrine\ORM\EntityManager;
16
use Drest\DrestException;
17
use Drest\Helper\ObjectToArray;
18
use Drest\Route\Matcher as RouteMatcher;
19
use DrestCommon\Request\Request;
20
21
/**
22
 *
23
 * A class metadata instance that holds all the information for a Drest entity
24
 * @author Lee
25
 *
26
 */
27
class RouteMetaData implements \Serializable
28
{
29
    /**
30
     * This route objects parent
31
     * @var ClassMetaData $classMetaData
32
     */
33
    protected $class_metadata;
34
35
    /**
36
     * A string route pattern to be matched on. eg /user/:id
37
     * @var string $route_pattern
38
     */
39
    protected $route_pattern;
40
41
    /**
42
     * Any custom regex conditions that are needed when matching a route param component.
43
     * Eg array('year' => '(19|20)\d\d')
44
     * @var array $route_conditions
45
     */
46
    protected $route_conditions = [];
47
48
    /**
49
     * Key-value array of URL parameter names
50
     * @var array $param_names
51
     */
52
    protected $param_names = [];
53
54
    /**
55
     * Key-value array of URL parameters with + at the end
56
     * @var array $param_names_path
57
     */
58
    protected $param_names_path = [];
59
60
    /**
61
     * Key-value array of URL parameters populated after a match has been successful
62
     * - or directly by using available setter
63
     * @var array $route_params
64
     */
65
    protected $route_params;
66
67
    /**
68
     * An index array of URL parameters that exist but didn't match a route pattern parameter
69
     * Eg: pattern: /user/:id+  with url: /user/1/some/additional/params.
70
     * The value id => 1 will go into $route_params
71
     * All the rest will go in here.
72
     * @var array $unmapped_route_params
73
     */
74
    protected $unmapped_route_params;
75
76
    /**
77
     * The route name (must be unique)
78
     * @var string $name
79
     */
80
    protected $name;
81
82
    /**
83
     * Any array of verbs allowed on this route.
84
     * They match the constant values defined in DrestCommon\Request\Request eg array('GET', 'POST')
85
     * @var array $verbs
86
     */
87
    protected $verbs;
88
89
    /**
90
     * Whether get requests to this route should be exposed / handled as collections
91
     * @var boolean $collection
92
     */
93
    protected $collection = false;
94
95
    /**
96
     * A handle function call for this route (if one is configured)
97
     * @var string $handle_call
98
     */
99
    protected $handle_call;
100
101
    /**
102
     * An array of fields to be exposed to the end client
103
     * @var array $expose
104
     */
105
    protected $expose = [];
106
107
108
    /**
109
     * Prevent expose settings lookup
110
     * @var boolean $disable_expose
111
     */
112
    protected $disable_expose;
113
114
    /**
115
     * Whether this route is open to allow OPTION requests to detail available $verbs
116
     * -1 = not set
117
     * 0  = not allowed
118
     * 1  = allowed
119
     * @var integer $allowed_option_request ;
120
     */
121
    protected $allowed_option_request = -1;
122
123
124
    /**
125
     * Set this objects parent metadata class
126
     * @param ClassMetaData $class_metadata
127
     */
128 48
    public function setClassMetaData(ClassMetaData $class_metadata)
129
    {
130 48
        $this->class_metadata = $class_metadata;
131 48
    }
132
133
    /**
134
     * Get this classes metadata object
135
     * @return ClassMetaData $class_metadata
136
     */
137 31
    public function getClassMetaData()
138
    {
139 31
        return $this->class_metadata;
140
    }
141
142
    /**
143
     * Get this routes route pattern
144
     * @return string $route_pattern
145
     */
146 30
    public function getRoutePattern()
147
    {
148 30
        return $this->route_pattern;
149
    }
150
151
    /**
152
     * Add the route path. eg '/users/:id'
153
     * @param string $route_pattern
154
     */
155 45
    public function setRoutePattern($route_pattern)
156
    {
157 45
        $this->route_pattern = $route_pattern;
158 45
    }
159
160
    /**
161
     * Add an array of route conditions
162
     * @param array $route_conditions
163
     */
164 36
    public function setRouteConditions(array $route_conditions)
165
    {
166 36
        foreach ($route_conditions as $field => $condition) {
167 36
            $this->route_conditions[preg_replace("/[^a-zA-Z0-9_]+/", "", $field)] = $condition;
168 36
        }
169 36
    }
170
171
    /**
172
     * Get the route conditions
173
     * @return array|null
174
     */
175 27
    public function getRouteConditions()
176
    {
177 27
        return $this->route_conditions;
178
    }
179
180
    /**
181
     * Get the name of this route
182
     * @return string $name
183
     */
184 51
    public function getName()
185
    {
186 51
        return $this->name;
187
    }
188
189
    /**
190
     * Sets a unique reference name for the resource.
191
     * If other resources are created with this name an exception is thrown (must be unique)
192
     * @param string $name
193
     */
194 51
    public function setName($name)
195
    {
196 51
        $this->name = $name;
197 51
    }
198
199
    /**
200
     * The unique named route to reference this route by
201
     * Note that is should use the entities fully qualified class names
202
     * @return string
203
     */
204 30
    public function getNamedRoute()
205
    {
206 30
        return ltrim($this->getClassMetaData()->getClassName(), '\\') . '::' . $this->getName();
207
    }
208
209
    /**
210
     * Whether requests to this route should be handled as collections
211
     * @param boolean $value
212
     */
213 34
    public function setCollection($value = true)
214
    {
215 34
        $this->collection = (bool) $value;
216 34
    }
217
218
    /**
219
     * Should this route be handled as a collection
220
     */
221 22
    public function isCollection()
222
    {
223 22
        return (bool) $this->collection;
224
    }
225
226
    /**
227
     * Get an array of verbs that are allowed on this route
228
     * @return array
229
     */
230 37
    public function getVerbs()
231
    {
232 37
        return $this->verbs;
233
    }
234
235
    /**
236
     * Add verbs that are to be allowed on this route.
237
     * @param  mixed          $verbs = a single or array of verbs valid for this route. eg array('GET', 'PUT')
238
     * @throws DrestException if verb is invalid
239
     */
240 45
    public function setVerbs($verbs)
241
    {
242 45
        foreach ((array) $verbs as $verb) {
243 45
            $verb = strtoupper($verb);
244 45
            if (!defined('DrestCommon\Request\Request::METHOD_' . $verb)) {
245 2
                throw DrestException::invalidHttpVerbUsed($verb);
246
            }
247 43
            $this->verbs[] = $verb;
248 43
        }
249 43
    }
250
251
    /**
252
     * Inject route params onto this object without performing a match. Useful when calling a named route directly
253
     * @param array $params - should be an associative array. keyed values are ignored
254
     */
255 2
    public function setRouteParams(array $params = [])
256
    {
257 2
        $this->route_params = array_flip(
258 2
            array_filter(
259 2
                array_flip($params),
260
                function ($entry) {
261 1
                    return !is_int($entry);
262
                }
263 2
            )
264 2
        );
265 2
    }
266
267
    /**
268
     * Get any params that were set after a successful match
269
     * @return array $params
270
     */
271 25
    public function getRouteParams()
272
    {
273 25
        return (!empty($this->route_params)) ? $this->route_params : [];
274
    }
275
276
    /**
277
     * Inject unmapped route params onto this object without performing a match.
278
     * Useful when calling a named route directly
279
     * @param array $params - should be a keyed array. associative values are ignored
280
     */
281
    public function setUnmappedRouteParams(array $params = [])
282
    {
283
        $this->unmapped_route_params = array_flip(
284
            array_filter(
285
                array_flip($params),
286
                function ($entry) {
287
                    return !is_string($entry);
288
                }
289
            )
290
        );
291
    }
292
293
    /**
294
     * An array of fields we're allowed to expose to the client
295
     * @param array $expose
296
     */
297 36
    public function setExpose(array $expose)
298
    {
299 36
        $this->expose = $expose;
300 36
    }
301
302
    /**
303
     * Get the field exposure on this route
304
     * @return array $expose
305
     */
306 30
    public function getExpose()
307
    {
308 30
        return $this->expose;
309
    }
310
311
    /**
312
     * Set disable expose flag
313
     * @param bool $flag
314
     */
315
    public function setDisableExpose($flag = true)
316
    {
317
        $this->disable_expose = (bool) $flag;
318
    }
319
320
    /**
321
     * Is the expose lookup disabled
322
     * @return bool
323
     */
324 25
    public function isExposeDisabled()
325
    {
326 25
        return $this->disable_expose;
327
    }
328
329
    /**
330
     * Set the handle function call
331
     * @param string $handle_call
332
     */
333 36
    public function setHandleCall($handle_call)
334
    {
335 36
        if (is_string($handle_call)) {
336 36
            $this->handle_call = $handle_call;
337 36
        }
338 36
    }
339
340
    /**
341
     * Get the handle call function
342
     * @return string $handle_call
343
     */
344 4
    public function getHandleCall()
345
    {
346 4
        return $this->handle_call;
347
    }
348
349
    /**
350
     *
351
     * Does this route have an annotated handle call
352
     */
353 37
    public function hasHandleCall()
354
    {
355 37
        return isset($this->handle_call);
356
    }
357
358
    /**
359
     * Does this route need a handle call? Required for POST/PUT/PATCH verbs
360
     * @return boolean $response
361
     */
362 25
    public function needsHandleCall()
363
    {
364 25
        foreach ($this->getVerbs() as $verb) {
365
            switch ($verb) {
366 25
                case Request::METHOD_POST:
367 25
                case Request::METHOD_PUT:
368 25
                case Request::METHOD_PATCH:
369 3
                    return true;
370
            }
371 22
        }
372
373 22
        return false;
374
    }
375
376
    /**
377
     * Set whether we would like to expose this route (and its verbs) to OPTIONS requests
378
     * @param  integer|boolean $value - if using integer -1 to unset, 0 for no and 1 if yes
379
     * @throws DrestException
380
     */
381
    public function setAllowedOptionRequest($value = true)
382
    {
383
        if (is_bool($value)) {
384
            $this->allowed_option_request = ((bool) $value) ? 1 : 0;
385
        }
386
387
        // No need to test for -1 value, it cannot be anything else at this point.
388
389
        // Value is converted and saved as an int.
390
        $this->allowed_option_request = (int) $value;
391
    }
392
393
    /**
394
     * Is this route allowed to expose its verbs to OPTIONS requests
395
     * @return integer $result -1 if not set, 0 if no and 1 if yes
396
     */
397 2
    public function isAllowedOptionRequest()
398
    {
399 2
        return $this->allowed_option_request;
400
    }
401
402
    /**
403
     * Generate the location string from the provided object
404
     * @param  object        $object
405
     * @param  string        $url    - the Url to be prepended to the location
406
     * @param  EntityManager $em     - Optionally pass the entity manager to assist in determining a GET origin location
407
     * @return string|false
408
     */
409 3
    public function getOriginLocation($object, $url, EntityManager $em = null)
410
    {
411 3
        $exposedObjectArray = ObjectToArray::execute($object);
412 3
        if (($route = $this->class_metadata->getOriginRoute($em)) !== null) {
413 3
            if (!is_null($em)) {
414 3
                $pattern = $route->getRoutePattern();
415 3
                $ormClassMetadata = $em->getClassMetadata($this->getClassMetaData()->getClassName());
416 3
                foreach ($ormClassMetadata->getIdentifierFieldNames() as $identifier) {
417 3
                    if (isset($exposedObjectArray[$identifier])) {
418 3
                        $pattern = str_replace(':' . $identifier, $exposedObjectArray[$identifier], $pattern);
419 3
                    }
420 3
                }
421
422 3
                return $url . '/' . ltrim($pattern, '/');
423
            }
424
        }
425
426
        return false;
427
    }
428
429
    /**
430
     * Does this request match the route pattern
431
     * @param  Request $request
432
     * @param  boolean $matchVerb - Whether you want to match the route using the request HTTP verb
433
     *                            - useful for OPTIONS requests to provide route info
434
     * @param  string  $basePath  - add a base path to the route pattern
435
     * @return boolean $result
436
     */
437 30
    public function matches(Request $request, $matchVerb = true, $basePath = null)
438
    {
439 30
        $matcher = new RouteMatcher($this);
440 30
        if (!$matcher->matches($request, $matchVerb, $basePath))
441 30
        {
442 30
            return false;
443
        }
444
445
        // set determined parameters from running the match
446 27
        $this->route_params = $matcher->getRouteParams();
447 27
        $this->unmapped_route_params = $matcher->getUnmappedRouteParams();
448 27
        $this->param_names = $matcher->getParamNames();
449 27
        $this->param_names_path = $matcher->getParamNamesPath();
450
451 27
        return true;
452
    }
453
454
    /**
455
     * Is this route specific to defined HTTP verbs
456
     */
457 30
    public function usesHttpVerbs()
458
    {
459 30
        return !empty($this->verbs);
460
    }
461
462
    /**
463
     * Serialise this object
464
     * @return string
465
     */
466 1
    public function serialize()
467
    {
468 1
        $trace = debug_backtrace();
469 1
        if (!isset($trace[2]) || $trace[2]['class'] != 'Drest\Mapping\ClassMetaData') {
470
            trigger_error('RouteMetaData can only be serialized from a parent instance of ClassMetaData', E_USER_ERROR);
471
        }
472
473 1
        return serialize(
474
            [
475 1
                $this->route_pattern,
476 1
                $this->route_conditions,
477 1
                $this->param_names,
478 1
                $this->param_names_path,
479 1
                $this->route_params,
480 1
                $this->unmapped_route_params,
481 1
                $this->name,
482 1
                $this->verbs,
483 1
                $this->collection,
484 1
                $this->handle_call,
485 1
                $this->expose,
486 1
                $this->disable_expose,
487 1
                $this->allowed_option_request
488 1
            ]
489 1
        );
490
    }
491
492
    /**
493
     * Un-serialise this object and reestablish it's state
494
     * @param string $string
495
     */
496 1
    public function unserialize($string)
497
    {
498
        list(
499 1
            $this->route_pattern,
500 1
            $this->route_conditions,
501 1
            $this->param_names,
502 1
            $this->param_names_path,
503 1
            $this->route_params,
504 1
            $this->unmapped_route_params,
505 1
            $this->name,
506 1
            $this->verbs,
507 1
            $this->collection,
508 1
            $this->handle_call,
509 1
            $this->expose,
510 1
            $this->disable_expose,
511 1
            $this->allowed_option_request
512 1
            ) = unserialize($string);
513 1
    }
514
}
515