Completed
Push — TYPO3V8 ( fb3bf5...e38a52 )
by Felix
13:20 queued 12s
created

RestApiRequest   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 322
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 48.18%

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 4
dl 0
loc 322
ccs 53
cts 110
cp 0.4818
rs 9.68
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A storeOriginalRestApiRequest() 0 10 2
A getRequestData() 0 12 5
A handle() 0 30 5
A executeRestApiRequest() 0 22 3
A getRestApiJsonFormat() 0 5 1
A convertDataToArray() 0 13 4
A handleRequestByTypo3Cache() 0 13 2
B overrideOriginalRestApiRequest() 0 31 6
A restoreOriginalRestApiRequest() 0 8 1
A __construct() 0 5 1
A __destruct() 0 3 1
A composeHeaders() 0 3 1
A postCall() 0 15 2
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 TYPO3\CMS\Extbase\Object\ObjectManager;
35
use Exception;
36
use stdClass;
37
38
/**
39
 * This class represents a single REST-API-request, which can be called from PHP.
40
 * For each REST-API request, we need a new object of this class, because restler stores some data in this object
41
 *
42
 * The logic/idea behind this class is:
43
 * 1. We override the data of $_GET, $_POST and $_SERVER and we override the REST-API-request-object (aka Restler-object)
44
 *    ...because this data/object 'defines' the REST-API-request, which we want to call
45
 * 2. We call/start restler to handle the REST-API-request
46
 * 3. We return the result of the 'called' REST-API-request - instead of sending the result to the client
47
 */
48
class RestApiRequest extends Restler
49
{
50
    /**
51
     * store data from $_GET in this property
52
     *
53
     * Attention:
54
     * This property must be static, because it can happen, that some REST-API-calls
55
     * will be called recursive, so we MUST store the 'really original' data
56
     *
57
     * @var array
58
     */
59
    private static $originalGetVars;
60
    /**
61
     * store data from $_POST in this property
62
     *
63
     * Attention:
64
     * This property must be static, because it can happen, that some REST-API-calls
65
     * will be called recursive, so we MUST store the 'really original' data
66
     *
67
     * @var array
68
     */
69
    private static $originalPostVars;
70
    /**
71
     * store data from $_SERVER in this property
72
     *
73
     * Attention:
74
     * This property must be static, because it can happen, that some REST-API-calls
75
     * will be called recursive, so we MUST store the 'really original' data
76
     *
77
     * @var array
78
     */
79
    private static $originalServerSettings;
80
81
    /**
82
     * @var array
83
     */
84
    private $restApiGetData;
85
    /**
86
     * @var array
87
     */
88
    private $restApiPostData;
89
    /**
90
     * This property defines the request-uri (without GET-params, e.g. '/api/products/320'), which should be called
91
     *
92
     * @var string
93
     */
94
    private $restApiRequestUri;
95
    /**
96
     * This property defines the request-method (e.g. 'GET', 'POST', 'PUT' or 'DELETE'), which should be used while calling the rest-api
97
     *
98
     * @var string
99
     */
100
    private $restApiRequestMethod;
101
    /**
102
     * @var RestApiRequestScope
103
     */
104
    private $restApiRequestScope;
105
    /**
106
     * @var Typo3Cache
107
     */
108
    private $typo3Cache;
109
110
111
112
    /***************************************************************************************************************************/
113
    /***************************************************************************************************************************/
114
    /* Block of methods, which does NOT override logic from parent-class *******************************************************/
115
    /***************************************************************************************************************************/
116
    /***************************************************************************************************************************/
117
    /**
118
     * @param string $requestMethod
119
     * @param string $requestUri
120
     * @param array|stdClass $getData
121
     * @param array|stdClass $postData
122
     * @return mixed can be a primitive or array or object
123
     * @throws RestException
124
     */
125 6
    public function executeRestApiRequest($requestMethod, $requestUri, $getData = null, $postData = null)
126
    {
127 6
        $this->restApiRequestMethod = $requestMethod;
128 6
        $this->restApiRequestUri = $requestUri;
129 6
        $this->restApiGetData = $this->convertDataToArray($getData);
130 6
        $this->restApiPostData = $this->convertDataToArray($postData);
131
132 6
        $this->storeOriginalRestApiRequest();
133 6
        $this->overrideOriginalRestApiRequest();
134
135
        try {
136 6
            $result = $this->handle();
137 2
            $this->restoreOriginalRestApiRequest();
138 2
            return $result;
139 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...
140 2
            $this->restoreOriginalRestApiRequest();
141 2
            throw $e;
142 2
        } catch (Exception $e) {
143 2
            $this->restoreOriginalRestApiRequest();
144 2
            throw new RestException(500, $e->getMessage(), array(), $e);
145
        }
146
    }
147
148
    /**
149
     * Return class, which can decode a JSON-string into a stdClass-object (instead of an array)
150
     *
151
     * @return RestApiJsonFormat
152
     */
153
    protected function getRestApiJsonFormat()
154
    {
155
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
156
        return $objectManager->get(RestApiJsonFormat::class);
157
    }
158
159
    /**
160
     * @param array|stdClass $data
161
     * @return array
162
     * @throws RestException
163
     */
164 6
    private function convertDataToArray($data)
165
    {
166 6
        if ($data === null) {
167 3
            return array();
168
        }
169 3
        if (is_array($data)) {
170 3
            return $data;
171
        }
172
        if ($data instanceof stdClass) {
173
            return json_decode(json_encode($data), true); // convert stdClass to array
174
        }
175
        throw new RestException(500, 'data must be type of null, array or stdClass');
176
    }
177
178
    /**
179
     * @return string
180
     */
181
    private function handleRequestByTypo3Cache()
182
    {
183
        $cacheEntry = $this->typo3Cache->getCacheEntry($this->url, $_GET);
184
        $this->responseData = $cacheEntry['responseData'];
185
        $this->responseFormat = new $cacheEntry['responseFormatClass']();
186
187
        // send data to client
188
        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...
189
            // return stdClass-object (instead of an array)
190
            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...
191
        }
192
        return $this->responseFormat->decode($this->responseData);
193
    }
