diff --git a/.gitignore b/.gitignore index 8b22654..6d847b0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ .idea/ .php_cs.cache composer.lock +composer.phar tests.log +/.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index 1aa3397..755b941 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,9 @@ before_install: - docker run -d -p 6379:6379 redislabs/redisearch:edge --protected-mode no --loadmodule /usr/lib/redis/modules/redisearch.so language: php php: - - 7.0 - - 7.1 - - 7.2 - 7.3 + - 7.4 + - 8.0 before_script: - printf "yes\n" | pecl install igbinary - composer install diff --git a/composer.json b/composer.json index 9d0d8dd..05d2633 100644 --- a/composer.json +++ b/composer.json @@ -21,18 +21,18 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "php": ">=7", + "php": ">=7.3||>=8.0", "psr/log": "^1.0", - "ethanhann/redis-raw": "^0.3" + "ethanhann/redis-raw": "v1.x-dev" }, "require-dev": { - "phpunit/phpunit": "^6.0", + "phpunit/phpunit": "^8.0", "mockery/mockery": "^0.9.9", "predis/predis": "^1.1", "friendsofphp/php-cs-fixer": "^2.2", "consolidation/robo": "^2.0", "monolog/monolog": "^1.23", - "cheprasov/php-redis-client": "^1.8", - "ukko/phpredis-phpdoc": "^5.0@beta" + "ukko/phpredis-phpdoc": "^5.0@beta", + "cheprasov/php-redis-client": "^1.9" } } diff --git a/docs-src/indexing.md b/docs-src/indexing.md index 5591147..0c74dbd 100644 --- a/docs-src/indexing.md +++ b/docs-src/indexing.md @@ -227,3 +227,17 @@ An alias can be deleted like this: ```php-inline $index->deleteAlias('foo'); ``` + +## Managing an Index + +Whether or not an index exists can be checked: + +```php-inline +$indexExists = $index->exists(); +``` + +An index can be removed: + +```php-inline +$index->drop(); +``` diff --git a/src/Aggregate/Builder.php b/src/Aggregate/Builder.php index 7a55d59..d18d1d6 100644 --- a/src/Aggregate/Builder.php +++ b/src/Aggregate/Builder.php @@ -68,7 +68,7 @@ public function load(array $fieldNames): BuilderInterface * @param CanBecomeArrayInterface|array $reducer * @return BuilderInterface */ - public function groupBy($fieldName, CanBecomeArrayInterface $reducer = null): BuilderInterface + public function groupBy($fieldName = [], CanBecomeArrayInterface $reducer = null): BuilderInterface { $this->pipeline[] = new GroupBy(is_array($fieldName) ? $fieldName : [$fieldName]); if (!is_null($reducer)) { diff --git a/src/Aggregate/Operations/AbstractFieldNameOperation.php b/src/Aggregate/Operations/AbstractFieldNameOperation.php index 63d139e..6e440d4 100644 --- a/src/Aggregate/Operations/AbstractFieldNameOperation.php +++ b/src/Aggregate/Operations/AbstractFieldNameOperation.php @@ -17,12 +17,11 @@ public function __construct(string $operationName, array $fieldNames) public function toArray(): array { - $count = count($this->fieldNames); - return $count > 0 ? array_merge( - [$this->operationName, $count], + return array_merge( + [$this->operationName, count($this->fieldNames)], array_map(function ($fieldName) { return "@$fieldName"; }, $this->fieldNames) - ) : []; + ); } } diff --git a/src/Exceptions/DocumentAlreadyInIndexException.php b/src/Exceptions/DocumentAlreadyInIndexException.php new file mode 100644 index 0000000..ec248bb --- /dev/null +++ b/src/Exceptions/DocumentAlreadyInIndexException.php @@ -0,0 +1,13 @@ +setSeparator($tagSeparator); + } if ($value instanceof Tag) { return new TagField($name, $value); } diff --git a/src/Index.php b/src/Index.php index 4a3a26f..98b907c 100644 --- a/src/Index.php +++ b/src/Index.php @@ -7,6 +7,7 @@ use Ehann\RediSearch\Document\AbstractDocumentFactory; use Ehann\RediSearch\Document\DocumentInterface; use Ehann\RediSearch\Exceptions\NoFieldsInIndexException; +use Ehann\RediSearch\Exceptions\UnknownIndexNameException; use Ehann\RediSearch\Fields\FieldInterface; use Ehann\RediSearch\Fields\GeoField; use Ehann\RediSearch\Fields\NumericField; @@ -15,6 +16,8 @@ use Ehann\RediSearch\Query\Builder as QueryBuilder; use Ehann\RediSearch\Query\BuilderInterface as QueryBuilderInterface; use Ehann\RediSearch\Query\SearchResult; +use Ehann\RedisRaw\Exceptions\RawCommandErrorException; +use RedisException; class Index extends AbstractIndex implements IndexInterface { @@ -65,10 +68,23 @@ public function create() return $this->rawCommand('FT.CREATE', array_merge($properties, $fieldDefinitions)); } + /** + * @return bool + */ + public function exists(): bool + { + try { + $this->info(); + return true; + } catch (UnknownIndexNameException $exception) { + return false; + } + } + /** * @return array */ - protected function getFields(): array + public function getFields(): array { $fields = []; foreach (get_object_vars($this) as $field) { @@ -127,6 +143,15 @@ public function addTagField(string $name, bool $sortable = false, bool $noindex return $this; } + /** + * @param string $name + * @return array + */ + public function tagValues(string $name): array + { + return $this->rawCommand('FT.TAGVALS', [$this->getIndexName(), $name]); + } + /** * @return mixed */ @@ -449,14 +474,30 @@ public function verbatim(): QueryBuilderInterface /** * @param array $documents * @param bool $disableAtomicity + * @param bool $replace */ - public function addMany(array $documents, $disableAtomicity = false) + public function addMany(array $documents, $disableAtomicity = false, $replace = false) { + $result = null; + $pipe = $this->redisClient->multi($disableAtomicity); foreach ($documents as $document) { - $this->_add($document); + if (is_array($document)) { + $document = $this->arrayToDocument($document); + } + $this->_add($document->setReplace($replace)); + } + try { + $pipe->exec(); + } catch (RedisException $exception) { + $result = $exception->getMessage(); + } catch (RawCommandErrorException $exception) { + $result = $exception->getPrevious()->getMessage(); + } + + if ($result) { + $this->redisClient->validateRawCommandResults($result, 'PIPE', [$this->indexName, '*MANY']); } - $pipe->exec(); } /** @@ -472,7 +513,7 @@ protected function _add(DocumentInterface $document, bool $isFromHash = false) $properties = $isFromHash ? $document->getHashDefinition() : $document->getDefinition(); array_unshift($properties, $this->getIndexName()); - return $this->rawCommand($isFromHash ? 'FT.ADDHASH' : 'FT.ADD', $properties); + return $this->rawCommand($isFromHash ? 'HSET' : 'FT.ADD', $properties); } /** @@ -505,6 +546,15 @@ public function replace($document): bool return $this->_add($this->arrayToDocument($document)->setReplace(true)); } + /** + * @param array $documents + * @param bool $disableAtomicity + */ + public function replaceMany(array $documents, $disableAtomicity = false) + { + $this->addMany($documents, $disableAtomicity, true); + } + /** * @param $document * @return bool diff --git a/src/IndexInterface.php b/src/IndexInterface.php index e410dea..4720555 100644 --- a/src/IndexInterface.php +++ b/src/IndexInterface.php @@ -9,9 +9,11 @@ interface IndexInterface extends BuilderInterface { public function create(); + public function exists(): bool; public function drop(); public function info(); public function delete($id, $deleteDocument = false); + public function getFields(): array; public function makeDocument($id = null): DocumentInterface; public function makeAggregateBuilder(): AggregateBuilderInterface; public function getRedisClient(): RediSearchRedisClient; @@ -29,9 +31,11 @@ public function addTextField(string $name, float $weight = 1.0, bool $sortable = public function addNumericField(string $name, bool $sortable = false, bool $noindex = false): IndexInterface; public function addGeoField(string $name, bool $noindex = false): IndexInterface; public function addTagField(string $name, bool $sortable = false, bool $noindex = false, string $separator = ','): IndexInterface; + public function tagValues(string $name): array; public function add($document): bool; - public function addMany(array $documents, $disableAtomicity = false); + public function addMany(array $documents, $disableAtomicity = false, $replace = false); public function replace($document): bool; + public function replaceMany(array $documents, $disableAtomicity = false); public function addHash($document): bool; public function replaceHash($document): bool; public function addAlias(string $name): bool; diff --git a/src/RediSearchRedisClient.php b/src/RediSearchRedisClient.php index 1f7cbf9..9774793 100644 --- a/src/RediSearchRedisClient.php +++ b/src/RediSearchRedisClient.php @@ -3,12 +3,14 @@ namespace Ehann\RediSearch; use Ehann\RediSearch\Exceptions\AliasDoesNotExistException; +use Ehann\RediSearch\Exceptions\DocumentAlreadyInIndexException; use Ehann\RediSearch\Exceptions\RediSearchException; use Ehann\RediSearch\Exceptions\UnknownIndexNameException; use Ehann\RediSearch\Exceptions\UnknownIndexNameOrNameIsAnAliasItselfException; use Ehann\RediSearch\Exceptions\UnknownRediSearchCommandException; use Ehann\RediSearch\Exceptions\UnsupportedRediSearchLanguageException; use Ehann\RedisRaw\AbstractRedisRawClient; +use Ehann\RedisRaw\Exceptions\RawCommandErrorException; use Ehann\RedisRaw\RedisRawClientInterface; use Exception; use Psr\Log\LoggerInterface; @@ -23,7 +25,7 @@ public function __construct(RedisRawClientInterface $redis) $this->redis = $redis; } - public function validateRawCommandResults($payload) + public function validateRawCommandResults($payload, string $command, array $arguments) { $isPayloadException = $payload instanceof Exception; $message = $isPayloadException ? $payload->getMessage() : $payload; @@ -38,7 +40,7 @@ public function validateRawCommandResults($payload) throw new UnknownIndexNameException(); } - if (in_array($message, ['unsupported language', 'unsupported stemmer language', 'bad argument for `language`'])) { + if (in_array($message, ['no such language', 'unsupported language', 'unsupported stemmer language', 'bad argument for `language`'])) { throw new UnsupportedRediSearchLanguageException(); } @@ -54,6 +56,10 @@ public function validateRawCommandResults($payload) throw new UnknownRediSearchCommandException($message); } + if (in_array($message, ['document already in index', 'document already exists'])) { + throw new DocumentAlreadyInIndexException($arguments[0], $arguments[1]); + } + throw new RediSearchException($payload); } @@ -72,11 +78,22 @@ public function multi(bool $usePipeline = false) return $this->redis->multi($usePipeline); } - public function rawCommand(string $command, array $arguments) + public function rawCommand(string $command, array $arguments = []) { - $result = $this->redis->rawCommand($command, $arguments); + try { + foreach ($arguments as $index => $value) { + /* The various RedisRaw clients have different expectations about arg types, but generally they all + * agree that they can be strings. + */ + $arguments[$index] = strval($value); + } + $result = $this->redis->rawCommand($command, $arguments); + } catch (RawCommandErrorException $exception) { + $result = $exception->getPrevious()->getMessage(); + } + if ($command !== 'FT.EXPLAIN') { - $this->validateRawCommandResults($result); + $this->validateRawCommandResults($result, $command, $arguments); } return $result; diff --git a/tests/RediSearch/Aggregate/AggregationResultTest.php b/tests/RediSearch/Aggregate/AggregationResultTest.php index dd9be38..07bef94 100644 --- a/tests/RediSearch/Aggregate/AggregationResultTest.php +++ b/tests/RediSearch/Aggregate/AggregationResultTest.php @@ -14,7 +14,7 @@ class AggregationResultTest extends RediSearchTestCase protected $subject; protected $expectedDocuments; - public function setUp() + public function setUp(): void { $this->expectedDocuments = [ ['title' => 'part1'], diff --git a/tests/RediSearch/Aggregate/BuilderTest.php b/tests/RediSearch/Aggregate/BuilderTest.php index c30fa96..a612e0e 100644 --- a/tests/RediSearch/Aggregate/BuilderTest.php +++ b/tests/RediSearch/Aggregate/BuilderTest.php @@ -19,7 +19,7 @@ class BuilderTest extends RediSearchTestCase private $expectedResult3; private $expectedResult4; - public function setUp() + public function setUp(): void { $this->indexName = 'AggregateBuilderTest'; $index = (new TestIndex($this->redisClient, $this->indexName)) @@ -67,7 +67,7 @@ public function setUp() $this->subject = (new Builder($this->redisClient, $this->indexName)); } - public function tearDown() + public function tearDown(): void { $this->redisClient->flushAll(); } @@ -244,7 +244,7 @@ public function testGetAbsoluteMin() $expected = 9.99; $result = $this->subject - ->groupBy('_') + ->groupBy() ->min('price') ->search(); @@ -256,7 +256,7 @@ public function testGetAbsoluteMax() $expected = 38.85; $result = $this->subject - ->groupBy('_') + ->groupBy() ->max('price') ->search(); @@ -284,7 +284,7 @@ public function testGetAbsoluteQuantile() $expected = 38.85; $result = $this->subject - ->groupBy('_') + ->groupBy() ->quantile('price', 0.5) ->search(); diff --git a/tests/RediSearch/Fields/TextFieldTest.php b/tests/RediSearch/Fields/TextFieldTest.php index 11957b0..d48c183 100644 --- a/tests/RediSearch/Fields/TextFieldTest.php +++ b/tests/RediSearch/Fields/TextFieldTest.php @@ -18,7 +18,7 @@ class TextFieldTest extends TestCase /** @var float */ private $defaultWeight = 1.0; - public function setUp() + public function setUp(): void { $this->subject = new TextField($this->fieldName); } diff --git a/tests/RediSearch/IndexTest.php b/tests/RediSearch/IndexTest.php index f4fcb95..9de6d7c 100644 --- a/tests/RediSearch/IndexTest.php +++ b/tests/RediSearch/IndexTest.php @@ -3,6 +3,7 @@ namespace Ehann\Tests\RediSearch; use Ehann\RediSearch\Exceptions\AliasDoesNotExistException; +use Ehann\RediSearch\Exceptions\DocumentAlreadyInIndexException; use Ehann\RediSearch\Exceptions\FieldNotInSchemaException; use Ehann\RediSearch\Exceptions\NoFieldsInIndexException; use Ehann\RediSearch\Exceptions\RediSearchException; @@ -17,6 +18,7 @@ use Ehann\RediSearch\Fields\NumericField; use Ehann\RediSearch\Fields\TextField; use Ehann\RediSearch\IndexInterface; +use Ehann\RediSearch\RediSearchRedisClient; use Ehann\Tests\Stubs\TestDocument; use Ehann\Tests\Stubs\TestIndex; use Ehann\Tests\Stubs\IndexWithoutFields; @@ -27,7 +29,7 @@ class IndexTest extends RediSearchTestCase /** @var IndexInterface */ private $subject; - public function setUp() + public function setUp(): void { $this->indexName = 'ClientTest'; $this->subject = (new TestIndex($this->redisClient, $this->indexName)) @@ -41,7 +43,7 @@ public function setUp() $this->logger->debug('setUp...'); } - public function tearDown() + public function tearDown(): void { $this->redisClient->flushAll(); } @@ -60,6 +62,22 @@ public function testShouldCreateIndex() $this->assertTrue($result); } + public function testShouldVerifyIndexExists() + { + $this->subject->create(); + + $result = $this->subject->exists(); + + $this->assertTrue($result); + } + + public function testShouldVerifyIndexDoesNotExist() + { + $result = $this->subject->exists(); + + $this->assertFalse($result); + } + public function testShouldDropIndex() { $this->subject->create(); @@ -152,6 +170,30 @@ public function testCreateIndexWithTagField() $this->assertTrue($result); } + public function testGetTagValues() + { + $this->subject->create(); + $this->subject->add([ + new TextField('title', 'How to be awesome.'), + new TextField('author', 'Jack'), + new NumericField('price', 9.99), + new NumericField('stock', 231), + new TagField('color', 'red'), + ]); + $this->subject->add([ + new TextField('title', 'F.O.W.L'), + new TextField('author', 'Jill'), + new NumericField('price', 19.99), + new NumericField('stock', 31), + new TagField('color', 'blue'), + ]); + $expected = ['red', 'blue']; + + $actual = $this->subject->tagValues('color'); + + $this->assertEquals($expected, $actual); + } + public function testAddDocumentWithZeroScore() { $this->subject->create(); @@ -285,6 +327,21 @@ public function testAddDocumentToUndefinedIndex() $this->assertFalse($result); } + public function testAddDocumentAlreadyInIndex() + { + $this->subject->create(); + $this->expectException(DocumentAlreadyInIndexException::class); + /** @var TestDocument $document */ + $document = $this->subject->makeDocument(); + $document->title->setValue('How to be awesome.'); + $this->subject->add($document); + + $result = $this->subject->add($document); + + $this->assertFalse($result); + } + + public function testReplaceDocument() { $this->subject->create(); @@ -308,9 +365,9 @@ public function testReplaceDocument() public function testAddDocumentFromHash() { $this->subject->create(); - $id = 'gooblegobble'; - $this->redisClient->rawCommand('HSET', [ - $id, + + $result = $this->subject->addHash([ + 'gooblegobble', 'title', 'How to be awesome', 'author', @@ -320,20 +377,30 @@ public function testAddDocumentFromHash() 'stock', 231 ]); - $document = $this->subject->makeDocument($id); - - $result = $this->subject->addHash($document); $this->assertTrue($result); } - public function testShouldThrowExceptionWhenAddingFromHashThatDoesNotExist() + public function testFindDocumentAddedWithHash () { $this->subject->create(); - $document = $this->subject->makeDocument('does_not_exist'); - $this->expectException(RediSearchException::class); + $title = 'How to be awesome'; + $this->redisClient->rawCommand('HSET', [ + 'gooblegobble', + 'title', + $title, + 'author', + 'Jack', + 'price', + 9.99, + 'stock', + 231 + ]); - $this->subject->addHash($document); + $result = $this->subject->search($title); + + $this->assertEquals(1, $result->getCount()); + $this->assertEquals($title, $result->getDocuments()[0]->title); } public function testReplaceDocumentFromHash() @@ -360,7 +427,7 @@ public function testReplaceDocumentFromHash() ]); $document = $this->subject->makeDocument($id); - $result = $this->subject->replaceHash($document); + $result = $this->subject->addHash($document); $this->assertTrue($result); } @@ -606,7 +673,8 @@ public function testBatchIndexWithAddManyUsingPhpRedisWithAtomicityDisabled() $this->markTestSkipped('Skipping because test suite is not configured to use PhpRedis.'); } - $this->subject->setRedisClient($this->makePhpRedisAdapter())->create(); + $rediSearchRedisClient = new RediSearchRedisClient($this->makePhpRedisAdapter()); + $this->subject->setRedisClient($rediSearchRedisClient)->create(); $expectedDocumentCount = 10; $documents = $this->makeDocuments(); $expectedCount = count($documents); @@ -744,4 +812,24 @@ public function testShouldFailToDeleteAliasIfIndexDoesNotExist() $this->subject->deleteAlias('MyAlias'); } + + public function testShouldGetFields() + { + $this->subject->create(); + $expectedTitle = 'title TEXT WEIGHT 1'; + $expectedAuthor = 'author TEXT WEIGHT 1'; + $expectedPrice = 'price NUMERIC'; + $expectedStock = 'stock NUMERIC'; + $expectedPlace = 'place GEO'; + $expectedColor = 'color TAG SEPARATOR ,'; + + $fields = $this->subject->getFields(); + + $this->assertEquals($expectedTitle, implode(' ', $fields['title']->getTypeDefinition())); + $this->assertEquals($expectedAuthor, implode(' ', $fields['author']->getTypeDefinition())); + $this->assertEquals($expectedPrice, implode(' ', $fields['price']->getTypeDefinition())); + $this->assertEquals($expectedStock, implode(' ', $fields['stock']->getTypeDefinition())); + $this->assertEquals($expectedPlace, implode(' ', $fields['place']->getTypeDefinition())); + $this->assertEquals($expectedColor, implode(' ', $fields['color']->getTypeDefinition())); + } } diff --git a/tests/RediSearch/Query/BuilderTest.php b/tests/RediSearch/Query/BuilderTest.php index 79a1ab7..f99e837 100644 --- a/tests/RediSearch/Query/BuilderTest.php +++ b/tests/RediSearch/Query/BuilderTest.php @@ -14,7 +14,7 @@ class BuilderTest extends RediSearchTestCase private $expectedResult1; private $expectedResult2; private $expectedResult3; - public function setUp() + public function setUp(): void { $this->indexName = 'QueryBuilderTest'; $index = (new TestIndex($this->redisClient, $this->indexName)) @@ -54,7 +54,7 @@ public function setUp() $this->subject = (new Builder($this->redisClient, $this->indexName)); } - public function tearDown() + public function tearDown(): void { $this->redisClient->flushAll(); } diff --git a/tests/RediSearch/RuntimeConfigurationTest.php b/tests/RediSearch/RuntimeConfigurationTest.php index dea1753..cb9ec40 100644 --- a/tests/RediSearch/RuntimeConfigurationTest.php +++ b/tests/RediSearch/RuntimeConfigurationTest.php @@ -10,18 +10,22 @@ class RuntimeConfigurationTest extends RediSearchTestCase /** @var RuntimeConfiguration */ private $subject; - public function setUp() + public function setUp(): void { $this->subject = (new RuntimeConfiguration($this->redisClient, 'foo')); } - public function tearDown() + public function tearDown(): void { $this->redisClient->flushAll(); } public function testShouldSetMinPrefix() { + if ($this->isUsingPhpRedis()) { + $this->markTestSkipped('Skipping because test suite is configured to use PhpRedis.'); + } + $expected = 3; $result = $this->subject->setMinPrefix($expected); @@ -32,6 +36,10 @@ public function testShouldSetMinPrefix() public function testShouldSetMaxExpansions() { + if ($this->isUsingPhpRedis()) { + $this->markTestSkipped('Skipping because test suite is configured to use PhpRedis.'); + } + $expected = 300; $result = $this->subject->setMaxExpansions($expected); @@ -42,6 +50,10 @@ public function testShouldSetMaxExpansions() public function testShouldSetTimeout() { + if ($this->isUsingPhpRedis()) { + $this->markTestSkipped('Skipping because test suite is configured to use PhpRedis.'); + } + $expected = 100; $result = $this->subject->setTimeoutInMilliseconds($expected); @@ -52,6 +64,10 @@ public function testShouldSetTimeout() public function testIsOnTimeoutPolicyReturn() { + if ($this->isUsingPhpRedis()) { + $this->markTestSkipped('Skipping because test suite is configured to use PhpRedis.'); + } + $this->subject->setOnTimeoutPolicyToReturn(); $result = $this->subject->isOnTimeoutPolicyReturn(); @@ -61,6 +77,10 @@ public function testIsOnTimeoutPolicyReturn() public function testIsOnTimeoutPolicyFail() { + if ($this->isUsingPhpRedis()) { + $this->markTestSkipped('Skipping because test suite is configured to use PhpRedis.'); + } + $this->subject->setOnTimeoutPolicyToFail(); $result = $this->subject->isOnTimeoutPolicyFail(); @@ -70,6 +90,10 @@ public function testIsOnTimeoutPolicyFail() public function testShouldSetMinPhoneticTermLength() { + if ($this->isUsingPhpRedis()) { + $this->markTestSkipped('Skipping because test suite is configured to use PhpRedis.'); + } + $expected = 5; $result = $this->subject->setMinPhoneticTermLength($expected); diff --git a/tests/RediSearch/SuggestionTest.php b/tests/RediSearch/SuggestionTest.php index dfa442c..6134f21 100644 --- a/tests/RediSearch/SuggestionTest.php +++ b/tests/RediSearch/SuggestionTest.php @@ -10,12 +10,12 @@ class SuggestionTest extends RediSearchTestCase /** @var Suggestion */ private $subject; - public function setUp() + public function setUp(): void { $this->subject = (new Suggestion($this->redisClient, 'foo')); } - public function tearDown() + public function tearDown(): void { $this->redisClient->flushAll(); } diff --git a/tests/TestTimeListener.php b/tests/TestTimeListener.php index 8472f85..f2809e4 100644 --- a/tests/TestTimeListener.php +++ b/tests/TestTimeListener.php @@ -2,119 +2,61 @@ namespace Ehann\Tests; +use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; class TestTimeListener implements TestListener { - /** - * @param Test $test - * @param float $time - */ - public function endTest(Test $test, $time) + public function endTest(Test $test, float $time): void { $formattedNumber = round($time, 4); print "{$test->getName()} took $formattedNumber seconds." . PHP_EOL; } - /** - * An error occurred. - * - * @param Test $test - * @param \Exception $e - * @param float $time - */ - public function addError(Test $test, \Exception $e, $time) + public function addError(Test $test, \Throwable $t, float $time): void { // TODO: Implement addError() method. } - /** - * A warning occurred. - * - * @param Test $test - * @param \PHPUnit\Framework\Warning $e - * @param float $time - */ - public function addWarning(Test $test, \PHPUnit\Framework\Warning $e, $time) + public function addWarning(Test $test, Warning $e, float $time): void { // TODO: Implement addWarning() method. } - /** - * A failure occurred. - * - * @param Test $test - * @param \PHPUnit\Framework\AssertionFailedError $e - * @param float $time - */ - public function addFailure(Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time) + public function addFailure(Test $test, AssertionFailedError $e, float $time): void { // TODO: Implement addFailure() method. } - /** - * Incomplete test. - * - * @param Test $test - * @param \Exception $e - * @param float $time - */ - public function addIncompleteTest(Test $test, \Exception $e, $time) + public function addIncompleteTest(Test $test, \Throwable $t, float $time): void { // TODO: Implement addIncompleteTest() method. } - /** - * Risky test. - * - * @param Test $test - * @param \Exception $e - * @param float $time - */ - public function addRiskyTest(Test $test, \Exception $e, $time) + public function addRiskyTest(Test $test, \Throwable $t, float $time): void { // TODO: Implement addRiskyTest() method. } - /** - * Skipped test. - * - * @param Test $test - * @param \Exception $e - * @param float $time - */ - public function addSkippedTest(Test $test, \Exception $e, $time) + public function addSkippedTest(Test $test, \Throwable $t, float $time): void { // TODO: Implement addSkippedTest() method. } - /** - * A test suite started. - * - * @param \PHPUnit\Framework\TestSuite $suite - */ - public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) + public function startTestSuite(TestSuite $suite): void { // TODO: Implement startTestSuite() method. } - /** - * A test suite ended. - * - * @param \PHPUnit\Framework\TestSuite $suite - */ - public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) + public function endTestSuite(TestSuite $suite): void { // TODO: Implement endTestSuite() method. } - /** - * A test started. - * - * @param Test $test - */ - public function startTest(Test $test) + public function startTest(Test $test): void { // TODO: Implement startTest() method. }