diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6dc1fdd7..a7ab022d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,25 @@ jobs: - name: Validate composer.json run: composer validate --strict --no-check-lock + static_analysis: + name: Static analysis + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + coverage: none + php-version: '8.2' + + - name: Install dependencies + run: composer update --ansi --no-progress --prefer-dist --no-interaction + + - name: Install PHPUnit + run: vendor/bin/simple-phpunit install + + - run: vendor/bin/phpstan analyze + tests: name: "Tests on PHP ${{ matrix.php }}${{ matrix.name_suffix }}" runs-on: ubuntu-latest diff --git a/composer.json b/composer.json index ccb9b5da..439f3d65 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,11 @@ "gedmo/doctrine-extensions": "^3.5.0" }, "require-dev": { + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5", + "phpstan/phpstan-symfony": "^1.3", "symfony/mime": "^5.4 || ^6.0 || ^7.0", "symfony/phpunit-bridge": "^v6.4.1 || ^7.0.1", "symfony/security-core": "^5.4 || ^6.0 || ^7.0" @@ -40,5 +45,8 @@ "branch-alias": { "dev-main": "1.x-dev" } + }, + "config": { + "sort-packages": true } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..fa7c3153 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,13 @@ +parameters: + level: max + paths: + - src/ + - tests/ + +includes: + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-symfony/extension.neon + - vendor/phpstan/phpstan-symfony/rules.neon diff --git a/src/DependencyInjection/Compiler/ValidateExtensionConfigurationPass.php b/src/DependencyInjection/Compiler/ValidateExtensionConfigurationPass.php index eea7bf5a..2e75a18b 100644 --- a/src/DependencyInjection/Compiler/ValidateExtensionConfigurationPass.php +++ b/src/DependencyInjection/Compiler/ValidateExtensionConfigurationPass.php @@ -2,6 +2,7 @@ namespace Stof\DoctrineExtensionsBundle\DependencyInjection\Compiler; +use Stof\DoctrineExtensionsBundle\DependencyInjection\StofDoctrineExtensionsExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -23,6 +24,9 @@ class ValidateExtensionConfigurationPass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - $container->getExtension('stof_doctrine_extensions')->configValidate($container); + $extension = $container->getExtension('stof_doctrine_extensions'); + \assert($extension instanceof StofDoctrineExtensionsExtension); + + $extension->configValidate($container); } } diff --git a/src/DependencyInjection/StofDoctrineExtensionsExtension.php b/src/DependencyInjection/StofDoctrineExtensionsExtension.php index fff3f362..278314e5 100644 --- a/src/DependencyInjection/StofDoctrineExtensionsExtension.php +++ b/src/DependencyInjection/StofDoctrineExtensionsExtension.php @@ -81,7 +81,9 @@ class StofDoctrineExtensionsExtension extends Extension ), ); + /** @var list */ private $entityManagers = array(); + /** @var list */ private $documentManagers = array(); /** @@ -150,7 +152,7 @@ public function load(array $configs, ContainerBuilder $container) /** * @internal */ - public function configValidate(ContainerBuilder $container) + public function configValidate(ContainerBuilder $container): void { foreach ($this->entityManagers as $name) { if (!$container->hasDefinition(sprintf('doctrine.dbal.%s_connection', $name))) { @@ -166,13 +168,13 @@ public function configValidate(ContainerBuilder $container) } /** - * @param array $configs - * @param ContainerBuilder $container - * @param LoaderInterface $loader - * @param array $loaded - * @param string $doctrineListenerTag + * @param array> $configs + * @param ContainerBuilder $container + * @param LoaderInterface $loader + * @param array $loaded + * @param string $doctrineListenerTag * - * @return array + * @return list */ private function processObjectManagerConfigurations(array $configs, ContainerBuilder $container, LoaderInterface $loader, array &$loaded, string $doctrineListenerTag) { diff --git a/src/EventListener/BlameListener.php b/src/EventListener/BlameListener.php index 31e1933d..5ba8214a 100644 --- a/src/EventListener/BlameListener.php +++ b/src/EventListener/BlameListener.php @@ -18,8 +18,11 @@ */ class BlameListener implements EventSubscriberInterface { + /** @var AuthorizationCheckerInterface|null */ private $authorizationChecker; + /** @var TokenStorageInterface|null */ private $tokenStorage; + /** @var BlameableListener */ private $blameableListener; public function __construct(BlameableListener $blameableListener, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authorizationChecker = null) @@ -32,7 +35,7 @@ public function __construct(BlameableListener $blameableListener, TokenStorageIn /** * @internal */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; @@ -48,9 +51,6 @@ public function onKernelRequest(RequestEvent $event) } } - /** - * @return string[] - */ public static function getSubscribedEvents() { return array( diff --git a/src/EventListener/IpTraceListener.php b/src/EventListener/IpTraceListener.php index f3fd21d8..c62b1e37 100644 --- a/src/EventListener/IpTraceListener.php +++ b/src/EventListener/IpTraceListener.php @@ -13,6 +13,7 @@ */ final class IpTraceListener implements EventSubscriberInterface { + /** @var IpTraceableListener */ private $ipTraceableListener; public function __construct(IpTraceableListener $ipTraceableListener) @@ -20,7 +21,7 @@ public function __construct(IpTraceableListener $ipTraceableListener) $this->ipTraceableListener = $ipTraceableListener; } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/EventListener/LocaleListener.php b/src/EventListener/LocaleListener.php index 9979c56d..aaf981fa 100644 --- a/src/EventListener/LocaleListener.php +++ b/src/EventListener/LocaleListener.php @@ -14,6 +14,7 @@ */ class LocaleListener implements EventSubscriberInterface { + /** @var TranslatableListener */ private $translatableListener; public function __construct(TranslatableListener $translatableListener) @@ -24,14 +25,11 @@ public function __construct(TranslatableListener $translatableListener) /** * @internal */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $this->translatableListener->setTranslatableLocale($event->getRequest()->getLocale()); } - /** - * @return string[] - */ public static function getSubscribedEvents() { return array( diff --git a/src/EventListener/LoggerListener.php b/src/EventListener/LoggerListener.php index f2acfd7b..959fc41a 100644 --- a/src/EventListener/LoggerListener.php +++ b/src/EventListener/LoggerListener.php @@ -2,6 +2,7 @@ namespace Stof\DoctrineExtensionsBundle\EventListener; +use Gedmo\Loggable\Loggable; use Gedmo\Loggable\LoggableListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -14,13 +15,21 @@ * Sets the username from the security context by listening on kernel.request * * @author Christophe Coevoet + * + * @phpstan-template T of Loggable|object */ class LoggerListener implements EventSubscriberInterface { + /** @var AuthorizationCheckerInterface|null */ private $authorizationChecker; + /** @var TokenStorageInterface|null */ private $tokenStorage; + /** @var LoggableListener */ private $loggableListener; + /** + * @param LoggableListener $loggableListener + */ public function __construct(LoggableListener $loggableListener, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authorizationChecker = null) { $this->loggableListener = $loggableListener; @@ -31,7 +40,7 @@ public function __construct(LoggableListener $loggableListener, TokenStorageInte /** * @internal */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; @@ -48,9 +57,6 @@ public function onKernelRequest(RequestEvent $event) } } - /** - * @return string[] - */ public static function getSubscribedEvents() { return array( diff --git a/src/Uploadable/UploadableManager.php b/src/Uploadable/UploadableManager.php index 504e2bda..0d8c2b33 100644 --- a/src/Uploadable/UploadableManager.php +++ b/src/Uploadable/UploadableManager.php @@ -3,14 +3,20 @@ namespace Stof\DoctrineExtensionsBundle\Uploadable; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Gedmo\Uploadable\FileInfo\FileInfoInterface; use Gedmo\Uploadable\UploadableListener; class UploadableManager { /** @var \Gedmo\Uploadable\UploadableListener */ private $listener; + /** @var class-string */ private $fileInfoClass; + /** + * @param UploadableListener $listener + * @param class-string $fileInfoClass + */ public function __construct(UploadableListener $listener, $fileInfoClass) { $this->listener = $listener; @@ -24,6 +30,8 @@ public function __construct(UploadableListener $listener, $fileInfoClass) * * @param object $entity - The entity you are marking to "Upload" as soon as you call "flush". * @param mixed $fileInfo - The file info object or array. In Symfony, this will be typically an UploadedFile instance. + * + * @return void */ public function markEntityToUpload($entity, $fileInfo) { diff --git a/src/Uploadable/UploadedFileInfo.php b/src/Uploadable/UploadedFileInfo.php index 4a7bf9df..59aac5fd 100644 --- a/src/Uploadable/UploadedFileInfo.php +++ b/src/Uploadable/UploadedFileInfo.php @@ -7,6 +7,7 @@ class UploadedFileInfo implements FileInfoInterface { + /** @var UploadedFile */ private $uploadedFile; public function __construct(UploadedFile $uploadedFile) @@ -31,11 +32,13 @@ public function getName() } /** - * @return ?string + * @return int|null */ public function getSize() { - return $this->uploadedFile->getSize(); + $size = $this->uploadedFile->getSize(); + + return $size !== false ? $size : null; } /** diff --git a/src/Uploadable/ValidatorConfigurator.php b/src/Uploadable/ValidatorConfigurator.php index ee738fc7..84705e3d 100644 --- a/src/Uploadable/ValidatorConfigurator.php +++ b/src/Uploadable/ValidatorConfigurator.php @@ -9,6 +9,7 @@ */ class ValidatorConfigurator { + /** @var bool */ private $validateWritableDirectory; /** @@ -19,7 +20,7 @@ public function __construct($validateWritableDirectory) $this->validateWritableDirectory = $validateWritableDirectory; } - public function configure() + public function configure(): void { Validator::$validateWritableDirectory = $this->validateWritableDirectory; } diff --git a/tests/DependencyInjection/StofDoctrineExtensionsExtensionTest.php b/tests/DependencyInjection/StofDoctrineExtensionsExtensionTest.php index 63a380a0..56c39c44 100644 --- a/tests/DependencyInjection/StofDoctrineExtensionsExtensionTest.php +++ b/tests/DependencyInjection/StofDoctrineExtensionsExtensionTest.php @@ -10,6 +10,9 @@ class StofDoctrineExtensionsExtensionTest extends TestCase { + /** + * @return iterable + */ public static function provideExtensions() { return array( @@ -30,7 +33,7 @@ public static function provideExtensions() /** * @dataProvider provideExtensions */ - public function testLoadORMConfig($listener) + public function testLoadORMConfig(string $listener): void { $extension = new StofDoctrineExtensionsExtension(); $container = new ContainerBuilder(); @@ -42,22 +45,22 @@ public function testLoadORMConfig($listener) $extension->load(array($config), $container); - $this->assertTrue($container->hasDefinition('stof_doctrine_extensions.listener.'.$listener)); + self::assertTrue($container->hasDefinition('stof_doctrine_extensions.listener.'.$listener)); $def = $container->getDefinition('stof_doctrine_extensions.listener.'.$listener); - $this->assertTrue($def->hasTag('doctrine.event_listener')); + self::assertTrue($def->hasTag('doctrine.event_listener')); $tags = $def->getTag('doctrine.event_listener'); $configuredManagers = array_unique(array_column($tags, 'connection')); - $this->assertCount(2, $configuredManagers); + self::assertCount(2, $configuredManagers); } /** * @dataProvider provideExtensions */ - public function testLoadMongodbConfig($listener) + public function testLoadMongodbConfig(string $listener): void { $extension = new StofDoctrineExtensionsExtension(); $container = new ContainerBuilder(); @@ -69,22 +72,22 @@ public function testLoadMongodbConfig($listener) $extension->load(array($config), $container); - $this->assertTrue($container->hasDefinition('stof_doctrine_extensions.listener.'.$listener)); + self::assertTrue($container->hasDefinition('stof_doctrine_extensions.listener.'.$listener)); $def = $container->getDefinition('stof_doctrine_extensions.listener.'.$listener); - $this->assertTrue($def->hasTag('doctrine_mongodb.odm.event_listener')); + self::assertTrue($def->hasTag('doctrine_mongodb.odm.event_listener')); $tags = $def->getTag('doctrine_mongodb.odm.event_listener'); $configuredManagers = array_unique(array_column($tags, 'connection')); - $this->assertCount(2, $configuredManagers); + self::assertCount(2, $configuredManagers); } /** * @dataProvider provideExtensions */ - public function testLoadBothConfig($listener) + public function testLoadBothConfig(string $listener): void { $extension = new StofDoctrineExtensionsExtension(); $container = new ContainerBuilder(); @@ -96,21 +99,21 @@ public function testLoadBothConfig($listener) $extension->load(array($config), $container); - $this->assertTrue($container->hasDefinition('stof_doctrine_extensions.listener.'.$listener)); + self::assertTrue($container->hasDefinition('stof_doctrine_extensions.listener.'.$listener)); $def = $container->getDefinition('stof_doctrine_extensions.listener.'.$listener); - $this->assertTrue($def->hasTag('doctrine.event_listener')); - $this->assertTrue($def->hasTag('doctrine_mongodb.odm.event_listener')); + self::assertTrue($def->hasTag('doctrine.event_listener')); + self::assertTrue($def->hasTag('doctrine_mongodb.odm.event_listener')); - $this->assertCount(1, array_unique(array_column($def->getTag('doctrine.event_listener'), 'connection'))); - $this->assertCount(1, array_unique(array_column($def->getTag('doctrine_mongodb.odm.event_listener'), 'connection'))); + self::assertCount(1, array_unique(array_column($def->getTag('doctrine.event_listener'), 'connection'))); + self::assertCount(1, array_unique(array_column($def->getTag('doctrine_mongodb.odm.event_listener'), 'connection'))); } /** * @dataProvider provideExtensions */ - public function testEventConsistency(string $listener) + public function testEventConsistency(string $listener): void { $extension = new StofDoctrineExtensionsExtension(); $container = new ContainerBuilder(); @@ -128,9 +131,9 @@ public function testEventConsistency(string $listener) $listenerInstance = $container->get('stof_doctrine_extensions.listener.'.$listener); if (!$listenerInstance instanceof EventSubscriber) { - $this->markTestSkipped(sprintf('The listener for "%s" is not a Doctrine event subscriber.', $listener)); + self::markTestSkipped(sprintf('The listener for "%s" is not a Doctrine event subscriber.', $listener)); } - $this->assertEqualsCanonicalizing($listenerInstance->getSubscribedEvents(), $configuredEvents); + self::assertEqualsCanonicalizing($listenerInstance->getSubscribedEvents(), $configuredEvents); } }