194
195
    /**
196
     * Override (the stored) data of $_GET, $_POST and $_SERVER (which are used in several restler-PHP-classes) and the original
197
     * REST-API-Request-object, because this data/object 'defines' the REST-API-request, which we want to call
198
     *
199
     * @return void
200
     */
201 6
    private function overrideOriginalRestApiRequest()
202
    {
203 6
        $_GET = $this->restApiGetData;
204 6
        $_POST = $this->restApiPostData;
205 6
        $_SERVER['REQUEST_METHOD'] = $this->restApiRequestMethod;
206 6
        $_SERVER['REQUEST_URI'] = $this->restApiRequestUri;
207
208 6
        if ($this->restApiRequestMethod !== 'POST' && $this->restApiRequestMethod !== 'PUT') {
209
            // content-type and content-length should only exist when request-method is
210
            // POST or PUT (because in this case there can be the request-data in the body)
211 6
            if (array_key_exists('CONTENT_TYPE', $_SERVER)) {
212
                unset($_SERVER['CONTENT_TYPE']);
213
            }
214 6
            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
215
                unset($_SERVER['HTTP_CONTENT_TYPE']);
216
            }
217 6
            if (array_key_exists('CONTENT_LENGTH', $_SERVER)) {
218
                unset($_SERVER['CONTENT_LENGTH']);
219
            }
220
        }
221
222 6
        $this->restApiRequestScope->overrideOriginalRestApiRequest($this);
223 6
        $this->restApiRequestScope->removeRestApiAuthenticationObjects();
224
225
        /**
226
         * add all authentication-classes:
227
         *  - we must add all authentication-classes, because the authentication-classes are stored in this object
228
         *  - we don't must add all REST-API-classes, because the REST-API-classes are not stored in this object
229
         */
230 6
        $this->authClasses = $this->restApiRequestScope->getOriginalRestApiRequest()->_authClasses;
231 6
    }
232
233
    /**
234
     * Restore (the overridden) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
235
     * @return void
236
     */
237 6
    private function restoreOriginalRestApiRequest()
238
    {
239 6
        $_GET = self::$originalGetVars;
240 6
        $_POST = self::$originalPostVars;
241 6
        $_SERVER = self::$originalServerSettings;
242 6
        $this->restApiRequestScope->restoreOriginalRestApiRequest();
243 6
        $this->restApiRequestScope->restoreOriginalRestApiAuthenticationObjects();
244 6
    }
245
246
    /**
247
     * Store (the original) data of $_GET, $_POST and $_SERVER and the original REST-API-request-object
248
     * @return void
249
     */
250 6
    private function storeOriginalRestApiRequest()
