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

@@ -1,39 +1,39 @@
name: PHP Composer name: PHP Composer
on: on:
push: push:
branches: [ "master" ] branches: ["master"]
pull_request: pull_request:
branches: [ "master" ] branches: ["master"]
permissions: permissions:
contents: read contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest
runs-on: ubuntu-latest steps:
- uses: actions/checkout@v4
steps: - uses: shivammathur/setup-php@1a18b2267f80291a81ca1d33e7c851fe09e7dfc4
- uses: actions/checkout@v4 with:
php-version: "8.3"
- name: Validate composer.json and composer.lock - name: Validate composer.json and composer.lock
run: composer validate --strict run: composer validate --strict
- name: Cache Composer packages - name: Cache Composer packages
id: composer-cache id: composer-cache
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: vendor path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-php- ${{ runner.os }}-php-
- name: Install dependencies - name: Install dependencies
run: composer install --prefer-dist --no-progress run: composer install --prefer-dist --no-progress
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" - name: Run test suite
# Docs: https://getcomposer.org/doc/articles/scripts.md run: composer run-script test
- name: Run test suite
run: composer run-script test

View File

@@ -1,25 +1,22 @@
language: php language: php
php: php:
- 5.6 - 8.3
- 7.0 - nightly
- 7.1
- nightly
matrix: matrix:
allow_failures: allow_failures:
- php: 5.6 - php: nightly
- php: nightly fast_finish: true
fast_finish: true
cache: cache:
directories: directories:
- $HOME/.composer/cache/files - $HOME/.composer/cache/files
before_script: before_script:
- phpenv global "$TRAVIS_PHP_VERSION" - phpenv global "$TRAVIS_PHP_VERSION"
- composer config extra.platform.php $TRAVIS_PHP_VERSION - composer config extra.platform.php $TRAVIS_PHP_VERSION
- composer require php-coveralls/php-coveralls - composer require php-coveralls/php-coveralls
install: install:
- flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress"
- composer install $flags - composer install $flags
script: script:
- ./vendor/bin/phpunit -c phpunit.xml --coverage-clover build/logs/clover.xml - ./vendor/bin/phpunit -c phpunit.xml --coverage-clover build/logs/clover.xml
after_script: after_script:
- php vendor/bin/coveralls -v - php vendor/bin/coveralls -v

View File

