Completed
Push — master ( 0ff4f4...35f374 )
by Alex
38s
created

BaseService::getResponseContentType()   D

Complexity

Conditions 20
Paths 84

Size

Total Lines 140
Code Lines 86

Duplication

Lines 39
Ratio 27.86 %

Importance

Changes 0
Metric Value
dl 39
loc 140
rs 4.7294
c 0
b 0
f 0
cc 20
eloc 86
nc 84
nop 3

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
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 54 and the first side effect is on line 51.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace POData;
4
5
use POData\Common\MimeTypes;
6
use POData\Common\Version;
7
use POData\OperationContext\HTTPRequestMethod;
8
use POData\Common\ErrorHandler;
9
use POData\Common\Messages;
10
use POData\Common\ODataException;
11
use POData\Common\ODataConstants;
12
use POData\Common\NotImplementedException;
13
use POData\Common\InvalidOperationException;
14
use POData\Common\HttpStatus;
15
use POData\Providers\Metadata\Type\IType;
16
use POData\Providers\ProvidersWrapper;
17
use POData\Providers\Stream\StreamProviderWrapper;
18
use POData\Configuration\ServiceConfiguration;
19
use POData\UriProcessor\UriProcessor;
20
use POData\UriProcessor\RequestDescription;
21
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
22
use POData\OperationContext\ServiceHost;
23
use POData\Providers\Metadata\ResourceType;
24
use POData\Providers\Metadata\Type\Binary;
25
use POData\ObjectModel\ObjectModelSerializer;
26
use POData\Writers\Atom\AtomODataWriter;
27
use POData\Writers\Json\JsonLightMetadataLevel;
28
use POData\Writers\Json\JsonLightODataWriter;
29
use POData\Writers\Json\JsonODataV1Writer;
30
use POData\Writers\Json\JsonODataV2Writer;
31
use POData\Writers\ResponseWriter;
32
use POData\Providers\Query\IQueryProvider;
33
use POData\Providers\Metadata\IMetadataProvider;
34
use POData\OperationContext\IOperationContext;
35
use POData\Writers\ODataWriterRegistry;
36
37
/**
38
 * Class BaseService.
39
 *
40
 * The base class for all BaseService specific classes. This class implements
41
 * the following interfaces:
42
 *  (1) IRequestHandler
43
 *      Implementing this interface requires defining the function
44
 *      'handleRequest' that will be invoked by dispatcher
45
 *  (2) IService
46
 *      Force BaseService class to implement functions for custom
47
 *      data service providers
48
 */
