From 9e3df378b335f74f9ab399ec02ecb99d500f00e6 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Thu, 23 May 2024 07:57:54 +0200 Subject: [PATCH] Improve Query feature codebase --- src/Query/Constraint/Column.php | 6 ---- src/Query/Constraint/Comparison.php | 6 ++++ src/Query/Constraint/Criteria.php | 9 ++---- src/Query/Limit.php | 7 ++-- src/Query/Ordering/Column.php | 7 +++- src/Query/Ordering/ColumnTest.php | 6 ++-- src/Query/Ordering/MultiSort.php | 2 +- src/Query/Ordering/MultiSortTest.php | 7 ++-- src/Query/Predicate.php | 11 +++++++ src/Query/PredicateCombinator.php | 1 - src/Query/Row.php | 48 ++++++++++++++-------------- src/Query/Sort.php | 10 ++++++ src/Serializer/Denormalizer.php | 17 ++++++++++ src/Statement.php | 6 ++-- src/TabularDataReader.php | 4 +-- 15 files changed, 92 insertions(+), 55 deletions(-) diff --git a/src/Query/Constraint/Column.php b/src/Query/Constraint/Column.php index 7f916b8c..9fc00e8f 100644 --- a/src/Query/Constraint/Column.php +++ b/src/Query/Constraint/Column.php @@ -21,12 +21,6 @@ use ReflectionException; use Traversable; -use function is_array; -use function array_filter; -use function iterator_to_array; - -use const ARRAY_FILTER_USE_BOTH; - /** * Enable filtering a record based on the value of a one of its cell. * diff --git a/src/Query/Constraint/Comparison.php b/src/Query/Constraint/Comparison.php index 6dffa9e6..449495dc 100644 --- a/src/Query/Constraint/Comparison.php +++ b/src/Query/Constraint/Comparison.php @@ -76,6 +76,10 @@ public static function fromOperator(string $operator): self } /** + * Values comparison. + * + * The method return true if the values satisfy the comparison operator, otherwise false is returned. + * * @throws QueryException */ public function compare(mixed $subject, mixed $reference): bool @@ -108,6 +112,8 @@ private static function isSingleValue(mixed $value): bool } /** + * Assert if the reference value can be used with the Enum operator. + * * @throws QueryException */ public function accept(mixed $reference): void diff --git a/src/Query/Constraint/Criteria.php b/src/Query/Constraint/Criteria.php index 3c4e61a9..ed49fb88 100644 --- a/src/Query/Constraint/Criteria.php +++ b/src/Query/Constraint/Criteria.php @@ -21,11 +21,8 @@ use League\Csv\Query\Predicate; use League\Csv\Query\PredicateCombinator; +use Traversable; use function array_reduce; -use function is_array; -use function array_filter; - -use const ARRAY_FILTER_USE_BOTH; /** * @phpstan-import-type Condition from PredicateCombinator @@ -116,9 +113,9 @@ public function __invoke(mixed $value, int|string $key): bool public function filter(iterable $value): Iterator { return new CallbackFilterIterator(match (true) { - is_array($value) => new ArrayIterator($value), $value instanceof Iterator => $value, - default => new IteratorIterator($value), + $value instanceof Traversable => new IteratorIterator($value), + default => new ArrayIterator($value), }, $this); } diff --git a/src/Query/Limit.php b/src/Query/Limit.php index c39f39a8..bc0f7572 100644 --- a/src/Query/Limit.php +++ b/src/Query/Limit.php @@ -19,10 +19,6 @@ use LimitIterator; use Traversable; -use function array_slice; -use function is_array; -use function iterator_to_array; - final class Limit { private function __construct( @@ -43,6 +39,9 @@ public static function new(int $offset, int $length): self return new self($offset, $length); } + /** + * Allows iteration over a limited subset of items in an iterable structure. + */ public function slice(iterable $value): LimitIterator { return new LimitIterator( diff --git a/src/Query/Ordering/Column.php b/src/Query/Ordering/Column.php index 99718289..75ea949c 100644 --- a/src/Query/Ordering/Column.php +++ b/src/Query/Ordering/Column.php @@ -22,6 +22,9 @@ use OutOfBoundsException; use ReflectionException; +use function is_array; +use function is_string; +use function iterator_to_array; use function strtoupper; use function trim; @@ -45,8 +48,10 @@ private function __construct( /** * @param ?Closure(mixed, mixed): int $callback + * + * @throws QueryException */ - public static function sortBy( + public static function sortOn( string|int $column, string|int $direction, ?Closure $callback = null diff --git a/src/Query/Ordering/ColumnTest.php b/src/Query/Ordering/ColumnTest.php index 84b79a44..319ac088 100644 --- a/src/Query/Ordering/ColumnTest.php +++ b/src/Query/Ordering/ColumnTest.php @@ -23,7 +23,7 @@ final class ColumnTest extends QueryTestCase public function it_can_order_the_tabular_date_in_descending_order(): void { $stmt = $this->stmt->orderBy( - Column::sortBy('Country', 'down') + Column::sortOn('Country', 'down') ); self::assertSame('UK', $stmt->process($this->document)->first()['Country']); @@ -33,7 +33,7 @@ public function it_can_order_the_tabular_date_in_descending_order(): void public function it_can_order_the_tabular_date_in_ascending_order(): void { $stmt = $this->stmt->orderBy( - Column::sortBy('Country', 'up') + Column::sortOn('Country', 'up') ); self::assertSame('UK', $stmt->process($this->document)->nth(4)['Country']); @@ -43,7 +43,7 @@ public function it_can_order_the_tabular_date_in_ascending_order(): void public function it_can_order_using_a_specific_order_algo(): void { $stmt = $this->stmt->orderBy( - Column::sortBy( + Column::sortOn( 'Country', 'desc', fn (string $first, string $second): int => strlen($first) <=> strlen($second) /* @phpstan-ignore-line */ diff --git a/src/Query/Ordering/MultiSort.php b/src/Query/Ordering/MultiSort.php index ad96723e..15788933 100644 --- a/src/Query/Ordering/MultiSort.php +++ b/src/Query/Ordering/MultiSort.php @@ -15,13 +15,13 @@ use ArrayIterator; use Closure; - use Iterator; use IteratorIterator; use League\Csv\Query\Sort; use League\Csv\Query\SortCombinator; use OutOfBoundsException; use Traversable; + use function array_map; /** diff --git a/src/Query/Ordering/MultiSortTest.php b/src/Query/Ordering/MultiSortTest.php index e93320a4..5c514f52 100644 --- a/src/Query/Ordering/MultiSortTest.php +++ b/src/Query/Ordering/MultiSortTest.php @@ -17,7 +17,6 @@ use PHPUnit\Framework\Attributes\Test; final class MultiSortTest extends QueryTestCase - { #[Test] public function it_will_sort_nothing_if_no_sort_algorithm_is_provided(): void @@ -32,7 +31,7 @@ public function it_will_sort_nothing_if_no_sort_algorithm_is_provided(): void public function it_can_order_the_tabular_date_when_an_algo_is_provided(): void { $stmt = $this->stmt->orderBy( - MultiSort::all()->append(Column::sortBy('Country', 'up')) + MultiSort::all()->append(Column::sortOn('Country', 'up')) ); self::assertSame('UK', $stmt->process($this->document)->nth(4)['Country']); @@ -41,8 +40,8 @@ public function it_can_order_the_tabular_date_when_an_algo_is_provided(): void #[Test] public function it_respect_the_fifo_order_to_apply_sorting(): void { - $countryOrder = Column::sortBy('Country', 'ASC'); - $idOrder = Column::sortBy('CustomerID', 'DeSc'); + $countryOrder = Column::sortOn('Country', 'ASC'); + $idOrder = Column::sortOn('CustomerID', 'DeSc'); self::assertNotSame( $this->stmt diff --git a/src/Query/Predicate.php b/src/Query/Predicate.php index b179104a..359e59f0 100644 --- a/src/Query/Predicate.php +++ b/src/Query/Predicate.php @@ -26,7 +26,18 @@ */ interface Predicate { + /** + * The class predicate method. + * + * Evaluates each element of an iterable structure based on its value and its offset. + * The method must return true if the predicate is satisfied, false otherwise. + */ public function __invoke(mixed $value, string|int $key): bool; + /** + * Filters elements of an iterable structure using the class predicate method. + * + * @see Predicate::__invoke + */ public function filter(iterable $value): Iterator; } diff --git a/src/Query/PredicateCombinator.php b/src/Query/PredicateCombinator.php index c8b43a22..b5b31680 100644 --- a/src/Query/PredicateCombinator.php +++ b/src/Query/PredicateCombinator.php @@ -14,7 +14,6 @@ namespace League\Csv\Query; use Closure; -use League\Csv\Query\Predicate; /** * @phpstan-type Condition Predicate|Closure(mixed, array-key): bool diff --git a/src/Query/Row.php b/src/Query/Row.php index 070d53fa..95a7a773 100644 --- a/src/Query/Row.php +++ b/src/Query/Row.php @@ -42,7 +42,7 @@ public static function from(mixed $value): Row }); } - private function __construct(private readonly array|object $record) + private function __construct(private readonly array|object $row) { } @@ -51,7 +51,7 @@ private function __construct(private readonly array|object $record) * * @throws ReflectionException * @throws QueryException If the value can not be retrieved - *@see Row::select() + * @see Row::select() * */ public function value(string|int $key): mixed @@ -74,8 +74,8 @@ public function value(string|int $key): mixed public function select(string|int ...$key): array { return match (true) { - is_object($this->record) => self::getObjectPropertyValue($this->record, ...$key), - default => self::getArrayEntry($this->record, ...$key), + is_object($this->row) => self::getObjectPropertyValue($this->row, ...$key), + default => self::getArrayEntry($this->row, ...$key), }; } @@ -84,26 +84,26 @@ public function select(string|int ...$key): array * * @return non-empty-array */ - private function getArrayEntry(array $value, string|int ...$keys): array + private function getArrayEntry(array $row, string|int ...$keys): array { $res = []; - $arrValues = array_values($value); + $arrValues = array_values($row); foreach ($keys as $key) { if (array_key_exists($key, $res)) { continue; } $offset = $key; if (is_int($offset)) { - if (!array_is_list($value)) { - $value = $arrValues; + if (!array_is_list($row)) { + $row = $arrValues; } if ($offset < 0) { - $offset += count($value); + $offset += count($row); } } - $res[$key] = array_key_exists($offset, $value) ? $value[$offset] : throw QueryException::dueToUnknownColumn($key, $value); + $res[$key] = array_key_exists($offset, $row) ? $row[$offset] : throw QueryException::dueToUnknownColumn($key, $row); } return [] !== $res ? $res : throw QueryException::dueToMissingColumn(); @@ -115,21 +115,21 @@ private function getArrayEntry(array $value, string|int ...$keys): array * * @return non-empty-array */ - private static function getObjectPropertyValue(object $value, string|int ...$keys): array + private static function getObjectPropertyValue(object $row, string|int ...$keys): array { $res = []; - $refl = new ReflectionObject($value); + $object = new ReflectionObject($row); foreach ($keys as $key) { if (array_key_exists($key, $res)) { continue; } if (is_int($key)) { - throw QueryException::dueToUnknownColumn($key, $value); + throw QueryException::dueToUnknownColumn($key, $row); } - if ($refl->hasProperty($key) && $refl->getProperty($key)->isPublic()) { - $res[$key] = $refl->getProperty($key)->getValue($value); + if ($object->hasProperty($key) && $object->getProperty($key)->isPublic()) { + $res[$key] = $object->getProperty($key)->getValue($row); continue; } @@ -139,26 +139,26 @@ private static function getObjectPropertyValue(object $value, string|int ...$key } $methodNameList[] = self::camelCase($key, 'get'); foreach ($methodNameList as $methodName) { - if ($refl->hasMethod($methodName) - && $refl->getMethod($methodName)->isPublic() - && 1 > $refl->getMethod($methodName)->getNumberOfRequiredParameters() + if ($object->hasMethod($methodName) + && $object->getMethod($methodName)->isPublic() + && 1 > $object->getMethod($methodName)->getNumberOfRequiredParameters() ) { - $res[$key] = $refl->getMethod($methodName)->invoke($value); + $res[$key] = $object->getMethod($methodName)->invoke($row); continue 2; } } - if (method_exists($value, '__call')) { - $res[$key] = $refl->getMethod('__call')->invoke($value, $methodNameList[1]); + if (method_exists($row, '__call')) { + $res[$key] = $object->getMethod('__call')->invoke($row, $methodNameList[1]); continue; } - if ($value instanceof ArrayAccess && $value->offsetExists($key)) { - $res[$key] = $value->offsetGet($key); + if ($row instanceof ArrayAccess && $row->offsetExists($key)) { + $res[$key] = $row->offsetGet($key); continue; } - throw QueryException::dueToUnknownColumn($key, $value); + throw QueryException::dueToUnknownColumn($key, $row); } return [] !== $res ? $res : throw QueryException::dueToMissingColumn(); diff --git a/src/Query/Sort.php b/src/Query/Sort.php index 93b98d3b..d8f5cd13 100644 --- a/src/Query/Sort.php +++ b/src/Query/Sort.php @@ -27,7 +27,17 @@ */ interface Sort { + /** + * The class comparison method. + * + * The method must return an integer less than, equal to, or greater than zero + * if the first argument is considered to be respectively less than, equal to, + * or greater than the second. + */ public function __invoke(mixed $valueA, mixed $valueB): int; + /** + * Sort an iterable structure with the class comparison method and maintain index association. + */ public function sort(iterable $value): Iterator; } diff --git a/src/Serializer/Denormalizer.php b/src/Serializer/Denormalizer.php index 7b94bcfb..11d181dd 100644 --- a/src/Serializer/Denormalizer.php +++ b/src/Serializer/Denormalizer.php @@ -55,17 +55,25 @@ public function __construct(string $className, array $propertyNames = []) $this->postMapCalls = $this->setPostMapCalls(); } + /** + * Enable converting empty string to the null value. + */ public static function allowEmptyStringAsNull(): void { self::$emptyStringAsNull = true; } + /** + * Disable converting empty string to the null value. + */ public static function disallowEmptyStringAsNull(): void { self::$emptyStringAsNull = false; } /** + * Register a global type conversion callback to convert a field into a specific type. + * * @throws MappingFailed */ public static function registerType(string $type, Closure $callback): void @@ -73,6 +81,13 @@ public static function registerType(string $type, Closure $callback): void CallbackCasting::register($type, $callback); } + /** + * Unregister a global type conversion callback to convert a field into a specific type. + * + * @param string $type + * + * @return bool + */ public static function unregisterType(string $type): bool { return CallbackCasting::unregisterType($type); @@ -84,6 +99,8 @@ public static function unregisterAllTypes(): void } /** + * Register a callback to convert a field into a specific type. + * * @throws MappingFailed */ public static function registerAlias(string $alias, string $type, Closure $callback): void diff --git a/src/Statement.php b/src/Statement.php index 367e64fb..73c71695 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -230,7 +230,7 @@ final protected function appendCondition(string $joiner, Query\Predicate $predic * * @param OrderingExtended $order_by */ - public function orderBy(callable $order_by): self + public function orderBy(callable|Query\Sort|Closure $order_by): self { $clone = clone $this; $clone->order_by[] = $order_by; @@ -245,7 +245,7 @@ public function orderBy(callable $order_by): self */ public function orderByAsc(string|int $column, ?Closure $callback = null): self { - return $this->orderBy(Query\Ordering\Column::sortBy($column, 'asc', $callback)); + return $this->orderBy(Query\Ordering\Column::sortOn($column, 'asc', $callback)); } /** @@ -255,7 +255,7 @@ public function orderByAsc(string|int $column, ?Closure $callback = null): self */ public function orderByDesc(string|int $column, ?Closure $callback = null): self { - return $this->orderBy(Query\Ordering\Column::sortBy($column, 'desc', $callback)); + return $this->orderBy(Query\Ordering\Column::sortOn($column, 'desc', $callback)); } /** diff --git a/src/TabularDataReader.php b/src/TabularDataReader.php index 06ca1ec5..5b35a9c3 100644 --- a/src/TabularDataReader.php +++ b/src/TabularDataReader.php @@ -33,9 +33,9 @@ * @method mixed reduce(Closure $callback, mixed $initial = null) reduces the collection to a single value, passing the result of each iteration into the subsequent iteration * @method Iterator getObjects(string $className, array $header = []) Returns the tabular data records as an iterator object containing instance of the defined class name. * @method Iterator getRecordsAsObject(string $className, array $header = []) Returns the tabular data records as an iterator object containing instance of the defined class name. - * @method TabularDataReader filter(Constraint\Predicate|Closure $predicate) returns all the elements of this collection for which your callback function returns `true` + * @method TabularDataReader filter(Query\Predicate|Closure $predicate) returns all the elements of this collection for which your callback function returns `true` * @method TabularDataReader slice(int $offset, int $length = null) extracts a slice of $length elements starting at position $offset from the Collection. - * @method TabularDataReader sorted(Ordering\Sort|Closure $orderBy) sorts the Collection according to the closure provided see Statement::orderBy method + * @method TabularDataReader sorted(Query\Sort|Closure $orderBy) sorts the Collection according to the closure provided see Statement::orderBy method * @method TabularDataReader select(string|int ...$columnOffsetOrName) extract a selection of the tabular data records columns. * @method TabularDataReader matchingFirstOrFail(string $expression) extract the first found fragment identifier of the tabular data or fail * @method TabularDataReader|null matchingFirst(string $expression) extract the first found fragment identifier of the tabular data or return null if none is found