Skip to content

Commit

Permalink
Increase psalm level to 2 (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed Sep 6, 2021
1 parent 63d51e3 commit e2d0af4
Show file tree
Hide file tree
Showing 17 changed files with 137 additions and 61 deletions.
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -24,7 +24,7 @@
"phpunit/phpunit": "^9.4",
"roave/infection-static-analysis-plugin": "^1.5",
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.2"
"vimeo/psalm": "^4.9"
},
"autoload": {
"psr-4": {
Expand Down
6 changes: 1 addition & 5 deletions psalm.xml
@@ -1,15 +1,11 @@
<?xml version="1.0"?>
<psalm
errorLevel="6"
resolveFromConfigFile="true"
errorLevel="2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>
55 changes: 42 additions & 13 deletions src/Paginator/KeysetPaginator.php
Expand Up @@ -4,6 +4,8 @@

namespace Yiisoft\Data\Paginator;

use InvalidArgumentException;
use RuntimeException;
use Yiisoft\Arrays\ArrayHelper;
use Yiisoft\Data\Reader\Filter\CompareFilter;
use Yiisoft\Data\Reader\Filter\GreaterThan;
Expand All @@ -15,6 +17,10 @@
use Yiisoft\Data\Reader\Sort;
use Yiisoft\Data\Reader\SortableDataInterface;

use function count;
use function is_callable;
use function is_object;

/**
* Keyset paginator
*
Expand All @@ -24,6 +30,8 @@
*
* @link https://use-the-index-luke.com/no-offset
*
* @psalm-template DataReaderType = ReadableDataInterface<TKey, TValue>&FilterableDataInterface&SortableDataInterface
*
* @template TKey as array-key
* @template TValue
*
Expand All @@ -33,13 +41,14 @@ class KeysetPaginator implements PaginatorInterface
{
/**
* @var FilterableDataInterface|ReadableDataInterface|SortableDataInterface
* @psalm-var DataReaderType
*/
private ReadableDataInterface $dataReader;
private int $pageSize = self::DEFAULT_PAGE_SIZE;
private ?string $firstValue = null;
private ?string $lastValue = null;
private $currentFirstValue;
private $currentLastValue;
private ?string $currentFirstValue = null;
private ?string $currentLastValue = null;

/**
* @var bool Previous page has item indicator.
Expand All @@ -59,28 +68,29 @@ class KeysetPaginator implements PaginatorInterface
private ?array $readCache = null;

/**
* @psalm-param ReadableDataInterface<TKey, TValue> $dataReader
* @psalm-param DataReaderType $dataReader
*/
public function __construct(ReadableDataInterface $dataReader)
{
if (!$dataReader instanceof FilterableDataInterface) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
'Data reader should implement FilterableDataInterface to be used with keyset paginator.'
);
}

if (!$dataReader instanceof SortableDataInterface) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
'Data reader should implement SortableDataInterface to be used with keyset paginator.'
);
}

if ($dataReader->getSort() === null) {
throw new \RuntimeException('Data sorting should be configured to work with keyset pagination.');
throw new RuntimeException('Data sorting should be configured to work with keyset pagination.');
}

/** @psalm-suppress PossiblyNullReference */
if ($dataReader->getSort()->getOrder() === []) {
throw new \RuntimeException('Data should be always sorted to work with keyset pagination.');
throw new RuntimeException('Data should be always sorted to work with keyset pagination.');
}

$this->dataReader = $dataReader;
Expand Down Expand Up @@ -150,15 +160,15 @@ public function getPreviousPageToken(): ?string
if ($this->isOnFirstPage()) {
return null;
}
return (string)$this->currentFirstValue;
return $this->currentFirstValue;
}

public function getNextPageToken(): ?string
{
if ($this->isOnLastPage()) {
return null;
}
return (string)$this->currentLastValue;
return $this->currentLastValue;
}

