WordsList   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 311
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 87.72%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 5
dl 0
loc 311
rs 9.1199
c 0
b 0
f 0
ccs 100
cts 114
cp 0.8772

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setName() 0 5 1
A getName() 0 4 1
A getWordForText() 0 10 3
A addWord() 0 8 2
A removeWord() 0 5 1
A getWords() 0 4 1
A getWordsOrdered() 0 8 2
A sortWords() 0 31 4
A getWordsMaxCount() 0 11 3
A getWordsCount() 0 4 1
A importWords() 0 11 4
A importWord() 0 25 4
A importHtml() 0 20 4
A importUrl() 0 4 1
A filterWords() 0 12 3
A randomizeOrientation() 0 13 3
A randomizeColors() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like WordsList 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 WordsList, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SixtyNine\Cloud\Model;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use SixtyNine\Cloud\Color\ColorGeneratorInterface;
7
use SixtyNine\Cloud\Filters\Filters;
8
use JMS\Serializer\Annotation as JMS;
9
use Webmozart\Assert\Assert;
10
11
class WordsList
12
{
13
    const SORT_ALPHA = 'text';
14
    const SORT_COUNT = 'count';
15
    const SORT_ANGLE = 'angle';
16
17
    const SORT_ASC = 'asc';
18
    const SORT_DESC = 'desc';
19
20
    /**
21
     * @var string
22
     * @JMS\Type("string")
23
     */
24
    protected $name;
25
26
    /**
27
     * @var ArrayCollection
28
     * @JMS\Type("ArrayCollection<SixtyNine\Cloud\Model\Word>")
29
     */
30
    protected $words;
31
32
    /**
33
     * @var array
34
     * @JMS\Exclude()
35
     */
36
    protected $allowedSortBy = array(self::SORT_ALPHA, self::SORT_ANGLE, self::SORT_COUNT);
37
38
    /**
39
     * @var array
40
     * @JMS\Exclude()
41
     */
42
    protected $allowedSortOrder = array(self::SORT_ASC, self::SORT_DESC);
43
44
    /**
45
     * Constructor
46
     */
47 8
    public function __construct()
48
    {
49 8
        $this->words = new ArrayCollection();
50 8
    }
51
52
    /**
53
     * @param string $name
54
     * @return $this
55
     */
56 7
    public function setName($name)
57
    {
58 7
        $this->name = $name;
59 7
        return $this;
60
    }
61
62
    /**
63
     * @return string
64
     */
65 4
    public function getName()
66
    {
67 4
        return $this->name;
68
    }
69
70
    /**
71
     * Check if a Word with the same text already exists in the Cloud
72
     * @param string $text
73
     * @return null|Word
74
     */
75 7
    public function getWordForText($text)
76
    {
77
        /** @var Word $word */
78 7
        foreach ($this->words as $word) {
79 7
            if ($word->getText() === $text) {
80 7
                return $word;
81
            }
82
        }
83 7
        return null;
84
    }
85
86
    /**
87
     * @param Word $word
88
     * @return $this
89
     */
90 7
    public function addWord(Word $word)
91
    {
92 7
        if (!$this->words->contains($word)) {
93 7
            $this->words->add($word);
94
        }
95
96 7
        return $this;
97
    }
98
99
    /**
100
     * @param Word $word
101
     * @return $this
102
     */
103
    public function removeWord(Word $word)
104
    {
105
        $this->words->removeElement($word);
106
        return $this;
107
    }
108
109
    /**
110
     * @return ArrayCollection
111
     */
112 7
    public function getWords()
113
    {
114 7
        return $this->words;
115
    }
116
117
    /**
118
     * @return ArrayCollection
119
     */
120 3
    public function getWordsOrdered()
121
    {
122 3
        $iterator = $this->words->getIterator();
123
        $iterator->uasort(function ($a, $b) {
124 3
            return ($a->getPosition() < $b->getPosition()) ? -1 : 1;
125 3
        });
126 3
        return new ArrayCollection(iterator_to_array($iterator));
127
    }
128
129
    /**
130
     * @param string $sortBy
131
     * @param string $sortOrder
132
     * @throws \InvalidArgumentException
133
     */
134 1
    public function sortWords($sortBy, $sortOrder)
135
    {
136 1
        Assert::oneOf($sortBy, $this->allowedSortBy, 'Invalid sort by: ' . $sortBy);
137 1
        Assert::oneOf($sortOrder, $this->allowedSortOrder, 'Invalid sort order: ' . $sortOrder);
138
139 1
        $sorter = function ($a, $b) use ($sortBy, $sortOrder) {
140 1
            $method = $sortBy === self::SORT_ANGLE
141
                ? 'getOrientation'
142 1
                : 'get' . ucfirst($sortBy)
143
            ;
144
145 1
            $attr1 = $a->$method();
146 1
            $attr2 = $b->$method();
147
148 1
            if ($sortOrder === self::SORT_ASC) {
149
                return $attr1 > $attr2;
150
            }
151
152 1
            return $attr1 < $attr2;
153 1
        };
154
155
156 1
        $iterator = $this->words->getIterator();
157 1
        $iterator->uasort($sorter);
158
159 1
        $index = 0;
160 1
        foreach ($iterator as $word) {
161 1
            $word->setPosition($index);
162 1
            $index++;
163
        }
164 1
    }
165
166
    /**
167
     * @return int
168
     * @JMS\VirtualProperty
169
     * @JMS\SerializedName("max-count")
170
     */
171 5
    public function getWordsMaxCount()
172
    {
173 5
        $max = 0;
174
        /** @var Word $word */
175 5
        foreach ($this->words as $word) {
176 5
            if ($word->getCount() > $max) {
177 5
                $max = $word->getCount();
178
            }
179
        }
180 5
        return $max;
181
    }
182
183
    /**
184
     * @return int
185
     * @JMS\VirtualProperty
186
     * @JMS\SerializedName("count")
187
     */
188 3
    public function getWordsCount()
189
    {
190 3
        return $this->words->count();
191
    }
192
193
    /**
194
     * @param string $words
195
     * @param Filters $filters
196
     * @param int $maxWords
197
     */
198 7
    public function importWords($words, Filters $filters = null, $maxWords = 100)
199
    {
200 7
        $array = preg_split("/[\n\r\t ]+/", $words);
201
202 7
        foreach ($array as $word) {
203 7
            $this->importWord($word, $filters);
204 7
            if ($maxWords && $this->words->count() >= $maxWords) {
205 7
                break;
206
            }
207
        }
208 7
    }
209
210
    /**
211
     * @param string $word
212
     * @param Filters $filters
213
     */
214 7
    public function importWord($word, Filters $filters = null)
215
    {
216 7
        if ($filters) {
217 6
            $word = $filters->apply($word);
218
219 6
            if (!$word) {
220 2
                return;
221
            }
222
        }
223
224 7
        $entity = $this->getWordForText($word);
0 ignored issues
show
Bug introduced by
It seems like $word defined by $filters->apply($word) on line 217 can also be of type boolean; however, SixtyNine\Cloud\Model\WordsList::getWordForText() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
225
226 7
        if (!$entity) {
227 7
            $entity = new Word();
228
            $entity
229 7
                ->setList($this)
230 7
                ->setText($word)
0 ignored issues
show
Bug introduced by
It seems like $word defined by $filters->apply($word) on line 217 can also be of type boolean; however, SixtyNine\Cloud\Model\Word::setText() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
231 7
                ->setOrientation(Word::DIR_HORIZONTAL)
232 7
                ->setColor('#000000')
233
            ;
234 7
            $this->addWord($entity);
235
        }
236
237 7
        $entity->setCount($entity->getCount() + 1);
238 7
    }
239
240
    /**
241
     * @param string $html
242
     * @param Filters $filters
243
     * @param int $maxWords
244
     */
245 1
    public function importHtml($html, Filters $filters = null, $maxWords = 100)
246
    {
247 1
        if (!$html) {
248
            return;
249
        }
250
251 1
        $d = new \DOMDocument;
252 1
        $mock = new \DOMDocument;
253 1
        libxml_use_internal_errors(true);
254 1
        $d->loadHTML($html);
255 1
        libxml_use_internal_errors(false);
256 1
        $body = $d->getElementsByTagName('body')->item(0);
257 1
        if ($body) {
258 1
            foreach ($body->childNodes as $child) {
259 1
                $mock->appendChild($mock->importNode($child, true));
260
            }
261
        }
262 1
        $text = html_entity_decode(strip_tags($mock->saveHTML()));
263 1
        $this->importWords($text, $filters, $maxWords);
264 1
    }
265
266
    /**
267
     * @param string $url
268
     * @param Filters $filters
269
     * @param int $maxWords
270
     */
271 1
    public function importUrl($url, Filters $filters = null, $maxWords = 100)
272
    {
273 1
        $this->importHtml(file_get_contents($url), $filters, $maxWords);
274 1
    }
275
276
    /**
277
     * Apply the given $filters to the $list.
278
     * @param Filters $filters
279
     */
280
    public function filterWords(Filters $filters)
281
    {
282
        /** @var Word $word */
283
        foreach ($this->getWords() as $word) {
284
            $filtered = $filters->apply($word->getText());
285
            if (!$filtered) {
286
                $this->words->remove($word);
287
                continue;
288
            }
289
            $word->setText($filtered);
290
        }
291
    }
292
293
    /**
294
     * @param int $verticalProbability
295
     */
296 3
    public function randomizeOrientation($verticalProbability = 50)
297
    {
298
        /** @var \SixtyNine\CloudBundle\Entity\Word $word */
299 3
        foreach ($this->getWords() as $word) {
300
301 3
            $orientation = random_int(0, 99) < $verticalProbability
302 3
                ? Word::DIR_VERTICAL
303 3
                : Word::DIR_HORIZONTAL
304
            ;
305
306 3
            $word->setOrientation($orientation);
307
        }
308 3
    }
309
310
    /**
311
     * @param ColorGeneratorInterface $colorGenerator
312
     */
313 3
    public function randomizeColors(ColorGeneratorInterface $colorGenerator)
314
    {
315
        /** @var \SixtyNine\CloudBundle\Entity\Word $word */
316 3
        foreach ($this->getWords() as $word) {
317 3
            $word->setColor($colorGenerator->getNextColor());
318
        }
319 3
    }
320
321
}
322
323