251
    {
252 6
        if (false === isset(self::$originalServerSettings)) {
253 1
            self::$originalGetVars = $_GET;
254 1
            self::$originalPostVars = $_POST;
255 1
            self::$originalServerSettings = $_SERVER;
256 1
            $this->restApiRequestScope->storeOriginalRestApiRequest();
257 1
            $this->restApiRequestScope->storeOriginalRestApiAuthenticationObjects();
258
        }
259 6
    }
260
261
262
263
    /***************************************************************************************************************************/
264
    /***************************************************************************************************************************/
265
    /* Block of methods, which MUST be overriden from parent-class (otherwise this class can not work) *************************/
266
    /***************************************************************************************************************************/
267
    /***************************************************************************************************************************/
268
    /**
269
     * Override parent method...because we don't want to call it!
270
     * The original method would set some properties (e.g. set this object into static properties of global classes)
271
     *
272
     * @param RestApiRequestScope $restApiRequestScope
273
     * @param Typo3Cache $typo3Cache
274
     */
275 6
    public function __construct(RestApiRequestScope $restApiRequestScope, Typo3Cache $typo3Cache)
276
    {
277 6
        $this->restApiRequestScope = $restApiRequestScope;
278 6
        $this->typo3Cache = $typo3Cache;
279 6
    }
280
281
    /**
282
     * Override parent method...because we don't want to call it (the original method would cache the determined routes)!
283
     */
284
    public function __destruct()
285
    {
286
    }
287
288
    /**
289
     * Override parent method...because we don't want to call it (the original method would send some headers to the client)!
290
     */
291
    public function composeHeaders(RestException $e = null)
292
    {
293
    }
294
295
    /**
296
     * Override parent method...because we must return the request-data of THIS REST-API request!
297
     * The original method would return the request-data of the ORIGINAL called REST-API request
298
     *
299
     * @param boolean $includeQueryParameters
300
     * @return array
301
     */
302
    public function getRequestData($includeQueryParameters = true)
303
    {
304
        $requestData = array();
305
        if ($this->restApiRequestMethod == 'PUT' || $this->restApiRequestMethod == 'PATCH' || $this->restApiRequestMethod == 'POST') {
306
            $requestData = array_merge($this->restApiPostData, array(Defaults::$fullRequestDataName => $this->restApiPostData));
307
        }
308
309
        if ($includeQueryParameters === true) {
310
            return $requestData + $this->restApiGetData;
311
        }
312
        return $requestData;
313
    }
314
315
    /**
316
     * Override parent method...because we must return the data of the REST-API request and we need NO exception-handling!
317
     *
318
     * @return mixed can be a primitive or array or object
319
     */
320
    public function handle()
321
    {
322
        // get information about the REST-request
323
        $this->get();
324
325
        if ($this->requestMethod === 'GET' && $this->typo3Cache->hasCacheEntry($this->url, $_GET)) {
326
            return $this->handleRequestByTypo3Cache();
327
        }
328
329
        // if no cache exist: restler should handle the request
330
        if (Defaults::$useVendorMIMEVersioning) {
331
            $this->responseFormat = $this->negotiateResponseFormat();
332
        }
333
        $this->route();
334
        $this->negotiate();
335
        $this->preAuthFilter();
336
        $this->authenticate();
337
        $this->postAuthFilter();
338
        $this->validate();
339
        $this->preCall();
340
        $this->call();
341
        $this->compose();
342
        $this->postCall();
343
344
        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...
345
            // return stdClass-object (instead of an array)
346
            return $this->getRestApiJsonFormat()->decode($this->responseData);
347
        }
348
        return $this->responseFormat->decode($this->responseData);
349
    }
350
351
    /**
352
     * override postCall so that we can cache response via TYPO3-caching-framework - if it's possible
353
     */
354
    protected function postCall()
355
    {
356
        parent::postCall();
357
358
        if ($this->typo3Cache->isResponseCacheableByTypo3Cache($this->requestMethod, $this->apiMethodInfo->metadata)) {
359
            $this->typo3Cache->cacheResponseByTypo3Cache(
360
                $this->url,
361
                $_GET,
362
                $this->apiMethodInfo->metadata,
363
                $this->responseData,
364
                get_class($this->responseFormat),
365
                array() // we don't know which headers would be 'normally' send - because this is an internal REST-API-call
366
            );
367
        }
368
    }
369
}
370