49
// TODO: Look at ripping this out after phockito gets binned
50
if (class_exists('POData\BaseService')) {
51
    return;
52
}
53
54
abstract class BaseService implements IRequestHandler, IService
55
{
56
    /**
57
     * The wrapper over IQueryProvider and IMetadataProvider implementations.
58
     *
59
     * @var ProvidersWrapper
60
     */
61
    private $providersWrapper;
62
63
    /**
64
     * The wrapper over IStreamProvider implementation.
65
     *
66
     * @var StreamProviderWrapper
67
     */
68
    private $_streamProvider;
69
70
    /**
71
     * Hold reference to the ServiceHost instance created by dispatcher,
72
     * using this library can access headers and body of Http Request
73
     * dispatcher received and the Http Response Dispatcher is going to send.
74
     *
75
     * @var ServiceHost
76
     */
77
    private $_serviceHost;
78
79
    /**
80
     * To hold reference to ServiceConfiguration instance where the
81
     * service specific rules (page limit, resource set access rights
82
     * etc...) are defined.
83
     *
84
     * @var ServiceConfiguration
85
     */
86
    private $config;
87
88
    /**
89
     * Gets reference to ServiceConfiguration instance so that
90
     * service specific rules defined by the developer can be
91
     * accessed.
92
     *
93
     * @return ServiceConfiguration
94
     */
95
    public function getConfiguration()
96
    {
97
        return $this->config;
98
    }
99
100
    //TODO: shouldn't we hide this from the interface..if we need it at all.
101
    /**
102
     * Get the wrapper over developer's IQueryProvider and IMetadataProvider implementation.
103
     *
104
     * @return ProvidersWrapper
105
     */
106
    public function getProvidersWrapper()
107
    {
108
        return $this->providersWrapper;
109
    }
110
111
    /**
112
     * Gets reference to wrapper class instance over IDSSP implementation.
113
     *
114
     * @return StreamProviderWrapper
115
     */
116
    public function getStreamProviderWrapper()
117
    {
118
        return $this->_streamProvider;
119
    }
120
121
    /**
122
     * Get reference to the data service host instance.
123
     *
124
     * @return ServiceHost
125
     */
126
    public function getHost()
127
    {
128
        return $this->_serviceHost;
129
    }
130
131
    /**
132
     * Sets the data service host instance.
133
     *
134
     * @param ServiceHost $serviceHost The data service host instance
135
     */
136
    public function setHost(ServiceHost $serviceHost)
137
    {
138
        $this->_serviceHost = $serviceHost;
139
    }
140
141
    /**
142
     * To get reference to operation context where we have direct access to
143
     * headers and body of Http Request we have received and the Http Response
144
     * We are going to send.
145
     *
146
     * @return IOperationContext
147
     */
148
    public function getOperationContext()
149
    {
150
        return $this->_serviceHost->getOperationContext();
151
    }
152
153
    /**
154
     * Get reference to the wrapper over IStreamProvider or
155
     * IStreamProvider2 implementations.
156
     *
157
     * @return StreamProviderWrapper
158
     */
159
    public function getStreamProvider()
160
    {
161
        if (is_null($this->_streamProvider)) {
162
            $this->_streamProvider = new StreamProviderWrapper();
163
            $this->_streamProvider->setService($this);
164
        }
165
166
        return $this->_streamProvider;
167
    }
168
169
    /**
170
     * Top-level handler invoked by Dispatcher against any request to this
171
     * service. This method will hand over request processing task to other
172
     * functions which process the request, set required headers and Response
173
     * stream (if any in Atom/Json format) in
174
     * WebOperationContext::Current()::OutgoingWebResponseContext.
175
     * Once this function returns, dispatcher uses global WebOperationContext
176
     * to write out the request response to client.
177
     * This function will perform the following operations:
178
     * (1) Check whether the top level service class implements
179
     *     IServiceProvider which means the service is a custom service, in
180
     *     this case make sure the top level service class implements
181
     *     IMetaDataProvider and IQueryProvider.
182
     *     These are the minimal interfaces that a custom service to be
183
     *     implemented in order to expose its data as OData. Save reference to
184
     *     These interface implementations.
185
     *     NOTE: Here we will ensure only providers for IDSQP and IDSMP. The
186
     *     IDSSP will be ensured only when there is an GET request on MLE/Named
187
     *     stream.
188
     *
189
     * (2). Invoke 'Initialize' method of top level service for
190
     *      collecting the configuration rules set by the developer for this
191
     *      service.
192
     *
193
     * (3). Invoke the Uri processor to process the request URI. The uri
194
     *      processor will do the following:
195
     *      (a). Validate the request uri syntax using OData uri rules
196
     *      (b). Validate the request using metadata of this service
197
     *      (c). Parse the request uri and using, IQueryProvider
198
     *           implementation, fetches the resources pointed by the uri
199
     *           if required
200
     *      (d). Build a RequestDescription which encapsulate everything
201
     *           related to request uri (e.g. type of resource, result
202
     *           etc...)
203
     * (3). Invoke handleRequest2 for further processing
204
     */
205
    public function handleRequest()
206
    {
207
        try {
208
            $this->createProviders();
209
            $this->_serviceHost->validateQueryParameters();
210
            //$requestMethod = $this->getOperationContext()->incomingRequest()->getMethod();
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
211
            //if ($requestMethod != HTTPRequestMethod::GET()) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
212
                // Now supporting GET and trying to support PUT
213
                //throw ODataException::createNotImplementedError(Messages::onlyReadSupport($requestMethod));
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
214
            //}
215
216
            $uriProcessor = UriProcessor::process($this);
217
            $request = $uriProcessor->getRequest();
218
            $this->serializeResult($request, $uriProcessor);
219
        } catch (\Exception $exception) {
220
            ErrorHandler::handleException($exception, $this);
221
            // Return to dispatcher for writing serialized exception
222
            return;
223
        }
224
    }
225
226
    /**
227
     * @return IQueryProvider
228
     */
229
    abstract public function getQueryProvider();
230
231
    /**
232
     * @return IMetadataProvider
233
     */
234
    abstract public function getMetadataProvider();
235
236
    /**
237
     * @return \POData\Providers\Stream\IStreamProvider
238
     */
239
    abstract public function getStreamProviderX();
240
241
    /** @var ODataWriterRegistry */
242
    private $writerRegistry;
243
244
    /**
245
     * Returns the ODataWriterRegistry to use when writing the response to a service document or resource request.
246
     *
247
     * @return ODataWriterRegistry
248
     */
249
    public function getODataWriterRegistry()
250
    {
251
        return $this->writerRegistry;
252
    }
253
254
    /**
255
     * This method will query and validates for IMetadataProvider and IQueryProvider implementations, invokes
256
     * BaseService::Initialize to initialize service specific policies.
257
     *
258
     *
259
     * @throws ODataException
260
     */
261
    protected function createProviders()
262
    {
263
        $metadataProvider = $this->getMetadataProvider();
264
        if (is_null($metadataProvider)) {
265
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
266
        }
267
268
        if (!is_object($metadataProvider) || !$metadataProvider instanceof IMetadataProvider) {
269
            throw ODataException::createInternalServerError(Messages::invalidMetadataInstance());
270
        }
271
272
        $queryProvider = $this->getQueryProvider();
273
274
        if (is_null($queryProvider)) {
275
            throw ODataException::createInternalServerError(Messages::providersWrapperNull());
276
        }
277
278
        if (!is_object($queryProvider)) {
279
            throw ODataException::createInternalServerError(Messages::invalidQueryInstance());
280
        }
281
282
        if (!$queryProvider instanceof IQueryProvider) {
283
            throw ODataException::createInternalServerError(Messages::invalidQueryInstance());
284
        }
285
286
        $this->config = new ServiceConfiguration($metadataProvider);
287
        $this->providersWrapper = new ProvidersWrapper(
288
            $metadataProvider,
289
            $queryProvider,
290
            $this->config
291
        );
292
293
        $this->initialize($this->config);
294
295
        //TODO: this seems like a bad spot to do this
296
        $this->writerRegistry = new ODataWriterRegistry();
297
        $this->registerWriters();
298
    }
299
300
    //TODO: i don't want this to be public..but it's the only way to test it right now...
301
    public function registerWriters()
302
    {
303
        $registry = $this->getODataWriterRegistry();
304
        $serviceVersion = $this->getConfiguration()->getMaxDataServiceVersion();
305
        $serviceURI = $this->getHost()->getAbsoluteServiceUri()->getUrlAsString();
306
307
        //We always register the v1 stuff
308
        $registry->register(new JsonODataV1Writer());
309
        $registry->register(new AtomODataWriter($serviceURI));
310
311
        if ($serviceVersion->compare(Version::v2()) > -1) {
312
            $registry->register(new JsonODataV2Writer());
313
        }
314
315
        if ($serviceVersion->compare(Version::v3()) > -1) {
316
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::NONE(), $serviceURI));
317
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::MINIMAL(), $serviceURI));
318
            $registry->register(new JsonLightODataWriter(JsonLightMetadataLevel::FULL(), $serviceURI));
