diff --git a/Build/Scripts/phpstan.sh b/Build/Scripts/phpstan.sh new file mode 100755 index 0000000..7a95b00 --- /dev/null +++ b/Build/Scripts/phpstan.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +THIS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd "$THIS_SCRIPT_DIR" || exit 1 +cd ../../ || exit 1 +CORE_ROOT="${PWD}" + +Build/Scripts/runTests.sh -s composerInstall + +Build/Scripts/runTests.sh -s phpstan + +Build/Scripts/runTests.sh -s clean +Build/Scripts/additionalTests.sh -s clean diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon new file mode 100644 index 0000000..1339890 --- /dev/null +++ b/Build/phpstan/phpstan-baseline.neon @@ -0,0 +1,2 @@ +parameters: + ignoreErrors: \ No newline at end of file diff --git a/Build/phpstan/phpstan-constants.php b/Build/phpstan/phpstan-constants.php new file mode 100644 index 0000000..0bb0280 --- /dev/null +++ b/Build/phpstan/phpstan-constants.php @@ -0,0 +1,4 @@ +getOrderField(); if ($orderField !== '') { $orderings = [$orderField => $this->getOrderDirection()]; @@ -116,6 +125,9 @@ protected function displayError(string $type): void throw new ImmediateResponseException($response); } + /** + * @param QueryResultInterface|QueryResultInterface|QueryResultInterface|array $result + */ protected function addPaginatorToView(QueryResultInterface|array $result): void { $paginator = $this->getPaginator($result); @@ -130,6 +142,9 @@ protected function addPaginatorToView(QueryResultInterface|array $result): void ); } + /** + * @param QueryResultInterface|array $result + */ protected function getPaginator(QueryResultInterface|array $result): PaginatorInterface { $currentPage = $this->request->hasArgument('currentPage') diff --git a/Classes/Controller/CategoryController.php b/Classes/Controller/CategoryController.php index ed1d603..9a4ee5f 100644 --- a/Classes/Controller/CategoryController.php +++ b/Classes/Controller/CategoryController.php @@ -57,6 +57,10 @@ protected function listAction(): ResponseInterface return new HtmlResponse($this->view->render()); } + /** + * @param QueryResultInterface $categories + * @return QueryResultInterface + */ protected function removeExcludeCategories(QueryResultInterface $categories): QueryResultInterface { $excludeCategories = GeneralUtility::intExplode(',', $this->settings['excludeCategories'], true); diff --git a/Classes/Controller/SearchController.php b/Classes/Controller/SearchController.php index 3a7c03e..71c0ec6 100644 --- a/Classes/Controller/SearchController.php +++ b/Classes/Controller/SearchController.php @@ -28,11 +28,14 @@ public function searchAction(): ResponseInterface return new HtmlResponse($this->view->render()); } + /** + * @param array $search + */ public function startSearchAction(array $search): ResponseInterface { if (($search['query'] ?? '') != '') { if (isset($search['searchBy'])) { - switch ((string)$search['searchFor'] ?? '') { + switch ((string)($search['searchFor'] ?? '')) { case 'author': $controller = 'Author'; $pageId = (int)$this->settings['authorPageId']; @@ -49,7 +52,7 @@ public function startSearchAction(array $search): ResponseInterface $pageId = $this->request->getAttribute('currentContentObject')->data['pid']; } - $this->redirect('search', $controller, null, $search, $pageId, $controller); + return $this->redirect('search', $controller, null, $search, $pageId); } $response = new HtmlResponse($this->view->render()); } else { @@ -59,6 +62,9 @@ public function startSearchAction(array $search): ResponseInterface return $response; } + /** + * @param ?array $arguments + */ protected function redirect( $actionName, $controllerName = null, diff --git a/Classes/Domain/Model/Author.php b/Classes/Domain/Model/Author.php index 993ad92..8e2a667 100644 --- a/Classes/Domain/Model/Author.php +++ b/Classes/Domain/Model/Author.php @@ -45,11 +45,17 @@ public function initializeObject(): void $this->books = new ObjectStorage(); } + /** + * @param ObjectStorage $books + */ public function setBooks(ObjectStorage $books): void { $this->books = $books; } + /** + * @return ObjectStorage + */ public function getBooks(): ObjectStorage { return $this->books; diff --git a/Classes/Domain/Model/Book.php b/Classes/Domain/Model/Book.php index 19624f0..9366788 100644 --- a/Classes/Domain/Model/Book.php +++ b/Classes/Domain/Model/Book.php @@ -91,61 +91,97 @@ public function initializeObject(): void $this->samplePdf = new ObjectStorage(); } + /** + * @param ObjectStorage $author + */ public function setAuthor(ObjectStorage $author): void { $this->author = $author; } + /** + * @return ObjectStorage + */ public function getAuthor(): ObjectStorage { return $this->author; } + /** + * @param ObjectStorage $category + */ public function setCategory(ObjectStorage $category): void { $this->category = $category; } + /** + * @return ObjectStorage + */ public function getCategory(): ObjectStorage { return $this->category; } + /** + * @param ObjectStorage $extras + */ public function setExtras(ObjectStorage $extras): void { $this->extras = $extras; } + /** + * @return ObjectStorage + */ public function getExtras(): ObjectStorage { return $this->extras; } + /** + * @param ObjectStorage $cover + */ public function setCover(ObjectStorage $cover): void { $this->cover = $cover; } + /** + * @return ObjectStorage + */ public function getCover(): ObjectStorage { return $this->cover; } + /** + * @param ObjectStorage $coverLarge + */ public function setCoverLarge(ObjectStorage $coverLarge): void { $this->coverLarge = $coverLarge; } + /** + * @return ObjectStorage + */ public function getCoverLarge(): ObjectStorage { return $this->coverLarge; } + /** + * @param ObjectStorage $samplePdf + */ public function setSamplePdf(ObjectStorage $samplePdf): void { $this->samplePdf = $samplePdf; } + /** + * @return ObjectStorage + */ public function getSamplePdf(): ObjectStorage { return $this->samplePdf; diff --git a/Classes/Domain/Model/Category.php b/Classes/Domain/Model/Category.php index 7cff72e..e431f17 100644 --- a/Classes/Domain/Model/Category.php +++ b/Classes/Domain/Model/Category.php @@ -53,21 +53,33 @@ public function initializeObject(): void $this->books = new ObjectStorage(); } + /** + * @param ObjectStorage $children + */ public function setChildren(ObjectStorage $children): void { $this->children = $children; } + /** + * @return ObjectStorage + */ public function getChildren(): ObjectStorage { return $this->children; } + /** + * @param ObjectStorage $books + */ public function setBooks(ObjectStorage $books): void { $this->books = $books; } + /** + * @return ObjectStorage + */ public function getBooks(): ObjectStorage { return $this->books; diff --git a/Classes/Domain/Model/Series.php b/Classes/Domain/Model/Series.php index 8240850..d5dc0b7 100644 --- a/Classes/Domain/Model/Series.php +++ b/Classes/Domain/Model/Series.php @@ -46,11 +46,17 @@ public function initializeObject(): void $this->books = new ObjectStorage(); } + /** + * @param ObjectStorage $books + */ public function setBooks(ObjectStorage $books): void { $this->books = $books; } + /** + * @return ObjectStorage + */ public function getBooks(): ObjectStorage { return $this->books; diff --git a/Classes/Domain/Repository/AuthorRepository.php b/Classes/Domain/Repository/AuthorRepository.php index 42841f5..43e8622 100644 --- a/Classes/Domain/Repository/AuthorRepository.php +++ b/Classes/Domain/Repository/AuthorRepository.php @@ -19,10 +19,18 @@ use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException; +use TYPO3\CMS\Extbase\Persistence\Generic\Exception; +use TYPO3\CMS\Extbase\Persistence\Generic\Query; use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; use TYPO3\CMS\Extbase\Persistence\Repository; +/** + * A repository for authors + * + * @extends Repository + */ class AuthorRepository extends Repository { public function __construct( @@ -33,8 +41,13 @@ public function __construct( parent::__construct(); } + /** + * @return array + * @throws Exception + */ public function findAuthorGroupedByLetters(): array { + /** @var Query $query */ $query = $this->createQuery(); $queryBuilder = $this->getQueryBuilderForTable('tx_sfbooks_domain_model_author'); @@ -57,7 +70,7 @@ public function findAuthorGroupedByLetters(): array /** @var Author $author */ foreach ($result as $author) { $letter = $author->getCapitalLetter(); - if (!isset($groupedAuthors[$letter]) || !is_array($groupedAuthors[$letter])) { + if (!is_array($groupedAuthors[$letter] ?? '')) { $groupedAuthors[$letter] = []; } @@ -67,6 +80,11 @@ public function findAuthorGroupedByLetters(): array return $groupedAuthors; } + /** + * @param string[] $searchFields + * @return QueryResultInterface + * @throws InvalidQueryException + */ public function findBySearch(string $searchString, array $searchFields): QueryResultInterface { $query = $this->createQuery(); diff --git a/Classes/Domain/Repository/BookRepository.php b/Classes/Domain/Repository/BookRepository.php index 2b630cf..af06e87 100644 --- a/Classes/Domain/Repository/BookRepository.php +++ b/Classes/Domain/Repository/BookRepository.php @@ -15,11 +15,23 @@ namespace Evoweb\SfBooks\Domain\Repository; +use Evoweb\SfBooks\Domain\Model\Book; +use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; use TYPO3\CMS\Extbase\Persistence\Repository; +/** + * A repository for books + * + * @extends Repository + */ class BookRepository extends Repository { + /** + * @param int[] $categories + * @return QueryResultInterface + * @throws InvalidQueryException + */ public function findByCategories(array $categories): QueryResultInterface { $query = $this->createQuery(); @@ -35,6 +47,11 @@ public function findByCategories(array $categories): QueryResultInterface return $query->execute(); } + /** + * @param string[] $searchFields + * @return QueryResultInterface + * @throws InvalidQueryException + */ public function findBySearch(string $searchString, array $searchFields): QueryResultInterface { $query = $this->createQuery(); diff --git a/Classes/Domain/Repository/CategoryRepository.php b/Classes/Domain/Repository/CategoryRepository.php index 3611bc2..d8cf118 100644 --- a/Classes/Domain/Repository/CategoryRepository.php +++ b/Classes/Domain/Repository/CategoryRepository.php @@ -15,11 +15,23 @@ namespace Evoweb\SfBooks\Domain\Repository; +use Evoweb\SfBooks\Domain\Model\Category; +use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; use TYPO3\CMS\Extbase\Persistence\Repository; +/** + * A repository for categories + * + * @extends Repository + */ class CategoryRepository extends Repository { + /** + * @param int[] $categories + * @return QueryResultInterface + * @throws InvalidQueryException + */ public function findByUids(array $categories): QueryResultInterface { $query = $this->createQuery(); diff --git a/Classes/Domain/Repository/SeriesRepository.php b/Classes/Domain/Repository/SeriesRepository.php index 27c53ea..b4d67e6 100644 --- a/Classes/Domain/Repository/SeriesRepository.php +++ b/Classes/Domain/Repository/SeriesRepository.php @@ -18,10 +18,17 @@ use Evoweb\SfBooks\Domain\Model\Series; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; +use TYPO3\CMS\Extbase\Persistence\Generic\Exception; +use TYPO3\CMS\Extbase\Persistence\Generic\Query; use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; use TYPO3\CMS\Extbase\Persistence\Repository; +/** + * A repository for series + * + * @extends Repository + */ class SeriesRepository extends Repository { public function __construct( @@ -32,8 +39,13 @@ public function __construct( parent::__construct(); } + /** + * @return array> + * @throws Exception + */ public function findSeriesGroupedByLetters(): array { + /** @var Query $query */ $query = $this->createQuery(); $queryBuilder = $this->getQueryBuilderForTable('tx_sfbooks_domain_model_series'); @@ -56,7 +68,7 @@ public function findSeriesGroupedByLetters(): array /** @var Series $series */ foreach ($result as $series) { $letter = $series->getCapitalLetter(); - if (!isset($groupedSeries[$letter]) || !is_array($groupedSeries[$letter])) { + if (!is_array($groupedSeries[$letter] ?? '')) { $groupedSeries[$letter] = []; } @@ -66,6 +78,10 @@ public function findSeriesGroupedByLetters(): array return $groupedSeries; } + /** + * @param int[] $series + * @return QueryResultInterface + */ public function findBySeries(array $series): QueryResultInterface { $query = $this->createQuery(); diff --git a/Classes/User/IsbnEvaluation.php b/Classes/User/IsbnEvaluation.php index 3830065..3dd0941 100644 --- a/Classes/User/IsbnEvaluation.php +++ b/Classes/User/IsbnEvaluation.php @@ -34,22 +34,24 @@ public function returnFieldJS(): JavaScriptModuleInstruction /** * Server-side validation/evaluation on saving the record * - * @param array $parameters The parameters value, is_in and set + * @param string[] $parameters The parameters value, is_in and set * @return string Evaluated field value */ public function evaluateFieldValue(...$parameters): string { - return preg_replace('/[^0-9X\-]/i', '', $parameters[0]); + $parameter = $parameters[0] ?? ''; + return preg_replace('/[^0-9X\-]/i', '', $parameter); } /** * Server-side validation/evaluation on opening the record * - * @param array $parameters Array with key 'value' containing the field value from the database + * @param array $parameters Array with key 'value' containing the field value from the database * @return string Evaluated field value */ public function deevaluateFieldValue(array $parameters): string { - return preg_replace('/[^0-9X\-]/i', '', $parameters['value']); + $value = $parameters['value'] ?? ''; + return preg_replace('/[^0-9X\-]/i', '', $value); } } diff --git a/Tests/Functional/AbstractTestCase.php b/Tests/Functional/AbstractTestCase.php index 98d54fd..be928e6 100644 --- a/Tests/Functional/AbstractTestCase.php +++ b/Tests/Functional/AbstractTestCase.php @@ -13,12 +13,12 @@ namespace Evoweb\SfBooks\Tests\Functional; +use Doctrine\DBAL\Exception; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Http\NormalizedParams; use TYPO3\CMS\Core\Http\ServerRequest; -use TYPO3\CMS\Core\TypoScript\FrontendTypoScript; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; @@ -40,7 +40,7 @@ protected function setUp(): void $GLOBALS['TYPO3_REQUEST'] = $request; } - private function createServerRequest(string $url, string $method = 'GET'): ServerRequestInterface + private function createServerRequest(string $url): ServerRequestInterface { $requestUrlParts = parse_url($url); $docRoot = $this->instancePath; @@ -58,7 +58,7 @@ private function createServerRequest(string $url, string $method = 'GET'): Serve 'PATH_TRANSLATED' => $docRoot . '/index.php', 'QUERY_STRING' => $requestUrlParts['query'] ?? '', 'REQUEST_URI' => $requestUrlParts['path'] . (isset($requestUrlParts['query']) ? '?' . $requestUrlParts['query'] : ''), - 'REQUEST_METHOD' => $method, + 'REQUEST_METHOD' => 'GET', ]; // Define HTTPS and server port if (isset($requestUrlParts['scheme'])) { @@ -75,7 +75,7 @@ private function createServerRequest(string $url, string $method = 'GET'): Serve $serverParams['SERVER_PORT'] = $requestUrlParts['port']; } // set up normalizedParams - $request = new ServerRequest($url, $method, null, [], $serverParams); + $request = new ServerRequest($url, 'GET', null, [], $serverParams); $request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); return $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request)); } @@ -101,9 +101,13 @@ protected function assertNoLogEntries(): void } } + /** + * @return array> + */ protected function getLogEntries(): array { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log'); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('sys_log'); $result = $queryBuilder ->select('*') ->from('sys_log') @@ -113,8 +117,11 @@ protected function getLogEntries(): array [1, 2] ) ) - ->executeQuery() - ->fetchAllAssociative(); + ->executeQuery(); + try { + $result = $result->fetchAllAssociative(); + } catch (Exception) { + } return is_array($result) ? $result : []; } } diff --git a/Tests/Functional/Domain/Repository/AuthorRepositoryTest.php b/Tests/Functional/Domain/Repository/AuthorRepositoryTest.php index 7853013..a03d4ce 100644 --- a/Tests/Functional/Domain/Repository/AuthorRepositoryTest.php +++ b/Tests/Functional/Domain/Repository/AuthorRepositoryTest.php @@ -37,7 +37,7 @@ protected function setUp(): void } #[Test] - public function findByUidReturnsOneAuthor() + public function findByUidReturnsOneAuthor(): void { $author = $this->subject->findByUid(1); $properties = [ @@ -62,7 +62,7 @@ public function findByUidReturnsOneAuthor() } #[Test] - public function findAuthorGroupedByLetters() + public function findAuthorGroupedByLetters(): void { $result = $this->subject->findAuthorGroupedByLetters(); /** @var Author $author */ diff --git a/Tests/Functional/Domain/Repository/SeriesRepositoryTest.php b/Tests/Functional/Domain/Repository/SeriesRepositoryTest.php index d0104ce..f297671 100644 --- a/Tests/Functional/Domain/Repository/SeriesRepositoryTest.php +++ b/Tests/Functional/Domain/Repository/SeriesRepositoryTest.php @@ -37,7 +37,7 @@ protected function setUp(): void } #[Test] - public function findByUidReturnsOneSeries() + public function findByUidReturnsOneSeries(): void { /** @var Series $series */ $series = $this->subject->findByUid(1); @@ -63,7 +63,7 @@ public function findByUidReturnsOneSeries() } #[Test] - public function findSeriesGroupedByLetters() + public function findSeriesGroupedByLetters(): void { $result = $this->subject->findSeriesGroupedByLetters(); /** @var Series $series */ diff --git a/composer.json b/composer.json index 2488ebf..7cfcfd4 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,12 @@ "doctrine/dbal": "^3.7.2 || ^4.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.57.1", - "typo3/testing-framework": "dev-main", - "phpunit/phpunit": "^11.3.1", - "webmozart/assert": "^1.11.0" + "friendsofphp/php-cs-fixer": "^3.64.0", + "friendsoftypo3/phpstan-typo3": "^0.9.0", + "phpstan/phpdoc-parser": "^1.30.0", + "phpstan/phpstan": "^1.12.5", + "phpunit/phpunit": "^11.0.3", + "typo3/testing-framework": "dev-main" }, "minimum-stability": "dev", "prefer-stable": true,