diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0a3961f..3a6e338 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,7 +2,7 @@ includes: - /tools/.composer/vendor-bin/phpstan/vendor/phpstan/phpstan-deprecation-rules/rules.neon - /tools/.composer/vendor-bin/phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon - /tools/.composer/vendor-bin/phpstan/vendor/phpstan/phpstan-phpunit/extension.neon - # - /tools/.composer/vendor-bin/phpstan/vendor/pepakriz/phpstan-exception-rules/extension.neon + - /tools/.composer/vendor-bin/phpstan/vendor/pepakriz/phpstan-exception-rules/extension.neon parameters: tmpDir: %currentWorkingDirectory%/var/phpstan level: max @@ -12,10 +12,8 @@ parameters: - var/ - vendor/ exceptionRules: - uncheckedExceptions: - - LogicException - - PHPUnit\Framework\Exception - - PHPUnit\Framework\MockObject\RuntimeException + checkedExceptions: + - RuntimeException ignoreErrors: - message: '#Access to an undefined property object\:\:\$scalar\.#' path: %currentWorkingDirectory%/tests/Xezilaires/FilterIteratorTest.php @@ -25,3 +23,6 @@ parameters: paths: - %currentWorkingDirectory%/tests/Xezilaires/Bridge/Spout/SpreadsheetTest.php - %currentWorkingDirectory%/tests/Xezilaires/Functional/FunctionalTestCase.php + - message: '#Missing @throws .* annotation#' + paths: + - %currentWorkingDirectory%/tests diff --git a/psalm.xml.dist b/psalm.xml.dist index 93b14a0..a3aa623 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -1,13 +1,12 @@ - - + - + + @@ -18,37 +17,42 @@ - - + + - + - - + + + + + + + - + - + - - - + + + diff --git a/src/Xezilaires/Bridge/PhpSpreadsheet/Spreadsheet.php b/src/Xezilaires/Bridge/PhpSpreadsheet/Spreadsheet.php index e18157b..4319f0d 100644 --- a/src/Xezilaires/Bridge/PhpSpreadsheet/Spreadsheet.php +++ b/src/Xezilaires/Bridge/PhpSpreadsheet/Spreadsheet.php @@ -49,9 +49,6 @@ public function __construct(\SplFileObject $file) $this->file = $file; } - /** - * {@inheritdoc} - */ public function createIterator(int $startRowIndex): void { if (null !== $this->iterator) { @@ -63,9 +60,6 @@ public function createIterator(int $startRowIndex): void $this->iterator = new RowIterator($sheet->getRowIterator($startRowIndex)); } - /** - * {@inheritdoc} - */ public function getIterator(): Iterator { if (null === $this->iterator) { @@ -75,9 +69,6 @@ public function getIterator(): Iterator return $this->iterator; } - /** - * {@inheritdoc} - */ public function getCurrentRow(): array { /** @var Row $row */ @@ -86,9 +77,6 @@ public function getCurrentRow(): array return $this->getRow($row->getRowIndex()); } - /** - * {@inheritdoc} - */ public function getRow(int $rowIndex): array { $data = []; @@ -103,14 +91,14 @@ public function getRow(int $rowIndex): array return $data; } - /** - * {@inheritdoc} - */ public function getHighestRow(): int { return $this->getActiveWorksheet()->getHighestRow(); } + /** + * @throws SpreadsheetException + */ private function getSpreadsheet(): PhpSpreadsheet { if (null === $this->spreadsheet) { @@ -130,6 +118,9 @@ private function getSpreadsheet(): PhpSpreadsheet return $this->spreadsheet; } + /** + * @throws SpreadsheetException + */ private function getActiveWorksheet(): Worksheet { try { @@ -140,12 +131,18 @@ private function getActiveWorksheet(): Worksheet } /** + * @throws SpreadsheetException + * * @return null|float|int|string */ private function fetchCell(string $columnName, int $rowIndex) { $worksheet = $this->getActiveWorksheet(); - $columnIndex = Coordinate::columnIndexFromString($columnName); + try { + $columnIndex = Coordinate::columnIndexFromString($columnName); + } catch (\Exception $exception) { + throw SpreadsheetException::invalidCell($exception); + } /** @var null|Cell $cell */ $cell = $worksheet->getCellByColumnAndRow($columnIndex, $rowIndex, self::CELL_NO_AUTO_CREATE); diff --git a/src/Xezilaires/Bridge/Spout/Spreadsheet.php b/src/Xezilaires/Bridge/Spout/Spreadsheet.php index 74086b2..0838019 100644 --- a/src/Xezilaires/Bridge/Spout/Spreadsheet.php +++ b/src/Xezilaires/Bridge/Spout/Spreadsheet.php @@ -51,9 +51,6 @@ public function __construct(\SplFileObject $file) $this->file = $file; } - /** - * {@inheritdoc} - */ public function createIterator(int $startRowIndex): void { if (null !== $this->iterator) { @@ -68,9 +65,6 @@ public function createIterator(int $startRowIndex): void $this->iterator = new RowIterator($iterator, $startRowIndex); } - /** - * {@inheritdoc} - */ public function getIterator(): Iterator { if (null === $this->iterator) { @@ -80,9 +74,6 @@ public function getIterator(): Iterator return $this->iterator; } - /** - * {@inheritdoc} - */ public function getRow(int $rowIndex): array { $iterator = $this->getIterator(); @@ -94,9 +85,6 @@ public function getRow(int $rowIndex): array return $row; } - /** - * {@inheritdoc} - */ public function getCurrentRow(): array { /** @var \ArrayObject $rowArrayObject */ @@ -118,9 +106,6 @@ public function getCurrentRow(): array return $row; } - /** - * {@inheritdoc} - */ public function getHighestRow(): int { if (null === $this->iterator) { @@ -151,6 +136,9 @@ private static function stringFromColumnIndex(int $columnIndex): string return self::$indexCache[$columnIndex]; } + /** + * @throws SpreadsheetException + */ private function getReader(): ReaderInterface { if (null === $this->reader) { @@ -170,6 +158,9 @@ private function getReader(): ReaderInterface return $this->reader; } + /** + * @throws SpreadsheetException + */ private function getActiveWorksheet(): SheetInterface { try { diff --git a/src/Xezilaires/Bridge/Symfony/Command/SerializeCommand.php b/src/Xezilaires/Bridge/Symfony/Command/SerializeCommand.php index e2094f9..f661f42 100644 --- a/src/Xezilaires/Bridge/Symfony/Command/SerializeCommand.php +++ b/src/Xezilaires/Bridge/Symfony/Command/SerializeCommand.php @@ -14,6 +14,7 @@ namespace Xezilaires\Bridge\Symfony\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -42,6 +43,7 @@ final class SerializeCommand extends Command public function __construct(SpreadsheetIteratorFactory $iteratorFactory, Serializer $serializer) { + /** @psalm-suppress MissingThrowsDocblock */ parent::__construct('xezilaires:serialize'); $this->setDescription('Serialize Excel files into JSON, XML, CSV'); @@ -51,7 +53,7 @@ public function __construct(SpreadsheetIteratorFactory $iteratorFactory, Seriali } /** - * {@inheritdoc} + * @throws InvalidArgumentException */ protected function configure(): void { @@ -65,7 +67,7 @@ protected function configure(): void } /** - * @throws \ReflectionException + * @throws InvalidArgumentException * @throws \RuntimeException * @throws \ReflectionException */ @@ -86,14 +88,15 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int $xmlRoot = $input->getOption('xml-root'); if (null === $format) { - throw new \RuntimeException('Format is required'); + throw new \UnexpectedValueException('Format is required'); } $context = []; switch ($format) { case 'csv': if (false === class_exists(CsvEncoder::class)) { - throw new \RuntimeException('CSV format is only available with Symfony 4.0+'); + /** @psalm-suppress MissingThrowsDocblock */ + throw new \LogicException('CSV format is only available with Symfony 4.0+'); } break; case 'json': @@ -101,7 +104,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int break; case 'xml': if (null === $xmlRoot) { - throw new \RuntimeException('XML root node name cannot be empty if XML format requested'); + throw new \UnexpectedValueException('XML root node name cannot be empty if XML format requested'); } $context['xml_root_node_name'] = $xmlRoot; break; diff --git a/src/Xezilaires/Exception/AnnotationException.php b/src/Xezilaires/Exception/AnnotationException.php index e5d744a..ed223a2 100644 --- a/src/Xezilaires/Exception/AnnotationException.php +++ b/src/Xezilaires/Exception/AnnotationException.php @@ -16,7 +16,7 @@ use Doctrine\Common\Annotations\AnnotationException as DoctrineAnnotationException; use Xezilaires\Exception; -final class AnnotationException extends \InvalidArgumentException implements Exception +final class AnnotationException extends \UnexpectedValueException implements Exception { public static function unsupportedAnnotation(): self { diff --git a/src/Xezilaires/Exception/DenormalizerException.php b/src/Xezilaires/Exception/DenormalizerException.php index 9ff38f9..8ddf5c3 100644 --- a/src/Xezilaires/Exception/DenormalizerException.php +++ b/src/Xezilaires/Exception/DenormalizerException.php @@ -16,7 +16,7 @@ use Xezilaires\Bridge\Symfony\Serializer\Exception as SerializerException; use Xezilaires\Exception; -final class DenormalizerException extends \InvalidArgumentException implements Exception +final class DenormalizerException extends \UnexpectedValueException implements Exception { public static function denormalizationFailed(SerializerException $exception): self { diff --git a/src/Xezilaires/Exception/MappingException.php b/src/Xezilaires/Exception/MappingException.php index 2c564b9..4ddde4d 100644 --- a/src/Xezilaires/Exception/MappingException.php +++ b/src/Xezilaires/Exception/MappingException.php @@ -16,7 +16,7 @@ use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; use Xezilaires\Exception; -final class MappingException extends \InvalidArgumentException implements Exception +final class MappingException extends \UnexpectedValueException implements Exception { public static function missingHeaderOption(): self { diff --git a/src/Xezilaires/Exception/NestableIteratorException.php b/src/Xezilaires/Exception/NestableIteratorException.php index 8499a33..673e8fc 100644 --- a/src/Xezilaires/Exception/NestableIteratorException.php +++ b/src/Xezilaires/Exception/NestableIteratorException.php @@ -15,7 +15,7 @@ use Xezilaires\Exception; -final class NestableIteratorException extends \InvalidArgumentException implements Exception +final class NestableIteratorException extends \UnexpectedValueException implements Exception { public static function iteratorMustBeNestable(): self { diff --git a/src/Xezilaires/Exception/SpreadsheetException.php b/src/Xezilaires/Exception/SpreadsheetException.php index 58ea947..f75433f 100644 --- a/src/Xezilaires/Exception/SpreadsheetException.php +++ b/src/Xezilaires/Exception/SpreadsheetException.php @@ -15,7 +15,7 @@ use Xezilaires\Exception; -final class SpreadsheetException extends \InvalidArgumentException implements Exception +final class SpreadsheetException extends \UnexpectedValueException implements Exception { public static function noSpreadsheetFound(): self { diff --git a/src/Xezilaires/Metadata/Annotation/AnnotationDriver.php b/src/Xezilaires/Metadata/Annotation/AnnotationDriver.php index 9451fd1..617f54b 100644 --- a/src/Xezilaires/Metadata/Annotation/AnnotationDriver.php +++ b/src/Xezilaires/Metadata/Annotation/AnnotationDriver.php @@ -38,7 +38,7 @@ final class AnnotationDriver public function __construct(AnnotationReader $reader = null) { if (false === class_exists(AnnotationReader::class)) { - throw new \RuntimeException('Xezilaires annotations support requires Doctrine Annotations component. Install "doctrine/annotations" to use it.'); + throw new \LogicException('Xezilaires annotations support requires Doctrine Annotations component. Install "doctrine/annotations" to use it.'); } AnnotationRegistry::registerUniqueLoader('class_exists'); diff --git a/src/Xezilaires/Metadata/Mapping.php b/src/Xezilaires/Metadata/Mapping.php index 59e0cbd..673f44c 100644 --- a/src/Xezilaires/Metadata/Mapping.php +++ b/src/Xezilaires/Metadata/Mapping.php @@ -42,6 +42,8 @@ final class Mapping /** * @param array $references + * + * @throws MappingException */ public function __construct(string $className, array $references, array $options = null) { @@ -84,6 +86,9 @@ public function getOption(string $option) return $this->options[$option]; } + /** + * @throws OptionsResolverException + */ private function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ @@ -101,6 +106,8 @@ private function configureOptions(OptionsResolver $resolver): void /** * @psalm-param string $className + * + * @throws MappingException */ private function setClassName(string $className): void { @@ -113,6 +120,8 @@ private function setClassName(string $className): void /** * @param array $references + * + * @throws MappingException */ private function setReferences(array $references): void { @@ -142,6 +151,8 @@ private function setReferences(array $references): void /** * @param array $options + * + * @throws MappingException */ private function setOptions(array $options): void { diff --git a/src/Xezilaires/Spreadsheet.php b/src/Xezilaires/Spreadsheet.php index 1d38d55..5422968 100644 --- a/src/Xezilaires/Spreadsheet.php +++ b/src/Xezilaires/Spreadsheet.php @@ -13,28 +13,41 @@ namespace Xezilaires; +use Xezilaires\Exception\SpreadsheetException; + interface Spreadsheet { /** * @param int $startRowIndex row index where this iterator starts, one-based + * + * @throws SpreadsheetException */ public function createIterator(int $startRowIndex): void; + /** + * @throws SpreadsheetException + */ public function getIterator(): Iterator; /** * @param int $rowIndex row index to fetch, one-based * + * @throws SpreadsheetException + * * @return array */ public function getRow(int $rowIndex): array; /** + * @throws SpreadsheetException + * * @return array */ public function getCurrentRow(): array; /** + * @throws SpreadsheetException + * * @return int row index of the last row, one-based */ public function getHighestRow(): int; diff --git a/src/Xezilaires/SpreadsheetIterator.php b/src/Xezilaires/SpreadsheetIterator.php index 1094424..eab97d1 100644 --- a/src/Xezilaires/SpreadsheetIterator.php +++ b/src/Xezilaires/SpreadsheetIterator.php @@ -16,6 +16,7 @@ use Xezilaires\Bridge\Symfony\Serializer\Exception as SerializerException; use Xezilaires\Exception\DenormalizerException; use Xezilaires\Exception\MappingException; +use Xezilaires\Exception\SpreadsheetException; use Xezilaires\Metadata\ArrayReference; use Xezilaires\Metadata\ColumnReference; use Xezilaires\Metadata\HeaderReference; @@ -68,7 +69,8 @@ public function __construct(Spreadsheet $spreadsheet, Mapping $mapping, Denormal } /** - * {@inheritdoc} + * @throws SpreadsheetException + * @throws DenormalizerException */ public function current(): object { diff --git a/src/Xezilaires/SpreadsheetIteratorFactory.php b/src/Xezilaires/SpreadsheetIteratorFactory.php index aded32e..62e94c0 100644 --- a/src/Xezilaires/SpreadsheetIteratorFactory.php +++ b/src/Xezilaires/SpreadsheetIteratorFactory.php @@ -27,9 +27,6 @@ public function __construct(Denormalizer $denormalizer) $this->denormalizer = $denormalizer; } - /** - * @throws \RuntimeException - */ public function fromFile(\SplFileObject $path, Mapping $mapping): Iterator { switch (true) { @@ -40,7 +37,7 @@ public function fromFile(\SplFileObject $path, Mapping $mapping): Iterator $spreadsheet = new Bridge\Spout\Spreadsheet($path); break; default: - throw new \RuntimeException('Install either phpoffice/phpspreadsheet or box/spout to read Excel files'); + throw new \LogicException('Install either phpoffice/phpspreadsheet or box/spout to read Excel files'); } return $this->fromSpreadsheet($spreadsheet, $mapping); diff --git a/src/Xezilaires/TreeBuilder.php b/src/Xezilaires/TreeBuilder.php index fa71272..d5a400f 100644 --- a/src/Xezilaires/TreeBuilder.php +++ b/src/Xezilaires/TreeBuilder.php @@ -34,6 +34,9 @@ final class TreeBuilder */ private $paths = []; + /** + * @throws NestableIteratorException + */ public function __construct(Iterator $iterator, string $rootNode = 'Root') { $this->root = new Node($rootNode); @@ -59,6 +62,8 @@ public function getRoot(): NodeInterface } /** + * @throws NestableIteratorException + * * @return NodeInterface[] */ public function getAncestors(?string $id): array @@ -74,6 +79,9 @@ public function getAncestors(?string $id): array return $this->paths[$id]; } + /** + * @throws NestableIteratorException + */ public function getPath(?string $id): string { return '/'.ltrim( @@ -92,6 +100,8 @@ static function (NodeInterface $node): string { /** * @param float|int|string $id + * + * @throws NestableIteratorException */ private function fetch($id): NodeInterface { diff --git a/tests/Xezilaires/Functional/FunctionalTestCase.php b/tests/Xezilaires/Functional/FunctionalTestCase.php index f9e7dcc..42885cd 100644 --- a/tests/Xezilaires/Functional/FunctionalTestCase.php +++ b/tests/Xezilaires/Functional/FunctionalTestCase.php @@ -138,7 +138,6 @@ public function testCanLoadSparseFixtureWithHeaderReference(): void * * @throws \ReflectionException * @throws \RuntimeException - * @throws \ReflectionException */ public function testCanLoadSparseFixtureWithAnnotations(): void { diff --git a/tests/Xezilaires/SpreadsheetIteratorTest.php b/tests/Xezilaires/SpreadsheetIteratorTest.php index 2be1069..be34d05 100644 --- a/tests/Xezilaires/SpreadsheetIteratorTest.php +++ b/tests/Xezilaires/SpreadsheetIteratorTest.php @@ -17,7 +17,9 @@ use PHPUnit\Framework\MockObject\Builder\InvocationMocker; use PHPUnit\Framework\TestCase; use Xezilaires\Denormalizer; +use Xezilaires\Exception\DenormalizerException; use Xezilaires\Exception\MappingException; +use Xezilaires\Exception\SpreadsheetException; use Xezilaires\Iterator; use Xezilaires\Metadata\HeaderReference; use Xezilaires\Metadata\Mapping; @@ -76,6 +78,10 @@ public function testCanPerformNextCorrectly(): void $iterator->next(); } + /** + * @throws SpreadsheetException + * @throws DenormalizerException + */ public function testWillOfferAnDidYouMeanForInvalidHeader(): void { $spreadsheet = $this