319
        }
320
    }
321
322
    /**
323
     * Serialize the requested resource.
324
     *
325
     * @param RequestDescription $request      The description of the request  submitted by the client
326
     * @param UriProcessor       $uriProcessor Reference to the uri processor
327
     */
328
    protected function serializeResult(RequestDescription $request, UriProcessor $uriProcessor)
329
    {
330
        $isETagHeaderAllowed = $request->isETagHeaderAllowed();
331
        if ($this->config->getValidateETagHeader() && !$isETagHeaderAllowed) {
332
            if (!is_null($this->_serviceHost->getRequestIfMatch())
333
                || !is_null($this->_serviceHost->getRequestIfNoneMatch())
334
            ) {
335
                throw ODataException::createBadRequestError(
336
                    Messages::eTagCannotBeSpecified($this->getHost()->getAbsoluteRequestUri()->getUrlAsString())
337
                );
338
            }
339
        }
340
341
        $responseContentType = self::getResponseContentType($request, $uriProcessor, $this);
342
343
        if (is_null($responseContentType) && $request->getTargetKind() != TargetKind::MEDIA_RESOURCE()) {
344
            //the responseContentType can ONLY be null if it's a stream (media resource) and that stream is storing null as the content type
345
            throw new ODataException(Messages::unsupportedMediaType(), 415);
346
        }
347
348
        $odataModelInstance = null;
349
        $hasResponseBody = true;
350
        // Execution required at this point if request target to any resource other than
351
352
        // (1) media resource - For Media resource 'getResponseContentType' already performed execution as it needs to know the mime type of the stream
353
        // (2) metadata - internal resource
354
        // (3) service directory - internal resource
355
        if ($request->needExecution()) {
356
            $uriProcessor->execute();
357
            $objectModelSerializer = new ObjectModelSerializer($this, $request);
358
            if (!$request->isSingleResult()) {
359
                // Code path for collection (feed or links)
360
                $entryObjects = $request->getTargetResult();
361
                self::assert(
362
                    !is_null($entryObjects) && is_array($entryObjects),
363
                    '!is_null($entryObjects) && is_array($entryObjects)'
364
                );
365
                // If related resource set is empty for an entry then we should
366
                // not throw error instead response must be empty feed or empty links
367
                if ($request->isLinkUri()) {
368
                    $odataModelInstance = $objectModelSerializer->writeUrlElements($entryObjects);
369
                    self::assert(
370
                        $odataModelInstance instanceof \POData\ObjectModel\ODataURLCollection,
371
                        '$odataModelInstance instanceof ODataURLCollection'
372
                    );
373
                } else {
374
                    $odataModelInstance = $objectModelSerializer->writeTopLevelElements($entryObjects);
375
                    self::assert(
376
                        $odataModelInstance instanceof \POData\ObjectModel\ODataFeed,
377
                        '$odataModelInstance instanceof ODataFeed'
378
                    );
379
                }
380
            } else {
381
                // Code path for entry, complex, bag, resource reference link,
382
                // primitive type or primitive value
383
                $result = $request->getTargetResult();
384
                $requestTargetKind = $request->getTargetKind();
385
                if ($request->isLinkUri()) {
386
                    // In the query 'Orders(1245)/$links/Customer', the targeted
387
                    // Customer might be null
388
                    if (is_null($result)) {
389
                        throw ODataException::createResourceNotFoundError(
390
                            $request->getIdentifier()
391
                        );
392
                    }
393
394
                    $odataModelInstance = $objectModelSerializer->writeUrlElement($result);
395
                } elseif ($requestTargetKind == TargetKind::RESOURCE()) {
396
                    if (!is_null($this->_serviceHost->getRequestIfMatch())
397
                        && !is_null($this->_serviceHost->getRequestIfNoneMatch())
398
                    ) {
399
                        throw ODataException::createBadRequestError(
400
                            Messages::bothIfMatchAndIfNoneMatchHeaderSpecified()
401
                        );
402
                    }
403
                    // handle entry resource
404
                    $needToSerializeResponse = true;
405
                    $targetResourceType = $request->getTargetResourceType();
406
                    $eTag = $this->compareETag(
407
                        $result,
408
                        $targetResourceType,
0 ignored issues
show
Bug introduced by
It seems like $targetResourceType defined by $request->getTargetResourceType() on line 405 can be null; however, POData\BaseService::compareETag() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
409
                        $needToSerializeResponse
410
                    );
411
412
                    if ($needToSerializeResponse) {
413
                        if (is_null($result)) {
414
                            // In the query 'Orders(1245)/Customer', the targeted
415
                            // Customer might be null
416
                            // set status code to 204 => 'No Content'
417
                            $this->_serviceHost->setResponseStatusCode(HttpStatus::CODE_NOCONTENT);
418
                            $hasResponseBody = false;
419
                        } else {
420
                            $odataModelInstance = $objectModelSerializer->writeTopLevelElement($result);
421
                        }
422
                    } else {
423
                        // Resource is not modified so set status code
424
                        // to 304 => 'Not Modified'
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
425
                        $this->_serviceHost->setResponseStatusCode(HttpStatus::CODE_NOT_MODIFIED);
426
                        $hasResponseBody = false;
427
                    }
428
429
                    // if resource has eTagProperty then eTag header needs to written
430
                    if (!is_null($eTag)) {
431
                        $this->_serviceHost->setResponseETag($eTag);
432
                    }
433
                } elseif ($requestTargetKind == TargetKind::COMPLEX_OBJECT()) {
434
                    $odataModelInstance = $objectModelSerializer->writeTopLevelComplexObject(
435
                        $result,
436
                        $request->getProjectedProperty()->getName(),
437
                        $request->getTargetResourceType()
0 ignored issues
show
Bug introduced by
$request->getTargetResourceType() cannot be passed to writetoplevelcomplexobject() as the parameter $resourceType expects a reference.
Loading history...
Bug introduced by
It seems like $request->getTargetResourceType() can be null; however, writeTopLevelComplexObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
438
                    );
439
                } elseif ($requestTargetKind == TargetKind::BAG()) {
440
                    $odataModelInstance = $objectModelSerializer->writeTopLevelBagObject(
441
                        $result,
442
                        $request->getProjectedProperty()->getName(),
443
                        $request->getTargetResourceType(),
0 ignored issues
show
Bug introduced by
$request->getTargetResourceType() cannot be passed to writetoplevelbagobject() as the parameter $resourceType expects a reference.
Loading history...
Bug introduced by
It seems like $request->getTargetResourceType() can be null; however, writeTopLevelBagObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
444
                        $odataModelInstance
0 ignored issues
show
Unused Code introduced by
The call to ObjectModelSerializer::writeTopLevelBagObject() has too many arguments starting with $odataModelInstance.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
445
                    );
446
                } elseif ($requestTargetKind == TargetKind::PRIMITIVE()) {
447
                    $odataModelInstance = $objectModelSerializer->writeTopLevelPrimitive(
448
                        $result,
449
                        $request->getProjectedProperty(),
0 ignored issues
show
Bug introduced by
$request->getProjectedProperty() cannot be passed to writetoplevelprimitive() as the parameter $resourceProperty expects a reference.
Loading history...
Bug introduced by
It seems like $request->getProjectedProperty() can be null; however, writeTopLevelPrimitive() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
450
                        $odataModelInstance
0 ignored issues
show
Unused Code introduced by
The call to ObjectModelSerializer::writeTopLevelPrimitive() has too many arguments starting with $odataModelInstance.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
451
                    );
452
                } elseif ($requestTargetKind == TargetKind::PRIMITIVE_VALUE()) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
453
                    // Code path for primitive value (Since its primitve no need for
454
                    // object model serialization)
455
                    // Customers('ANU')/CompanyName/$value => string
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
456
                    // Employees(1)/Photo/$value => binary stream
457
                    // Customers/$count => string
458
                } else {
459
                    self::assert(false, 'Unexpected resource target kind');
460
                }
461
            }
462
        }