/**
Expand All @@ -169,7 +179,7 @@ public function getNextPageToken(): ?string
public function withPageSize(int $pageSize): self
{
if ($pageSize < 1) {
throw new \InvalidArgumentException('Page size should be at least 1.');
throw new InvalidArgumentException('Page size should be at least 1.');
}

$new = clone $this;
Expand Down Expand Up @@ -212,6 +222,9 @@ public function __clone()
$this->currentLastValue = null;
}

/**
* @psalm-assert array<TKey, TValue> $this->readCache
*/
protected function initializeInternal(): void
{
if ($this->readCache !== null) {
Expand All @@ -229,6 +242,10 @@ public function isRequired(): bool
return !$this->isOnFirstPage() || !$this->isOnLastPage();
}

/**
* @psalm-assert-if-true string $this->firstValue
* @psalm-assert-if-true null $this->lastValue
*/
private function isGoingToPreviousPage(): bool
{
return $this->firstValue !== null && $this->lastValue === null;
Expand Down Expand Up @@ -257,6 +274,10 @@ private function getReverseFilter(Sort $sort): CompareFilter
return new GreaterThanOrEqual($field, $this->getValue());
}

/**
* @psalm-suppress NullableReturnStatement,InvalidNullableReturnType The code calling this method
* must ensure that at least one of the properties `$firstValue` or `$lastValue` is not `null`.
*/
private function getValue(): string
{
return $this->isGoingToPreviousPage() ? $this->firstValue : $this->lastValue;
Expand Down Expand Up @@ -293,12 +314,12 @@ private function readData(ReadableDataInterface $dataReader, Sort $sort): array

foreach ($dataReader->read() as $key => $item) {
if ($this->currentFirstValue === null) {
$this->currentFirstValue = $this->getValueFromItem($item, $field);
$this->currentFirstValue = (string)$this->getValueFromItem($item, $field);
}
if (count($data) === $this->pageSize) {
$this->hasNextPageItem = true;
} else {
$this->currentLastValue = $this->getValueFromItem($item, $field);
$this->currentLastValue = (string)$this->getValueFromItem($item, $field);
$data[$key] = $item;
}
}
Expand All @@ -307,7 +328,7 @@ private function readData(ReadableDataInterface $dataReader, Sort $sort): array
}

/**
* @psalm-param array<TKey, TValue>
* @psalm-param array<TKey, TValue> $data
* @psalm-return array<TKey, TValue>
*/
private function reverseData(array $data): array
Expand All @@ -317,6 +338,9 @@ private function reverseData(array $data): array
return array_reverse($data, true);
}

