| Total Complexity | 54 |
| Total Lines | 447 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like Image 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.
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 Image, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 43 | class Image |
||
| 44 | { |
||
| 45 | /** |
||
| 46 | * The current width of the image |
||
| 47 | * @var int |
||
| 48 | */ |
||
| 49 | protected int $width = 0; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * The current height of the image |
||
| 53 | * @var int |
||
| 54 | */ |
||
| 55 | protected int $height = 0; |
||
| 56 | |||
| 57 | /** |
||
| 58 | * The image bits information |
||
| 59 | * @var int|null |
||
| 60 | */ |
||
| 61 | protected ?int $bits = null; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * The image mime type |
||
| 65 | * @var string |
||
| 66 | */ |
||
| 67 | protected string $mimetype = ''; |
||
| 68 | |||
| 69 | /** |
||
| 70 | * The GD image resource instance |
||
| 71 | * @var GdImage |
||
| 72 | */ |
||
| 73 | protected GdImage $image; |
||
| 74 | |||
| 75 | /** |
||
| 76 | * Create new instance |
||
| 77 | * @param string $filePath |
||
| 78 | */ |
||
| 79 | public function __construct(protected string $filePath) |
||
| 80 | { |
||
| 81 | if (extension_loaded('gd') === false) { |
||
| 82 | throw new RuntimeException('PHP GD extension is not installed or enabled'); |
||
| 83 | } |
||
| 84 | |||
| 85 | // Now load image |
||
| 86 | $this->loadImage($filePath); |
||
| 87 | } |
||
| 88 | |||
| 89 | /** |
||
| 90 | * Resize the current image |
||
| 91 | * @param int $width |
||
| 92 | * @param int $height |
||
| 93 | * @param bool|null $useWidth if "true" will use the width as scale factor, if "false" |
||
| 94 | * will use the height if "null" will use the minimum scale between width and height. |
||
| 95 | * @return void |
||
| 96 | */ |
||
| 97 | public function resize(int $width = 0, int $height = 0, ?bool $useWidth = null): void |
||
| 98 | { |
||
| 99 | if ($width <= 0) { |
||
| 100 | $width = $this->width; |
||
| 101 | } |
||
| 102 | |||
| 103 | if ($height <= 0) { |
||
| 104 | $height = $this->height; |
||
| 105 | } |
||
| 106 | |||
| 107 | // imagecreatetruecolor need minimum width and height to be >= 1 |
||
| 108 | if ($width < 1 || $height < 1) { |
||
| 109 | return; |
||
| 110 | } |
||
| 111 | |||
| 112 | $scale = 1; |
||
| 113 | $scaleWidth = (int)($width / $this->width); |
||
| 114 | $scaleHeight = (int)($height / $this->height); |
||
| 115 | if ($useWidth) { |
||
| 116 | $scale = $scaleWidth; |
||
| 117 | } elseif ($useWidth === false) { |
||
| 118 | $scale = $scaleHeight; |
||
| 119 | } else { |
||
| 120 | $scale = (int) min($scaleWidth, $scaleHeight); |
||
| 121 | } |
||
| 122 | |||
| 123 | if ( |
||
| 124 | $scale === 1 && |
||
| 125 | $scaleWidth === $scaleHeight && |
||
| 126 | $this->mimetype !== 'image/png' |
||
| 127 | ) { |
||
| 128 | return; |
||
| 129 | } |
||
| 130 | |||
| 131 | $newWidth = (int)($this->width * $scale); |
||
| 132 | $newHeight = (int)($this->height * $scale); |
||
| 133 | $xpos = (int)(($width - $newWidth) / 2); |
||
| 134 | $ypos = (int)(($height - $newHeight) / 2); |
||
| 135 | $oldImage = $this->image; |
||
| 136 | $this->image = imagecreatetruecolor($width, $height); |
||
|
|
|||
| 137 | if ($this->mimetype === 'image/png') { |
||
| 138 | imagealphablending($this->image, false); |
||
| 139 | imagesavealpha($this->image, false); |
||
| 140 | $background = imagecolorallocatealpha($this->image, 255, 255, 255, 127); |
||
| 141 | if ($background === false) { |
||
| 142 | throw new RuntimeException('Can not allocate color alpha for PNG image'); |
||
| 143 | } |
||
| 144 | imagecolortransparent($this->image, $background); |
||
| 145 | } else { |
||
| 146 | $background = imagecolorallocate($this->image, 255, 255, 255); |
||
| 147 | if ($background === false) { |
||
| 148 | throw new RuntimeException('Can not allocate color for image resize'); |
||
| 149 | } |
||
| 150 | } |
||
| 151 | imagefilledrectangle($this->image, 0, 0, $width, $height, $background); |
||
| 152 | imagecopyresampled( |
||
| 153 | $this->image, |
||
| 154 | $oldImage, |
||
| 155 | $xpos, |
||
| 156 | $ypos, |
||
| 157 | 0, |
||
| 158 | 0, |
||
| 159 | $newWidth, |
||
| 160 | $newHeight, |
||
| 161 | $this->width, |
||
| 162 | $this->height |
||
| 163 | ); |
||
| 164 | imagedestroy($oldImage); |
||
| 165 | $this->width = $width; |
||
| 166 | $this->height = $height; |
||
| 167 | } |
||
| 168 | |||
| 169 | /** |
||
| 170 | * Write the watermark in the given image |
||
| 171 | * @param Image $watermark |
||
| 172 | * @param string $position |
||
| 173 | * @return void |
||
| 174 | */ |
||
| 175 | public function watermark(Image $watermark, string $position = 'bottomright'): void |
||
| 176 | { |
||
| 177 | $watermarkPosX = 0; |
||
| 178 | $watermarkPosY = 0; |
||
| 179 | |||
| 180 | $positionMaps = [ |
||
| 181 | 'topleft' => [ |
||
| 182 | 0, |
||
| 183 | 0 |
||
| 184 | ], |
||
| 185 | 'topcenter' => [ |
||
| 186 | intval(($this->width - $watermark->getWidth()) / 2), |
||
| 187 | 0 |
||
| 188 | ], |
||
| 189 | 'topright' => [ |
||
| 190 | $this->width - $watermark->getWidth(), |
||
| 191 | 0 |
||
| 192 | ], |
||
| 193 | 'middleleft' => [ |
||
| 194 | 0, |
||
| 195 | intval(($this->height - $watermark->getHeight()) / 2) |
||
| 196 | ], |
||
| 197 | 'middlecenter' => [ |
||
| 198 | intval(($this->width - $watermark->getWidth()) / 2), |
||
| 199 | intval(($this->height - $watermark->getHeight()) / 2) |
||
| 200 | ], |
||
| 201 | 'middleright' => [ |
||
| 202 | $this->width - $watermark->getWidth(), |
||
| 203 | intval(($this->height - $watermark->getHeight()) / 2) |
||
| 204 | ], |
||
| 205 | 'bottomleft' => [ |
||
| 206 | 0, |
||
| 207 | $this->height - $watermark->getHeight() |
||
| 208 | ], |
||
| 209 | 'bottomcenter' => [ |
||
| 210 | intval(($this->width - $watermark->getWidth()) / 2), |
||
| 211 | $this->height - $watermark->getHeight() |
||
| 212 | ], |
||
| 213 | 'bottomright' => [ |
||
| 214 | $this->width - $watermark->getWidth(), |
||
| 215 | $this->height - $watermark->getHeight() |
||
| 216 | ], |
||
| 217 | ]; |
||
| 218 | |||
| 219 | if (isset($positionMaps[$position])) { |
||
| 220 | $watermarkPosX = $positionMaps[$position][0]; |
||
| 221 | $watermarkPosY = $positionMaps[$position][1]; |
||
| 222 | } |
||
| 223 | imagealphablending($this->image, true); |
||
| 224 | imagesavealpha($this->image, true); |
||
| 225 | imagecopy( |
||
| 226 | $this->image, |
||
| 227 | $watermark->getImage(), |
||
| 228 | $watermarkPosX, |
||
| 229 | $watermarkPosY, |
||
| 230 | 0, |
||
| 231 | 0, |
||
| 232 | $watermark->getWidth(), |
||
| 233 | $watermark->getHeight() |
||
| 234 | ); |
||
| 235 | imagedestroy($watermark->getImage()); |
||
| 236 | } |
||
| 237 | |||
| 238 | /** |
||
| 239 | * Crop the current image |
||
| 240 | * @param int $topX |
||
| 241 | * @param int $topY |
||
| 242 | * @param int $bottomX |
||
| 243 | * @param int $bottomY |
||
| 244 | * @return void |
||
| 245 | */ |
||
| 246 | public function crop(int $topX, int $topY, int $bottomX, int $bottomY): void |
||
| 247 | { |
||
| 248 | $oldImage = $this->image; |
||
| 249 | $width = $bottomY - $topX; |
||
| 250 | $height = $bottomY - $topY; |
||
| 251 | |||
| 252 | // imagecreatetruecolor need minimum width and height to be >= 1 |
||
| 253 | if ($width < 1 || $height < 1) { |
||
| 254 | return; |
||
| 255 | } |
||
| 256 | |||
| 257 | $this->image = imagecreatetruecolor($width, $height); |
||
| 258 | imagecopy( |
||
| 259 | $this->image, |
||
| 260 | $oldImage, |
||
| 261 | 0, |
||
| 262 | 0, |
||
| 263 | $topX, |
||
| 264 | $topY, |
||
| 265 | $this->width, |
||
| 266 | $this->height |
||
| 267 | ); |
||
| 268 | imagedestroy($oldImage); |
||
| 269 | $this->width = $width; |
||
| 270 | $this->height = $height; |
||
| 271 | } |
||
| 272 | |||
| 273 | /** |
||
| 274 | * Rotate the current image |
||
| 275 | * @param float $degree |
||
| 276 | * @param string $color |
||
| 277 | * @return void |
||
| 278 | */ |
||
| 279 | public function rotate(float $degree, string $color = 'ffffff'): void |
||
| 293 | } |
||
| 294 | |||
| 295 | /** |
||
| 296 | * Write text in the current image |
||
| 297 | * @param string $text |
||
| 298 | * @param int $x |
||
| 299 | * @param int $y |
||
| 300 | * @param int $size |
||
| 301 | * @param string $color |
||
| 302 | * @return void |
||
| 303 | */ |
||
| 304 | public function text( |
||
| 305 | string $text, |
||
| 306 | int $x = 0, |
||
| 307 | int $y = 0, |
||
| 308 | int $size = 5, |
||
| 309 | string $color = '000000' |
||
| 310 | ): void { |
||
| 311 | [$red, $green, $blue] = self::htmlToRGBColor($color); |
||
| 312 | $background = imagecolorallocate($this->image, $red, $green, $blue); |
||
| 313 | if ($background === false) { |
||
| 314 | throw new RuntimeException('Can not allocate color for image text'); |
||
| 315 | } |
||
| 316 | imagestring($this->image, $size, $x, $y, $text, $background); |
||
| 317 | } |
||
| 318 | |||
| 319 | public function merge( |
||
| 320 | Image $merge, |
||
| 321 | int $x = 0, |
||
| 322 | int $y = 0, |
||
| 323 | int $opacity = 100 |
||
| 324 | ): void { |
||
| 325 | imagecopymerge( |
||
| 326 | $this->image, |
||
| 327 | $merge->getImage(), |
||
| 328 | $x, |
||
| 329 | $y, |
||
| 330 | 0, |
||
| 331 | 0, |
||
| 332 | $merge->getWidth(), |
||
| 333 | $merge->getHeight(), |
||
| 334 | $opacity |
||
| 335 | ); |
||
| 336 | } |
||
| 337 | |||
| 338 | /** |
||
| 339 | * Apply filter to the current image |
||
| 340 | * @param int $filter |
||
| 341 | * @param array<mixed>|int|float|bool ...$args |
||
| 342 | * @return void |
||
| 343 | */ |
||
| 344 | public function filter(int $filter, array|int|float|bool ...$args): void |
||
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * Save the current image into the given path |
||
| 351 | * @param string $filePath |
||
| 352 | * @param int $quality only used for JPEG image |
||
| 353 | * @return void |
||
| 354 | */ |
||
| 355 | public function save(string $filePath, int $quality = 90): void |
||
| 356 | { |
||
| 357 | $extension = pathinfo($filePath, PATHINFO_EXTENSION); |
||
| 358 | if (in_array($extension, ['jpeg', 'jpg'])) { |
||
| 359 | imagejpeg($this->image, $filePath, $quality); |
||
| 360 | } elseif ($extension === 'png') { |
||
| 361 | imagepng($this->image, $filePath); |
||
| 362 | } elseif ($extension === 'gif') { |
||
| 363 | imagegif($this->image, $filePath); |
||
| 364 | } |
||
| 365 | |||
| 366 | imagedestroy($this->image); |
||
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * Return the current width |
||
| 371 | * @return int |
||
| 372 | */ |
||
| 373 | public function getWidth(): int |
||
| 376 | } |
||
| 377 | |||
| 378 | /** |
||
| 379 | * Return the current height |
||
| 380 | * @return int |
||
| 381 | */ |
||
| 382 | public function getHeight(): int |
||
| 383 | { |
||
| 384 | return $this->height; |
||
| 385 | } |
||
| 386 | |||
| 387 | /** |
||
| 388 | * Return the image mime type |
||
| 389 | * @return string |
||
| 390 | */ |
||
| 391 | public function getMimetype(): string |
||
| 392 | { |
||
| 393 | return $this->mimetype; |
||
| 394 | } |
||
| 395 | |||
| 396 | |||
| 397 | /** |
||
| 398 | * Return the current image instance |
||
| 399 | * @return GdImage |
||
| 400 | */ |
||
| 401 | public function getImage(): GdImage |
||
| 402 | { |
||
| 403 | return $this->image; |
||
| 404 | } |
||
| 405 | |||
| 406 | /** |
||
| 407 | * Convert the HTML color to RGB |
||
| 408 | * @param string $color |
||
| 409 | * @return array{0: int<0, 255>, 1:int<0, 255>, 2:int<0, 255>} the color with each index R, G, B |
||
| 410 | */ |
||
| 411 | public static function htmlToRGBColor(string $color): array |
||
| 449 | } |
||
| 450 | |||
| 451 | /** |
||
| 452 | * Load the image into GD instance |
||
| 453 | * @param string $filePath |
||
| 454 | * @return void |
||
| 455 | */ |
||
| 456 | protected function loadImage(string $filePath): void |
||
| 490 | } |
||
| 491 | } |
||
| 492 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.