463
464
        //Note: Response content type can be null for named stream
465
        if ($hasResponseBody && !is_null($responseContentType)) {
466
            if ($request->getTargetKind() != TargetKind::MEDIA_RESOURCE() && $responseContentType != MimeTypes::MIME_APPLICATION_OCTETSTREAM) {
467
                //append charset for everything except:
468
                //stream resources as they have their own content type
469
                //binary properties (they content type will be App Octet for those...is this a good way? we could also decide based upon the projected property
470
471
                $responseContentType .= ';charset=utf-8';
472
            }
473
        }
474
475
        if ($hasResponseBody) {
476
            ResponseWriter::write(
477
                $this,
478
                $request,
479
                $odataModelInstance,
480
                $responseContentType
481
            );
482
        }
483
    }
484
485
    /**
486
     * Gets the response format for the requested resource.
487
     *
488
     * @param RequestDescription $request      The request submitted by client and it's execution result
489
     * @param UriProcessor       $uriProcessor The reference to the UriProcessor
490
     * @param IService           $service      Reference to the service implementation instance
491
     *
492
     * @return string the response content-type, a null value means the requested resource
493
     *                is named stream and IDSSP2::getStreamContentType returned null
494
     *
495
     * @throws ODataException, HttpHeaderFailure
496
     */
