From 82fe912e3fa007fbe32366a71fdb97c001b42031 Mon Sep 17 00:00:00 2001 From: xmarchegay Date: Thu, 12 Dec 2024 19:18:19 +0100 Subject: [PATCH] #11 Push to phpstan level 8 --- phpstan.neon | 15 +--------- src/Task/Database/DatabaseReaderTask.php | 28 ++++++++++++++++--- src/Task/Database/DatabaseUpdaterTask.php | 21 +++++++++++--- .../AbstractDoctrineQueryTask.php | 17 ++++++++++- .../EntityManager/DoctrineBatchWriterTask.php | 3 +- src/Task/EntityManager/DoctrineReaderTask.php | 23 +++++++++++---- src/Task/EntityManager/DoctrineWriterTask.php | 4 +-- 7 files changed, 78 insertions(+), 33 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 30e9572..1cd333b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,18 +1,5 @@ parameters: - level: 6 + level: 8 paths: - src - tests - ignoreErrors: - - '#type has no value type specified in iterable type#' - - '#has parameter .* with no value type specified in iterable type#' - - '#has no value type specified in iterable type array#' - - '#configureOptions\(\) has no return type specified.#' - - '#configure\(\) has no return type specified#' - - '#process\(\) has no return type specified#' - - '#should return Iterator but returns Traversable#' - - '#Negated boolean expression is always false#' - checkGenericClassInNonGenericObjectType: false - reportUnmatchedIgnoredErrors: false - inferPrivatePropertyTypeFromConstructor: true - treatPhpDocTypesAsCertain: false diff --git a/src/Task/Database/DatabaseReaderTask.php b/src/Task/Database/DatabaseReaderTask.php index a0d3890..22f15f9 100644 --- a/src/Task/Database/DatabaseReaderTask.php +++ b/src/Task/Database/DatabaseReaderTask.php @@ -19,6 +19,7 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Result; +use Doctrine\DBAL\Types\Type; use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; @@ -26,6 +27,18 @@ /** * Fetch entities from doctrine. + * + * @phpstan-type Options array{ + * 'sql': ?string, + * 'table': string, + * 'limit': ?int, + * 'empty_log_level': string, + * 'paginate': ?int, + * 'offset': ?int, + * 'input_as_params': bool, + * 'params': array|string, mixed>, + * 'types': array|array + * } */ class DatabaseReaderTask extends AbstractConfigurableTask implements IterableTaskInterface, FinalizableTaskInterface { @@ -42,7 +55,7 @@ public function __construct( /** * Moves the internal pointer to the next element, * return true if the task has a next element - * return false if the task has terminated it's iteration. + * return false if the task has terminated its iteration. */ public function next(ProcessState $state): bool { @@ -57,6 +70,7 @@ public function next(ProcessState $state): bool public function execute(ProcessState $state): void { + /** @var Options $options */ $options = $this->getOptions($state); if (!$this->statement instanceof Result) { $this->statement = $this->initializeStatement($state); @@ -102,6 +116,7 @@ public function finalize(ProcessState $state): void protected function initializeStatement(ProcessState $state): Result { + /** @var Options $options */ $options = $this->getOptions($state); $connection = $this->getConnection($state); $sql = $options['sql']; @@ -121,7 +136,10 @@ protected function initializeStatement(ProcessState $state): Result $sql = $qb->getSQL(); } - $params = $options['input_as_params'] ? $state->getInput() : $options['params']; + + /** @var array $inputAsParams */ + $inputAsParams = $state->getInput(); + $params = $options['input_as_params'] ? $inputAsParams : $options['params']; return $connection->executeQuery($sql, $params, $options['types']); } @@ -168,7 +186,9 @@ protected function configureOptions(OptionsResolver $resolver): void protected function getConnection(ProcessState $state): Connection { - /* @noinspection PhpIncompatibleReturnTypeInspection */ - return $this->doctrine->getConnection($this->getOption($state, 'connection')); + /** @var Connection $connection */ + $connection = $this->doctrine->getConnection($this->getOption($state, 'connection')); + + return $connection; } } diff --git a/src/Task/Database/DatabaseUpdaterTask.php b/src/Task/Database/DatabaseUpdaterTask.php index c7d6884..5484037 100644 --- a/src/Task/Database/DatabaseUpdaterTask.php +++ b/src/Task/Database/DatabaseUpdaterTask.php @@ -17,6 +17,7 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Types\Type; use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -25,6 +26,13 @@ * Execute an update/delete in the database from a SQL statement. * * @see https://www.doctrine-project.org/projects/doctrine-dbal/en/2.9/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion + * + * @phpstan-type Options array{ + * 'sql': string, + * 'input_as_params': bool, + * 'params': mixed, + * 'types': array|array + * } */ class DatabaseUpdaterTask extends AbstractConfigurableTask { @@ -48,15 +56,18 @@ public function execute(ProcessState $state): void */ protected function initializeStatement(ProcessState $state): int { + /** @var Options $options */ $options = $this->getOptions($state); $connection = $this->getConnection($state); - $params = $options['input_as_params'] ? $state->getInput() : $options['params']; + /** @var array $inputAsParams */ + $inputAsParams = $state->getInput(); + $params = $options['input_as_params'] ? $inputAsParams : $options['params']; if (!\is_array($params)) { throw new \UnexpectedValueException('Expecting an array of params'); } - return $connection->executeStatement($options['sql'], $params, $options['types']); + return (int) $connection->executeStatement($options['sql'], $params, $options['types']); } protected function configureOptions(OptionsResolver $resolver): void @@ -77,7 +88,9 @@ protected function configureOptions(OptionsResolver $resolver): void protected function getConnection(ProcessState $state): Connection { - /* @noinspection PhpIncompatibleReturnTypeInspection */ - return $this->doctrine->getConnection($this->getOption($state, 'connection')); + /** @var Connection $connection */ + $connection = $this->doctrine->getConnection($this->getOption($state, 'connection')); + + return $connection; } } diff --git a/src/Task/EntityManager/AbstractDoctrineQueryTask.php b/src/Task/EntityManager/AbstractDoctrineQueryTask.php index f63991e..ef26c21 100644 --- a/src/Task/EntityManager/AbstractDoctrineQueryTask.php +++ b/src/Task/EntityManager/AbstractDoctrineQueryTask.php @@ -20,6 +20,15 @@ /** * Easily extendable task to query entities in their repository. + * + * @phpstan-type Options array{ + * 'class_name': class-string, + * 'criteria': array|null>, + * 'order_by': array, + * 'limit': ?int, + * 'offset': ?int, + * 'empty_log_level': string, + * } */ abstract class AbstractDoctrineQueryTask extends AbstractDoctrineTask { @@ -56,6 +65,13 @@ protected function configureOptions(OptionsResolver $resolver): void ); } + /** + * @template TEntityClass of object + * + * @param EntityRepository $repository + * @param array|null> $criteria + * @param array $orderBy + */ protected function getQueryBuilder( EntityRepository $repository, array $criteria, @@ -80,7 +96,6 @@ protected function getQueryBuilder( $qb->setParameter($parameterName, $value); } } - /* @noinspection ForeachSourceInspection */ foreach ($orderBy as $field => $order) { $qb->addOrderBy("e.{$field}", $order); } diff --git a/src/Task/EntityManager/DoctrineBatchWriterTask.php b/src/Task/EntityManager/DoctrineBatchWriterTask.php index e1ae918..c4c3b67 100644 --- a/src/Task/EntityManager/DoctrineBatchWriterTask.php +++ b/src/Task/EntityManager/DoctrineBatchWriterTask.php @@ -24,6 +24,7 @@ */ class DoctrineBatchWriterTask extends AbstractDoctrineTask implements FlushableTaskInterface { + /** @var array */ protected array $batch = []; public function flush(ProcessState $state): void @@ -60,9 +61,9 @@ protected function writeBatch(ProcessState $state): void } // Support for multiple entity managers is overkill but might be necessary + /** @var \SplObjectStorage $entityManagers */ $entityManagers = new \SplObjectStorage(); foreach ($this->batch as $entity) { - /** @var object $entity */ $class = ClassUtils::getClass($entity); $entityManager = $this->doctrine->getManagerForClass($class); if (!$entityManager instanceof EntityManagerInterface) { diff --git a/src/Task/EntityManager/DoctrineReaderTask.php b/src/Task/EntityManager/DoctrineReaderTask.php index f69b8d0..2b4ce1c 100644 --- a/src/Task/EntityManager/DoctrineReaderTask.php +++ b/src/Task/EntityManager/DoctrineReaderTask.php @@ -22,10 +22,12 @@ /** * Fetch entities from doctrine. + * + * @phpstan-import-type Options from AbstractDoctrineQueryTask */ class DoctrineReaderTask extends AbstractDoctrineQueryTask implements IterableTaskInterface { - protected ?\IteratorIterator $iterator = null; + protected ?\Iterator $iterator = null; public function __construct( protected LoggerInterface $logger, @@ -41,7 +43,7 @@ public function __construct( */ public function next(ProcessState $state): bool { - if (!$this->iterator instanceof \IteratorIterator) { + if (!$this->iterator instanceof \Iterator) { return false; } $this->iterator->next(); @@ -51,8 +53,9 @@ public function next(ProcessState $state): bool public function execute(ProcessState $state): void { + /** @var Options $options */ $options = $this->getOptions($state); - if (!$this->iterator instanceof \IteratorIterator) { + if (!$this->iterator instanceof \Iterator) { /** @var class-string $class */ $class = $options['class_name']; $entityManager = $this->doctrine->getManagerForClass($class); @@ -62,10 +65,12 @@ public function execute(ProcessState $state): void $repository = $entityManager->getRepository($class); $this->initIterator($repository, $options); } - $result = $this->iterator->current(); + if ($this->iterator instanceof \Iterator) { + $result = $this->iterator->current(); + } // Handle empty results - if (false === $result) { + if (!isset($result) || false === $result) { $logContext = [ 'options' => $options, ]; @@ -79,6 +84,12 @@ public function execute(ProcessState $state): void $state->setOutput($result); } + /** + * @template TEntityClass of object + * + * @param EntityRepository $repository + * @param Options $options + */ protected function initIterator(EntityRepository $repository, array $options): void { $qb = $this->getQueryBuilder( @@ -89,7 +100,7 @@ protected function initIterator(EntityRepository $repository, array $options): v $options['offset'] ); - $this->iterator = new \IteratorIterator($qb->getQuery()->toIterable()); + $this->iterator = new \ArrayIterator(iterator_to_array($qb->getQuery()->toIterable())); $this->iterator->rewind(); } } diff --git a/src/Task/EntityManager/DoctrineWriterTask.php b/src/Task/EntityManager/DoctrineWriterTask.php index 984e2a4..f069376 100644 --- a/src/Task/EntityManager/DoctrineWriterTask.php +++ b/src/Task/EntityManager/DoctrineWriterTask.php @@ -15,7 +15,6 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Doctrine\Common\Util\ClassUtils; -use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; /** @@ -31,14 +30,13 @@ public function execute(ProcessState $state): void protected function writeEntity(ProcessState $state): mixed { $this->getOptions($state); - /** @var object $entity */ + /** @var ?object $entity */ $entity = $state->getInput(); if (null === $entity) { throw new \RuntimeException('DoctrineWriterTask does not allow null input'); } $class = ClassUtils::getClass($entity); - /** @var ?EntityManager $entityManager */ $entityManager = $this->doctrine->getManagerForClass($class); if (!$entityManager instanceof EntityManagerInterface) { throw new \UnexpectedValueException("No manager found for class {$class}");