Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/on-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: phpcs
run: composer phpcs
- name: phpstan
run: composer phpstan
- name: PHPUnit
run: composer test

Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"type": "library",
"require-dev": {
"phpunit/phpunit": "^12.0",
"squizlabs/php_codesniffer": "^3.9"
"squizlabs/php_codesniffer": "^3.9",
"phpstan/phpstan": "^2"
},
"require": {
"ext-json": "*",
Expand All @@ -35,6 +36,7 @@
"scripts": {
"test": "phpunit",
"phpcs": "phpcs",
"phpcbf": "phpcbf"
"phpcbf": "phpcbf",
"phpstan": "phpstan analyse --memory-limit=1G"
}
}
5 changes: 5 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
level: 8
paths:
- src
- tests
12 changes: 10 additions & 2 deletions src/ErrorsCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@

/**
* Collection of errors
*
* @implements ArrayAccess<int|string, string>
*/
class ErrorsCollection implements ArrayAccess, Countable, JsonSerializable, Stringable
{
/**
* Errors data
*/
/** @var array<int|string, string> */
private array $errors = [];

/**
Expand Down Expand Up @@ -158,29 +161,34 @@ public function count(): int

public function __serialize(): array
{
/** @var array{ParamsMode, array<int|string, string>} */
return [
$this->mode,
$this->errors
];
}

/**
* @param array{0: ParamsMode, 1: array<int|string, string>} $data
*/
public function __unserialize(array $data): void
{
[$this->mode, $this->errors] = $data;
}

/**
* @inheritDoc
* @return array<int|string, string>
*/
public function jsonSerialize(): array
{
/** @var array<int|string, string> */
return $this->errors;
}

/**
* Export data to array
*
* @return array
* @return array<int|string, string>
*/
public function toArray(): array
{
Expand Down
2 changes: 1 addition & 1 deletion src/ErrorsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function __construct()
*
* @param bool $asHTML Format using HTML?
*
* @return array|string an array of errors
* @return array<int|string, string>|string an array of errors
*/
public function getErrors(bool $asHTML = true): array|string
{
Expand Down
108 changes: 53 additions & 55 deletions tests/ErrorsCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function testDefaultConstructorParams(): void
$this->assertSame(
ParamsMode::Mode1,
$instance->mode,
message: 'When creating ErrorsCollection instance default mode should be MODE_1_PARAM, but isn\'t'
'When creating ErrorsCollection instance default mode should be MODE_1_PARAM, but isn\'t'
);
}

Expand All @@ -29,70 +29,70 @@ public function testConstructorParams(ParamsMode $mode): void
$this->assertSame(
$mode,
$instance->mode,
message: 'Mode ' . $mode->name . ' is different after creating instance'
'Mode ' . $mode->name . ' is different after creating instance'
);
}

public function testOffsetExists(): void
{
$instance = new ErrorsCollection(ParamsMode::Mode2);
$key = crc32(time());
$key = (string)crc32((string)time());
$this->assertArrayNotHasKey(
$key,
$instance,
message: 'Random key already exists in array but shouldn\'t'
'Random key already exists in array but shouldn\'t'
);
$instance->add($key, crc32(time()));
$instance->add($key, (string)crc32((string)time()));
$this->assertArrayHasKey(
$key,
$instance,
message: 'Random key was not found but it should'
'Random key was not found but it should'
);
}

public function testOffsetGet(): void
{
$offset = crc32(time());
$data = sha1(time());
$offset = (string)crc32((string)time());
$data = sha1((string)time());

$instance = new ErrorsCollection(ParamsMode::Mode2);
$instance->add($offset, $data);
$this->assertSame($instance[$offset], $data, message: 'Data is not same #1');
$this->assertSame($instance->offsetGet($offset), $data, message: 'Data is not same #2');
$this->assertNotNull($instance[$offset], message: 'Data is not same #3');
$this->assertNotNull($instance->offsetGet($offset), message: 'Data is not same #4');
$this->assertSame($instance[$offset], $data, 'Data is not same #1');
$this->assertSame($instance->offsetGet($offset), $data, 'Data is not same #2');
$this->assertNotNull($instance[$offset], 'Data is not same #3');
$this->assertNotNull($instance->offsetGet($offset), 'Data is not same #4');
}

public function testOffsetSet(): void
{
$offset = crc32(time());
$data = sha1(time());
$offset = (string)crc32((string)time());
$data = sha1((string)time());

$instance = new ErrorsCollection(ParamsMode::Mode2);
$instance->add($offset, $data);

$ndata = md5(time());
$ndata = md5((string)time());
$instance[$offset] = $ndata;
$this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #1');
$this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #1');

$ndata = soundex($ndata);
$ndata = soundex((string)$ndata);
$instance->offsetSet($offset, $ndata);
$this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #2');
$this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #2');

$offset = md5(microtime(true));
$offset = md5((string)microtime(true));

$ndata = metaphone($ndata);
$ndata = metaphone((string)$ndata);
$instance[$offset] = $ndata;
$this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #3');
$this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #3');

$ndata = soundex($ndata);
$ndata = soundex((string)$ndata);
$instance->offsetSet($offset, $ndata);
$this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #4');
$this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #4');
}