497
    public static function getResponseContentType(
498
        RequestDescription $request,
499
        UriProcessor $uriProcessor,
500
        IService $service
501
    ) {
502
503
        // The Accept request-header field specifies media types which are acceptable for the response
504
505
        $host = $service->getHost();
506
        $requestAcceptText = $host->getRequestAccept();
507
        $requestVersion = $request->getResponseVersion();
508
509
        //if the $format header is present it overrides the accepts header
510
        $format = $host->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_FORMAT);
511
        if (!is_null($format)) {
512
            //There's a strange edge case..if application/json is supplied and it's V3
513
            if ($format == MimeTypes::MIME_APPLICATION_JSON && $requestVersion == Version::v3()) {
514
                //then it's actual minimalmetadata
515
                //TODO: should this be done with the header text too?
516
                $format = MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META;
517
            }
518
519
            $requestAcceptText = ServiceHost::translateFormatToMime($requestVersion, $format);
520
        }
521
522
        //The response format can be dictated by the target resource kind. IE a $value will be different then expected
523
        //getTargetKind doesn't deal with link resources directly and this can change things
524
        $targetKind = $request->isLinkUri() ? TargetKind::LINK() : $request->getTargetKind();
525
526
        switch ($targetKind) {
527
            case TargetKind::METADATA():
528
                return HttpProcessUtility::selectMimeType(
529
                    $requestAcceptText,
530
                    array(MimeTypes::MIME_APPLICATION_XML)
531
                );
532
533 View Code Duplication
            case TargetKind::SERVICE_DIRECTORY():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
534
                return HttpProcessUtility::selectMimeType(
535
                    $requestAcceptText,
536
                    array(
537
                        MimeTypes::MIME_APPLICATION_XML,
538
                        MimeTypes::MIME_APPLICATION_ATOMSERVICE,
539
                        MimeTypes::MIME_APPLICATION_JSON,
540
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
541
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
542
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
543
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
544
545
                    )
546
                );
547
548
            case TargetKind::PRIMITIVE_VALUE():
549
                $supportedResponseMimeTypes = array(MimeTypes::MIME_TEXTPLAIN);
550
551
                if ($request->getIdentifier() != '$count') {
552
                    $projectedProperty = $request->getProjectedProperty();
553
                    self::assert(
554
                        !is_null($projectedProperty),
555
                        '!is_null($projectedProperty)'
556
                    );
557
                    $type = $projectedProperty->getInstanceType();
558
                    self::assert(
559
                        !is_null($type) && $type instanceof IType,
560
                        '!is_null($type) && $type instanceof IType'
561
                    );
562
                    if ($type instanceof Binary) {
563
                        $supportedResponseMimeTypes = array(MimeTypes::MIME_APPLICATION_OCTETSTREAM);
564
                    }
565
                }
566
567
                return HttpProcessUtility::selectMimeType(
568
                    $requestAcceptText,
569
                    $supportedResponseMimeTypes
570
                );
571
572
            case TargetKind::PRIMITIVE():
573
            case TargetKind::COMPLEX_OBJECT():
574
            case TargetKind::BAG():
575 View Code Duplication
            case TargetKind::LINK():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
576
                return HttpProcessUtility::selectMimeType(
577
                    $requestAcceptText,
578
                    array(
579
                        MimeTypes::MIME_APPLICATION_XML,
580
                        MimeTypes::MIME_TEXTXML,
581
                        MimeTypes::MIME_APPLICATION_JSON,
582
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
583
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
584
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
585
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
586
                    )
587
                );
588
589 View Code Duplication
            case TargetKind::RESOURCE():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
                return HttpProcessUtility::selectMimeType(
591
                    $requestAcceptText,
592
                    array(
593
                        MimeTypes::MIME_APPLICATION_ATOM,
594
                        MimeTypes::MIME_APPLICATION_JSON,
595
                        MimeTypes::MIME_APPLICATION_JSON_FULL_META,
596
                        MimeTypes::MIME_APPLICATION_JSON_NO_META,
597
                        MimeTypes::MIME_APPLICATION_JSON_MINIMAL_META,
598
                        MimeTypes::MIME_APPLICATION_JSON_VERBOSE,
599
                    )
600
                );
601
602
            case TargetKind::MEDIA_RESOURCE():
603
                if (!$request->isNamedStream() && !$request->getTargetResourceType()->isMediaLinkEntry()) {
604
                    throw ODataException::createBadRequestError(
605
                        Messages::badRequestInvalidUriForMediaResource(
606
                            $host->getAbsoluteRequestUri()->getUrlAsString()
607
                        )
608
                    );
609
                }
610
611
                $uriProcessor->execute();
612
                $request->setExecuted();
613
                // DSSW::getStreamContentType can throw error in 2 cases
614
                // 1. If the required stream implementation not found
615
                // 2. If IDSSP::getStreamContentType returns NULL for MLE
616
                $responseContentType = $service->getStreamProviderWrapper()
617
                    ->getStreamContentType(
618
                        $request->getTargetResult(),
619
                        $request->getResourceStreamInfo()
0 ignored issues
show
Bug introduced by
It seems like $request->getResourceStreamInfo() can be null; however, getStreamContentType() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
620
                    );
621
622
                // Note StreamWrapper::getStreamContentType can return NULL if the requested named stream has not
623
                // yet been uploaded. But for an MLE if IDSSP::getStreamContentType returns NULL then StreamWrapper will throw error
624
                if (!is_null($responseContentType)) {
625
                    $responseContentType = HttpProcessUtility::selectMimeType(
626
                        $requestAcceptText,
627
                        array($responseContentType)
628
                    );
629
                }
630
631
                return $responseContentType;
632
        }
