Completed
Push — master ( 5d76d8...23accf )
by
unknown
04:14
created

RestApiRequest::handle()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 30
ccs 0
cts 20
cp 0
rs 8.439
cc 5
eloc 19
nc 5
nop 0
crap 30
1
<?php
2
namespace Aoe\Restler\System\RestApi;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2015 AOE GmbH <[email protected]>
8
 *
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use Aoe\Restler\System\TYPO3\Cache as Typo3Cache;
29
use Luracast\Restler\Restler;
30
use Luracast\Restler\RestException;
31
use Luracast\Restler\Defaults;
32
use Luracast\Restler\Format\JsonFormat;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use Exception;
35
use stdClass;
36
37
/**
38
 * This class represents a single REST-API-request, which can be called from PHP.
39
 * For each REST-API request, we need a new object of this class, because restler stores some data in this object
40
 *
41
 * The logic/idea behind this class is:
42
 * 1. We override the data of $_GET, $_POST and $_SERVER and we override the REST-API-request-object (aka Restler-object)
43
 *    ...because this data/object 'defines' the REST-API-request, which we want to call
44
 * 2. We call/start restler to handle the REST-API-request
45
 * 3. We return the result of the 'called' REST-API-request - instead of sending the result to the client
46
 */
47
class RestApiRequest extends Restler
48
{
49
    /**
50
     * store data from $_GET in this property
51
     *
52
     * Attention:
53
     * This property must be static, because it can happen, that some REST-API-calls
54
     * will be called recursive, so we MUST store the 'really original' data
55
     *
56
     * @var array
57
     */
58
    private static $originalGetVars;
59
    /**
60
     * store data from $_POST in this property
61
     *
62
     * Attention:
63
     * This property must be static, because it can happen, that some REST-API-calls
64
     * will be called recursive, so we MUST store the 'really original' data
65
     *
66
     * @var array
67
     */
68
    private static $originalPostVars;
69
    /**
70
     * store data from $_SERVER in this property
71
     *
72
     * Attention:
73
     * This property must be static, because it can happen, that some REST-API-calls
74
     * will be called recursive, so we MUST store the 'really original' data
75
     *
76
     * @var array
77
     */
78
    private static $originalServerSettings;
79
80
    /**
81
     * @var array
82
     */
83
    private $restApiGetData;
84
    /**
85
     * @var array
86
     */
87
    private $restApiPostData;
88
    /**
89
     * This property defines the request-uri (without GET-params, e.g. '/api/products/320'), which should be called
90
     *
91
     * @var string
92
     */
93
    private $restApiRequestUri;
94
    /**
95
     * This property defines the request-method (e.g. 'GET', 'POST', 'PUT' or 'DELETE'), which should be used while calling the rest-api
96
     *
97
     * @var string
98
     */
99
    private $restApiRequestMethod;
100
    /**
101
     * @var RestApiRequestScope
102
     */
103
    private $restApiRequestScope;
104
    /**
105
     * @var Typo3Cache
106
     */
107
    private $typo3Cache;
108
109
110
111
    /***************************************************************************************************************************/
112
    /***************************************************************************************************************************/
113
    /* Block of methods, which does NOT override logic from parent-class *******************************************************/
114
    /***************************************************************************************************************************/
115
    /***************************************************************************************************************************/
116
    /**
117
     * @param string $requestMethod
118
     * @param string $requestUri
119
     * @param array|stdClass $getData
120
     * @param array|stdClass $postData
121
     * @return mixed can be a primitive or array or object
122
     * @throws RestException
123
     */
124 6
    public function executeRestApiRequest($requestMethod, $requestUri, $getData = null, $postData = null)
125
    {
126 6
        $this->restApiRequestMethod = $requestMethod;
127 6
        $this->restApiRequestUri = $requestUri;
128 6
        $this->restApiGetData = $this->convertDataToArray($getData);
129 6
        $this->restApiPostData = $this->convertDataToArray($postData);
130
131 6
        $this->storeOriginalRestApiRequest();
132 6
        $this->overrideOriginalRestApiRequest();
133
134
        try {
135 6
            $result = $this->handle();
136 2
            $this->restoreOriginalRestApiRequest();
137 2
            return $result;
138 4
        } catch (RestException $e) {
0 ignored issues
show
Bug introduced by
The class Luracast\Restler\RestException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
139 2
            $this->restoreOriginalRestApiRequest();
140 2
            throw $e;
141 2
        } catch (Exception $e) {
142 2
            $this->restoreOriginalRestApiRequest();
143 2
            throw new RestException(500, $e->getMessage(), array(), $e);
144
        }
145
    }
146
147
    /**
148
     * Return class, which can decode a JSON-string into a stdClass-object (instead of an array)
149
     *
150
     * @return RestApiJsonFormat
151
     */
152
    protected function getRestApiJsonFormat()
153
    {
154
        $objectManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
155
        return $objectManager->get('Aoe\\Restler\\System\\RestApi\\RestApiJsonFormat');
156
    }
157
158
    /**
159
     * @param array|stdClass $data
160
     * @return array
161
     * @throws RestException
162
     */
163 6
    private function convertDataToArray($data)
164
    {
165 6
        if ($data === null) {
166 3
            return array();
167
        }
168 3
        if (is_array($data)) {
169 3
            return $data;
170
        }
171
        if ($data instanceof stdClass) {
172
            return json_decode(json_encode($data), true); // convert stdClass to array
173
        }
174
        throw new RestException(500, 'data must be type of null, array or stdClass');
175
    }
176
177
    /**
178
     * @return string
179
     */
180
    private function handleRequestByTypo3Cache()
181
    {
182
        $cacheEntry = $this->typo3Cache->getCacheEntry($this->url, $_GET);
183
        $this->responseData = $cacheEntry['responseData'];
184
        $this->responseFormat = new $cacheEntry['responseFormatClass']();
185
186
        // send data to client
187
        if ($this->responseFormat instanceof JsonFormat) {
0 ignored issues
show
Bug introduced by
The class Luracast\Restler\Format\JsonFormat does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
188
            // return stdClass-object (instead of an array)
189
            return $this->getRestApiJsonFormat()->decode($this->responseData);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getRestApi...e($this->responseData); (stdClass) is incompatible with the return type documented by Aoe\Restler\System\RestA...ndleRequestByTypo3Cache of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
190
        }
191
        return $this->responseFormat->decode($this->responseData);
192
    }
193
194
    /**
195
     * Override (the stored) data of $_GET, $_POST and $_SERVER (which are used in several restler-PHP-classes) and the original
196
     * REST-API-Request-object, because this data/object 'defines' the REST-API-request, which we want to call
197
     *
198
     * @return void
199
     */
200 6
    private function overrideOriginalRestApiRequest()
201
    {
202 6
        $_GET = $this->restApiGetData;
203 6
        $_POST = $this->restApiPostData;
204 6
        $_SERVER['REQUEST_METHOD'] = $this->restApiRequestMethod;
205 6
        $_SERVER['REQUEST_URI'] = $this->restApiRequestUri;
206 6
        $this->restApiRequestScope->overrideOriginalRestApiRequest($this);
207 6
        $this->restApiRequestScope->removeRestApiAuthenticationObjects();
208
209
        /**
210
         * add all authentication-classes:
211
         *  - we must add all authentication-classes, because the authentication-classes are stored in this object
212
         *  - we don't must add all REST-API-classes, because the REST-API-classes are not stored in this object
213
         */
214 6
        $this->authClasses = $this->restApiRequestScope->getOriginalRestApiRequest()->_authClasses;
215 6
    }
216
217
    /**
218
     * Restore (the overridden) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
219
     * @return void
220
     */
221 6
    private function restoreOriginalRestApiRequest()
222
    {
223 6
        $_GET = self::$originalGetVars;
224 6
        $_POST = self::$originalPostVars;
225 6
        $_SERVER = self::$originalServerSettings;
226 6
        $this->restApiRequestScope->restoreOriginalRestApiRequest();
227 6
        $this->restApiRequestScope->restoreOriginalRestApiAuthenticationObjects();
228 6
    }
229
230
    /**
231
     * Store (the original) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
232
     * @return void
233
     */
234 6
    private function storeOriginalRestApiRequest()
235
    {
236 6
        if (false === isset(self::$originalServerSettings)) {
237 1
            self::$originalGetVars = $_GET;
238 1
            self::$originalPostVars = $_POST;
239 1
            self::$originalServerSettings = $_SERVER;
240 1
            $this->restApiRequestScope->storeOriginalRestApiRequest();
241 1
            $this->restApiRequestScope->storeOriginalRestApiAuthenticationObjects();
242 1
        }
243 6
    }
244
245
246
247
    /***************************************************************************************************************************/
248
    /***************************************************************************************************************************/
249
    /* Block of methods, which MUST be overriden from parent-class (otherwise this class can not work) *************************/
250
    /***************************************************************************************************************************/
251
    /***************************************************************************************************************************/
252
    /**
253
     * Override parent method...because we don't want to call it!
254
     * The original method would set some properties (e.g. set this object into static properties of global classes)
255
     *
256
     * @param RestApiRequestScope $restApiRequestScope
257
     * @param Typo3Cache $typo3Cache
258
     */
259 6
    public function __construct(RestApiRequestScope $restApiRequestScope, Typo3Cache $typo3Cache)
260
    {
261 6
        $this->restApiRequestScope = $restApiRequestScope;
262 6
        $this->typo3Cache = $typo3Cache;
263 6
    }
264
265
    /**
266
     * Override parent method...because we don't want to call it (the original method would cache the determined routes)!
267
     */
268 6
    public function __destruct()
269
    {
270 6
    }
271
272
    /**
273
     * Override parent method...because we don't want to call it (the original method would send some headers to the client)!
274
     */
275
    public function composeHeaders(RestException $e = null)
276
    {
277
    }
278
279
    /**
280
     * Override parent method...because we must return the request-data of THIS REST-API request!
281
     * The original method would return the request-data of the ORIGINAL called REST-API request
282
     *
283
     * @param boolean $includeQueryParameters
284
     * @return array
285
     */
286
    public function getRequestData($includeQueryParameters = true)
287
    {
288
        $requestData = array();
289
        if ($this->restApiRequestMethod == 'PUT' || $this->restApiRequestMethod == 'PATCH' || $this->restApiRequestMethod == 'POST') {
290
            $requestData = array_merge($this->restApiPostData, array(Defaults::$fullRequestDataName => $this->restApiPostData));
291
        }
292
293
        if ($includeQueryParameters === true) {
294
            return $requestData + $this->restApiGetData;
295
        }
296
        return $requestData;
297
    }
298
299
    /**
300
     * Override parent method...because we must return the data of the REST-API request and we need NO exception-handling!
301
     *
302
     * @return mixed can be a primitive or array or object
303
     */
304
    public function handle()
305
    {
306
        // get information about the REST-request
307
        $this->get();
308
309
        if ($this->requestMethod === 'GET' && $this->typo3Cache->hasCacheEntry($this->url, $_GET)) {
310
            return $this->handleRequestByTypo3Cache();
311
        }
312
313
        // if no cache exist: restler should handle the request
314
        if (Defaults::$useVendorMIMEVersioning) {
315
            $this->responseFormat = $this->negotiateResponseFormat();
316
        }
317
        $this->route();
318
        $this->negotiate();
319
        $this->preAuthFilter();
320
        $this->authenticate();
321
        $this->postAuthFilter();
322
        $this->validate();
323
        $this->preCall();
324
        $this->call();
325
        $this->compose();
326
        $this->postCall();
327
328
        if ($this->responseFormat instanceof JsonFormat) {
0 ignored issues
show
Bug introduced by
The class Luracast\Restler\Format\JsonFormat does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
329
            // return stdClass-object (instead of an array)
330
            return $this->getRestApiJsonFormat()->decode($this->responseData);
331
        }
332
        return $this->responseFormat->decode($this->responseData);
333
    }
334
335
    /**
336
     * override postCall so that we can cache response via TYPO3-caching-framework - if it's possible
337
     */
338
    protected function postCall()
339
    {
340
        parent::postCall();
341
342
        if ($this->typo3Cache->isResponseCacheableByTypo3Cache($this->requestMethod, $this->apiMethodInfo->metadata)) {
343
            $this->typo3Cache->cacheResponseByTypo3Cache(
344
                $this->url,
345
                $_GET,
346
                $this->apiMethodInfo->metadata,
347
                $this->responseData,
348
                get_class($this->responseFormat),
349
                array() // we don't know which headers would be 'normally' send - because this is an internal REST-API-call
350
            );
351
        }
352
    }
353
}
354