From 773f5040d5660022f86d70d17a0ab195e5718651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Nizio=C5=82?= Date: Fri, 11 Nov 2022 14:26:54 +0100 Subject: [PATCH] Fix issues reported by PHPStan #1334 --- phpstan-baseline.neon | 457 ------------------ phpstan.neon.dist | 3 - src/Command/AddUserCommand.php | 12 +- src/Command/DeleteUserCommand.php | 4 +- src/Command/ListUsersCommand.php | 16 +- src/Controller/Admin/BlogController.php | 14 +- src/Controller/BlogController.php | 21 +- src/Controller/UserController.php | 5 +- src/DataFixtures/AppFixtures.php | 27 +- src/Entity/Post.php | 10 +- src/Entity/User.php | 14 +- .../CheckRequirementsSubscriber.php | 3 +- .../CommentNotificationSubscriber.php | 12 +- .../RedirectToPreferredLocaleSubscriber.php | 3 + .../TagArrayToStringTransformer.php | 26 +- src/Form/Type/DateTimePickerType.php | 5 +- src/Pagination/Paginator.php | 19 +- src/Repository/PostRepository.php | 14 +- src/Repository/TagRepository.php | 2 + src/Repository/UserRepository.php | 5 + src/Security/PostVoter.php | 2 + src/Twig/AppExtension.php | 9 + src/Twig/SourceCodeExtension.php | 21 +- tests/Command/AbstractCommandTest.php | 7 +- tests/Command/AddUserCommandTest.php | 20 +- tests/Command/ListUsersCommandTest.php | 2 +- tests/Controller/Admin/BlogControllerTest.php | 22 +- tests/Controller/BlogControllerTest.php | 7 +- tests/Controller/DefaultControllerTest.php | 14 +- tests/Controller/UserControllerTest.php | 7 +- .../TagArrayToStringTransformerTest.php | 2 +- tests/Utils/ValidatorTest.php | 4 +- 32 files changed, 271 insertions(+), 518 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9009e1362..e69de29bb 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,457 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Method App\\\\Command\\\\AddUserCommand\\:\\:validateUserData\\(\\) has parameter \\$email with no type specified\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Method App\\\\Command\\\\AddUserCommand\\:\\:validateUserData\\(\\) has parameter \\$fullName with no type specified\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Method App\\\\Command\\\\AddUserCommand\\:\\:validateUserData\\(\\) has parameter \\$plainPassword with no type specified\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Method App\\\\Command\\\\AddUserCommand\\:\\:validateUserData\\(\\) has parameter \\$username with no type specified\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Parameter \\#1 \\$email of method App\\\\Entity\\\\User\\:\\:setEmail\\(\\) expects string, mixed given\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Parameter \\#1 \\$fullName of method App\\\\Entity\\\\User\\:\\:setFullName\\(\\) expects string, mixed given\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Parameter \\#1 \\$string of function Symfony\\\\Component\\\\String\\\\u expects string\\|null, mixed given\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Parameter \\#1 \\$username of method App\\\\Entity\\\\User\\:\\:setUsername\\(\\) expects string, mixed given\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Parameter \\#2 \\$plainPassword of method Symfony\\\\Component\\\\PasswordHasher\\\\Hasher\\\\UserPasswordHasherInterface\\:\\:hashPassword\\(\\) expects string, mixed given\\.$#" - count: 1 - path: src/Command/AddUserCommand.php - - - - message: "#^Call to an undefined method App\\\\Repository\\\\UserRepository\\:\\:findOneByUsername\\(\\)\\.$#" - count: 1 - path: src/Command/DeleteUserCommand.php - - - - message: "#^Parameter \\#1 \\$username of method App\\\\Utils\\\\Validator\\:\\:validateUsername\\(\\) expects string\\|null, mixed given\\.$#" - count: 1 - path: src/Command/DeleteUserCommand.php - - - - message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(object\\)\\: mixed\\)\\|null, Closure\\(App\\\\Entity\\\\User\\)\\: array\\{int\\|null, string\\|null, string, string\\|null, string\\} given\\.$#" - count: 1 - path: src/Command/ListUsersCommand.php - - - - message: "#^Parameter \\#2 \\$recipient of method App\\\\Command\\\\ListUsersCommand\\:\\:sendReport\\(\\) expects string, mixed given\\.$#" - count: 1 - path: src/Command/ListUsersCommand.php - - - - message: "#^Parameter \\#3 \\$limit of method Doctrine\\\\ORM\\\\EntityRepository\\\\:\\:findBy\\(\\) expects int\\|null, mixed given\\.$#" - count: 1 - path: src/Command/ListUsersCommand.php - - - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Form\\\\FormInterface\\:\\:isClicked\\(\\)\\.$#" - count: 1 - path: src/Controller/Admin/BlogController.php - - - - message: "#^Parameter \\#2 \\$token of method Symfony\\\\Bundle\\\\FrameworkBundle\\\\Controller\\\\AbstractController\\:\\:isCsrfTokenValid\\(\\) expects string\\|null, bool\\|float\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/Controller/Admin/BlogController.php - - - - message: "#^Call to an undefined method object\\:\\:getName\\(\\)\\.$#" - count: 1 - path: src/Controller/BlogController.php - - - - message: "#^Cannot call method getFullName\\(\\) on App\\\\Entity\\\\User\\|null\\.$#" - count: 1 - path: src/Controller/BlogController.php - - - - message: "#^Parameter \\#1 \\$query of method App\\\\Repository\\\\PostRepository\\:\\:findBySearchQuery\\(\\) expects string, bool\\|float\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/Controller/BlogController.php - - - - message: "#^Parameter \\#1 \\$string of function htmlspecialchars expects string, string\\|null given\\.$#" - count: 3 - path: src/Controller/BlogController.php - - - - message: "#^Parameter \\#2 \\$limit of method App\\\\Repository\\\\PostRepository\\:\\:findBySearchQuery\\(\\) expects int, bool\\|float\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/Controller/BlogController.php - - - - message: "#^Parameter \\#2 \\$tag of method App\\\\Repository\\\\PostRepository\\:\\:findLatest\\(\\) expects App\\\\Entity\\\\Tag\\|null, object\\|null given\\.$#" - count: 1 - path: src/Controller/BlogController.php - - - - message: "#^Parameter \\#2 \\$plainPassword of method Symfony\\\\Component\\\\PasswordHasher\\\\Hasher\\\\UserPasswordHasherInterface\\:\\:hashPassword\\(\\) expects string, mixed given\\.$#" - count: 1 - path: src/Controller/UserController.php - - - - message: "#^Method App\\\\DataFixtures\\\\AppFixtures\\:\\:getPhrases\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/DataFixtures/AppFixtures.php - - - - message: "#^Method App\\\\DataFixtures\\\\AppFixtures\\:\\:getPostData\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/DataFixtures/AppFixtures.php - - - - message: "#^Method App\\\\DataFixtures\\\\AppFixtures\\:\\:getRandomTags\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/DataFixtures/AppFixtures.php - - - - message: "#^Method App\\\\DataFixtures\\\\AppFixtures\\:\\:getTagData\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/DataFixtures/AppFixtures.php - - - - message: "#^Method App\\\\DataFixtures\\\\AppFixtures\\:\\:getUserData\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/DataFixtures/AppFixtures.php - - - - message: "#^Parameter \\#1 \\$author of method App\\\\Entity\\\\Comment\\:\\:setAuthor\\(\\) expects App\\\\Entity\\\\User, object given\\.$#" - count: 1 - path: src/DataFixtures/AppFixtures.php - - - - message: "#^Method App\\\\Entity\\\\Post\\:\\:getComments\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#" - count: 1 - path: src/Entity/Post.php - - - - message: "#^Method App\\\\Entity\\\\Post\\:\\:getTags\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#" - count: 1 - path: src/Entity/Post.php - - - - message: "#^Property App\\\\Entity\\\\Post\\:\\:\\$comments with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#" - count: 1 - path: src/Entity/Post.php - - - - message: "#^Property App\\\\Entity\\\\Post\\:\\:\\$tags with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#" - count: 1 - path: src/Entity/Post.php - - - - message: "#^Method App\\\\Entity\\\\User\\:\\:__serialize\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Entity/User.php - - - - message: "#^Method App\\\\Entity\\\\User\\:\\:__unserialize\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Entity/User.php - - - - message: "#^Method App\\\\Entity\\\\User\\:\\:getUserIdentifier\\(\\) should return string but returns string\\|null\\.$#" - count: 1 - path: src/Entity/User.php - - - - message: "#^Method App\\\\Entity\\\\User\\:\\:setRoles\\(\\) has parameter \\$roles with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Entity/User.php - - - - message: "#^Property App\\\\Entity\\\\User\\:\\:\\$roles type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Entity/User.php - - - - message: "#^Ternary operator condition is always true\\.$#" - count: 1 - path: src/EventSubscriber/CheckRequirementsSubscriber.php - - - - message: "#^Cannot call method getAuthor\\(\\) on App\\\\Entity\\\\Post\\|null\\.$#" - count: 1 - path: src/EventSubscriber/CommentNotificationSubscriber.php - - - - message: "#^Cannot call method getEmail\\(\\) on App\\\\Entity\\\\User\\|null\\.$#" - count: 1 - path: src/EventSubscriber/CommentNotificationSubscriber.php - - - - message: "#^Cannot call method getSlug\\(\\) on App\\\\Entity\\\\Post\\|null\\.$#" - count: 1 - path: src/EventSubscriber/CommentNotificationSubscriber.php - - - - message: "#^Cannot call method getTitle\\(\\) on App\\\\Entity\\\\Post\\|null\\.$#" - count: 1 - path: src/EventSubscriber/CommentNotificationSubscriber.php - - - - message: "#^Parameter \\#1 \\.\\.\\.\\$addresses of method Symfony\\\\Component\\\\Mime\\\\Email\\:\\:to\\(\\) expects string\\|Symfony\\\\Component\\\\Mime\\\\Address, string\\|null given\\.$#" - count: 1 - path: src/EventSubscriber/CommentNotificationSubscriber.php - - - - message: "#^Property App\\\\EventSubscriber\\\\RedirectToPreferredLocaleSubscriber\\:\\:\\$locales type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/EventSubscriber/RedirectToPreferredLocaleSubscriber.php - - - - message: "#^Method App\\\\Form\\\\DataTransformer\\\\TagArrayToStringTransformer\\:\\:reverseTransform\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Form/DataTransformer/TagArrayToStringTransformer.php - - - - message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(Symfony\\\\Component\\\\String\\\\UnicodeString\\)\\: mixed\\)\\|null, 'trim' given\\.$#" - count: 1 - path: src/Form/DataTransformer/TagArrayToStringTransformer.php - - - - message: "#^Parameter \\#1 \\$format of method App\\\\Utils\\\\MomentFormatConverter\\:\\:convert\\(\\) expects string, mixed given\\.$#" - count: 1 - path: src/Form/Type/DateTimePickerType.php - - - - message: "#^Method App\\\\Pagination\\\\Paginator\\:\\:getResults\\(\\) return type has no value type specified in iterable type Traversable\\.$#" - count: 1 - path: src/Pagination/Paginator.php - - - - message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, mixed given\\.$#" - count: 2 - path: src/Pagination/Paginator.php - - - - message: "#^Property App\\\\Pagination\\\\Paginator\\:\\:\\$results type has no value type specified in iterable type Traversable\\.$#" - count: 1 - path: src/Pagination/Paginator.php - - - - message: "#^Class App\\\\Repository\\\\PostRepository extends generic class Doctrine\\\\Bundle\\\\DoctrineBundle\\\\Repository\\\\ServiceEntityRepository but does not specify its types\\: T$#" - count: 1 - path: src/Repository/PostRepository.php - - - - message: "#^Method App\\\\Repository\\\\PostRepository\\:\\:extractSearchTerms\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Repository/PostRepository.php - - - - message: "#^Method App\\\\Repository\\\\PostRepository\\:\\:findBySearchQuery\\(\\) should return array\\ but returns mixed\\.$#" - count: 1 - path: src/Repository/PostRepository.php - - - - message: "#^Class App\\\\Repository\\\\TagRepository extends generic class Doctrine\\\\Bundle\\\\DoctrineBundle\\\\Repository\\\\ServiceEntityRepository but does not specify its types\\: T$#" - count: 1 - path: src/Repository/TagRepository.php - - - - message: "#^Class App\\\\Repository\\\\UserRepository extends generic class Doctrine\\\\Bundle\\\\DoctrineBundle\\\\Repository\\\\ServiceEntityRepository but does not specify its types\\: T$#" - count: 1 - path: src/Repository/UserRepository.php - - - - message: "#^Method App\\\\Security\\\\PostVoter\\:\\:supports\\(\\) has parameter \\$subject with no type specified\\.$#" - count: 1 - path: src/Security/PostVoter.php - - - - message: "#^Method App\\\\Twig\\\\AppExtension\\:\\:getLocales\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Twig/AppExtension.php - - - - message: "#^Property App\\\\Twig\\\\AppExtension\\:\\:\\$localeCodes type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Twig/AppExtension.php - - - - message: "#^Property App\\\\Twig\\\\AppExtension\\:\\:\\$locales type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Twig/AppExtension.php - - - - message: "#^Method App\\\\Twig\\\\SourceCodeExtension\\:\\:getController\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Twig/SourceCodeExtension.php - - - - message: "#^Method App\\\\Twig\\\\SourceCodeExtension\\:\\:getTemplateSource\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Twig/SourceCodeExtension.php - - - - message: "#^Method App\\\\Twig\\\\SourceCodeExtension\\:\\:showSourceCode\\(\\) has parameter \\$template with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Twig/SourceCodeExtension.php - - - - message: "#^Parameter \\#1 \\$filename of function file expects string, string\\|false given\\.$#" - count: 1 - path: src/Twig/SourceCodeExtension.php - - - - message: "#^Parameter \\#1 \\$function of class ReflectionFunction constructor expects Closure\\|string, callable\\(\\)\\: mixed given\\.$#" - count: 1 - path: src/Twig/SourceCodeExtension.php - - - - message: "#^Property App\\\\Twig\\\\SourceCodeExtension\\:\\:\\$controller has no type specified\\.$#" - count: 1 - path: src/Twig/SourceCodeExtension.php - - - - message: "#^Cannot call method setApplication\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Command/AbstractCommandTest.php - - - - message: "#^Method App\\\\Tests\\\\Command\\\\AbstractCommandTest\\:\\:executeCommand\\(\\) has parameter \\$arguments with no value type specified in iterable type array\\.$#" - count: 1 - path: tests/Command/AbstractCommandTest.php - - - - message: "#^Method App\\\\Tests\\\\Command\\\\AbstractCommandTest\\:\\:executeCommand\\(\\) has parameter \\$inputs with no value type specified in iterable type array\\.$#" - count: 1 - path: tests/Command/AbstractCommandTest.php - - - - message: "#^Parameter \\#1 \\$command of class Symfony\\\\Component\\\\Console\\\\Tester\\\\CommandTester constructor expects Symfony\\\\Component\\\\Console\\\\Command\\\\Command, object\\|null given\\.$#" - count: 1 - path: tests/Command/AbstractCommandTest.php - - - - message: "#^Cannot call method findOneByEmail\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Command/AddUserCommandTest.php - - - - message: "#^Cannot call method isPasswordValid\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Command/AddUserCommandTest.php - - - - message: "#^Property App\\\\Tests\\\\Command\\\\AddUserCommandTest\\:\\:\\$userData has no type specified\\.$#" - count: 1 - path: tests/Command/AddUserCommandTest.php - - - - message: "#^Yield can be used only with these return types\\: Generator, Iterator, Traversable, iterable\\.$#" - count: 2 - path: tests/Command/AddUserCommandTest.php - - - - message: "#^Yield can be used only with these return types\\: Generator, Iterator, Traversable, iterable\\.$#" - count: 2 - path: tests/Command/ListUsersCommandTest.php - - - - message: "#^Cannot call method find\\(\\) on object\\|null\\.$#" - count: 2 - path: tests/Controller/Admin/BlogControllerTest.php - - - - message: "#^Cannot call method findOneByTitle\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Controller/Admin/BlogControllerTest.php - - - - message: "#^Parameter \\#2 \\$times of function str_repeat expects int, float given\\.$#" - count: 1 - path: tests/Controller/Admin/BlogControllerTest.php - - - - message: "#^Yield can be used only with these return types\\: Generator, Iterator, Traversable, iterable\\.$#" - count: 4 - path: tests/Controller/Admin/BlogControllerTest.php - - - - message: "#^Cannot access offset 'author' on mixed\\.$#" - count: 1 - path: tests/Controller/BlogControllerTest.php - - - - message: "#^Cannot access offset 'title' on mixed\\.$#" - count: 1 - path: tests/Controller/BlogControllerTest.php - - - - message: "#^Cannot access offset 0 on mixed\\.$#" - count: 2 - path: tests/Controller/BlogControllerTest.php - - - - message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" - count: 1 - path: tests/Controller/BlogControllerTest.php - - - - message: "#^Parameter \\#2 \\$haystack of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertCount\\(\\) expects Countable\\|iterable, mixed given\\.$#" - count: 1 - path: tests/Controller/BlogControllerTest.php - - - - message: "#^Cannot call method getRepository\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Controller/DefaultControllerTest.php - - - - message: "#^Yield can be used only with these return types\\: Generator, Iterator, Traversable, iterable\\.$#" - count: 7 - path: tests/Controller/DefaultControllerTest.php - - - - message: "#^Cannot call method findOneByEmail\\(\\) on object\\|null\\.$#" - count: 1 - path: tests/Controller/UserControllerTest.php - - - - message: "#^Yield can be used only with these return types\\: Generator, Iterator, Traversable, iterable\\.$#" - count: 2 - path: tests/Controller/UserControllerTest.php - - - - message: "#^Method App\\\\Tests\\\\Form\\\\DataTransformer\\\\TagArrayToStringTransformerTest\\:\\:getMockedTransformer\\(\\) has parameter \\$findByReturnValues with no value type specified in iterable type array\\.$#" - count: 1 - path: tests/Form/DataTransformer/TagArrayToStringTransformerTest.php - - - - message: "#^Method App\\\\Tests\\\\Utils\\\\ValidatorTest\\:\\:testValidateFullNameEmpty\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/Utils/ValidatorTest.php - - - - message: "#^Property App\\\\Tests\\\\Utils\\\\ValidatorTest\\:\\:\\$validator has no type specified\\.$#" - count: 1 - path: tests/Utils/ValidatorTest.php - diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e60011a08..b9fc14d8a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,6 +1,3 @@ -includes: - - phpstan-baseline.neon - parameters: level: max paths: diff --git a/src/Command/AddUserCommand.php b/src/Command/AddUserCommand.php index 545a12347..2bd4d24b5 100644 --- a/src/Command/AddUserCommand.php +++ b/src/Command/AddUserCommand.php @@ -129,7 +129,9 @@ protected function interact(InputInterface $input, OutputInterface $output): voi } // Ask for the password if it's not defined + /** @var string|null $password */ $password = $input->getArgument('password'); + if (null !== $password) { $this->io->text(' > Password: '.u('*')->repeat(u($password)->length())); } else { @@ -165,10 +167,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $stopwatch = new Stopwatch(); $stopwatch->start('add-user-command'); + /** @var string $username */ $username = $input->getArgument('username'); + + /** @var string $plainPassword */ $plainPassword = $input->getArgument('password'); + + /** @var string $email */ $email = $input->getArgument('email'); + + /** @var string $fullName */ $fullName = $input->getArgument('full-name'); + $isAdmin = $input->getOption('admin'); // make sure to validate the user data is correct @@ -198,7 +208,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - private function validateUserData($username, $plainPassword, $email, $fullName): void + private function validateUserData(string $username, string $plainPassword, string $email, string $fullName): void { // first check if a user with the same username already exists. $existingUser = $this->users->findOneBy(['username' => $username]); diff --git a/src/Command/DeleteUserCommand.php b/src/Command/DeleteUserCommand.php index c63b09a51..3da9f4730 100644 --- a/src/Command/DeleteUserCommand.php +++ b/src/Command/DeleteUserCommand.php @@ -107,7 +107,9 @@ protected function interact(InputInterface $input, OutputInterface $output): voi protected function execute(InputInterface $input, OutputInterface $output): int { - $username = $this->validator->validateUsername($input->getArgument('username')); + /** @var string|null $username */ + $username = $input->getArgument('username'); + $username = $this->validator->validateUsername($username); /** @var User|null $user */ $user = $this->users->findOneByUsername($username); diff --git a/src/Command/ListUsersCommand.php b/src/Command/ListUsersCommand.php index 34601045f..908937a97 100644 --- a/src/Command/ListUsersCommand.php +++ b/src/Command/ListUsersCommand.php @@ -88,12 +88,13 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { + /** @var int|null $maxResults */ $maxResults = $input->getOption('max-results'); + // Use ->findBy() instead of ->findAll() to allow result sorting and limiting $allUsers = $this->users->findBy([], ['id' => 'DESC'], $maxResults); - // Doctrine query returns an array of objects and we need an array of plain arrays - $usersAsPlainArrays = array_map(static function (User $user) { + $createUserArray = static function (User $user) { return [ $user->getId(), $user->getFullName(), @@ -101,7 +102,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $user->getEmail(), implode(', ', $user->getRoles()), ]; - }, $allUsers); + }; + + // Doctrine query returns an array of objects, and we need an array of plain arrays + /** @var callable $createUserArray */ + $usersAsPlainArrays = array_map($createUserArray, $allUsers); // In your console commands you should always use the regular output type, // which outputs contents directly in the console window. However, this @@ -119,7 +124,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $usersAsATable = $bufferedOutput->fetch(); $output->write($usersAsATable); - if (null !== $email = $input->getOption('send-to')) { + /** @var string $email */ + $email = $input->getOption('send-to'); + + if (null !== $email) { $this->sendReport($usersAsATable, $email); } diff --git a/src/Controller/Admin/BlogController.php b/src/Controller/Admin/BlogController.php index 869d85b07..3599f801c 100644 --- a/src/Controller/Admin/BlogController.php +++ b/src/Controller/Admin/BlogController.php @@ -19,6 +19,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\SubmitButton; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -80,7 +81,8 @@ public function new( // See https://symfony.com/doc/current/form/multiple_buttons.html $form = $this->createForm(PostType::class, $post) - ->add('saveAndCreateNew', SubmitType::class); + ->add('saveAndCreateNew', SubmitType::class) + ; $form->handleRequest($request); @@ -98,7 +100,10 @@ public function new( // See https://symfony.com/doc/current/controller.html#flash-messages $this->addFlash('success', 'post.created_successfully'); - if ($form->get('saveAndCreateNew')->isClicked()) { + /** @var SubmitButton $submit */ + $submit = $form->get('saveAndCreateNew'); + + if ($submit->isClicked()) { return $this->redirectToRoute('admin_post_new'); } @@ -156,7 +161,10 @@ public function edit(Request $request, Post $post, EntityManagerInterface $entit #[IsGranted('delete', subject: 'post')] public function delete(Request $request, Post $post, EntityManagerInterface $entityManager): Response { - if (!$this->isCsrfTokenValid('delete', $request->request->get('token'))) { + /** @var string|null $token */ + $token = $request->request->get('token'); + + if (!$this->isCsrfTokenValid('delete', $token)) { return $this->redirectToRoute('admin_post_index'); } diff --git a/src/Controller/BlogController.php b/src/Controller/BlogController.php index 15abf11b4..8b8c8979d 100644 --- a/src/Controller/BlogController.php +++ b/src/Controller/BlogController.php @@ -13,6 +13,7 @@ use App\Entity\Comment; use App\Entity\Post; +use App\Entity\Tag; use App\Entity\User; use App\Event\CommentCreatedEvent; use App\Form\CommentType; @@ -52,6 +53,7 @@ public function index(Request $request, int $page, string $_format, PostReposito { $tag = null; if ($request->query->has('tag')) { + /** @var Tag $tag */ $tag = $tags->findOneBy(['name' => $request->query->get('tag')]); } $latestPosts = $posts->findLatest($page, $tag); @@ -155,8 +157,8 @@ public function commentForm(Post $post): Response #[Route('/search', methods: ['GET'], name: 'blog_search')] public function search(Request $request, PostRepository $posts): Response { - $query = $request->query->get('q', ''); - $limit = $request->query->get('l', 10); + $query = (string) $request->query->get('q', ''); + $limit = (int) $request->query->get('l', 10); if (!$request->isXmlHttpRequest()) { return $this->render('blog/search.html.twig', ['query' => $query]); @@ -166,11 +168,20 @@ public function search(Request $request, PostRepository $posts): Response $results = []; foreach ($foundPosts as $post) { + /** @var string $author */ + $author = $post->getAuthor() ? $post->getAuthor()->getFullName() : ''; + + /** @var string $title */ + $title = $post->getTitle(); + + /** @var string $summary */ + $summary = $post->getSummary(); + $results[] = [ - 'title' => htmlspecialchars($post->getTitle(), \ENT_COMPAT | \ENT_HTML5), + 'title' => htmlspecialchars($title, \ENT_COMPAT | \ENT_HTML5), 'date' => $post->getPublishedAt()->format('M d, Y'), - 'author' => htmlspecialchars($post->getAuthor()->getFullName(), \ENT_COMPAT | \ENT_HTML5), - 'summary' => htmlspecialchars($post->getSummary(), \ENT_COMPAT | \ENT_HTML5), + 'author' => htmlspecialchars($author, \ENT_COMPAT | \ENT_HTML5), + 'summary' => htmlspecialchars($summary, \ENT_COMPAT | \ENT_HTML5), 'url' => $this->generateUrl('blog_post', ['slug' => $post->getSlug()]), ]; } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 425d8e720..17510b384 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -68,7 +68,10 @@ public function changePassword( $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $user->setPassword($passwordHasher->hashPassword($user, $form->get('newPassword')->getData())); + /** @var string $plainPassword */ + $plainPassword = $form->get('newPassword')->getData(); + + $user->setPassword($passwordHasher->hashPassword($user, $plainPassword)); $entityManager->flush(); return $this->redirectToRoute('security_logout'); diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 37e6a82e1..5001a1695 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -18,6 +18,7 @@ use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\String\AbstractUnicodeString; use Symfony\Component\String\Slugger\SluggerInterface; use function Symfony\Component\String\u; @@ -67,6 +68,8 @@ private function loadTags(ObjectManager $manager): void private function loadPosts(ObjectManager $manager): void { + /** @var User $author */ + /** @var array $tags */ foreach ($this->getPostData() as [$title, $slug, $summary, $content, $publishedAt, $author, $tags]) { $post = new Post(); $post->setTitle($title); @@ -78,8 +81,11 @@ private function loadPosts(ObjectManager $manager): void $post->addTag(...$tags); foreach (range(1, 5) as $i) { + /** @var User $commentAuthor */ + $commentAuthor = $this->getReference('john_user'); + $comment = new Comment(); - $comment->setAuthor($this->getReference('john_user')); + $comment->setAuthor($commentAuthor); $comment->setContent($this->getRandomText(random_int(255, 512))); $comment->setPublishedAt(new \DateTime('now + '.$i.'seconds')); @@ -92,6 +98,9 @@ private function loadPosts(ObjectManager $manager): void $manager->flush(); } + /** + * @return array}> + */ private function getUserData(): array { return [ @@ -102,6 +111,9 @@ private function getUserData(): array ]; } + /** + * @return string[] + */ private function getTagData(): array { return [ @@ -117,6 +129,11 @@ private function getTagData(): array ]; } + /** + * @throws \Exception + * + * @return array}> + */ private function getPostData(): array { $posts = []; @@ -137,6 +154,9 @@ private function getPostData(): array return $posts; } + /** + * @return string[] + */ private function getPhrases(): array { return [ @@ -226,6 +246,11 @@ private function getPostContent(): string MARKDOWN; } + /** + * @throws \Exception + * + * @return array + */ private function getRandomTags(): array { $tagNames = $this->getTagData(); diff --git a/src/Entity/Post.php b/src/Entity/Post.php index 70fe4be89..8d1913697 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -66,14 +66,14 @@ class Post private ?User $author = null; /** - * @var Comment[]|Collection + * @var Collection */ #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'post', orphanRemoval: true, cascade: ['persist'])] #[ORM\OrderBy(['publishedAt' => 'DESC'])] private Collection $comments; /** - * @var Tag[]|Collection + * @var Collection */ #[ORM\ManyToMany(targetEntity: Tag::class, cascade: ['persist'])] #[ORM\JoinTable(name: 'symfony_demo_post_tag')] @@ -143,6 +143,9 @@ public function setAuthor(User $author): void $this->author = $author; } + /** + * @return Collection + */ public function getComments(): Collection { return $this->comments; @@ -185,6 +188,9 @@ public function removeTag(Tag $tag): void $this->tags->removeElement($tag); } + /** + * @return Collection + */ public function getTags(): Collection { return $this->tags; diff --git a/src/Entity/User.php b/src/Entity/User.php index 76a191d17..52219df68 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -53,6 +53,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(type: Types::STRING)] private ?string $password = null; + /** + * @var string[] + */ #[ORM\Column(type: Types::JSON)] private array $roles = []; @@ -73,7 +76,7 @@ public function getFullName(): ?string public function getUserIdentifier(): string { - return $this->username; + return (string) $this->username; } public function getUsername(): string @@ -121,6 +124,9 @@ public function getRoles(): array return array_unique($roles); } + /** + * @param string[] $roles + */ public function setRoles(array $roles): void { $this->roles = $roles; @@ -151,12 +157,18 @@ public function eraseCredentials(): void // $this->plainPassword = null; } + /** + * @return array{int|null, string|null, string|null} + */ public function __serialize(): array { // add $this->salt too if you don't use Bcrypt or Argon2i return [$this->id, $this->username, $this->password]; } + /** + * @param array{int|null, string, string} $data + */ public function __unserialize(array $data): void { // add $this->salt too if you don't use Bcrypt or Argon2i diff --git a/src/EventSubscriber/CheckRequirementsSubscriber.php b/src/EventSubscriber/CheckRequirementsSubscriber.php index 5e65a53ee..fb485bf76 100644 --- a/src/EventSubscriber/CheckRequirementsSubscriber.php +++ b/src/EventSubscriber/CheckRequirementsSubscriber.php @@ -12,6 +12,7 @@ namespace App\EventSubscriber; use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleErrorEvent; @@ -94,6 +95,6 @@ private function isSQLitePlatform(): bool { $databasePlatform = $this->entityManager->getConnection()->getDatabasePlatform(); - return $databasePlatform ? 'sqlite' === $databasePlatform->getName() : false; + return $databasePlatform instanceof SqlitePlatform; } } diff --git a/src/EventSubscriber/CommentNotificationSubscriber.php b/src/EventSubscriber/CommentNotificationSubscriber.php index bad58d18a..5e2a981a4 100644 --- a/src/EventSubscriber/CommentNotificationSubscriber.php +++ b/src/EventSubscriber/CommentNotificationSubscriber.php @@ -11,6 +11,8 @@ namespace App\EventSubscriber; +use App\Entity\Post; +use App\Entity\User; use App\Event\CommentCreatedEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mailer\MailerInterface; @@ -43,8 +45,16 @@ public static function getSubscribedEvents(): array public function onCommentCreated(CommentCreatedEvent $event): void { $comment = $event->getComment(); + + /** @var Post $post */ $post = $comment->getPost(); + /** @var User $author */ + $author = $post->getAuthor(); + + /** @var string $emailAddress */ + $emailAddress = $author->getEmail(); + $linkToPost = $this->urlGenerator->generate('blog_post', [ 'slug' => $post->getSlug(), '_fragment' => 'comment_'.$comment->getId(), @@ -59,7 +69,7 @@ public function onCommentCreated(CommentCreatedEvent $event): void // See https://symfony.com/doc/current/mailer.html $email = (new Email()) ->from($this->sender) - ->to($post->getAuthor()->getEmail()) + ->to($emailAddress) ->subject($subject) ->html($body) ; diff --git a/src/EventSubscriber/RedirectToPreferredLocaleSubscriber.php b/src/EventSubscriber/RedirectToPreferredLocaleSubscriber.php index 75638e636..ed1c9dc5a 100644 --- a/src/EventSubscriber/RedirectToPreferredLocaleSubscriber.php +++ b/src/EventSubscriber/RedirectToPreferredLocaleSubscriber.php @@ -28,6 +28,9 @@ */ class RedirectToPreferredLocaleSubscriber implements EventSubscriberInterface { + /** + * @var string[] + */ private array $locales; private string $defaultLocale; diff --git a/src/Form/DataTransformer/TagArrayToStringTransformer.php b/src/Form/DataTransformer/TagArrayToStringTransformer.php index ce4b4d289..14f0a5081 100644 --- a/src/Form/DataTransformer/TagArrayToStringTransformer.php +++ b/src/Form/DataTransformer/TagArrayToStringTransformer.php @@ -36,6 +36,8 @@ public function __construct( /** * {@inheritdoc} + * + * @phpstan-param array|null $tags */ public function transform($tags): string { @@ -43,12 +45,15 @@ public function transform($tags): string // Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer::transform() // The value returned is a string that concatenates the string representation of those objects - /* @var Tag[] $tags */ return implode(',', $tags); } /** * {@inheritdoc} + * + * @phpstan-param string|null $string + * + * @phpstan-return array */ public function reverseTransform($string): array { @@ -56,9 +61,10 @@ public function reverseTransform($string): array return []; } - $names = array_filter(array_unique(array_map('trim', u($string)->split(',')))); + $names = array_filter(array_unique($this->trim(u($string)->split(',')))); // Get the current tags and find the new ones that should be created. + /** @var Tag[] $tags */ $tags = $this->tags->findBy([ 'name' => $names, ]); @@ -74,4 +80,20 @@ public function reverseTransform($string): array // See Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer::reverseTransform() return $tags; } + + /** + * @param string[] $strings + * + * @return string[] + */ + private function trim(array $strings): array + { + $result = []; + + foreach ($strings as $string) { + $result[] = trim($string); + } + + return $result; + } } diff --git a/src/Form/Type/DateTimePickerType.php b/src/Form/Type/DateTimePickerType.php index f736e3bd2..33e8d968f 100644 --- a/src/Form/Type/DateTimePickerType.php +++ b/src/Form/Type/DateTimePickerType.php @@ -39,7 +39,10 @@ public function __construct( */ public function buildView(FormView $view, FormInterface $form, array $options): void { - $view->vars['attr']['data-date-format'] = $this->formatConverter->convert($options['format']); + /** @var string $format */ + $format = $options['format']; + + $view->vars['attr']['data-date-format'] = $this->formatConverter->convert($format); $view->vars['attr']['data-date-locale'] = u(\Locale::getDefault())->replace('_', '-')->lower(); } diff --git a/src/Pagination/Paginator.php b/src/Pagination/Paginator.php index 0ca573cfa..dbfe4f3b0 100644 --- a/src/Pagination/Paginator.php +++ b/src/Pagination/Paginator.php @@ -29,9 +29,13 @@ class Paginator public const PAGE_SIZE = 10; private int $currentPage; - private \Traversable $results; private int $numResults; + /** + * @var \Traversable + */ + private \Traversable $results; + public function __construct( private DoctrineQueryBuilder $queryBuilder, private int $pageSize = self::PAGE_SIZE @@ -48,13 +52,19 @@ public function paginate(int $page = 1): self ->setMaxResults($this->pageSize) ->getQuery(); - if (0 === \count($this->queryBuilder->getDQLPart('join'))) { + /** @var array $joinDqlParts */ + $joinDqlParts = $this->queryBuilder->getDQLPart('join'); + + if (0 === \count($joinDqlParts)) { $query->setHint(CountWalker::HINT_DISTINCT, false); } $paginator = new DoctrinePaginator($query, true); - $useOutputWalkers = \count($this->queryBuilder->getDQLPart('having') ?: []) > 0; + /** @var array $havingDqlParts */ + $havingDqlParts = $this->queryBuilder->getDQLPart('having'); + + $useOutputWalkers = \count($havingDqlParts ?: []) > 0; $paginator->setUseOutputWalkers($useOutputWalkers); $this->results = $paginator->getIterator(); @@ -108,6 +118,9 @@ public function getNumResults(): int return $this->numResults; } + /** + * @return \Traversable + */ public function getResults(): \Traversable { return $this->results; diff --git a/src/Repository/PostRepository.php b/src/Repository/PostRepository.php index 27e6dcf27..cb49cb812 100644 --- a/src/Repository/PostRepository.php +++ b/src/Repository/PostRepository.php @@ -27,6 +27,10 @@ * @author Ryan Weaver * @author Javier Eguiluz * @author Yonel Ceruto + * + * @method Post|null findOneByTitle(string $postTitle) + * + * @template-extends ServiceEntityRepository */ class PostRepository extends ServiceEntityRepository { @@ -74,15 +78,21 @@ public function findBySearchQuery(string $query, int $limit = Paginator::PAGE_SI ; } - return $queryBuilder + /** @var Post[] $result */ + $result = $queryBuilder ->orderBy('p.publishedAt', 'DESC') ->setMaxResults($limit) ->getQuery() - ->getResult(); + ->getResult() + ; + + return $result; } /** * Transforms the search string into an array of search terms. + * + * @return string[] */ private function extractSearchTerms(string $searchQuery): array { diff --git a/src/Repository/TagRepository.php b/src/Repository/TagRepository.php index a79dc95fe..976a1bdcc 100644 --- a/src/Repository/TagRepository.php +++ b/src/Repository/TagRepository.php @@ -23,6 +23,8 @@ * See https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository * * @author Yonel Ceruto + * + * @template-extends ServiceEntityRepository */ class TagRepository extends ServiceEntityRepository { diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 1ac2bcd78..e7a70ff07 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -24,6 +24,11 @@ * * @author Ryan Weaver * @author Javier Eguiluz + * + * @method User|null findOneByUsername(string $username) + * @method User|null findOneByEmail(string $email) + * + * @template-extends ServiceEntityRepository */ class UserRepository extends ServiceEntityRepository { diff --git a/src/Security/PostVoter.php b/src/Security/PostVoter.php index 060cdaf29..939f2bd81 100644 --- a/src/Security/PostVoter.php +++ b/src/Security/PostVoter.php @@ -34,6 +34,8 @@ class PostVoter extends Voter /** * {@inheritdoc} + * + * @phpstan-param Post $subject */ protected function supports(string $attribute, $subject): bool { diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index 861bba6ca..b778324b8 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -24,7 +24,14 @@ */ class AppExtension extends AbstractExtension { + /** + * @var string[] + */ private array $localeCodes; + + /** + * @var array>|null + */ private ?array $locales = null; // The $locales argument is injected thanks to the service container. @@ -47,6 +54,8 @@ public function getFunctions(): array * Takes the list of codes of the locales (languages) enabled in the * application and returns an array with the name of each locale written * in its own language (e.g. English, Français, Español, etc.). + * + * @return array> */ public function getLocales(): array { diff --git a/src/Twig/SourceCodeExtension.php b/src/Twig/SourceCodeExtension.php index e3a4b2028..e7349fbf4 100644 --- a/src/Twig/SourceCodeExtension.php +++ b/src/Twig/SourceCodeExtension.php @@ -28,6 +28,9 @@ */ class SourceCodeExtension extends AbstractExtension { + /** + * @var callable|null + */ private $controller; public function setController(?callable $controller): void @@ -46,7 +49,7 @@ public function getFunctions(): array } /** - * @param string|TemplateWrapper|array $template + * @param string|TemplateWrapper $template */ public function showSourceCode(Environment $twig, $template): string { @@ -56,6 +59,9 @@ public function showSourceCode(Environment $twig, $template): string ]); } + /** + * @return array{file_path: string, starting_line: int|false, source_code: string}|null + */ private function getController(): ?array { // this happens for example for exceptions (404 errors, etc.) @@ -65,8 +71,11 @@ private function getController(): ?array $method = $this->getCallableReflector($this->controller); - if (false === $classCode = file($method->getFileName())) { - throw new \LogicException(sprintf('There was an error while trying to read the contents of the "%s" file.', $method->getFileName())); + /** @var string $fileName */ + $fileName = $method->getFileName(); + + if (false === $classCode = file($fileName)) { + throw new \LogicException(sprintf('There was an error while trying to read the contents of the "%s" file.', $fileName)); } $startLine = $method->getStartLine() - 1; @@ -85,7 +94,7 @@ private function getController(): ?array $controllerCode = implode('', \array_slice($classCode, $startLine, $endLine - $startLine)); return [ - 'file_path' => $method->getFileName(), + 'file_path' => $fileName, 'starting_line' => $method->getStartLine(), 'source_code' => $this->unindentCode($controllerCode), ]; @@ -108,9 +117,13 @@ private function getCallableReflector(callable $callable): \ReflectionFunctionAb return $r->getMethod('__invoke'); } + // @phpstan-ignore-next-line return new \ReflectionFunction($callable); } + /** + * @return array{file_path: string|false, starting_line: int, source_code: string} + */ private function getTemplateSource(TemplateWrapper $template): array { $templateSource = $template->getSourceContext(); diff --git a/tests/Command/AbstractCommandTest.php b/tests/Command/AbstractCommandTest.php index 59b4df490..62862cec9 100644 --- a/tests/Command/AbstractCommandTest.php +++ b/tests/Command/AbstractCommandTest.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandTester; abstract class AbstractCommandTest extends KernelTestCase @@ -21,14 +22,16 @@ abstract class AbstractCommandTest extends KernelTestCase * This helper method abstracts the boilerplate code needed to test the * execution of a command. * - * @param array $arguments All the arguments passed when executing the command - * @param array $inputs The (optional) answers given to the command when it asks for the value of the missing arguments + * @param array $arguments All the arguments passed when executing the command + * @param array $inputs The (optional) answers given to the command when it asks for the + * value of the missing arguments */ protected function executeCommand(array $arguments, array $inputs = []): CommandTester { self::bootKernel(); // this uses a special testing container that allows you to fetch private services + /** @var Command $command */ $command = static::getContainer()->get($this->getCommandFqcn()); $command->setApplication(new Application(self::$kernel)); diff --git a/tests/Command/AddUserCommandTest.php b/tests/Command/AddUserCommandTest.php index 773247ba7..008d1ea1c 100644 --- a/tests/Command/AddUserCommandTest.php +++ b/tests/Command/AddUserCommandTest.php @@ -13,10 +13,14 @@ use App\Command\AddUserCommand; use App\Repository\UserRepository; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class AddUserCommandTest extends AbstractCommandTest { - private $userData = [ + /** + * @var string[] + */ + private array $userData = [ 'username' => 'chuck_norris', 'password' => 'foobar', 'email' => 'chuck@norris.com', @@ -72,7 +76,7 @@ public function testCreateUserInteractive(bool $isAdmin): void * This is used to execute the same test twice: first for normal users * (isAdmin = false) and then for admin users (isAdmin = true). */ - public function isAdminDataProvider(): ?\Generator + public function isAdminDataProvider(): \Generator { yield [false]; yield [true]; @@ -84,13 +88,19 @@ public function isAdminDataProvider(): ?\Generator */ private function assertUserCreated(bool $isAdmin): void { + /** @var UserRepository $repository */ + $repository = $this->getContainer()->get(UserRepository::class); + /** @var \App\Entity\User $user */ - $user = $this->getContainer()->get(UserRepository::class)->findOneByEmail($this->userData['email']); - $this->assertNotNull($user); + $user = $repository->findOneByEmail($this->userData['email']); + + /** @var UserPasswordHasherInterface $passwordHasher */ + $passwordHasher = $this->getContainer()->get('test.user_password_hasher'); + $this->assertNotNull($user); $this->assertSame($this->userData['full-name'], $user->getFullName()); $this->assertSame($this->userData['username'], $user->getUsername()); - $this->assertTrue($this->getContainer()->get('test.user_password_hasher')->isPasswordValid($user, $this->userData['password'])); + $this->assertTrue($passwordHasher->isPasswordValid($user, $this->userData['password'])); $this->assertSame($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER'], $user->getRoles()); } diff --git a/tests/Command/ListUsersCommandTest.php b/tests/Command/ListUsersCommandTest.php index 3e81b4357..b34a8de36 100644 --- a/tests/Command/ListUsersCommandTest.php +++ b/tests/Command/ListUsersCommandTest.php @@ -30,7 +30,7 @@ public function testListUsers(int $maxResults): void $this->assertSame($emptyDisplayLines + $maxResults, mb_substr_count($tester->getDisplay(), "\n")); } - public function maxResultsProvider(): ?\Generator + public function maxResultsProvider(): \Generator { yield [1]; yield [2]; diff --git a/tests/Controller/Admin/BlogControllerTest.php b/tests/Controller/Admin/BlogControllerTest.php index 2f6670510..8e2fdae70 100644 --- a/tests/Controller/Admin/BlogControllerTest.php +++ b/tests/Controller/Admin/BlogControllerTest.php @@ -47,7 +47,7 @@ public function testAccessDeniedForRegularUsers(string $httpMethod, string $url) $this->assertResponseStatusCodeSame(Response::HTTP_FORBIDDEN); } - public function getUrlsForRegularUsers(): ?\Generator + public function getUrlsForRegularUsers(): \Generator { yield ['GET', '/en/admin/post/']; yield ['GET', '/en/admin/post/1']; @@ -95,8 +95,12 @@ public function testAdminNewPost(): void $this->assertResponseRedirects('/en/admin/post/', Response::HTTP_FOUND); + /** @var PostRepository $postRepository */ + $postRepository = static::getContainer()->get(PostRepository::class); + /** @var \App\Entity\Post $post */ - $post = static::getContainer()->get(PostRepository::class)->findOneByTitle($postTitle); + $post = $postRepository->findOneByTitle($postTitle); + $this->assertNotNull($post); $this->assertSame($postSummary, $post->getSummary()); $this->assertSame($postContent, $post->getContent()); @@ -159,8 +163,12 @@ public function testAdminEditPost(): void $this->assertResponseRedirects('/en/admin/post/1/edit', Response::HTTP_FOUND); + /** @var PostRepository $postRepository */ + $postRepository = static::getContainer()->get(PostRepository::class); + /** @var \App\Entity\Post $post */ - $post = static::getContainer()->get(PostRepository::class)->find(1); + $post = $postRepository->find(1); + $this->assertSame($newBlogPostTitle, $post->getTitle()); } @@ -181,14 +189,16 @@ public function testAdminDeletePost(): void $this->assertResponseRedirects('/en/admin/post/', Response::HTTP_FOUND); - $post = static::getContainer()->get(PostRepository::class)->find(1); - $this->assertNull($post); + /** @var PostRepository $postRepository */ + $postRepository = static::getContainer()->get(PostRepository::class); + + $this->assertNull($postRepository->find(1)); } private function generateRandomString(int $length): string { $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - return mb_substr(str_shuffle(str_repeat($chars, ceil($length / mb_strlen($chars)))), 1, $length); + return mb_substr(str_shuffle(str_repeat($chars, (int) ceil($length / mb_strlen($chars)))), 1, $length); } } diff --git a/tests/Controller/BlogControllerTest.php b/tests/Controller/BlogControllerTest.php index 5602d616f..8e931e80f 100644 --- a/tests/Controller/BlogControllerTest.php +++ b/tests/Controller/BlogControllerTest.php @@ -11,7 +11,6 @@ namespace App\Tests\Controller; -use App\Entity\Post; use App\Pagination\Paginator; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; @@ -88,7 +87,11 @@ public function testAjaxSearch(): void $client = static::createClient(); $client->xmlHttpRequest('GET', '/en/blog/search', ['q' => 'lorem']); - $results = json_decode($client->getResponse()->getContent(), true); + /** @var string $content */ + $content = $client->getResponse()->getContent(); + + /** @var array> $results */ + $results = json_decode($content, true); $this->assertResponseHeaderSame('Content-Type', 'application/json'); $this->assertCount(1, $results); diff --git a/tests/Controller/DefaultControllerTest.php b/tests/Controller/DefaultControllerTest.php index 0ffb8bbc1..463c3b3ad 100644 --- a/tests/Controller/DefaultControllerTest.php +++ b/tests/Controller/DefaultControllerTest.php @@ -12,6 +12,7 @@ namespace App\Tests\Controller; use App\Entity\Post; +use Doctrine\Bundle\DoctrineBundle\Registry; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Response; @@ -52,10 +53,15 @@ public function testPublicUrls(string $url): void public function testPublicBlogPost(): void { $client = static::createClient(); + // the service container is always available via the test client - $blogPost = $client->getContainer()->get('doctrine')->getRepository(Post::class)->find(1); - $client->request('GET', sprintf('/en/blog/posts/%s', $blogPost->getSlug())); + /** @var Registry $registry */ + $registry = $client->getContainer()->get('doctrine'); + /** @var Post $blogPost */ + $blogPost = $registry->getRepository(Post::class)->find(1); + + $client->request('GET', sprintf('/en/blog/posts/%s', $blogPost->getSlug())); $this->assertResponseIsSuccessful(); } @@ -78,14 +84,14 @@ public function testSecureUrls(string $url): void ); } - public function getPublicUrls(): ?\Generator + public function getPublicUrls(): \Generator { yield ['/']; yield ['/en/blog/']; yield ['/en/login']; } - public function getSecureUrls(): ?\Generator + public function getSecureUrls(): \Generator { yield ['/en/admin/post/']; yield ['/en/admin/post/new']; diff --git a/tests/Controller/UserControllerTest.php b/tests/Controller/UserControllerTest.php index 8fbf472c8..15551f468 100644 --- a/tests/Controller/UserControllerTest.php +++ b/tests/Controller/UserControllerTest.php @@ -47,7 +47,7 @@ public function testAccessDeniedForAnonymousUsers(string $httpMethod, string $ur ); } - public function getUrlsForAnonymousUsers(): ?\Generator + public function getUrlsForAnonymousUsers(): \Generator { yield ['GET', '/en/profile/edit']; yield ['GET', '/en/profile/change-password']; @@ -68,8 +68,11 @@ public function testEditUser(): void $this->assertResponseRedirects('/en/profile/edit', Response::HTTP_FOUND); + /** @var UserRepository $userRepository */ + $userRepository = static::getContainer()->get(UserRepository::class); + /** @var \App\Entity\User $user */ - $user = static::getContainer()->get(UserRepository::class)->findOneByEmail($newUserEmail); + $user = $userRepository->findOneByEmail($newUserEmail); $this->assertNotNull($user); $this->assertSame($newUserEmail, $user->getEmail()); diff --git a/tests/Form/DataTransformer/TagArrayToStringTransformerTest.php b/tests/Form/DataTransformer/TagArrayToStringTransformerTest.php index abbb4dde2..6ecbc82b6 100644 --- a/tests/Form/DataTransformer/TagArrayToStringTransformerTest.php +++ b/tests/Form/DataTransformer/TagArrayToStringTransformerTest.php @@ -101,7 +101,7 @@ public function testTransform(): void * This helper method mocks the real TagArrayToStringTransformer class to * simplify the tests. See https://phpunit.de/manual/current/en/test-doubles.html. * - * @param array $findByReturnValues The values returned when calling to the findBy() method + * @param array $findByReturnValues The values returned when calling to the findBy() method */ private function getMockedTransformer(array $findByReturnValues = []): TagArrayToStringTransformer { diff --git a/tests/Utils/ValidatorTest.php b/tests/Utils/ValidatorTest.php index 32efe9b78..0c061fa45 100644 --- a/tests/Utils/ValidatorTest.php +++ b/tests/Utils/ValidatorTest.php @@ -16,7 +16,7 @@ class ValidatorTest extends TestCase { - private $validator; + private Validator $validator; protected function setUp(): void { @@ -93,7 +93,7 @@ public function testValidateFullName(): void $this->assertSame($test, $this->validator->validateFullName($test)); } - public function testValidateFullNameEmpty() + public function testValidateFullNameEmpty(): void { $this->expectException('Exception'); $this->expectExceptionMessage('The full name can not be empty.');