@@ -1,7 +1,12 @@
{ {
"name": "ivuorinen/palette", "name": "ivuorinen/palette",
"description": "Get most used colors from an image", "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", "homepage": "https://github.com/ivuorinen/palette",
"type": "library", "type": "library",
"license": "MIT", "license": "MIT",
@@ -16,22 +21,27 @@
"issues": "https://github.com/ivuorinen/palette/issues" "issues": "https://github.com/ivuorinen/palette/issues"
}, },
"autoload": { "autoload": {
"psr-0": { "ivuorinen\\Palette\\": "src/" }, "psr-4": {
"classmap": ["src/"] "ivuorinen\\Palette\\": "src/"
}
}, },
"autoload-dev": { "autoload-dev": {
"psr-0": { "psr-4": {
"ivuorinen\\Palette\\Tests\\": "tests/" "ivuorinen\\Palette\\Tests\\": "tests/"
} }
}, },
"require": { "require": {
"php": ">=5.6.0" "php": "^8.3",
"ext-gd": "*",
"ext-exif": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "10.5.10", "phpunit/phpunit": "^11",
"squizlabs/php_codesniffer": "3.9.0" "squizlabs/php_codesniffer": "^3"
}, },
"scripts": { "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"?> <?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"> <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">
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>
<testsuites> <testsuites>
<testsuite name="Test Suite"> <testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory> <directory suffix="Test.php">./tests</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<source>
<include>
<directory>./src/</directory>
</include>
</source>
</phpunit> </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 <?php
/** /**
* Palette * Palette
* Parses image and returns most used colors * Parses image and returns most used colors
@@ -36,6 +37,9 @@
namespace ivuorinen\Palette; namespace ivuorinen\Palette;
use GdImage;
use ivuorinen\Palette\Exceptions\GenericException;
/** /**
* Palette * Palette
* *
@@ -46,19 +50,19 @@ namespace ivuorinen\Palette;
class Palette class Palette
{ {
/** @var int Precision of palette, higher is more precise */ /** @var int Precision of palette, higher is more precise */
public $precision; public int $precision;
/** @var int Number of colors to return */ /** @var int Number of colors to return */
public $returnColors; public int $returnColors;
/** @var array Array of colors we use internally */ /** @var array Array of colors we use internally */
public $colorsArray; public array $colorsArray;
/** @var string Full path to image file name */ /** @var string Full path to image file name */
public $filename; public ?string $filename;
/** @var string Destination for .json-file, full path and filename */ /** @var string Destination for .json-file, full path and filename */
public $destination; public string $destination;
/** /**
* Constructor * Constructor
@@ -71,7 +75,7 @@ class Palette
* *
* @throws \Exception * @throws \Exception
*/ */
public function __construct($filename = null) public function __construct(string $filename)
{ {
// Define shortcut to directory separator // Define shortcut to directory separator
if (!defined('DS')) { if (!defined('DS')) {
@@ -100,10 +104,10 @@ class Palette
* @return bool Returns true always * @return bool Returns true always
* @throws \Exception * @throws \Exception
*/ */
public function run() public function run(): bool
{ {
if (empty($this->destination)) { if (empty($this->destination)) {
throw new \ErrorException("No destination provided, can't save."); throw new GenericException("No destination provided, can't save.");
} }
$this->getPalette(); $this->getPalette();
@@ -119,16 +123,16 @@ class Palette
* @return array|bool If we get array that has colors return the array * @return array|bool If we get array that has colors return the array
* @throws \Exception * @throws \Exception
*/ */
public function getPalette() public function getPalette(): array|bool
{ {
// We check for input // We check for input
if (empty($this->filename)) { if (empty($this->filename)) {
throw new \ErrorException('Image was not provided'); throw new GenericException('Image was not provided');
} }
// We check for readability // We check for readability
if (!is_readable($this->filename)) { 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(); $this->colorsArray = $this->countColors();
@@ -143,30 +147,26 @@ class Palette
/** /**
* countColors returns an array of colors in the image * countColors returns an array of colors in the image
* *
* @return array|boolean Array of colors sorted by times used * @return array Array of colors sorted by times used
* @throws \ErrorException * @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(); $colors = array();
// Test for image type // Test for image type
$img = $this->imageTypeToResource(); $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 // Get image size and check if it's really an image
$size = @getimagesize($this->filename); $size = @getimagesize($this->filename);
if ($size === false) { 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, // 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 ($x = 0; $x < $size[0]; $x += $this->precision) {
for ($y = 0; $y < $size[1]; $y += $this->precision) { for ($y = 0; $y < $size[1]; $y += $this->precision) {
$thisColor = imagecolorat($img, $x, $y); $thisColor = imagecolorat($img, $x, $y);
@@ -195,23 +195,23 @@ class Palette
* @return bool * @return bool
* @throws \Exception * @throws \Exception
*/ */
public function save() public function save(): bool
{ {
// Check for destination // Check for destination
if (empty($this->destination)) { if (empty($this->destination)) {
throw new \InvalidArgumentException('No destination given for save'); throw new \InvalidArgumentException('No destination given for save');
} }
// Check destination writability // Check destination for write permissions
$this->checkDestination(); $this->checkDestination();
// Check for data we should write // Check for data we should write
if (empty($this->colorsArray)) { 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 // 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 // Save and return the result of save operation
file_put_contents($this->destination, $colorsData); file_put_contents($this->destination, $colorsData);
@@ -225,30 +225,30 @@ class Palette
* Function takes $this->filename and returns * Function takes $this->filename and returns
* imagecreatefrom{gif|jpeg|png} for further processing * imagecreatefrom{gif|jpeg|png} for further processing
* *
* @return resource Image resource based on content * @return GdImage Image resource based on content
* @throws \ErrorException * @throws GenericException
*/ */
private function imageTypeToResource() private function imageTypeToResource(): GdImage
{ {
try { try {
if (filesize($this->filename) < 12) { 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); $type = exif_imagetype($this->filename);
} catch (\Exception $e) { } catch (\Exception $e) {
throw new \ErrorException($e->getMessage()); throw new GenericException($e->getMessage());
} }
switch ($type) { switch ($type) {
case '1': // IMAGETYPE_GIF case '1': // IMAGETYPE_GIF
return @imagecreatefromgif($this->filename); return @\imagecreatefromgif($this->filename);
case '2': // IMAGETYPE_JPEG case '2': // IMAGETYPE_JPEG
return @imagecreatefromjpeg($this->filename); return @\imagecreatefromjpeg($this->filename);
case '3': // IMAGETYPE_PNG case '3': // IMAGETYPE_PNG
return @imagecreatefrompng($this->filename); return @\imagecreatefrompng($this->filename);
default: default:
$image_type_code = image_type_to_mime_type($type); $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 * @return boolean True or false, with exceptions
* @throws \Exception * @throws \Exception
*/ */
private function checkDestination() private function checkDestination(): bool
{ {
$destination_dir = dirname($this->destination); $destination_dir = dirname($this->destination);
// Test if we have destination directory // Test if we have destination directory
if (!@mkdir($destination_dir, 0755) && !is_dir($destination_dir)) { 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 // Test if we can write to it
@@ -279,7 +279,7 @@ class Palette
chmod($destination_dir, 0755); chmod($destination_dir, 0755);
if (!is_writable($destination_dir)) { 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; return true;

View File

@@ -6,22 +6,22 @@ use PHPUnit\Framework\TestCase;
class PaletteTest extends 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->assertInstanceOf('ivuorinen\Palette\Palette', $palette);
$this->assertIsInt($palette->precision); $this->assertIsInt($palette->precision);
$this->assertIsInt($palette->returnColors); $this->assertIsInt($palette->returnColors);
$this->assertIsArray($palette->colorsArray); $this->assertIsArray($palette->colorsArray);
$this->assertNull($palette->filename); $this->assertIsString($palette->filename);
$this->assertIsString($palette->destination); $this->assertIsString($palette->destination);
} }
public function testKnownImagesWithOneColor() public function testKnownImagesWithOneColor(): void
{ {
$location = __DIR__ . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR; $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) { foreach ($images as $imageFile => $hex) {
$image = $location . $imageFile; $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; $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) { foreach ($images as $imageFile) {
$image = $location . $imageFile; $image = $location . $imageFile;
@@ -49,7 +49,7 @@ class PaletteTest extends TestCase
} }
} }
public function testFailureNoImage() public function testFailureNoImage(): void
{ {
$palette = new \ivuorinen\Palette\Palette(''); $palette = new \ivuorinen\Palette\Palette('');
$this->expectException(\ErrorException::class); $this->expectException(\ErrorException::class);
@@ -57,11 +57,11 @@ class PaletteTest extends TestCase
$palette->getPalette(); $palette->getPalette();
} }
public function testFailureNotAnImage() public function testFailureNotAnImage(): void
{ {
$palette = new \ivuorinen\Palette\Palette();
$palette->filename = 'NOT_HERE';
$this->expectException(\ErrorException::class); $this->expectException(\ErrorException::class);
$palette = new \ivuorinen\Palette\Palette('NOT_HERE');
$this->expectExceptionMessage('Image ' . $palette->filename . ' is not readable'); $this->expectExceptionMessage('Image ' . $palette->filename . ' is not readable');
$palette->getPalette(); $palette->getPalette();