Completed
Pull Request — master (#257)
by Éloi
01:50
created

Media   F

Complexity

Total Complexity 77

Size/Duplication

Total Lines 510
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 98.94%

Importance

Changes 0
Metric Value
wmc 77
lcom 1
cbo 19
dl 0
loc 510
ccs 279
cts 282
cp 0.9894
rs 2.24
c 0
b 0
f 0

34 Methods

Rating   Name   Duplication   Size   Complexity  
A getUrlAttributeName() 0 4 1
A setUrlAttributeName() 0 4 1
A setProperty() 0 24 4
A createMediaElement() 0 9 1
A handleMediaGroup() 0 8 4
A handleMediaRoot() 0 43 3
A handleMediaContent() 0 30 3
A handleMediaRating() 0 7 2
A handleMediaTitle() 0 9 1
A handleMediaDescription() 0 9 1
A handleMediaKeywords() 0 9 1
A handleMediaThumbnail() 0 11 2
A handleMediaCategory() 0 9 2
A handleMediaHash() 0 7 1
A handleMediaPlayer() 0 8 1
A handleMediaCredit() 0 13 2
A handleMediaCopyright() 0 7 1
A handleMediaText() 0 19 4
A handleMediaRestriction() 0 8 1
A handleMediaCommunity() 0 26 4
A handleMediaComments() 0 12 4
A handleMediaEmbed() 0 19 4
A handleMediaResponses() 0 12 4
A handleMediaBacklinks() 0 12 4
A handleMediaStatus() 0 7 1
A handleMediaPrice() 0 13 2
A handleMediaLicense() 0 8 1
A handleMediaSubtitle() 0 13 2
A handleMediaPeerlink() 0 7 1
A handleMediaRights() 0 6 1
B handleMediaScenes() 0 27 6
A hasValue() 0 4 2
A addElement() 0 6 2
A findTags() 0 18 3

How to fix   Complexity   

Complex Class

Complex classes like Media often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Media, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of the feed-io package.
4
 *
5
 * (c) Alexandre Debril <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace FeedIo\Rule;
12
13
use FeedIo\Feed\Item;
14
use FeedIo\Feed\Item\MediaInterface;
15
use FeedIo\Feed\ItemInterface;
16
use FeedIo\Feed\Item\MediaContentMedium;
17
use FeedIo\Feed\Item\MediaContentExpression;
18
use FeedIo\Feed\Item\MediaSimpleRating;
19
use FeedIo\Feed\Item\MediaDescriptionType;
20
use FeedIo\Feed\Item\MediaTitleType;
21
use FeedIo\Feed\Item\MediaHashAlgo;
22
use FeedIo\Feed\Item\MediaCreditScheme;
23
use FeedIo\Feed\Item\MediaPriceType;
24
use FeedIo\Feed\Item\MediaTextType;
25
use FeedIo\Feed\Item\MediaRestrictionRelationship;
26
use FeedIo\Feed\Item\MediaRestrictionType;
27
use FeedIo\Feed\Item\MediaRightsStatus;
28
use FeedIo\Feed\Item\MediaStatus;
29
use FeedIo\Feed\Item\MediaCredit;
30
use FeedIo\Feed\Item\MediaPrice;
31
use FeedIo\Feed\Item\MediaSubtitle;
32
use FeedIo\Feed\Item\MediaText;
33
use FeedIo\Feed\Item\MediaScene;
34
use FeedIo\Feed\NodeInterface;
35
use FeedIo\RuleAbstract;
36
37
class Media extends RuleAbstract
38
{
39
    const NODE_NAME = 'enclosure';
40
41
    const MRSS_NAMESPACE = "http://search.yahoo.com/mrss/";
42
43
    protected $urlAttributeName = 'url';
44
45
    /**
46
     * @return string
47
     */
48 5
    public function getUrlAttributeName() : string
49
    {
50 5
        return $this->urlAttributeName;
51
    }
52
53
    /**
54
     * @param  string $name
55
     */
56 12
    public function setUrlAttributeName(string $name) : void
57
    {
58 12
        $this->urlAttributeName = $name;
59 12
    }
60
61
    /**
62
     * @param  NodeInterface $node
63
     * @param  \DOMElement   $element
64
     */
65 51
    public function setProperty(NodeInterface $node, \DOMElement $element) : void
66
    {
67 51
        if ($node instanceof ItemInterface) {
68 51
            switch ($element->nodeName) {
69 51
                case 'media:content':
70 45
                    $this->handleMediaRoot($node, $element);
71 45
                    break;
72
73 7
                case 'media:group':
74 3
                    $this->handleMediaGroup($node, $element);
75 3
                    break;
76
77
                default:
78 4
                    $media = $node->newMedia();
79 4
                    $media->setNodeName($element->nodeName);
80
                    $media
81 4
                        ->setType($this->getAttributeValue($element, 'type'))
82 4
                        ->setUrl($this->getAttributeValue($element, $this->getUrlAttributeName()))
83 4
                        ->setLength($this->getAttributeValue($element, 'length'));
84 4
                    $node->addMedia($media);
85 4
                    break;
86
            }
87
        }
88 51
    }
89
90
    /**
91
     * @param  \DomDocument   $document
92
     * @param  MediaInterface $media
93
     * @return \DomElement
94
     */
95 1
    public function createMediaElement(\DomDocument $document, MediaInterface $media) : \DOMElement
96
    {
97 1
        $element = $document->createElement($this->getNodeName());
98 1
        $element->setAttribute($this->getUrlAttributeName(), $media->getUrl());
99 1
        $element->setAttribute('type', $media->getType() ?? '');
100 1
        $element->setAttribute('length', $media->getLength() ?? '');
101
102 1
        return $element;
103
    }
104
105
    /**
106
     * @param \NodeInterface $node
107
     * @param \DomElement $element
108
     * @return \MediaInterface
109
     */
110 3
    protected function handleMediaGroup(NodeInterface $node, \DOMElement $element): void
111
    {
112 3
        foreach ($element->childNodes as $mediaContentNode) {
113 3
            if (is_a($mediaContentNode, "DOMNode") && $mediaContentNode->nodeName == "media:content") {
114 3
                $this->handleMediaRoot($node, $mediaContentNode);
115
            }
116
        }
117 3
    }
118
119
    /**
120
     * @param \NodeInterface $node
121
     * @param \DomElement $element
122
     * @return \MediaInterface
123
     */
124 47
    protected function handleMediaRoot(NodeInterface $node, \DOMElement $element): void
125
    {
126 47
        $media = $node->newMedia();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface FeedIo\Feed\NodeInterface as the method newMedia() does only exist in the following implementations of said interface: FeedIo\Feed\Item.

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...
127 47
        $media->setNodeName($element->nodeName);
128
129 47
        $this->handleMediaContent($element, $media);
130
131
        $tags = array(
132 47
            'media:rating' => 'handleMediaRating',
133
            'media:title' => 'handleMediaTitle',
134
            'media:description' => 'handleMediaDescription',
135
            'media:keywords' => 'handleMediaKeywords',
136
            'media:thumbnail' => 'handleMediaThumbnail',
137
            'media:category' => 'handleMediaCategory',
138
            'media:hash' => 'handleMediaHash',
139
            'media:player' => 'handleMediaPlayer',
140
            'media:credit' => 'handleMediaCredit',
141
            'media:copyright' => 'handleMediaCopyright',
142
            'media:text' => 'handleMediaText',
143
            'media:restriction' => 'handleMediaRestriction',
144
            'media:community' => 'handleMediaCommunity',
145
            'media:comments' => 'handleMediaComments',
146
            'media:embed' => 'handleMediaEmbed',
147
            'media:responses' => 'handleMediaResponses',
148
            'media:backLinks' => 'handleMediaBacklinks',
149
            'media:status' => 'handleMediaStatus',
150
            'media:price' => 'handleMediaPrice',
151
            'media:license' => 'handleMediaLicense',
152
            'media:subTitle' => 'handleMediaSubtitle',
153
            'media:peerLink' => 'handleMediaPeerlink',
154
            'media:rights' => 'handleMediaRights',
155
            'media:scenes' => 'handleMediaScenes',
156
        );
157
158 47
        foreach ($tags as $tag => $callback) {
159 47
            $nodes = $this->findTags($element, $tag);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $nodes is correct as $this->findTags($element, $tag) (which targets FeedIo\Rule\Media::findTags()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
160 47
            if ($nodes) {
161 45
                $this->$callback($nodes, $media);
162
            }
163
        }
164
165 47
        $node->addMedia($media);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface FeedIo\Feed\NodeInterface as the method addMedia() does only exist in the following implementations of said interface: FeedIo\Feed\Item.

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...
166 47
    }
167
168 47
    protected function handleMediaContent(?\DOMElement $element, MediaInterface $media) : void
169
    {
170 47
        $media->setUrl($this->getAttributeValue($element, "url"));
171 47
        $media->setContentFileSize(intval($this->getAttributeValue($element, "fileSize")));
172 47
        $media->setContentBitrate(intval($this->getAttributeValue($element, "bitrate")));
173 47
        $media->setContentFramerate(intval($this->getAttributeValue($element, "framerate")));
174 47
        $media->setContentSamplingrate(floatval($this->getAttributeValue($element, "samplingrate")));
175 47
        $media->setContentDuration(intval($this->getAttributeValue($element, "duration")));
176 47
        $media->setContentHeight(intval($this->getAttributeValue($element, "height")));
177 47
        $media->setContentWidth(intval($this->getAttributeValue($element, "width")));
178 47
        $media->setContentLang($this->getAttributeValue($element, "lang"));
179 47
        $media->setType($this->getAttributeValue($element, "type"));
180 47
        $media->setContentExpression(MediaContentExpression::fromXML(
181 47
            $this->getAttributeValue($element, "expression")
182
        ));
183 47
        $media->setContentMedium(
184 47
            MediaContentMedium::fromXML(
185 47
                $this->getAttributeValue($element, "medium")
186
            )
187
        );
188
189 47
        switch ($this->getAttributeValue($element, "isDefault")) {
190 47
            case "true":
191 1
                $media->setDefault(true);
192 1
                break;
193 46
            case "false":
194
                $media->setDefault(false);
195
                break;
196
        }
197 47
    }
198
199 2
    protected function handleMediaRating(?\DOMNodeList $elements, MediaInterface $media) : void
200
    {
201 2
        $element = $elements->item(0);
202
203 2
        $media->setRating($element->nodeValue);
204 2
        $media->setRatingScheme($this->getAttributeValue($element, "scheme") ?: 'urn:simple');
205 2
    }
206
207 4
    protected function handleMediaTitle(?\DOMNodeList $elements, MediaInterface $media) : void
208
    {
209 4
        $element = $elements->item(0);
210
211 4
        $media->setTitle($element->nodeValue);
212 4
        $media->setTitleType(MediaTitleType::fromXML(
213 4
            $this->getAttributeValue($element, "type")
214
        ));
215 4
    }
216
217 5
    protected function handleMediaDescription(?\DOMNodeList $elements, MediaInterface $media) : void
218
    {
219 5
        $element = $elements->item(0);
220
221 5
        $media->setDescription($element->nodeValue);
222 5
        $media->setDescriptionType(MediaDescriptionType::fromXML(
223 5
            $this->getAttributeValue($element, "type")
224
        ));
225 5
    }
226
227 1
    protected function handleMediaKeywords(?\DOMNodeList $elements, MediaInterface $media) : void
228
    {
229 1
        $element = $elements->item(0);
230
231 1
        $media->setKeywords(array_map("trim", explode(
232 1
            ',',
233 1
            $element->nodeValue
234
        )));
235 1
    }
236
237 3
    protected function handleMediaThumbnail(?\DOMNodeList $elements, MediaInterface $media) : void
238
    {
239 3
        $element = $elements->item(0);
240
241 3
        $media->setThumbnail($this->getAttributeValue($element, "url"));
242 3
        $media->setThumbnailWidth(intval($this->getAttributeValue($element, "width")));
243 3
        $media->setThumbnailHeight(intval($this->getAttributeValue($element, "height")));
244 3
        if ($element->hasAttribute("time")) {
245 1
            $media->setThumbnailTime(new \DateTime($this->getAttributeValue($element, "time")));
0 ignored issues
show
Documentation introduced by
new \DateTime($this->get...alue($element, 'time')) is of type object<DateTime>, but the function expects a string.

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...
246
        }
247 3
    }
248
249 2
    protected function handleMediaCategory(?\DOMNodeList $elements, MediaInterface $media) : void
250
    {
251 2
        $element = $elements->item(0);
252
253 2
        $media->setCategory($element->nodeValue);
254 2
        $media->setCategoryLabel($this->getAttributeValue($element, "label"));
255 2
        $default_scheme = "http://search.yahoo.com/mrss/category_schema";
256 2
        $media->setCategoryScheme($this->getAttributeValue($element, "scheme") ?: $default_scheme);
257 2
    }
258
259 2
    protected function handleMediaHash(?\DOMNodeList $elements, MediaInterface $media) : void
260
    {
261 2
        $element = $elements->item(0);
262
263 2
        $media->setHash($element->nodeValue);
264 2
        $media->setHashAlgo(MediaHashAlgo::fromXML($this->getAttributeValue($element, "algo")));
265 2
    }
266
267 2
    protected function handleMediaPlayer(?\DOMNodeList $elements, MediaInterface $media) : void
268
    {
269 2
        $element = $elements->item(0);
270
271 2
        $media->setPlayerUrl($this->getAttributeValue($element, "url"));
272 2
        $media->setPlayerWidth(intval($this->getAttributeValue($element, "width")));
273 2
        $media->setPlayerHeight(intval($this->getAttributeValue($element, "height")));
274 2
    }
275
276 2
    protected function handleMediaCredit(?\DOMNodeList $elements, MediaInterface $media) : void
277
    {
278 2
        $credits = array();
279 2
        foreach ($elements as $element) {
280 2
            $credit = new MediaCredit();
281
282 2
            $credit->setValue($element->nodeValue);
283 2
            $credit->setScheme(MediaCreditScheme::fromXML($this->getAttributeValue($element, "scheme")));
284 2
            $credit->setRole($this->getAttributeValue($element, "role"));
285 2
            array_push($credits, $credit);
286
        }
287 2
        $media->setCredits($credits);
288 2
    }
289
290 2
    protected function handleMediaCopyright(?\DOMNodeList $elements, MediaInterface $media) : void
291
    {
292 2
        $element = $elements->item(0);
293
294 2
        $media->setCopyright($element->nodeValue);
295 2
        $media->setCopyrightUrl($this->getAttributeValue($element, "url"));
296 2
    }
297
298 2
    protected function handleMediaText(?\DOMNodeList $elements, MediaInterface $media) : void
299
    {
300 2
        $texts = array();
301 2
        foreach ($elements as $element) {
302 2
            $text = new MediaText();
303
304 2
            $text->setValue($element->nodeValue);
305 2
            $text->setType(MediaTextType::fromXML($this->getAttributeValue($element, "type")));
306 2
            $text->setLang($this->getAttributeValue($element, "lang"));
307 2
            if ($element->hasAttribute("start")) {
308 1
                $text->setStart(new \DateTime($this->getAttributeValue($element, "start")));
309
            }
310 2
            if ($element->hasAttribute("end")) {
311 1
                $text->setEnd(new \DateTime($this->getAttributeValue($element, "end")));
312
            }
313 2
            array_push($texts, $text);
314
        }
315 2
        $media->setTexts($texts);
316 2
    }
317
318 2
    protected function handleMediaRestriction(?\DOMNodeList $elements, MediaInterface $media) : void
319
    {
320 2
        $element = $elements->item(0);
321
322 2
        $media->setRestriction($element->nodeValue);
323 2
        $media->setRestrictionType(MediaRestrictionType::fromXML($this->getAttributeValue($element, "type")));
324 2
        $media->setRestrictionRelationship(MediaRestrictionType::fromXML($this->getAttributeValue($element, "relationship")));
325 2
    }
326
327 3
    protected function handleMediaCommunity(?\DOMNodeList $elements, MediaInterface $media) : void
328
    {
329 3
        $element = $elements->item(0);
330
331 3
        $media->setStarRatingAverage(floatval($this->getChildAttributeValue($element, "starRating", "average", static::MRSS_NAMESPACE)));
332 3
        $media->setStarRatingCount(intval($this->getChildAttributeValue($element, "starRating", "count", static::MRSS_NAMESPACE)));
333 3
        $media->setStarRatingMin(intval($this->getChildAttributeValue($element, "starRating", "min", static::MRSS_NAMESPACE)));
334 3
        $media->setStarRatingMax(intval($this->getChildAttributeValue($element, "starRating", "max", static::MRSS_NAMESPACE)));
335 3
        $media->setNbViews(intval($this->getChildAttributeValue($element, "statistics", "views", static::MRSS_NAMESPACE)));
336 3
        $media->setNbFavorites(intval($this->getChildAttributeValue($element, "statistics", "favorites", static::MRSS_NAMESPACE)));
337
338 3
        $tags = array();
339 3
        $tagsValue = $this->getChildValue($element, "tags", static::MRSS_NAMESPACE);
340 3
        if ($tagsValue) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tagsValue of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
341 1
            foreach (explode(",", $tagsValue) as $pair) {
342 1
                $values = explode(":", $pair);
343 1
                if (count($values) != 2) {
344
                    continue;
345
                }
346 1
                $key = trim($values[0]);
347 1
                $value = intval($values[1]);
348 1
                $tags[$key] = $value;
349
            }
350
        }
351 3
        $media->setTags($tags);
352 3
    }
353
354 2
    protected function handleMediaComments(?\DOMNodeList $elements, MediaInterface $media) : void
355
    {
356 2
        $element = $elements->item(0);
357
358 2
        $comments = array();
359 2
        foreach ($element->childNodes as $node) {
360 1
            if (is_a($node, "DOMNode") && $node->nodeName == "media:comment") {
361 1
                array_push($comments, $node->nodeValue);
362
            }
363
        }
364 2
        $media->setComments($comments);
365 2
    }
366
367 2
    protected function handleMediaEmbed(?\DOMNodeList $elements, MediaInterface $media) : void
368
    {
369 2
        $element = $elements->item(0);
370
371 2
        $media->setEmbedUrl($this->getAttributeValue($element, "url"));
372 2
        $media->setEmbedWidth(intval($this->getAttributeValue($element, "width")));
373 2
        $media->setEmbedHeight(intval($this->getAttributeValue($element, "height")));
374
375 2
        $params = array();
376 2
        foreach ($element->childNodes as $node) {
377 1
            if (is_a($node, "DOMNode") && $node->nodeName == "media:param") {
378 1
                $key = $this->getAttributeValue($node, "name");
379 1
                $value = $node->nodeValue;
380 1
                $params[$key] = trim($value);
381
            }
382
        }
383
384 2
        $media->setEmbedParams($params);
385 2
    }
386
387 2
    protected function handleMediaResponses(?\DOMNodeList $elements, MediaInterface $media) : void
388
    {
389 2
        $element = $elements->item(0);
390
391 2
        $responses = array();
392 2
        foreach ($element->childNodes as $node) {
393 1
            if (is_a($node, "DOMNode") && $node->nodeName == "media:response") {
394 1
                array_push($responses, $node->nodeValue);
395
            }
396
        }
397 2
        $media->setResponses($responses);
398 2
    }
399
400 2
    protected function handleMediaBacklinks(?\DOMNodeList $elements, MediaInterface $media) : void
401
    {
402 2
        $element = $elements->item(0);
403
404 2
        $backlinks = array();
405 2
        foreach ($element->childNodes as $node) {
406 1
            if (is_a($node, "DOMNode") && $node->nodeName == "media:backLink") {
407 1
                array_push($backlinks, $node->nodeValue);
408
            }
409
        }
410 2
        $media->setBacklinks($backlinks);
411 2
    }
412
413 2
    protected function handleMediaStatus(?\DOMNodeList $elements, MediaInterface $media) : void
414
    {
415 2
        $element = $elements->item(0);
416
417 2
        $media->setStatus(MediaStatus::fromXML($this->getAttributeValue($element, "state")));
418 2
        $media->setStatusReason($this->getAttributeValue($element, "reason"));
419 2
    }
420
421 2
    protected function handleMediaPrice(?\DOMNodeList $elements, MediaInterface $media) : void
422
    {
423 2
        $prices = array();
424 2
        foreach ($elements as $element) {
425 2
            $price = new MediaPrice();
426
427 2
            $price->setType(MediaPriceType::fromXML($this->getAttributeValue($element, "type")));
428 2
            $price->setValue(floatval($this->getAttributeValue($element, "price")));
429 2
            $price->setCurrency($this->getAttributeValue($element, "currency"));
430 2
            array_push($prices, $price);
431
        }
432 2
        $media->setPrices($prices);
433 2
    }
434
435 1
    protected function handleMediaLicense(?\DOMNodeList $elements, MediaInterface $media) : void
436
    {
437 1
        $element = $elements->item(0);
438
439 1
        $media->setLicense($element->nodeValue);
440 1
        $media->setLicenseUrl($this->getAttributeValue($element, "href"));
441 1
        $media->setLicenseType($this->getAttributeValue($element, "type"));
442 1
    }
443
444 1
    protected function handleMediaSubtitle(?\DOMNodeList $elements, MediaInterface $media) : void
445
    {
446 1
        $subtitles = array();
447 1
        foreach ($elements as $element) {
448 1
            $subtitle = new MediaSubtitle();
449
450 1
            $subtitle->setType($this->getAttributeValue($element, "type"));
451 1
            $subtitle->setLang($this->getAttributeValue($element, "lang"));
452 1
            $subtitle->setUrl($this->getAttributeValue($element, "href"));
453 1
            array_push($subtitles, $subtitle);
454
        }
455 1
        $media->setSubTitles($subtitles);
456 1
    }
457
458 1
    protected function handleMediaPeerlink(?\DOMNodeList $elements, MediaInterface $media) : void
459
    {
460 1
        $element = $elements->item(0);
461
462 1
        $media->setPeerLink($this->getAttributeValue($element, "href"));
463 1
        $media->setPeerLinkType($this->getAttributeValue($element, "type"));
464 1
    }
465
466 1
    protected function handleMediaRights(?\DOMNodeList $elements, MediaInterface $media) : void
467
    {
468 1
        $element = $elements->item(0);
469
470 1
        $media->setRights(MediaRightsStatus::fromXML($this->getAttributeValue($element, "status")));
471 1
    }
472
473 1
    protected function handleMediaScenes(?\DOMNodeList $elements, MediaInterface $media) : void
474
    {
475 1
        $element = $elements->item(0);
476
477 1
        $scenes = array();
478 1
        foreach ($element->childNodes as $node) {
479 1
            if (is_a($node, "DOMNode") && $node->nodeName == "media:scene") {
480 1
                $scene = new MediaScene();
481
482 1
                $scene->setTitle($this->getChildValue($node, "sceneTitle"));
483 1
                $scene->setDescription($this->getChildValue($node, "sceneDescription"));
484 1
                $startTime = $this->getChildValue($node, "sceneStartTime");
485 1
                $endTime = $this->getChildValue($node, "sceneEndTime");
486
487 1
                if ($startTime) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $startTime of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
488 1
                    $scene->setStartTime(new \DateTime($startTime));
489
                }
490
491 1
                if ($endTime) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $endTime of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
492 1
                    $scene->setEndTime(new \DateTime($endTime));
493
                }
494
495 1
                array_push($scenes, $scene);
496
            }
497
        }
498 1
        $media->setScenes($scenes);
499 1
    }