/**
* @psalm-param DataReaderType $dataReader
*/
private function previousPageExist(ReadableDataInterface $dataReader, Sort $sort): bool
{
$reverseFilter = $this->getReverseFilter($sort);
Expand All @@ -326,6 +350,11 @@ private function previousPageExist(ReadableDataInterface $dataReader, Sort $sort
return false;
}

/**
* @param mixed $item
*
* @return mixed
*/
private function getValueFromItem($item, string $field)
{
$methodName = 'get' . ucfirst($field);
Expand Down
22 changes: 15 additions & 7 deletions src/Paginator/OffsetPaginator.php
Expand Up @@ -4,36 +4,45 @@

namespace Yiisoft\Data\Paginator;

use Generator;
use InvalidArgumentException;
use Yiisoft\Data\Reader\CountableDataInterface;
use Yiisoft\Data\Reader\OffsetableDataInterface;
use Yiisoft\Data\Reader\ReadableDataInterface;
use Yiisoft\Data\Reader\Sort;
use Yiisoft\Data\Reader\SortableDataInterface;

/**
* @psalm-template DataReaderType = ReadableDataInterface<TKey, TValue>&OffsetableDataInterface&CountableDataInterface
*
* @template TKey as array-key
* @template TValue
*
* @implements PaginatorInterface<TKey, TValue>
*/
final class OffsetPaginator implements PaginatorInterface
{
/** @var CountableDataInterface|OffsetableDataInterface|ReadableDataInterface */
/**
* @var CountableDataInterface|OffsetableDataInterface|ReadableDataInterface
* @psalm-var DataReaderType
*/
private ReadableDataInterface $dataReader;

private int $currentPage = 1;
private int $pageSize = self::DEFAULT_PAGE_SIZE;

/**
* @psalm-var ReadableDataInterface<TKey, TValue>
* @psalm-var DataReaderType|null
*/
private ?ReadableDataInterface $cachedReader = null;

/**
* @psalm-param ReadableDataInterface<TKey, TValue> $dataReader
* @psalm-param DataReaderType $dataReader
*/
public function __construct(ReadableDataInterface $dataReader)
{
if (!$dataReader instanceof OffsetableDataInterface) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf(
'Data reader should implement %s in order to be used with offset paginator',
OffsetableDataInterface::class
Expand All @@ -42,7 +51,7 @@ public function __construct(ReadableDataInterface $dataReader)
}

if (!$dataReader instanceof CountableDataInterface) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf(
'Data reader should implement %s in order to be used with offset paginator',
CountableDataInterface::class
Expand Down Expand Up @@ -119,7 +128,7 @@ public function getOffset(): int
}

/**
* @psalm-return \Generator<TKey, TValue, mixed, void>
* @psalm-return Generator<TKey, TValue, mixed, void>
*/
public function read(): iterable
{
Expand All @@ -130,7 +139,6 @@ public function read(): iterable
if ($this->currentPage > $this->getInternalTotalPages()) {
throw new PaginatorException('Page not found');
}
/** @var CountableDataInterface|OffsetableDataInterface|ReadableDataInterface $reader */
$this->cachedReader = $this->dataReader->withLimit($this->pageSize)->withOffset($this->getOffset());
yield from $this->cachedReader->read();
}
Expand Down
4 changes: 3 additions & 1 deletion src/Reader/CountableDataInterface.php
Expand Up @@ -4,7 +4,9 @@

namespace Yiisoft\Data\Reader;

interface CountableDataInterface extends \Countable
use Countable;

interface CountableDataInterface extends Countable
{
public function count(): int;
}
11 changes: 10 additions & 1 deletion src/Reader/Filter/CompareFilter.php
Expand Up @@ -4,15 +4,24 @@

namespace Yiisoft\Data\Reader\Filter;

use InvalidArgumentException;

abstract class CompareFilter implements FilterInterface
{
private string $field;

/**
* @var bool|float|int|string
*/
private $value;

/**
* @param mixed $value
*/
public function __construct(string $field, $value)
{
if (!is_scalar($value)) {
throw new \InvalidArgumentException('Value should be scalar');
throw new InvalidArgumentException('Value should be scalar');
}

$this->field = $field;
Expand Down
15 changes: 10 additions & 5 deletions src/Reader/Filter/GroupFilter.php
Expand Up @@ -4,10 +4,15 @@

namespace Yiisoft\Data\Reader\Filter;

use InvalidArgumentException;

use function is_array;
use function is_string;

abstract class GroupFilter implements FilterInterface
{
/**
* @var FilterInterface[]
* @var array[]|FilterInterface[]
*/
private array $filters;

Expand Down Expand Up @@ -45,21 +50,21 @@ public function toArray(): array
*
* @param array $filtersArray
*
* @return $this
* @return static
*/
public function withFiltersArray(array $filtersArray)
public function withFiltersArray(array $filtersArray): self
{
foreach ($filtersArray as $key => $filter) {
if ($filter instanceof FilterInterface) {
continue;
}

if (!is_array($filter)) {
throw new \InvalidArgumentException(sprintf('Invalid filter at "%s" key', $key));
throw new InvalidArgumentException(sprintf('Invalid filter at "%s" key', $key));
}
$first = array_shift($filter);
if (!is_string($first) || $first === '') {
throw new \InvalidArgumentException(sprintf('Invalid filter operator on "%s" key', $key));
throw new InvalidArgumentException(sprintf('Invalid filter operator on "%s" key', $key));
}
}
$new = clone $this;
Expand Down
9 changes: 8 additions & 1 deletion src/Reader/Filter/In.php
Expand Up @@ -4,6 +4,10 @@

namespace Yiisoft\Data\Reader\Filter;

use InvalidArgumentException;

use function is_array;

final class In implements FilterInterface
{
private string $field;
Expand All @@ -17,12 +21,15 @@ public function __construct(string $field, array $value)
$this->value = $value;
}

/**
* @param mixed $value
*/
private function validateValue($value): void
{
if (is_array($value)) {
foreach ($value as $arrayValue) {
if (!is_scalar($arrayValue)) {
throw new \InvalidArgumentException('All array values should be scalar');
throw new InvalidArgumentException('All array values should be scalar');
}
}
}
Expand Down

0 comments on commit e2d0af4

Please sign in to comment.