public function testOffsetUnset(): void
{
$offset = crc32(time());
$offset = (string)crc32((string)time());

$instance = new ErrorsCollection(ParamsMode::Mode2);

Expand All @@ -103,18 +103,18 @@ public function testOffsetUnset(): void
public function testIsEmpty(): void
{
$instance = new ErrorsCollection();
$this->assertTrue($instance->isEmpty(), message: 'Is not empty after creation');
$this->assertTrue($instance->isEmpty(), 'Is not empty after creation');

$instance->add(crc32(time()));
$this->assertNotTrue($instance->isEmpty(), message: 'Is still empty after one element was added');
$instance->add((string)crc32((string)time()));
$this->assertNotTrue($instance->isEmpty(), 'Is still empty after one element was added');
}

public function testClear(): void
{
$instance = new ErrorsCollection();
$instance->add(crc32(time()));
$instance->add((string)crc32((string)time()));
$instance->clear();
$this->assertEmpty($instance, message: 'Clear() must clear');
$this->assertEmpty($instance, 'Clear() must clear');
}

public function testStringConversion(): void
Expand All @@ -123,70 +123,71 @@ public function testStringConversion(): void

$this->assertEmpty(
(string)$instance,
message: 'Converted to string empty ErrorsCollection must be empty'
'Converted to string empty ErrorsCollection must be empty'
);
$this->assertEmpty(
$instance->getHtml(),
message: 'Converted to HTML empty ErrorsCollection must be empty'
'Converted to HTML empty ErrorsCollection must be empty'
);

$instance->add(crc32(time()));
$instance->add((string)crc32((string)time()));

$this->assertNotEmpty(
(string)$instance,
message: 'Converted to string not empty ErrorsCollection must be not empty'
'Converted to string not empty ErrorsCollection must be not empty'
);
$this->assertNotEmpty(
$instance->getHtml(),
message: 'Converted to HTML not empty ErrorsCollection must be not empty'
);

$this->assertIsString(
$instance->getHtml(),
message: 'getHTML must generate strings'
'Converted to HTML not empty ErrorsCollection must be not empty'
);
}

public function testCount(): void
{
$instance = new ErrorsCollection();
$this->assertCount(0, $instance, message: 'Count is not 0 when collection was just created');
$this->assertCount(0, $instance, 'Count is not 0 when collection was just created');

$instance->add(crc32(time()));
$this->assertSame(1, $instance->count(), message: 'Count must be 1 after one element was added');
$instance->add((string)crc32((string)time()));
$this->assertSame(1, $instance->count(), 'Count must be 1 after one element was added');

$this->assertCount(1, $instance, message: 'Count function doesn\'t work');
$this->assertCount(1, $instance, 'Count function doesn\'t work');
}

/**
* @return Generator<array{mode: ParamsMode, addParams: array<int, int|string>, expectedKey: int|string}>
*/
public static function provideTestAddData(): Generator
{
yield 'mode1' => [
'mode' => ParamsMode::Mode1,
'addParams' => [
md5(time())
md5((string)time())
],
'expectedKey' => 0
];

yield 'mode2asprefix' => [
'mode' => ParamsMode::Mode2AsPrefix,
'addParams' => [
md5(time())
md5((string)time())
],
'expectedKey' => 0
];

$key = crc32(time());
$key = (string)crc32((string)time());
yield 'mode2' => [
'mode' => ParamsMode::Mode2,
'addParams' => [
$key,
md5(time())
md5((string)time())
],
'expectedKey' => $key
];
}

/**
* @param string[] $addParams
*/
#[DataProvider('provideTestAddData')]
public function testAdd(ParamsMode $mode, array $addParams, int|string $expectedKey): void
{
Expand All @@ -202,29 +203,26 @@ public function testSerialization(): void
{
$instance = new ErrorsCollection(ParamsMode::Mode2);

$instance->add(md5(time()), sha1(time()));
$instance->add(crc32(time()), soundex(time()));
$instance->add(md5((string)time()), sha1((string)time()));
$instance->add((string)crc32((string)time()), soundex((string)time()));

$serialized = serialize($instance);
$unserialized = unserialize($serialized);
$this->assertInstanceOf(
ErrorsCollection::class,
$unserialized,
message: 'Unserialized data is not ErrorsCollection class type but it should be #1'
'Unserialized data is not ErrorsCollection class type but it should be #1'
);
$this->assertSame(
$instance->mode,
$unserialized->mode,
message: 'Serialization-unserialization fails #1'
'Serialization-unserialization fails #1'
);
$this->assertSame(
$instance->toArray(),
$unserialized->toArray(),
message: 'Serialization-unserialization fails #2'
'Serialization-unserialization fails #2'
);

$this->assertIsArray($instance->toArray(), message: 'toArray doesn\'t makes an array');
$this->assertIsString($instance->toJson(), message: 'toJSON doesn\'t makes a string');
}

public static function provideConstructorParams(): Generator
Expand Down
7 changes: 7 additions & 0 deletions tests/ErrorsTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public function testGetErrors(): void
{
$mock = $this->createFakeInstance();

assert(method_exists($mock, 'getErrors'));

$this->assertIsArray($mock->getErrors(false));
$this->assertIsString($mock->getErrors(true));
}
Expand All @@ -26,13 +28,18 @@ public function testGetHtmlErrors(): void
{
$mock = $this->createFakeInstance();

assert(method_exists($mock, 'getHtmlErrors'));

$this->assertIsString($mock->getHtmlErrors());
}

public function testHasAndSetError(): void
{
$mock = $this->createFakeInstance();

assert(method_exists($mock, 'hasError'));
assert(method_exists($mock, 'setErrors'));

$this->assertIsBool($mock->hasError());

$this->assertFalse($mock->hasError());
Expand Down