633
634
        //If we got here, we just don't know what it is...
635
        throw new ODataException(Messages::unsupportedMediaType(), 415);
636
    }
637
638
    /**
639
     * For the given entry object compare it's eTag (if it has eTag properties)
640
     * with current eTag request headers (if it present).
641
     *
642
     * @param mixed        &$entryObject             entity resource for which etag
643
     *                                               needs to be checked
644
     * @param ResourceType &$resourceType            Resource type of the entry
645
     *                                               object
646
     * @param bool         &$needToSerializeResponse On return, this will contain
647
     *                                               True if response needs to be
648
     *                                               serialized, False otherwise
649
     * @param bool         $needToSerializeResponse
650
     *
651
     * @return string|null The ETag for the entry object if it has eTag properties
652
     *                     NULL otherwise
653
     */
654
    protected function compareETag(
655
        &$entryObject,
656
        ResourceType & $resourceType,
657
        &$needToSerializeResponse
658
    ) {
659
        $needToSerializeResponse = true;
660
        $eTag = null;
661
        $ifMatch = $this->_serviceHost->getRequestIfMatch();
662
        $ifNoneMatch = $this->_serviceHost->getRequestIfNoneMatch();
663
        if (is_null($entryObject)) {
664
            if (!is_null($ifMatch)) {
665
                throw ODataException::createPreConditionFailedError(
666
                    Messages::eTagNotAllowedForNonExistingResource()
667
                );
668
            }
669
670
            return null;
671
        }
672
673
        if ($this->config->getValidateETagHeader() && !$resourceType->hasETagProperties()) {
674
            if (!is_null($ifMatch) || !is_null($ifNoneMatch)) {
675
                // No eTag properties but request has eTag headers, bad request
676
                throw ODataException::createBadRequestError(
677
                    Messages::noETagPropertiesForType()
678
                );
679
            }
680
681
            // We need write the response but no eTag header
682
            return null;
683
        }
684
685
        if (!$this->config->getValidateETagHeader()) {
686
            // Configuration says do not validate ETag so we will not write ETag header in the
687
            // response even though the requested resource support it
688
            return null;
689
        }
690
691
        if (is_null($ifMatch) && is_null($ifNoneMatch)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
692
            // No request eTag header, we need to write the response
693
            // and eTag header
694
        } elseif (strcmp($ifMatch, '*') == 0) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
695
            // If-Match:* => we need to write the response and eTag header
696
        } elseif (strcmp($ifNoneMatch, '*') == 0) {
697
            // if-None-Match:* => Do not write the response (304 not modified),
698
            // but write eTag header
699
            $needToSerializeResponse = false;
700
        } else {
701
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
702
            // Note: The following code for attaching the prefix W\"
703
            // and the suffix " can be done in getETagForEntry function
704
            // but that is causing an issue in Linux env where the
705
            // firefix browser is unable to parse the ETag in this case.
706
            // Need to follow up PHP core devs for this.
707
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
708
            if (!is_null($ifMatch)) {
709
                if (strcmp($eTag, $ifMatch) != 0) {
710
                    // Requested If-Match value does not match with current
711
                    // eTag Value then pre-condition error
712
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
713
                    throw ODataException::createPreConditionFailedError(
714
                        Messages::eTagValueDoesNotMatch()
715
                    );
716
                }
717
            } elseif (strcmp($eTag, $ifNoneMatch) == 0) {
718
                //304 not modified, but in write eTag header
719
                $needToSerializeResponse = false;
720
            }
721
        }
