feat: php 8.3, phpunit 11 (#57)

* Upgrade runner php to 8.3, phpunit to 11
* Fix tests, tweaked the code, added missing ext- to composer.json
* Fix exception related code smells
This commit is contained in:
Ismo Vuorinen
2024-02-17 12:47:20 +02:00
committed by GitHub
parent fa16ba60aa
commit 197d08014b
8 changed files with 137 additions and 105 deletions

11
.editorconfig Normal file
View File

@@ -0,0 +1,11 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4

View File

@@ -2,21 +2,24 @@ name: PHP Composer
on:
push:
branches: [ "master" ]
branches: ["master"]
pull_request:
branches: [ "master" ]
branches: ["master"]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@1a18b2267f80291a81ca1d33e7c851fe09e7dfc4
with:
php-version: "8.3"
- name: Validate composer.json and composer.lock
run: composer validate --strict
@@ -32,8 +35,5 @@ jobs:
- name: Install dependencies
run: composer install --prefer-dist --no-progress
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
- name: Run test suite
run: composer run-script test

View File

@@ -1,12 +1,9 @@
language: php
php:
- 5.6
- 7.0
- 7.1
- 8.3
- nightly
matrix:
allow_failures:
- php: 5.6
- php: nightly
fast_finish: true
cache:

View File

@@ -1,7 +1,12 @@
{
"name": "ivuorinen/palette",
"description": "Get most used colors from an image",
"keywords": ["image", "colors", "palette", "psr-2"],
"keywords": [
"image",
"colors",
"palette",
"psr-2"
],
"homepage": "https://github.com/ivuorinen/palette",
"type": "library",
"license": "MIT",
@@ -16,22 +21,27 @@
"issues": "https://github.com/ivuorinen/palette/issues"
},
"autoload": {
"psr-0": { "ivuorinen\\Palette\\": "src/" },
"classmap": ["src/"]
"psr-4": {
"ivuorinen\\Palette\\": "src/"
}
},
"autoload-dev": {
"psr-0": {
"psr-4": {
"ivuorinen\\Palette\\Tests\\": "tests/"
}
},
"require": {
"php": ">=5.6.0"
"php": "^8.3",
"ext-gd": "*",
"ext-exif": "*"
},
"require-dev": {
"phpunit/phpunit": "10.5.10",
"squizlabs/php_codesniffer": "3.9.0"
"phpunit/phpunit": "^11",
"squizlabs/php_codesniffer": "^3"
},
"scripts": {
"test": "vendor/bin/phpunit"
"test": "vendor/bin/phpunit",
"lint": "vendor/bin/phpcs",
"lint-fix": "vendor/bin/phpcbf"
}
}

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" colors="true" processIsolation="false" stopOnFailure="false" bootstrap="vendor/autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" colors="true" processIsolation="false" stopOnFailure="false" bootstrap="vendor/autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>./src/</directory>
</include>
</source>
</phpunit>

View File

@@ -0,0 +1,14 @@
<?php
namespace ivuorinen\Palette\Exceptions;
use ErrorException;
use Throwable;
class GenericException extends ErrorException
{
public function __construct(string $message, int $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -1,4 +1,5 @@
<?php
/**
* Palette
* Parses image and returns most used colors
@@ -36,6 +37,9 @@
namespace ivuorinen\Palette;
use GdImage;
use ivuorinen\Palette\Exceptions\GenericException;
/**
* Palette
*
@@ -46,19 +50,19 @@ namespace ivuorinen\Palette;
class Palette
{
/** @var int Precision of palette, higher is more precise */
public $precision;
public int $precision;
/** @var int Number of colors to return */
public $returnColors;
public int $returnColors;
/** @var array Array of colors we use internally */
public $colorsArray;
public array $colorsArray;
/** @var string Full path to image file name */
public $filename;
public ?string $filename;
/** @var string Destination for .json-file, full path and filename */
public $destination;
public string $destination;
/**
* Constructor
@@ -71,7 +75,7 @@ class Palette
*
* @throws \Exception
*/
public function __construct($filename = null)
public function __construct(string $filename)
{
// Define shortcut to directory separator
if (!defined('DS')) {
@@ -100,10 +104,10 @@ class Palette
* @return bool Returns true always
* @throws \Exception
*/
public function run()
public function run(): bool
{
if (empty($this->destination)) {
throw new \ErrorException("No destination provided, can't save.");
throw new GenericException("No destination provided, can't save.");
}
$this->getPalette();
@@ -119,16 +123,16 @@ class Palette
* @return array|bool If we get array that has colors return the array
* @throws \Exception
*/
public function getPalette()
public function getPalette(): array|bool
{
// We check for input
if (empty($this->filename)) {
throw new \ErrorException('Image was not provided');
throw new GenericException('Image was not provided');
}
// We check for readability
if (!is_readable($this->filename)) {
throw new \ErrorException("Image {$this->filename} is not readable");
throw new GenericException("Image {$this->filename} is not readable");
}
$this->colorsArray = $this->countColors();
@@ -143,30 +147,26 @@ class Palette
/**
* countColors returns an array of colors in the image
*
* @return array|boolean Array of colors sorted by times used
* @throws \ErrorException
* @return array Array of colors sorted by times used
* @throws GenericException
*/
private function countColors()
private function countColors(): array
{
$this->precision = max(1, abs((int)$this->precision));
$this->precision = max(1, abs($this->precision));
$colors = array();
// Test for image type
$img = $this->imageTypeToResource();
if (!$img && $img !== null) {
throw new \ErrorException("Unable to open: {$this->filename}");
}
// Get image size and check if it's really an image
$size = @getimagesize($this->filename);
if ($size === false) {
throw new \ErrorException("Unable to get image size data: {$this->filename}");
throw new GenericException("Unable to get image size data: {$this->filename}");
}
// This is pretty naive approach,
// but looping through the image is only way I thought of
// but looping through the image is the only way I thought of
for ($x = 0; $x < $size[0]; $x += $this->precision) {
for ($y = 0; $y < $size[1]; $y += $this->precision) {
$thisColor = imagecolorat($img, $x, $y);
@@ -195,23 +195,23 @@ class Palette
* @return bool
* @throws \Exception
*/
public function save()
public function save(): bool
{
// Check for destination
if (empty($this->destination)) {
throw new \InvalidArgumentException('No destination given for save');
}
// Check destination writability
// Check destination for write permissions
$this->checkDestination();
// Check for data we should write
if (empty($this->colorsArray)) {
throw new \ErrorException("Couldn't detect colors from image: {$this->filename}");
throw new GenericException("Couldn't detect colors from image: {$this->filename}");
}
// Encode for saving
$colorsData = json_encode($this->colorsArray);
$colorsData = json_encode($this->colorsArray, JSON_THROW_ON_ERROR);
// Save and return the result of save operation
file_put_contents($this->destination, $colorsData);
@@ -225,30 +225,30 @@ class Palette
* Function takes $this->filename and returns
* imagecreatefrom{gif|jpeg|png} for further processing
*
* @return resource Image resource based on content
* @throws \ErrorException
* @return GdImage Image resource based on content
* @throws GenericException
*/
private function imageTypeToResource()
private function imageTypeToResource(): GdImage
{
try {
if (filesize($this->filename) < 12) {
throw new \ErrorException('File size smaller than 12');
throw new GenericException('File size smaller than 12');
}
$type = exif_imagetype($this->filename);
} catch (\Exception $e) {
throw new \ErrorException($e->getMessage());
throw new GenericException($e->getMessage());
}
switch ($type) {
case '1': // IMAGETYPE_GIF
return @imagecreatefromgif($this->filename);
return @\imagecreatefromgif($this->filename);
case '2': // IMAGETYPE_JPEG
return @imagecreatefromjpeg($this->filename);
return @\imagecreatefromjpeg($this->filename);
case '3': // IMAGETYPE_PNG
return @imagecreatefrompng($this->filename);
return @\imagecreatefrompng($this->filename);
default:
$image_type_code = image_type_to_mime_type($type);
throw new \ErrorException("Unknown image type: {$image_type_code} ({$type}): {$this->filename}");
throw new GenericException("Unknown image type: {$image_type_code} ({$type}): {$this->filename}");
}
}
@@ -263,13 +263,13 @@ class Palette
* @return boolean True or false, with exceptions
* @throws \Exception
*/
private function checkDestination()
private function checkDestination(): bool
{
$destination_dir = dirname($this->destination);
// Test if we have destination directory
if (!@mkdir($destination_dir, 0755) && !is_dir($destination_dir)) {
throw new \ErrorException("Couldn't create missing destination dir: {$destination_dir}");
throw new GenericException("Couldn't create missing destination dir: {$destination_dir}");
}
// Test if we can write to it
@@ -279,7 +279,7 @@ class Palette
chmod($destination_dir, 0755);
if (!is_writable($destination_dir)) {
throw new \ErrorException("Destination directory not writable: {$destination_dir}");
throw new GenericException("Destination directory not writable: {$destination_dir}");
}
return true;

View File

@@ -6,22 +6,22 @@ use PHPUnit\Framework\TestCase;
class PaletteTest extends TestCase
{
public function testClassIsFoundAndHasDefaultAttributes()
public function testClassIsFoundAndHasDefaultAttributes(): void
{
$palette = new \ivuorinen\Palette\Palette();
$palette = new \ivuorinen\Palette\Palette('');
$this->assertInstanceOf('ivuorinen\Palette\Palette', $palette);
$this->assertIsInt($palette->precision);
$this->assertIsInt($palette->returnColors);
$this->assertIsArray($palette->colorsArray);
$this->assertNull($palette->filename);
$this->assertIsString($palette->filename);
$this->assertIsString($palette->destination);
}
public function testKnownImagesWithOneColor()
public function testKnownImagesWithOneColor(): void
{
$location = __DIR__ . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR;
$images = [ 'black.png' => '000000', 'red.png' => 'CC3333' ];
$images = ['black.png' => '000000', 'red.png' => 'CC3333'];
foreach ($images as $imageFile => $hex) {
$image = $location . $imageFile;
@@ -34,10 +34,10 @@ class PaletteTest extends TestCase
}
}
public function testKnownImagesWithManyColors()
public function testKnownImagesWithManyColors(): void
{
$location = __DIR__ . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR;
$images = [ 'example.gif', 'example.jpg', 'example.png' ];
$images = ['example.gif', 'example.jpg', 'example.png'];
foreach ($images as $imageFile) {
$image = $location . $imageFile;
@@ -49,7 +49,7 @@ class PaletteTest extends TestCase
}
}
public function testFailureNoImage()
public function testFailureNoImage(): void
{
$palette = new \ivuorinen\Palette\Palette('');
$this->expectException(\ErrorException::class);
@@ -57,11 +57,11 @@ class PaletteTest extends TestCase
$palette->getPalette();
}
public function testFailureNotAnImage()
public function testFailureNotAnImage(): void
{
$palette = new \ivuorinen\Palette\Palette();
$palette->filename = 'NOT_HERE';
$this->expectException(\ErrorException::class);
$palette = new \ivuorinen\Palette\Palette('NOT_HERE');
$this->expectExceptionMessage('Image ' . $palette->filename . ' is not readable');
$palette->getPalette();