500
501
    /**
502
     * @inheritDoc
503
     */
504 5
    protected function hasValue(NodeInterface $node) : bool
505
    {
506 5
        return $node instanceof ItemInterface && !! $node->getMedias();
507
    }
508
509
    /**
510
     * @inheritDoc
511
     */
512 5
    protected function addElement(\DomDocument $document, \DOMElement $rootElement, NodeInterface $node) : void
513
    {
514 5
        foreach ($node->getMedias() as $media) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface FeedIo\Feed\NodeInterface as the method getMedias() does only exist in the following implementations of said interface: FeedIo\Feed\Item.

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...
515 1
            $rootElement->appendChild($this->createMediaElement($document, $media));
516
        }
517 5
    }
518
519
    /**
520
     * From http://www.rssboard.org/media-rss#optional-elements
521
     *
522
     * Duplicated elements appearing at deeper levels of the document tree
523
     * have higher priority over other levels. For example, <media:content>
524
     * level elements are favored over <item> level elements. The priority
525
     * level is listed from strongest to weakest:
526
     * <media:content>, <media:group>, <item>, <channel>.
527
     */
528 47
    public function findTags(\DOMElement $mediaContentNode, string $tag) : ? \DOMNodeList
529
    {
530 47
        $xpath = new \DOMXpath($mediaContentNode->ownerDocument);
531
        $queries = [
532 47
            $xpath->query("./descendant::$tag", $mediaContentNode),
533 47
            $xpath->query("./ancestor::media:group/child::$tag", $mediaContentNode),
534 47
            $xpath->query("./ancestor::item/child::$tag", $mediaContentNode),
535 47
            $xpath->query("./ancestor::channel/child::$tag", $mediaContentNode),
536
        ];
537
538 47
        foreach ($queries as $query) {
539 47
            if ($query->count()) {
540 45
                return $query;
541
            }
542
        }
543
544 47
        return null;
545
    }
546
}
547