Test Failed
Push — main ( c8394f...8477f1 )
by Rafael
66:21
created

Resources::_model()   F

Complexity

Conditions 20
Paths 1686

Size

Total Lines 95
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 69
nc 1686
nop 2
dl 0
loc 95
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Luracast\Restler;
4
5
use Luracast\Restler\Data\Text;
6
use Luracast\Restler\Scope;
7
use stdClass;
8
9
/**
10
 * API Class to create Swagger Spec 1.1 compatible id and operation
11
 * listing
12
 *
13
 * @category   Framework
14
 * @package    Restler
15
 * @author     R.Arul Kumaran <[email protected]>
16
 * @copyright  2010 Luracast
17
 * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
18
 * @link       http://luracast.com/products/restler/
19
 *
20
 */
21
class Resources implements iUseAuthentication, iProvideMultiVersionApi
22
{
23
    /**
24
     * @var bool should protected resources be shown to unauthenticated users?
25
     */
26
    public static $hideProtected = true;
27
    /**
28
     * @var bool should we use format as extension?
29
     */
30
    public static $useFormatAsExtension = true;
31
    /**
32
     * @var bool should we include newer apis in the list? works only when
33
     * Defaults::$useUrlBasedVersioning is set to true;
34
     */
35
    public static $listHigherVersions = true;
36
    /**
37
     * @var array all http methods specified here will be excluded from
38
     * documentation
39
     */
40
    public static $excludedHttpMethods = array('OPTIONS');
41
    /**
42
     * @var array all paths beginning with any of the following will be excluded
43
     * from documentation
44
     */
45
    public static $excludedPaths = array();
46
    /**
47
     * @var bool
48
     */
49
    public static $placeFormatExtensionBeforeDynamicParts = true;
50
    /**
51
     * @var bool should we group all the operations with the same url or not
52
     */
53
    public static $groupOperations = false;
54
    /**
55
     * @var null|callable if the api methods are under access control mechanism
56
     * you can attach a function here that returns true or false to determine
57
     * visibility of a protected api method. this function will receive method
58
     * info as the only parameter.
59
     */
60
    public static $accessControlFunction = null;
61
    /**
62
     * @var array type mapping for converting data types to javascript / swagger
63
     */
64
    public static $dataTypeAlias = array(
65
        'string' => 'string',
66
        'int' => 'int',
67
        'number' => 'float',
68
        'float' => 'float',
69
        'bool' => 'boolean',
70
        'boolean' => 'boolean',
71
        'NULL' => 'null',
72
        'array' => 'Array',
73
        'object' => 'Object',
74
        'stdClass' => 'Object',
75
        'mixed' => 'string',
76
        'DateTime' => 'Date'
77
    );
78
    /**
79
     * @var array configurable symbols to differentiate public, hybrid and
80
     * protected api
81
     */
82
    public static $apiDescriptionSuffixSymbols = array(
83
        0 => '&nbsp; <i class="icon-unlock-alt icon-large"></i>', //public api
84
        1 => '&nbsp; <i class="icon-adjust icon-large"></i>', //hybrid api
85
        2 => '&nbsp; <i class="icon-lock icon-large"></i>', //protected api
86
    );
87
88
    /**
89
     * Injected at runtime
90
     *
91
     * @var Restler instance of restler
92
     */
93
    public $restler;
94
    /**
95
     * @var string when format is not used as the extension this property is
96
     * used to set the extension manually
97
     */
98
    public $formatString = '';
99
    protected $_models;
100
    protected $_bodyParam;
101
    /**
102
     * @var bool|stdClass
103
     */
104
    protected $_fullDataRequested = false;
105
    protected $crud = array(
106
        'POST' => 'create',
107
        'GET' => 'retrieve',
108
        'PUT' => 'update',
109
        'DELETE' => 'delete',
110
        'PATCH' => 'partial update'
111
    );
112
    protected static $prefixes = array(
113
        'get' => 'retrieve',
114
        'index' => 'list',
115
        'post' => 'create',
116
        'put' => 'update',
117
        'patch' => 'modify',
118
        'delete' => 'remove',
119
    );
120
    protected $_authenticated = false;
121
    protected $cacheName = '';
122
123
    public function __construct()
124
    {
125
        if (static::$useFormatAsExtension) {
126
            $this->formatString = '.{format}';
127
        }
128
    }
129
130
    /**
131
     * This method will be called first for filter classes and api classes so
132
     * that they can respond accordingly for filer method call and api method
133
     * calls
134
     *
135
     *
136
     * @param bool $isAuthenticated passes true when the authentication is
137
     *                              done, false otherwise
138
     *
139
     * @return mixed
140
     */
141
    public function __setAuthenticationStatus($isAuthenticated = false)
142
    {
143
        $this->_authenticated = $isAuthenticated;
144
    }
145
146
    /**
147
     * pre call for get($id)
148
     *
149
     * if cache is present, use cache
150
     */
151
    public function _pre_get_json($id)
152
    {
153
        $userClass = Defaults::$userIdentifierClass;
154
        $this->cacheName = $userClass::getCacheIdentifier() . '_resources_' . $id;
155
        if (
156
            $this->restler->getProductionMode()
157
            && !$this->restler->refreshCache
158
            && $this->restler->cache->isCached($this->cacheName)
159
        ) {
160
            //by pass call, compose, postCall stages and directly send response
161
            $this->restler->composeHeaders();
162
            die($this->restler->cache->get($this->cacheName));
163
        }
164
    }
165
166
    /**
167
     * post call for get($id)
168
     *
169
     * create cache if in production mode
170
     *
171
     * @param $responseData
172
     *
173
     * @internal param string $data composed json output
174
     *
175
     * @return string
176
     */
177
    public function _post_get_json($responseData)
178
    {
179
        if ($this->restler->getProductionMode()) {
180
            $this->restler->cache->set($this->cacheName, $responseData);
181
        }
182
        return $responseData;
183
    }
184
185
    /**
186
     * @access hybrid
187
     *
188
     * @param string $id
189
     *
190
     * @throws RestException
191
     * @return null|stdClass
192
     *
193
     * @url    GET {id}
194
     */
195
    public function get($id = '')
196
    {
197
        $version = $this->restler->getRequestedApiVersion();
198
        if (empty($id)) {
199
            //do nothing
200
        } elseif (false !== ($pos = strpos($id, '-v'))) {
201
            //$version = intval(substr($id, $pos + 2));
202
            $id = substr($id, 0, $pos);
203
        } elseif ($id[0] == 'v' && is_numeric($v = substr($id, 1))) {
204
            $id = '';
205
            //$version = $v;
206
        } elseif ($id == 'root' || $id == 'index') {
207
            $id = '';
208
        }
209
        $this->_models = new stdClass();
210
        $r = null;
211
        $count = 0;
212
213
        $tSlash = !empty($id);
214
        $target = empty($id) ? '' : $id;
215
        $tLen = strlen($target);
216
217
        $filter = array();
218
219
        $routes
220
            = Util::nestedValue(Routes::toArray(), "v$version")
221
            ? : array();
222
223
        $prefix = Defaults::$useUrlBasedVersioning ? "/v$version" : '';
224
225
        foreach ($routes as $value) {
226
            foreach ($value as $httpMethod => $route) {
227
                if (in_array($httpMethod, static::$excludedHttpMethods)) {
228
                    continue;
229
                }
230
                $fullPath = $route['url'];
231
                if ($fullPath !== $target && !Text::beginsWith($fullPath, $target)) {
232
                    continue;
233
                }
234
                $fLen = strlen($fullPath);
235
                if ($tSlash) {
236
                    if ($fLen != $tLen && !Text::beginsWith($fullPath, $target . '/'))
237
                        continue;
238
                } elseif ($fLen > $tLen + 1 && $fullPath[$tLen + 1] != '{' && !Text::beginsWith($fullPath, '{')) {
239
                    //when mapped to root exclude paths that have static parts
240
                    //they are listed else where under that static part name
241
                    continue;
242
                }
243
244
                if (!static::verifyAccess($route)) {
245
                    continue;
246
                }
247
                foreach (static::$excludedPaths as $exclude) {
248
                    if (empty($exclude)) {
249
                        if ($fullPath == $exclude)
250
                            continue 2;
251
                    } elseif (Text::beginsWith($fullPath, $exclude)) {
252
                        continue 2;
253
                    }
254
                }
255
                $m = $route['metadata'];
256
                if ($id == '' && $m['resourcePath'] != '') {
257
                    continue;
258
                }
259
                if (isset($filter[$httpMethod][$fullPath])) {
260
                    continue;
261
                }
262
                $filter[$httpMethod][$fullPath] = true;
263
                // reset body params
264
                $this->_bodyParam = array(
265
                    'required' => false,
266
                    'description' => array()
267
                );
268
                $count++;
269
                $className = $this->_noNamespace($route['className']);
270
                if (!$r) {
271
                    $resourcePath = '/'
272
                        . trim($m['resourcePath'], '/');
273
                    $r = $this->_operationListing($resourcePath);
274
                }
275
                $parts = explode('/', $fullPath);
276
                $pos = count($parts) - 1;
277
                if (count($parts) == 1 && $httpMethod == 'GET') {
278
                } else {
279
                    for ($i = 0; $i < count($parts); $i++) {
280
                        if (strlen($parts[$i]) && $parts[$i][0] == '{') {
281
                            $pos = $i - 1;
282
                            break;
283
                        }
284
                    }
285
                }
286
                $nickname = $this->_nickname($route);
287
                $index = static::$placeFormatExtensionBeforeDynamicParts && $pos > 0 ? $pos : 0;
288
                if (!empty($parts[$index]))
289
                    $parts[$index] .= $this->formatString;
290
291
                $fullPath = implode('/', $parts);
292
                $description = isset(
293
                $m['classDescription'])
294
                    ? $m['classDescription']
295
                    : $className . ' API';
296
                if (empty($m['description'])) {
297
                    $m['description'] = $this->restler->getProductionMode()
298
                        ? ''
299
                        : 'routes to <mark>'
300
                        . $route['className']
301
                        . '::'
302
                        . $route['methodName'] . '();</mark>';
303
                }
304
                if (empty($m['longDescription'])) {
305
                    $m['longDescription'] = $this->restler->getProductionMode()
306
                        ? ''
307
                        : 'Add PHPDoc long description to '
308
                        . "<mark>$className::"
309
                        . $route['methodName'] . '();</mark>'
310
                        . '  (the api method) to write here';
311
                }
312
                $operation = $this->_operation(
313
                    $route,
314
                    $nickname,
315
                    $httpMethod,
316
                    $m['description'],
317
                    $m['longDescription']
318
                );
319
                if (isset($m['throws'])) {
320
                    foreach ($m['throws'] as $exception) {
321
                        $operation->errorResponses[] = array(
322
                            'reason' => $exception['message'],
323
                            'code' => $exception['code']);
324
                    }
325
                }
326
                if (isset($m['param'])) {
327
                    foreach ($m['param'] as $param) {
328
                        //combine body params as one
329
                        $p = $this->_parameter($param);
330
                        if ($p->paramType == 'body') {
331
                            $this->_appendToBody($p);
332
                        } else {
333
                            $operation->parameters[] = $p;
334
                        }
335
                    }
336
                }
337
                if (
338
                    count($this->_bodyParam['description']) ||
339
                    (
340
                        $this->_fullDataRequested &&
341
                        $httpMethod != 'GET' &&
342
                        $httpMethod != 'DELETE'
343
                    )
344
                ) {
345
                    $operation->parameters[] = $this->_getBody();
346
                }
347
                if (isset($m['return']['type'])) {
348
                    $responseClass = $m['return']['type'];
349
                    if (is_string($responseClass)) {
350
                        if (class_exists($responseClass)) {
351
                            $this->_model($responseClass);
352
                            $operation->responseClass
353
                                = $this->_noNamespace($responseClass);
354
                        } elseif (strtolower($responseClass) == 'array') {
355
                            $operation->responseClass = 'Array';
356
                            $rt = $m['return'];
357
                            if (
358
                                isset(
359
                                $rt[CommentParser::$embeddedDataName]['type'])
360
                            ) {
361
                                $rt = $rt[CommentParser::$embeddedDataName]
362
                                ['type'];
363
                                if (class_exists($rt)) {
364
                                    $this->_model($rt);
365
                                    $operation->responseClass .= '[' .
366
                                        $this->_noNamespace($rt) . ']';
367
                                }
368
                            }
369
                        }
370
                    }
371
                }
372
                $api = false;
373
374
                if (static::$groupOperations) {
375
                    foreach ($r->apis as $a) {
376
                        if ($a->path == "$prefix/$fullPath") {
377
                            $api = $a;
378
                            break;
379
                        }
380
                    }
381
                }
382
383
                if (!$api) {
384
                    $api = $this->_api("$prefix/$fullPath", $description);
385
                    $r->apis[] = $api;
386
                }
387
388
                $api->operations[] = $operation;
389
            }
390
        }
391
        if (!$count) {
392
            throw new RestException(404);
393
        }
394
        if (!is_null($r))
395
            $r->models = $this->_models;
396
        usort(
397
            $r->apis,
398
            function ($a, $b) {
399
                $order = array(
400
                    'GET' => 1,
401
                    'POST' => 2,
402
                    'PUT' => 3,
403
                    'PATCH' => 4,
404
                    'DELETE' => 5
405
                );
406
                return
407
                    $a->operations[0]->httpMethod ==
408
                    $b->operations[0]->httpMethod
409
                        ? $a->path > $b->path
410
                        : $order[$a->operations[0]->httpMethod] >
411
                        $order[$b->operations[0]->httpMethod];
412
            }
413
        );
414
        return $r;
415
    }
416
417
    protected function _nickname(array $route)
418
    {
419
        static $hash = array();
420
        $method = $route['methodName'];
421
        if (isset(static::$prefixes[$method])) {
422
            $method = static::$prefixes[$method];
423
        } else {
424
            $method = str_replace(
425
                array_keys(static::$prefixes),
426
                array_values(static::$prefixes),
427
                $method
428
            );
429
        }
430
        while (isset($hash[$method]) && $route['url'] != $hash[$method]) {
431
            //create another one
432
            $method .= '_';
433
        }
434
        $hash[$method] = $route['url'];
435
        return $method;
436
    }
437
438
    protected function _noNamespace($className)
439
    {
440
        $className = explode('\\', $className);
441
        return end($className);
442
    }
443
444
    protected function _operationListing($resourcePath = '/')
445
    {
446
        $r = $this->_resourceListing();
447
        $r->resourcePath = $resourcePath;
448
        $r->models = new stdClass();
449
        return $r;
450
    }
451
452
    protected function _resourceListing()
453
    {
454
        $r = new stdClass();
455
        $r->apiVersion = (string)$this->restler->_requestedApiVersion;
456
        $r->swaggerVersion = "1.1";
457
        $r->basePath = $this->restler->getBaseUrl();
458
        $r->produces = $this->restler->getWritableMimeTypes();
459
        $r->consumes = $this->restler->getReadableMimeTypes();
460
        $r->apis = array();
461
        return $r;
462
    }
463
464
    protected function _api($path, $description = '')
465
    {
466
        $r = new stdClass();
467
        $r->path = $path;
468
        $r->description =
469
            empty($description) && $this->restler->getProductionMode()
470
                ? 'Use PHPDoc comment to describe here'
471
                : $description;
472
        $r->operations = array();
473
        return $r;
474
    }
475
476
    protected function _operation(
477
        $route,
478
        $nickname,
479
        $httpMethod = 'GET',
480
        $summary = 'description',
481
        $notes = 'long description',
482
        $responseClass = 'void'
483
    ) {
484
        //reset body params
485
        $this->_bodyParam = array(
486
            'required' => false,
487
            'description' => array()
488
        );
489
490
        $r = new stdClass();
491
        $r->httpMethod = $httpMethod;
492
        $r->nickname = $nickname;
493
        $r->responseClass = $responseClass;
494
495
        $r->parameters = array();
496
497
        $r->summary = $summary . ($route['accessLevel'] > 2
498
                ? static::$apiDescriptionSuffixSymbols[2]
499
                : static::$apiDescriptionSuffixSymbols[$route['accessLevel']]
500
            );
501
        $r->notes = $notes;
502
503
        $r->errorResponses = array();
504
        return $r;
505
    }
506
507
    protected function _parameter($param)
508
    {
509
        $r = new stdClass();
510
        $r->name = $param['name'];
511
        $r->description = !empty($param['description'])
512
            ? $param['description'] . '.'
513
            : ($this->restler->getProductionMode()
514
                ? ''
515
                : 'add <mark>@param {type} $' . $r->name
516
                . ' {comment}</mark> to describe here');
517
        //paramType can be path or query or body or header
518
        $r->paramType = Util::nestedValue($param, CommentParser::$embeddedDataName, 'from') ? : 'query';
519
        $r->required = isset($param['required']) && $param['required'];
520
        if (isset($param['default'])) {
521
            $r->defaultValue = $param['default'];
522
        } elseif (isset($param[CommentParser::$embeddedDataName]['example'])) {
523
            $r->defaultValue
524
                = $param[CommentParser::$embeddedDataName]['example'];
525
        }
526
        $r->allowMultiple = false;
527
        $type = 'string';
528
        if (isset($param['type'])) {
529
            $type = $param['type'];
530
            if (is_array($type)) {
531
                $type = array_shift($type);
532
            }
533
            if ($type == 'array') {
534
                $contentType = Util::nestedValue(
535
                    $param,
536
                    CommentParser::$embeddedDataName,
537
                    'type'
538
                );
539
                if ($contentType) {
540
                    if ($contentType == 'indexed') {
541
                        $type = 'Array';
542
                    } elseif ($contentType == 'associative') {
543
                        $type = 'Object';
544
                    } else {
545
                        $type = "Array[$contentType]";
546
                    }
547
                    if (Util::isObjectOrArray($contentType)) {
548
                        $this->_model($contentType);
549
                    }
550
                } elseif (isset(static::$dataTypeAlias[$type])) {
551
                    $type = static::$dataTypeAlias[$type];
552
                }
553
            } elseif (Util::isObjectOrArray($type)) {
554
                $this->_model($type);
555
            } elseif (isset(static::$dataTypeAlias[$type])) {
556
                $type = static::$dataTypeAlias[$type];
557
            }
558
        }
559
        $r->dataType = $type;
560
        if (isset($param[CommentParser::$embeddedDataName])) {
561
            $p = $param[CommentParser::$embeddedDataName];
562
            if (isset($p['min']) && isset($p['max'])) {
563
                $r->allowableValues = array(
564
                    'valueType' => 'RANGE',
565
                    'min' => $p['min'],
566
                    'max' => $p['max'],
567
                );
568
            } elseif (isset($p['choice'])) {
569
                $r->allowableValues = array(
570
                    'valueType' => 'LIST',
571
                    'values' => $p['choice']
572
                );
573
            }
574
        }
575
        return $r;
576
    }
577
578
    protected function _appendToBody($p)
579
    {
580
        if ($p->name === Defaults::$fullRequestDataName) {
581
            $this->_fullDataRequested = $p;
582
            unset($this->_bodyParam['names'][Defaults::$fullRequestDataName]);
583
            return;
584
        }
585
        $this->_bodyParam['description'][$p->name]
586
            = "$p->name"
587
            . ' : <tag>' . $p->dataType . '</tag> '
588
            . ($p->required ? ' <i>(required)</i> - ' : ' - ')
589
            . $p->description;
590
        $this->_bodyParam['required'] = $p->required
591
            || $this->_bodyParam['required'];
592
        $this->_bodyParam['names'][$p->name] = $p;
593
    }
594
595
    protected function _getBody()
596
    {
597
        $r = new stdClass();
598
        $n = isset($this->_bodyParam['names'])
599
            ? array_values($this->_bodyParam['names'])
600
            : array();
601
        if (count($n) == 1) {
602
            if (isset($this->_models->{$n[0]->dataType})) {
603
                // ============ custom class ===================
604
                $r = $n[0];
605
                $c = $this->_models->{$r->dataType};
606
                $a = $c->properties;
607
                $r->description = "Paste JSON data here";
608
                if (count($a)) {
609
                    $r->description .= " with the following"
610
                        . (count($a) > 1 ? ' properties.' : ' property.');
611
                    foreach ($a as $k => $v) {
612
                        $r->description .= "<hr/>$k : <tag>"
613
                            . $v['type'] . '</tag> '
614
                            . (isset($v['required']) ? '(required)' : '')
615
                            . ' - ' . $v['description'];
616
                    }
617
                }
618
                $r->defaultValue = "{\n    \""
619
                    . implode(
620
                        "\": \"\",\n    \"",
621
                        array_keys($c->properties))
622
                    . "\": \"\"\n}";
623
                return $r;
624
            } elseif (false !== ($p = strpos($n[0]->dataType, '['))) {
625
                // ============ array of custom class ===============
626
                $r = $n[0];
627
                $t = substr($r->dataType, $p + 1, -1);
628
                if ($c = Util::nestedValue($this->_models, $t)) {
629
                    $a = $c->properties;
630
                    $r->description = "Paste JSON data here";
631
                    if (count($a)) {
632
                        $r->description .= " with an array of objects with the following"
633
                            . (count($a) > 1 ? ' properties.' : ' property.');
634
                        foreach ($a as $k => $v) {
635
                            $r->description .= "<hr/>$k : <tag>"
636
                                . $v['type'] . '</tag> '
637
                                . (isset($v['required']) ? '(required)' : '')
638
                                . ' - ' . $v['description'];
639
                        }
640
                    }
641
                    $r->defaultValue = "[\n    {\n        \""
642
                        . implode(
643
                            "\": \"\",\n        \"",
644
                            array_keys($c->properties))
645
                        . "\": \"\"\n    }\n]";
646
                    return $r;
647
                } else {
648
                    $r->description = "Paste JSON data here with an array of $t values.";
649
                    $r->defaultValue = "[ ]";
650
                    return $r;
651
                }
652
            } elseif ($n[0]->dataType == 'Array') {
653
                // ============ array ===============================
654
                $r = $n[0];
655
                $r->description = "Paste JSON array data here"
656
                    . ($r->required ? ' (required) . ' : '. ')
657
                    . "<br/>$r->description";
658
                $r->defaultValue = "[\n    {\n        \""
659
                    . "property\" : \"\"\n    }\n]";
660
                return $r;
661
            } elseif ($n[0]->dataType == 'Object') {
662
                // ============ object ==============================
663
                $r = $n[0];
664
                $r->description = "Paste JSON object data here"
665
                    . ($r->required ? ' (required) . ' : '. ')
666
                    . "<br/>$r->description";
667
                $r->defaultValue = "{\n    \""
668
                    . "property\" : \"\"\n}";
669
                return $r;
670
            }
671
        }
672
        $p = array_values($this->_bodyParam['description']);
673
        $r->name = 'REQUEST_BODY';
674
        $r->description = "Paste JSON data here";
675
        if (count($p) == 0 && $this->_fullDataRequested) {
676
            $r->required = $this->_fullDataRequested->required;
677
            $r->defaultValue = "{\n    \"property\" : \"\"\n}";
678
        } else {
679
            $r->description .= " with the following"
680
                . (count($p) > 1 ? ' properties.' : ' property.')
681
                . '<hr/>'
682
                . implode("<hr/>", $p);
683
            $r->required = $this->_bodyParam['required'];
684
            // Create default object that includes parameters to be submitted
685
            $defaultObject = new \StdClass();
686
            foreach ($this->_bodyParam['names'] as $name => $values) {
687
                if (!$values->required)
688
                    continue;
689
                if (class_exists($values->dataType)) {
690
                    $myClassName = $values->dataType;
691
                    $defaultObject->$name = new $myClassName();
692
                } else {
693
                    $defaultObject->$name = '';
694
                }
695
            }
696
            $r->defaultValue = Scope::get('JsonFormat')->encode($defaultObject, true);
697
        }
698
        $r->paramType = 'body';
699
        $r->allowMultiple = false;
700
        $r->dataType = 'Object';
701
        return $r;
702
    }
703
704
    protected function _model($className, $instance = null)
705
    {
706
        $id = $this->_noNamespace($className);
707
        if (isset($this->_models->{$id})) {
708
            return;
709
        }
710
        $properties = array();
711
        if (!$instance) {
712
            if (!class_exists($className))
713
                return;
714
            $instance = new $className();
715
        }
716
        $data = get_object_vars($instance);
717
        $reflectionClass = new \ReflectionClass($className);
718
        foreach ($data as $key => $value) {
719
            $propertyMetaData = null;
720
721
            try {
722
                $property = $reflectionClass->getProperty($key);
723
                if ($c = $property->getDocComment()) {
724
                    $propertyMetaData = Util::nestedValue(
725
                        CommentParser::parse($c),
726
                        'var'
727
                    );
728
                }
729
            } catch (\ReflectionException $e) {
730
            }
731
732
            if (is_null($propertyMetaData)) {
733
                $type = $this->getType($value, true);
734
                $description = '';
735
            } else {
736
                $type = Util::nestedValue(
737
                    $propertyMetaData,
738
                    'type'
739
                ) ? : $this->getType($value, true);
740
                $description = Util::nestedValue(
741
                    $propertyMetaData,
742
                    'description'
743
                ) ? : '';
744
745
                if (class_exists($type)) {
746
                    $this->_model($type);
747
                    $type = $this->_noNamespace($type);
748
                }
749
            }
750
751
            if (isset(static::$dataTypeAlias[$type])) {
752
                $type = static::$dataTypeAlias[$type];
753
            }
754
            $properties[$key] = array(
755
                'type' => $type,
756
                'description' => $description
757
            );
758
            if (
759
                Util::nestedValue(
760
                $propertyMetaData,
761
                CommentParser::$embeddedDataName,
762
                'required'
763
                )
764
            ) {
765
                $properties[$key]['required'] = true;
766
            }
767
            if ($type == 'Array') {
768
                $itemType = Util::nestedValue(
769
                    $propertyMetaData,
770
                    CommentParser::$embeddedDataName,
771
                    'type'
772
                ) ? :
773
                    (count($value)
774
                        ? $this->getType(end($value), true)
775
                        : 'string');
776
                if (class_exists($itemType)) {
777
                    $this->_model($itemType);
778
                    $itemType = $this->_noNamespace($itemType);
779
                }
780
                $properties[$key]['items'] = array(
781
                    'type' => $itemType,
782
                    /*'description' => '' */ //TODO: add description
783
                );
784
            } else if (preg_match('/^Array\[(.+)\]$/', $type, $matches)) {
785
                $itemType = $matches[1];
786
                $properties[$key]['type'] = 'Array';
787
                $properties[$key]['items']['type'] = $this->_noNamespace($itemType);
788
789
                if (class_exists($itemType)) {
790
                    $this->_model($itemType);
791
                }
792
            }
793
        }
794
        if (!empty($properties)) {
795
            $model = new stdClass();
796
            $model->id = $id;
797
            $model->properties = $properties;
798
            $this->_models->{$id} = $model;
799
        }
800
    }
801
802
    /**
803
     * Find the data type of the given value.
804
     *
805
     *
806
     * @param mixed $o given value for finding type
807
     *
808
     * @param bool $appendToModels if an object is found should we append to
809
     *                              our models list?
810
     *
811
     * @return string
812
     *
813
     * @access private
814
     */
815
    public function getType($o, $appendToModels = false)
816
    {
817
        if (is_object($o)) {
818
            $oc = get_class($o);
819
            if ($appendToModels) {
820
                $this->_model($oc, $o);
821
            }
822
            return $this->_noNamespace($oc);
823
        }
824
        if (is_array($o)) {
825
            if (count($o)) {
826
                $child = end($o);
827
                if (Util::isObjectOrArray($child)) {
828
                    $childType = $this->getType($child, $appendToModels);
829
                    return "Array[$childType]";
830
                }
831
            }
832
            return 'array';
833
        }
834
        if (is_bool($o)) return 'boolean';
835
        if (is_numeric($o)) return is_float($o) ? 'float' : 'int';
836
        return 'string';
837
    }
838
839
    /**
840
     * pre call for index()
841
     *
842
     * if cache is present, use cache
843
     */
844
    public function _pre_index_json()
845
    {
846
        $userClass = Defaults::$userIdentifierClass;
847
        $this->cacheName = $userClass::getCacheIdentifier()
848
            . '_resources-v'
849
            . $this->restler->_requestedApiVersion;
850
        if (
851
            $this->restler->getProductionMode()
852
            && !$this->restler->refreshCache
853
            && $this->restler->cache->isCached($this->cacheName)
854
        ) {
855
            //by pass call, compose, postCall stages and directly send response
856
            $this->restler->composeHeaders();
857
            die($this->restler->cache->get($this->cacheName));
858
        }
859
    }
860
861
    /**
862
     * post call for index()
863
     *
864
     * create cache if in production mode
865
     *
866
     * @param $responseData
867
     *
868
     * @internal param string $data composed json output
869
     *
870
     * @return string
871
     */
872
    public function _post_index_json($responseData)
873
    {
874
        if ($this->restler->getProductionMode()) {
875
            $this->restler->cache->set($this->cacheName, $responseData);
876
        }
877
        return $responseData;
878
    }
879
880
    /**
881
     * @access hybrid
882
     * @return \stdClass
883
     */
884
    public function index()
885
    {
886
        if (!static::$accessControlFunction && Defaults::$accessControlFunction)
887
            static::$accessControlFunction = Defaults::$accessControlFunction;
888
        $version = $this->restler->getRequestedApiVersion();
889
        $allRoutes = Util::nestedValue(Routes::toArray(), "v$version");
890
        $r = $this->_resourceListing();
891
        $map = array();
892
        if (isset($allRoutes['*'])) {
893
            $this->_mapResources($allRoutes['*'], $map, $version);
894
            unset($allRoutes['*']);
895
        }
896
        $this->_mapResources($allRoutes, $map, $version);
897
        foreach ($map as $path => $description) {
898
            if (!Text::contains($path, '{')) {
899
                //add id
900
                $r->apis[] = array(
901
                    'path' => $path . $this->formatString,
902
                    'description' => $description
903
                );
904
            }
905
        }
906
        if (Defaults::$useUrlBasedVersioning && static::$listHigherVersions) {
907
            $nextVersion = $version + 1;
908
            if ($nextVersion <= $this->restler->getApiVersion()) {
909
                list($status, $data) = $this->_loadResource("/v$nextVersion/resources.json");
910
                if ($status == 200) {
911
                    $r->apis = array_merge($r->apis, $data->apis);
912
                    $r->apiVersion = $data->apiVersion;
913
                }
914
            }
915
        }
916
        return $r;
917
    }
918
919
    protected function _loadResource($url)
920
    {
921
        $ch = curl_init($this->restler->getBaseUrl() . $url
922
            . (empty($_GET) ? '' : '?' . http_build_query($_GET)));
923
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
924
        curl_setopt($ch, CURLOPT_TIMEOUT, 15);
925
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
926
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
927
            'Accept:application/json',
928
        ));
