StreamProviderWrapper::_loadStreamProvider()   D
last analyzed

Complexity

Conditions 10
Paths 8

Size

Total Lines 25
Code Lines 15

Duplication

Lines 10
Ratio 40 %

Importance

Changes 0
Metric Value
dl 10
loc 25
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 15
nc 8
nop 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace POData\Providers\Stream;
4
5
use POData\IService;
6
use POData\Providers\Metadata\ResourceStreamInfo;
7
use POData\OperationContext\ServiceHost;
8
use POData\Common\Version;
9
use POData\Common\ODataException;
10
use POData\Common\ODataConstants;
11
use POData\Common\Messages;
12
use POData\Common\InvalidOperationException;
13
use POData\Providers\Stream\IStreamProvider;
14
use POData\Providers\Stream\IStreamProvider2;
15
16
/**
17
 * Class StreamProviderWrapper Wrapper over IDSSP and IDSSP2 implementations.
18
 * @package POData\Providers\Stream
19
 */
20
class StreamProviderWrapper
21
{
22
    /**
23
     * Holds reference to the data service instance.
24
     * 
25
     * @var IService
26
     */
27
    private $_service;
28
29
    /**
30
     * Holds reference to the implementation of IStreamProvider or IStreamProvider2.
31
     *
32
     * 
33
     * @var IStreamProvider|IStreamProvider2
34
     */
35
    private $_streamProvider;
36
37
    /**
38
     * Used to check whether interface implementation modified response content type header or not.
39
     *
40
     *
41
     * @var string|null
42
     */
43
    private $_responseContentType;
44
45
    /**
46
     * Used to check whether interface implementation modified ETag header or not.
47
     *
48
     * @var string|null
49
     */
50
    private $_responseETag;
51
52
53
    /**
54
     * To set reference to the data service instance.
55
     * 
56
     * @param IService $service The data service instance.
57
     * 
58
     * @return void
59
     */
60
    public function setService(IService $service)
61
    {
62
        $this->_service = $service;
63
    }
64
65
    /**
66
     * To get stream associated with the given media resource.
67
     * 
68
     * @param object             $entity             The media resource.
69
     * @param ResourceStreamInfo $resourceStreamInfo This will be null if media
70
     *                                               resource is MLE, if media 
71
     *                                               resource is named
72
     *                                               stream then will be the 
73
     *                                               ResourceStreamInfo instance 
74
     *                                               holding the details of 
75
     *                                               named stream.
76
     * 
77
     * @return string|null
78
     */
79
    public function getReadStream($entity, $resourceStreamInfo)
80
    {
81
        $requestETag = null;
82
        $checkETagForEquality = null;
83
        $this->_getETagFromHeaders($requestETag, $checkETagForEquality);
84
        $stream = null;
85
        try {
86
            $this->_saveContentTypeAndETag();
87
            if (is_null($resourceStreamInfo)) {
88
                $this->_loadAndValidateStreamProvider();
89
                $stream = $this->_streamProvider->getReadStream(
90
                    $entity,
91
                    $requestETag,
92
                    $checkETagForEquality,
93
                    $this->_service->getOperationContext()
94
                );
95
            } else {
96
                $this->_loadAndValidateStreamProvider2();
97
                $stream = $this->_streamProvider->getReadStream2(
0 ignored issues
show
Bug introduced by
The method getReadStream2 does only exist in POData\Providers\Stream\IStreamProvider2, but not in POData\Providers\Stream\IStreamProvider.

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...
98
                    $entity,
99
                    $resourceStreamInfo,
100
                    $requestETag,
101
                    $checkETagForEquality,
102
                    $this->_service->getOperationContext()
103
                );
104
            }
105
106
            $this->_verifyContentTypeOrETagModified('IDSSP::getReadStream');
107
        } catch(ODataException $ex) {
108
            //Check for status code 304 (stream Not Modified)
109
            if ($ex->getStatusCode() == 304) {
110
                $eTag = $this->getStreamETag($entity, $resourceStreamInfo);
0 ignored issues
show
Bug introduced by
It seems like $resourceStreamInfo can be null; however, getStreamETag() 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...
111
                if (!is_null($eTag)) {
112
                    $this->_service->getHost()->setResponseETag($eTag);
113
                }
114
            }
115
            throw $ex;
116
        }
117
118
        if ($resourceStreamInfo == null) {
119
            // For default streams, we always expect getReadStream()
120
            // to return a non-null stream.
121
            if (is_null($stream)) {
122
                throw new InvalidOperationException(
123
                    Messages::streamProviderWrapperInvalidStreamFromGetReadStream()
124
                );
125
            }
126
        } else {
127
            // For named streams, getReadStream() can return null to indicate
128
            // that the stream has not been created.
129
            if (is_null($stream)) {
130
                // 204 == no content                
131
                $this->_service->getHost()->setResponseStatusCode(204);
132
            }
133
        }
134
135
        return $stream;
136
    }
137
138
    /**
139
     * Gets the IANA content type (aka media type) of the stream associated with 
140
     * the specified media resource.
141
     * 
142
     * @param object             $entity             The entity instance 
143
     *                                               (media resource) associated with
144
     *                                               the stream for which the content
145
     *                                               type is to be obtained.
146
     * @param ResourceStreamInfo $resourceStreamInfo This will be null if 
147
     *                                               media resource is MLE, 
148
     *                                               if media resource is named
149
     *                                               stream then will be the 
150
     *                                               ResourceStreamInfo instance 
151
     *                                               holding the details of 
152
     *                                               named stream.
153
     * 
154
     * @return string|null
155
     */
156 View Code Duplication
    public function getStreamContentType($entity, $resourceStreamInfo)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
157
    {
158
        $contentType = null;
0 ignored issues
show
Unused Code introduced by
$contentType is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
159
        $this->_saveContentTypeAndETag();
160
        if (is_null($resourceStreamInfo)) {
161
            $this->_loadAndValidateStreamProvider();
162
            $contentType = $this->_streamProvider->getStreamContentType(
163
                $entity,
164
                $this->_service->getOperationContext()
165
            );
166
            if (is_null($contentType)) {
167
                throw new InvalidOperationException(
168
                    Messages::streamProviderWrapperGetStreamContentTypeReturnsEmptyOrNull()
169
                );
170
            }
171
        } else {
172
            $this->_loadAndValidateStreamProvider2();
173
            $contentType = $this->_streamProvider->getStreamContentType2(
0 ignored issues
show
Bug introduced by
The method getStreamContentType2 does only exist in POData\Providers\Stream\IStreamProvider2, but not in POData\Providers\Stream\IStreamProvider.

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...
174
                $entity,
175
                $resourceStreamInfo,
176
                $this->_service->getOperationContext()
177
            );
178
        }
179
180
        $this->_verifyContentTypeOrETagModified('IDSSP::getStreamContentType');
181
        return $contentType;
182
    }
183
184
    /**
185
     * Get the ETag of the stream associated with the entity specified.
186
     * 
187
     * @param object             $entity             The entity instance 
188
     *                                               (media resource) associated
189
     *                                               with the stream for which 
190
     *                                               the etag is to be obtained.
191
     * @param ResourceStreamInfo $resourceStreamInfo This will be null if 
192
     *                                               media resource is MLE, 
193
     *                                               if media resource is named
194
     *                                               stream then will be the 
195
     *                                               ResourceStreamInfo
196
     *                                               instance holding the 
197
     *                                               details of named stream.
198
     * 
199
     * @throws InvalidOperationException
200
     * @return String Etag
201
     */
202 View Code Duplication
    public function getStreamETag($entity, $resourceStreamInfo)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
203
    {
204
        $eTag = null;
0 ignored issues
show
Unused Code introduced by
$eTag is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
205
        $this->_saveContentTypeAndETag();
206
        if (is_null($resourceStreamInfo)) {
207
            $this->_loadAndValidateStreamProvider();
208
            $eTag = $this->_streamProvider->getStreamETag(
209
                $entity,
210
                $this->_service->getOperationContext()
211
            );
212
        } else {
213
            $this->_loadAndValidateStreamProvider2();
214
            $eTag = $this->_streamProvider->getStreamETag2(
0 ignored issues
show
Bug introduced by
The method getStreamETag2 does only exist in POData\Providers\Stream\IStreamProvider2, but not in POData\Providers\Stream\IStreamProvider.

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...
215
                $entity,
216
                $resourceStreamInfo,
217
                $this->_service->getOperationContext()
218
            );
219
        }
220
221
        $this->_verifyContentTypeOrETagModified('IDSSP::getStreamETag');
222
        if (!self::isETagValueValid($eTag, true)) {
223
            throw new InvalidOperationException(
224
                Messages::streamProviderWrapperGetStreamETagReturnedInvalidETagFormat()
225
            );
226
        }
227
228
        return $eTag;
229
    }
230
231
    /**
232
     * Gets the URI clients should use when making retrieve (ie. GET) requests 
233
     * to the stream.
234
     * 
235
     * @param object             $entity             The entity instance 
236
     *                                               associated with the
237
     *                                               stream for which a 
238
     *                                               read stream URI is to
239
     *                                               be obtained.
240
     * @param ResourceStreamInfo $resourceStreamInfo This will be null 
241
     *                                               if media resource
242
     *                                               is MLE, if media 
243
     *                                               resource is named
244
     *                                               stream then will be 
245
     *                                               the ResourceStreamInfo
246
     *                                               instance holding the 
247
     *                                               details of named stream.
248
     * @param string             $mediaLinkEntryUri  MLE uri.
249
     * 
250
     * @return string
251
     * 
252
     * @throws InvalidOperationException
253
     */
254
    public function getReadStreamUri($entity, $resourceStreamInfo, 
255
        $mediaLinkEntryUri
256
    ) {
257
        $readStreamUri = null;
0 ignored issues
show
Unused Code introduced by
$readStreamUri is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
258
        $this->_saveContentTypeAndETag();
259
        if (is_null($resourceStreamInfo)) {
260
            $this->_loadAndValidateStreamProvider();
261
            $readStreamUri = $this->_streamProvider->getReadStreamUri(
262
                $entity,
263
                $this->_service->getOperationContext()
264
            );
265
        } else {
266
            $this->_loadAndValidateStreamProvider2();
267
            $readStreamUri = $this->_streamProvider->getReadStreamUri2(
0 ignored issues
show
Bug introduced by
The method getReadStreamUri2 does only exist in POData\Providers\Stream\IStreamProvider2, but not in POData\Providers\Stream\IStreamProvider.

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...
268
                $entity,
269
                $resourceStreamInfo,
270
                $this->_service->getOperationContext()
271
            );
272
        }
273
274
        $this->_verifyContentTypeOrETagModified('IDSSP::getReadStreamUri');
275
        if (!is_null($readStreamUri)) {
276
            try {
277
                new \POData\Common\Url($readStreamUri);
278
            } catch (\POData\Common\UrlFormatException $ex) {
279
                throw new InvalidOperationException(
280
                    Messages::streamProviderWrapperGetReadStreamUriMustReturnAbsoluteUriOrNull()
281
                );
282
            }            
283
        } else {
284
            if (is_null($resourceStreamInfo)) {
285
                // For MLEs the content src attribute is 
286
                //required so we cannot return null.
287
                $readStreamUri
288
                    = $this->getDefaultStreamEditMediaUri(
289
                        $mediaLinkEntryUri,
290
                        null
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<POData\Providers\...ata\ResourceStreamInfo>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
291
                    );
292
            }
293
        }
294
295
        // Note if readStreamUri is null, the self link for the
296
        // named stream will be omitted.
297
        return $readStreamUri;
298
    }
299
300
    /**
301
     * Checks the given value is a valid eTag.
302
     * 
303
     * @param string  $etag            eTag to validate.
304
     * @param boolean $allowStrongEtag True if strong eTag is allowed 
305
     *                                 False otherwise.
306
     * 
307
     * @return boolean
308
     */
309
    public static function isETagValueValid($etag, $allowStrongEtag)
310
    {
311
        if (is_null($etag) || $etag === '*') {
312
            return true;
313
        }
314
315
        // HTTP RFC 2616, section 3.11:
316
        //   entity-tag = [ weak ] opaque-tag
317
        //   weak       = "W/"
318
        //   opaque-tag = quoted-string
319
        $etagValueStartIndex = 1;
320
        $eTagLength = strlen($etag);
321
322
        if (strpos($etag, "W/\"") === 0 && $etag[$eTagLength - 1] == '"') {
323
            $etagValueStartIndex = 3;
324
        } else if (!$allowStrongEtag || $etag[0] != '"' 
325
            || $etag[$eTagLength - 1] != '"'
326
        ) {
327
            return false;
328
        }
329
330
        for ($i = $etagValueStartIndex; $i < $eTagLength - 1; $i++) {
331
            // Format of etag looks something like: W/"etag property values" 
332
            // or "strong etag value" according to HTTP RFC 2616, if someone 
333
            // wants to specify more than 1 etag value, then need to specify 
334
            // something like this: W/"etag values", W/"etag values", ...
335
            // To make sure only one etag is specified, we need to ensure 
336
            // that if " is part of the key value, it needs to be escaped.
337
            if ($etag[$i] == '"') {
338
                return false;
339
            }
340
        }
341
342
        return true;
343
    }
344
345
    /**
346
     * Get ETag header value from request header.
347
     *      
348
     * @param mixed &$eTag                 On return, this parameter will hold
349
     *                                     value of IfMatch or IfNoneMatch
350
     *                                     header, if this header is absent then
351
     *                                     this parameter will hold NULL.
352
     * @param mixed &$checkETagForEquality On return, this parameter will hold
353
     *                                     true if IfMatch is present, false if
354
     *                                     IfNoneMatch header is present, null
355
     *                                     otherwise.
356
     * 
357
     * @return void
358
     */
359
    private function _getETagFromHeaders(&$eTag, &$checkETagForEquality)
360
    {
361
        $dataServiceHost = $this->_service->getHost();
362
        //TODO Do check for mutual exclusion of RequestIfMatch and
363
        //RequestIfNoneMatch in ServiceHost
364
        $eTag = $dataServiceHost->getRequestIfMatch();
365
        if (!is_null($eTag)) {
366
            $checkETagForEquality = true;
367
            return;
368
        }
369
370
        $eTag = $dataServiceHost->getRequestIfNoneMatch();
371
        if (!is_null($eTag)) {
372
            $checkETagForEquality = false;
373
            return;
374
        }
375
376
        $checkETagForEquality = null;
377
    }
378
379
    /**
380
     * Validates that an implementation of IStreamProvider exists and
381
     * load it.
382
     * 
383
     * @return void
384
     * 
385
     * @throws ODataException
386
     */
387
    private function _loadAndValidateStreamProvider()
388
    {
389
        if (is_null($this->_streamProvider)) {
390
            $this->_loadStreamProvider();
391
            if (is_null($this->_streamProvider)) {
392
                throw ODataException::createInternalServerError(
393
                    Messages::streamProviderWrapperMustImplementIStreamProviderToSupportStreaming()
394
                );
395
            }
396
        }
397
    }
398
399
    /**
400
     * Validates that an implementation of IStreamProvider2 exists and
401
     * load it.
402
     * 
403
     * @return void
404
     * 
405
     * @throws ODataException
406
     */
407
    private function _loadAndValidateStreamProvider2()
408
    {
409
        $maxServiceVersion = $this->_service
410
            ->getConfiguration()
411
            ->getMaxDataServiceVersion();
412
        if ($maxServiceVersion->compare(new Version(3, 0)) < 0) {
413
            throw ODataException::createInternalServerError(
414
                Messages::streamProviderWrapperMaxProtocolVersionMustBeV3OrAboveToSupportNamedStreams()
415
            );
416
        }
417
418
        if (is_null($this->_streamProvider)) {
419
            $this->_loadStreamProvider();
420
            if (is_null($this->_streamProvider)) {
421
                throw ODataException::createInternalServerError(
422
                    Messages::streamProviderWrapperMustImplementStreamProvider2ToSupportNamedStreams()
423
                );
424
            } else if (!$this->_streamProvider instanceof IStreamProvider2) {
425
                throw ODataException::createInternalServerError(
426
                    Messages::streamProviderWrapperInvalidStream2Instance()
427
                );
428
            }
429
        }
430
    }
431
432
    /**
433
     * Ask data service to load stream provider instance.
434
     * 
435
     * @return void
436
     * 
437
     * @throws ODataException
438
     */
439
    private function _loadStreamProvider()
440
    {
441
        if (is_null($this->_streamProvider)) {
442
            $maxServiceVersion = $this->_service
443
                ->getConfiguration()
444
                ->getMaxDataServiceVersion();
445
            if ($maxServiceVersion->compare(new Version(3, 0)) >= 0) {
446
                $this->_streamProvider = $this->_service->getService('IStreamProvider2');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface POData\IService as the method getService() does only exist in the following implementations of said interface: NorthWindDataService, WordPressDataService.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
447 View Code Duplication
                if (!is_null($this->_streamProvider) && (!is_object($this->_streamProvider) || !$this->_streamProvider instanceof IStreamProvider2)) {
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...
448
                    throw ODataException::createInternalServerError(
449
                        Messages::streamProviderWrapperInvalidStream2Instance()
450
                    ); 
451
                }
452
            }
453
454
            if (is_null($this->_streamProvider)) {
455
                $this->_streamProvider = $this->_service->getService('IStreamProvider');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface POData\IService as the method getService() does only exist in the following implementations of said interface: NorthWindDataService, WordPressDataService.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
456 View Code Duplication
                if (!is_null($this->_streamProvider) && (!is_object($this->_streamProvider) || !$this->_streamProvider instanceof IStreamProvider)) {
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...
457
                    throw ODataException::createInternalServerError(
458
                        Messages::streamProviderWrapperInvalidStreamInstance()
459
                    );
460
                }
461
            }
462
        }
463
    }
464
465
    /**
466
     * Construct the default edit media uri from the given media link entry uri.
467
     * 
468
     * @param string             $mediaLinkEntryUri  Uri to the media link entry.
469
     * @param ResourceStreamInfo $resourceStreamInfo Stream info instance, if its
470
     *                                               null default stream is assumed.
471
     * 
472
     * @return string Uri to the media resource.
473
     */
474
    public function getDefaultStreamEditMediaUri($mediaLinkEntryUri, $resourceStreamInfo)
475
    {
476
        if (is_null($resourceStreamInfo)) {
477
            return rtrim($mediaLinkEntryUri, '/') . '/' . ODataConstants::URI_VALUE_SEGMENT;
478
        } else {
479
            return rtrim($mediaLinkEntryUri, '/') . '/' . ltrim($resourceStreamInfo->getName(), '/');
480
        }
481
    }
482
483
    /**
484
     * Save value of content type and etag headers before invoking implementor
485
     * methods.
486
     * 
487
     * @return void
488
     */
489
    private function _saveContentTypeAndETag()
490
    {
491
        $this->_responseContentType = $this->_service->getHost()->getResponseContentType();
492
        $this->_responseETag = $this->_service->getHost()->getResponseETag();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_service->getHost()->getResponseETag() of type object<POData\OperationContext\Web\reference> is incompatible with the declared type string|null of property $_responseETag.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
493
    }
494
495
    /**
496
     * Check whether implementor modified content type or etag header
497
     * if so throw InvalidOperationException.
498
     * 
499
     * @param string $methodName NAme of the method
500
     * 
501
     * @return void
502
     * 
503
     * @throws InvalidOperationException
504
     */
505
    private function _verifyContentTypeOrETagModified($methodName)
506
    {
507
        if ($this->_responseContentType !== $this->_service->getHost()->getResponseContentType()
508
            || $this->_responseETag !== $this->_service->getHost()->getResponseETag()
509
        ) {
510
            throw new InvalidOperationException(
511
                Messages::streamProviderWrapperMustNotSetContentTypeAndEtag($methodName)
512
            );
513
        }
514
    }
515
}
516