722
723
        if (is_null($eTag)) {
724
            $eTag = $this->getETagForEntry($entryObject, $resourceType);
725
            // Note: The following code for attaching the prefix W\"
726
            // and the suffix " can be done in getETagForEntry function
727
            // but that is causing an issue in Linux env where the
728
            // firefix browser is unable to parse the ETag in this case.
729
            // Need to follow up PHP core devs for this.
730
            $eTag = ODataConstants::HTTP_WEAK_ETAG_PREFIX . $eTag . '"';
731
        }
732
733
        return $eTag;
734
    }
735
736
    /**
737
     * Returns the etag for the given resource.
738
     * Note: This function will not add W\" prefix and " suffix, its callers
739
     * repsonsability.
740
     *
741
     * @param mixed        &$entryObject  Resource for which etag value needs to
742
     *                                    be returned
743
     * @param ResourceType &$resourceType Resource type of the $entryObject
744
     *
745
     * @return string|null ETag value for the given resource (with values encoded
746
     *                     for use in a URI) there are etag properties, NULL if
747
     *                     there is no etag property
748
     */
749
    protected function getETagForEntry(&$entryObject, ResourceType & $resourceType)
750
    {
751
        $eTag = null;
752
        $comma = null;
753
        foreach ($resourceType->getETagProperties() as $eTagProperty) {
754
            $type = $eTagProperty->getInstanceType();
755
            self::assert(
756
                !is_null($type) && $type instanceof IType,
757
                '!is_null($type) && $type instanceof IType'
758
            );
759
760
            $value = null;
761
            try {
762
                //TODO #88...also this seems like dupe work
763
                $reflectionProperty = new \ReflectionProperty($entryObject, $eTagProperty->getName());
764
                $value = $reflectionProperty->getValue($entryObject);
765
            } catch (\ReflectionException $reflectionException) {
766
                throw ODataException::createInternalServerError(
767
                    Messages::failedToAccessProperty($eTagProperty->getName(), $resourceType->getName())
768
                );
769
            }
770
771 View Code Duplication
            if (is_null($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
772
                $eTag = $eTag . $comma . 'null';
773
            } else {
774
                $eTag = $eTag . $comma . $type->convertToOData($value);
0 ignored issues
show
Bug introduced by
The method convertToOData does only exist in POData\Providers\Metadata\Type\IType, but not in ReflectionClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
775
            }
776
777
            $comma = ',';
778
        }
779
780 View Code Duplication
        if (!is_null($eTag)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
781
            // If eTag is made up of datetime or string properties then the above
782
            // IType::convertToOData will perform utf8 and url encode. But we don't
783
            // want this for eTag value.
784
            $eTag = urldecode(utf8_decode($eTag));
785
786
            return rtrim($eTag, ',');
787
        }
788
789
        return null;
790
    }
791
792
    /**
793
     * This function will perform the following operations:
794
     * (1) Invoke delegateRequestProcessing method to process the request based
795
     *     on request method (GET, PUT/MERGE, POST, DELETE)
796
     * (3) If the result of processing of request needs to be serialized as HTTP
797
     *     response body (e.g. GET request result in single resource or resource
798
     *     collection, successful POST operation for an entity need inserted
799
     *     entity to be serialized back etc..), Serialize the result by using
800
     *     'serializeReultForResponseBody' method
801
     *     Set the serialized result to
802
     *     WebOperationContext::Current()::OutgoingWebResponseContext::Stream.
803
     */
804
    protected function handleRequest2()
805
    {
806
    }
807
808
    /**
809
     * This method will perform the following operations:
810
     * (1) If request method is GET, then result is already there in the
811
     *     RequestDescription so simply return the RequestDescription
812
     * (2). If request method is for CDU
813
     *      (Create/Delete/Update - POST/DELETE/PUT-MERGE) hand
814
     *      over the responsibility to respective handlers. The handler
815
     *      methods are:
816
     *      (a) handlePOSTOperation() => POST
817
     *      (b) handlePUTOperation() => PUT/MERGE
818
     *      (c) handleDELETEOperation() => DELETE
819
     * (3). Check whether its required to write any result to the response
820
     *      body
821
     *      (a). Request method is GET
822
     *      (b). Request is a POST for adding NEW Entry
823
     *      (c). Request is a POST for adding Media Resource Stream
824
     *      (d). Request is a POST for adding a link
825
     *      (e). Request is a DELETE for deleting entry or relationship
826
     *      (f). Request is a PUT/MERGE for updating an entry
827
     *      (g). Request is a PUT for updating a link
828
     *     In case a, b and c we need to write the result to response body,
829
     *     for d, e, f and g no body content.
830
     *
831
     * @return RequestDescription|null Instance of RequestDescription with
832
     *                                 result to be write back Null if no result to write
833
     */
834
    protected function delegateRequestProcessing()
835
    {
836
    }
837
838
    /**
839
     * Serialize the result in the current request description using
840
     * appropriate odata writer (AtomODataWriter/JSONODataWriter).
841
     */
842
    protected function serializeReultForResponseBody()
843
    {
844
    }
845
846
    /**
847
     * Handle POST request.
848
     *
849
     *
850
     * @throws NotImplementedException
851
     */
852
    protected function handlePOSTOperation()
853
    {
854
    }
855
856
    /**
857
     * Handle PUT/MERGE request.
858
     *
859
     *
860
     * @throws NotImplementedException
861
     */
862
    protected function handlePUTOperation()
863
    {
864
    }
865
866
    /**
867
     * Handle DELETE request.
868
     *
869
     *
870
     * @throws NotImplementedException
871
     */
872
    protected function handleDELETEOperation()
873
    {
874
    }
875
876
    /**
877
     * Assert that the given condition is true.
878
     *
879
     * @param bool   $condition         The condtion to check
880
     * @param string $conditionAsString Message to show if assertion fails
881
     *
882
     * @throws InvalidOperationException
883
     */
884
    protected static function assert($condition, $conditionAsString)
885
    {
886
        if (!$condition) {
887
            throw new InvalidOperationException(
888
                "Unexpected state, expecting $conditionAsString"
889
            );
890
        }
891
    }
892
}
893