929
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);        
930
        $result = json_decode(curl_exec($ch));
931
        $http_status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
932
        return array($http_status, $result);
933
    }
934
935
    protected function _mapResources(array $allRoutes, array &$map, $version = 1)
936
    {
937
        foreach ($allRoutes as $fullPath => $routes) {
938
            $path = explode('/', $fullPath);
939
            $resource = isset($path[0]) ? $path[0] : '';
940
            if ($resource == 'resources' || Text::endsWith($resource, 'index'))
941
                continue;
942
            foreach ($routes as $httpMethod => $route) {
943
                if (in_array($httpMethod, static::$excludedHttpMethods)) {
944
                    continue;
945
                }
946
                if (!static::verifyAccess($route)) {
947
                    continue;
948
                }
949
950
                foreach (static::$excludedPaths as $exclude) {
951
                    if (empty($exclude)) {
952
                        if ($fullPath == $exclude)
953
                            continue 2;
954
                    } elseif (Text::beginsWith($fullPath, $exclude)) {
955
                        continue 2;
956
                    }
957
                }
958
959
                $res = $resource
960
                    ? ($version == 1 ? "/resources/$resource" : "/v$version/resources/$resource-v$version")
961
                    : ($version == 1 ? "/resources/root" : "/v$version/resources/root-v$version");
962
963
                if (empty($map[$res])) {
964
                    $map[$res] = isset(
965
                    $route['metadata']['classDescription'])
966
                        ? $route['metadata']['classDescription'] : '';
967
                }
968
            }
969
        }
970
    }
971
972
    /**
973
     * Maximum api version supported by the api class
974
     * @return int
975
     */
976
    public static function __getMaximumSupportedVersion()
977
    {
978
        return Scope::get('Restler')->getApiVersion();
979
    }
980
981
    /**
982
     * Verifies that the requesting user is allowed to view the docs for this API
983
     *
984
     * @param $route
985
     *
986
     * @return boolean True if the user should be able to view this API's docs
987
     */
988
    protected function verifyAccess($route)
989
    {
990
        if ($route['accessLevel'] < 2) {
991
            return true;
992
        }
993
        if (
994
            static::$hideProtected
995
            && !$this->_authenticated
996
            && $route['accessLevel'] > 1
997
        ) {
998
            return false;
999
        }
1000
        if (
1001
            $this->_authenticated
1002
            && static::$accessControlFunction
1003
            && (!call_user_func(
1004
                static::$accessControlFunction, $route['metadata']))
1005
        ) {
1006
            return false;
1007
        }
1008
        return true;
1009
    }
1010
}
1011