Usher   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 92.06%

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 10
dl 0
loc 153
rs 10
c 0
b 0
f 0
ccs 58
cts 63
cp 0.9206

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 2
A getPlace() 0 33 3
B searchPlace() 0 45 10
A getMask() 0 4 1
1
<?php
2
3
namespace SixtyNine\Cloud\Usher;
4
5
use Imagine\Image\Point;
6
use SixtyNine\Cloud\Drawer\Drawer;
7
use SixtyNine\Cloud\Factory\Logger;
8
use SixtyNine\Cloud\FontMetrics;
9
use SixtyNine\DataTypes\Box;
10
use SixtyNine\Cloud\Placer\PlacerInterface;
11
12
/**
13
 * Responsible to find a place for the word in the cloud
14
 */
15
16
class Usher
17
{
18
    const DEFAULT_MAX_TRIES = 100000;
19
20
    /** @var int */
21
    protected $maxTries;
22
23
    /** @var \SixtyNine\Cloud\Usher\MaskInterface */
24
    protected $mask;
25
26
    /** @var \SixtyNine\Cloud\Placer\PlacerInterface */
27
    protected $placer;
28
29
    /** @var \SixtyNine\Cloud\FontMetrics */
30
    protected $metrics;
31
32
    /** @var int */
33
    protected $precise;
34
35
    /** @var Logger */
36
    protected $logger;
37
38
    /**
39
     * @param int $imgWidth
40
     * @param int $imgHeight
41
     * @param PlacerInterface $placer
42 6
     * @param FontMetrics $metrics
43
     * @param bool $precise
44
     * @param int $maxTries
45
     */
46
    public function __construct(
47
        $imgWidth,
48
        $imgHeight,
49 6
        PlacerInterface $placer,
50 6
        FontMetrics $metrics,
51 6
        $precise = false,
52 6
        $maxTries = self::DEFAULT_MAX_TRIES
53 6
    ) {
54 6
        $this->metrics = $metrics;
55 6
        $this->imgHeight = $imgHeight;
0 ignored issues
show
Bug introduced by
The property imgHeight does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
56 6
        $this->imgWidth = $imgWidth;
0 ignored issues
show
Bug introduced by
The property imgWidth does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
57
        $this->maxTries = $maxTries;
58
        $this->placer = $placer;
59
        $this->logger = Logger::getInstance();
60
        $this->precise = $precise;
0 ignored issues
show
Documentation Bug introduced by
The property $precise was declared of type integer, but $precise is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
61
62
        $this->mask = $precise
63
            ? new PreciseMask($imgWidth, $imgHeight, $this->metrics)
64
            : new QuadTreeMask($imgWidth, $imgHeight)
65
        ;
66 6
    }
67
68 6
    /**
69 6
     * @param string $word
70 6
     * @param string $font
71 6
     * @param int $fontSize
72 6
     * @param int $angle
73 6
     * @return bool|Box
74 6
     */
75
    public function getPlace($word, $font, $fontSize, $angle)
76 6
    {
77
        $this->logger->log(
78
            sprintf(
79 6
                'Search place for "%s", font = %s(%s), angle = %s',
80 6
                $word,
81 6
                str_replace('.ttf', '', $font),
82
                $fontSize,
83 6
                $angle
84
            ),
85 6
            Logger::DEBUG
86
        );
87 6
88
        $bounds = new Box(0, 0, $this->imgWidth, $this->imgHeight);
89
        $size = $this->metrics->calculateSize($word, $font, $fontSize);
90
        $box = Drawer::getBoxForText(0, 0, $size->getWidth(), $size->getHeight(), $angle);
91 6
92 1
        $this->logger->log('  Text dimensions: ' . $size->getDimensions(), Logger::DEBUG);
93 1
94
        $place = $this->searchPlace($bounds, $box);
95
96 5
        if (!$place) {
97 5
            return false;
98
        }
99
100
        if ($this->precise) {
101
            $this->mask->addWordToMask($word, $place, $font, $fontSize, $angle);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SixtyNine\Cloud\Usher\MaskInterface as the method addWordToMask() does only exist in the following implementations of said interface: SixtyNine\Cloud\Usher\PreciseMask.

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...
102
            return $place;
103
        }
104
105
        $this->mask->add(new Point(0, 0), $place);
106
        return $place;
107 1
    }
108
109 1
    /**
110 1
     * Search a free place for a new box.
111 1
     * @param \SixtyNine\DataTypes\Box $bounds
112
     * @param \SixtyNine\DataTypes\Box $box
113 1
     * @return bool|Box
114 1
     */
115 1
    protected function searchPlace(Box $bounds, Box $box)
116 1
    {
117 1
118
        $this->logger->log('  Search place for ' . $box, Logger::DEBUG);
119
120
        $placeFound = false;
121 1
        $current = $this->placer->getFirstPlaceToTry();
122
        $curTry = 1;
123 1
        $currentBox = null;
124
125
        while (!$placeFound) {
126
127 1
            if (!$current) {
128
                return false;
129
            }
130
131 1
            if ($curTry > $this->maxTries) {
132 1
                return false;
133 1
            }
134
135 1
            $currentBox = $box->move($current->getX(), $current->getY());
136 1
137
            $outOfBounds = !$currentBox->inside($bounds);
138 1
139
            if (!$outOfBounds) {
140
                $placeFound = !$this->mask->overlaps($currentBox);
141
                $placeFound = $placeFound && !$outOfBounds;
142
            }
143
144
            $this->logger->log(sprintf(
145
                '  Trying %s --> %s',
146 6
                $currentBox,
147
                $outOfBounds ? 'Out of bounds' : ($placeFound ? 'OK' : 'Collision')
148
            ), Logger::DEBUG);
149 6
150
            if ($placeFound) {
151 6
                break;
152 6
            }
153 6
154 6
            $current = $this->placer->getNextPlaceToTry($current);
155
            $curTry++;
156 6
        }
157
158 6
        return $currentBox->inside($bounds) ? $currentBox : false;
159
    }
160
161
    /**
162 6
     * @return MaskInterface
163
     */
164
    public function getMask()
165
    {
166 6
        return $this->mask;
167
    }
168
}
169