diff --git a/.phpstan/phpstan-baseline.neon b/.phpstan/phpstan-baseline.neon index 92f76b5cfe..6775b2860e 100644 --- a/.phpstan/phpstan-baseline.neon +++ b/.phpstan/phpstan-baseline.neon @@ -44,21 +44,94 @@ parameters: - # will be fixed in v4. Code is marked as deprecated - message: "#^Method Sonata\\\\AdminBundle\\\\Admin\\\\AbstractAdmin\\:\\:getFormFieldDescription\\(\\) should return Sonata\\\\AdminBundle\\\\Admin\\\\FieldDescriptionInterface but returns null\\.$#" + message: "#^Method Sonata\\\\AdminBundle\\\\Admin\\\\AbstractAdmin\\:\\:getShowFieldDescription\\(\\) should return Sonata\\\\AdminBundle\\\\Admin\\\\FieldDescriptionInterface but returns null\\.$#" count: 1 path: ../src/Admin/AbstractAdmin.php - # will be fixed in v4. Code is marked as deprecated - message: "#^Method Sonata\\\\AdminBundle\\\\Admin\\\\AbstractAdmin\\:\\:getShowFieldDescription\\(\\) should return Sonata\\\\AdminBundle\\\\Admin\\\\FieldDescriptionInterface but returns null\\.$#" + message: "#^Result of \\&\\& is always false\\.$#" count: 1 path: ../src/Admin/AbstractAdmin.php - # will be fixed in v4. Code is marked as deprecated - message: "#^Method Sonata\\\\AdminBundle\\\\Admin\\\\AbstractAdmin\\:\\:getListFieldDescription\\(\\) should return Sonata\\\\AdminBundle\\\\Admin\\\\FieldDescriptionInterface but returns null\\.$#" + message: "#^Strict comparison using \\!\\=\\= between 'Sonata\\\\\\\\AdminBundle\\\\\\\\Admin\\\\\\\\AdminHelper' and 'Sonata\\\\\\\\AdminBundle\\\\\\\\Admin\\\\\\\\AdminHelper' will always evaluate to false\\.$#" count: 1 - path: ../src/Admin/AbstractAdmin.php + path: ../src/Admin/AdminHelper.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Result of && is always false\\.$#" + count: 2 + path: ../src/Admin/BaseFieldDescription.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: ../src/Block/AdminListBlockService.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Call to function is_object\\(\\) with string will always evaluate to false\\.$#" + count: 1 + path: ../src/Block/AdminListBlockService.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: ../src/Block/AdminListBlockService.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Call to function is_object\\(\\) with string will always evaluate to false\\.$#" + count: 1 + path: ../src/Block/AdminSearchBlockService.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: ../src/Block/AdminSearchBlockService.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Call to function is_object\\(\\) with string will always evaluate to false\\.$#" + count: 1 + path: ../src/Block/AdminStatsBlockService.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: ../src/Block/AdminStatsBlockService.php + + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Result of && is always false\\.$#" + count: 2 + path: ../src/Command/GenerateObjectAclCommand.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Instanceof between \\*NEVER\\* and Doctrine\\\\Persistence\\\\ManagerRegistry will always evaluate to false\\.$#" + count: 1 + path: ../src/Command/GenerateObjectAclCommand.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: ../src/Command/GenerateObjectAclCommand.php + + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Strict comparison using \\!\\=\\= between 'Sonata\\\\\\\\AdminBundle\\\\\\\\Command\\\\\\\\GenerateObjectAclCommand' and 'Sonata\\\\\\\\AdminBundle\\\\\\\\Command\\\\\\\\GenerateObjectAclCommand' will always evaluate to false\\.$#" + count: 1 + path: ../src/Command/GenerateObjectAclCommand.php - # will be fixed in v4. Currently BC break @@ -108,6 +181,18 @@ parameters: count: 1 path: ../src/Form/FormMapper.php + - + # will be fixed in v4. Code is marked as deprecated + message: "#^Right side of && is always true\\.$#" + count: 1 + path: ../src/Mapper/BaseGroupedMapper.php + + - + # will be fixed in v4. Currently BC break + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: ../src/Route/RoutesCache.php + - # will be fixed in v4. Currently BC break message: "#^Method Sonata\\\\AdminBundle\\\\Admin\\\\FieldDescriptionInterface\\:\\:getLabel\\(\\) invoked with 1 parameter, 0 required\\.$#" diff --git a/.phpstan/phpstan.neon.dist b/.phpstan/phpstan.neon.dist index 1cc5e8f3ba..fe5c491ae5 100644 --- a/.phpstan/phpstan.neon.dist +++ b/.phpstan/phpstan.neon.dist @@ -2,8 +2,7 @@ includes: - phpstan-baseline.neon parameters: - level: 3 - + level: 4 paths: - ../src excludes_analyse: diff --git a/src/Action/GetShortObjectDescriptionAction.php b/src/Action/GetShortObjectDescriptionAction.php index 21886bbbd1..5bfc6c1e22 100644 --- a/src/Action/GetShortObjectDescriptionAction.php +++ b/src/Action/GetShortObjectDescriptionAction.php @@ -48,9 +48,9 @@ public function __invoke(Request $request): Response $uniqid = $request->get('uniqid'); $linkParameters = $request->get('linkParameters', []); - $admin = $this->pool->getInstance($code); - - if (!$admin) { + try { + $admin = $this->pool->getInstance($code); + } catch (\InvalidArgumentException $e) { throw new NotFoundHttpException(sprintf('Could not find admin for code "%s"', $code)); } @@ -75,7 +75,9 @@ public function __invoke(Request $request): Response 'id' => $admin->id($object), 'label' => $admin->toString($object), ]]); - } elseif ('html' === $request->get('_format')) { + } + + if ('html' === $request->get('_format')) { return new Response($this->twig->render($admin->getTemplate('short_object_description'), [ 'admin' => $admin, 'description' => $admin->toString($object), diff --git a/src/Admin/AbstractAdmin.php b/src/Admin/AbstractAdmin.php index 0774bd7e81..d66e58c86c 100644 --- a/src/Admin/AbstractAdmin.php +++ b/src/Admin/AbstractAdmin.php @@ -3201,7 +3201,14 @@ final public function isDefaultFilter($name) */ public function canAccessObject($action, $object) { - return $object && $this->id($object) && $this->hasAccess($action, $object); + if (!\is_object($object)) { + return false; + } + if (!$this->id($object)) { + return false; + } + + return $this->hasAccess($action, $object); } protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface @@ -3488,7 +3495,7 @@ protected function predefinePerPageOptions() /** * Return list routes with permissions name. * - * @return array + * @return array */ protected function getAccess() { diff --git a/src/Admin/AdminInterface.php b/src/Admin/AdminInterface.php index 20d2c4b742..51c83ddecb 100644 --- a/src/Admin/AdminInterface.php +++ b/src/Admin/AdminInterface.php @@ -390,7 +390,7 @@ public function getSubject(); * * @param string $name * - * @return FieldDescriptionInterface + * @return FieldDescriptionInterface|null // NEXT_MAJOR: Return FieldDescriptionInterface */ public function getListFieldDescription($name); diff --git a/src/Admin/FieldDescriptionRegistryInterface.php b/src/Admin/FieldDescriptionRegistryInterface.php index 828be4dc7d..f28c1c7740 100644 --- a/src/Admin/FieldDescriptionRegistryInterface.php +++ b/src/Admin/FieldDescriptionRegistryInterface.php @@ -34,7 +34,7 @@ interface FieldDescriptionRegistryInterface * * @param string $name * - * @return FieldDescriptionInterface + * @return FieldDescriptionInterface|null // NEXT_MAJOR: Return FieldDescriptionInterface */ public function getFormFieldDescription($name); diff --git a/src/Controller/CRUDController.php b/src/Controller/CRUDController.php index 8a01f3dcb9..86fbb85b99 100644 --- a/src/Controller/CRUDController.php +++ b/src/Controller/CRUDController.php @@ -1384,7 +1384,7 @@ protected function getAclRoles() $aclRoles = array_unique($aclRoles); - return \is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles; + return new \ArrayIterator($aclRoles); } /** diff --git a/src/Datagrid/Pager.php b/src/Datagrid/Pager.php index a13f76a786..c7c07bee8b 100644 --- a/src/Datagrid/Pager.php +++ b/src/Datagrid/Pager.php @@ -559,7 +559,7 @@ public function setQuery($query) } /** - * @return ProxyQueryInterface + * @return ProxyQueryInterface|null */ public function getQuery() { diff --git a/src/Form/ChoiceList/ModelChoiceLoader.php b/src/Form/ChoiceList/ModelChoiceLoader.php index f03eb81049..4d75dd7da8 100644 --- a/src/Form/ChoiceList/ModelChoiceLoader.php +++ b/src/Form/ChoiceList/ModelChoiceLoader.php @@ -49,7 +49,7 @@ class ModelChoiceLoader implements ChoiceLoaderInterface private $choices; /** - * @var PropertyPath + * @var PropertyPath|null */ private $propertyPath; @@ -102,7 +102,7 @@ public function loadChoiceList($value = null) } $choices = []; - foreach ($entities as $key => $model) { + foreach ($entities as $model) { if ($this->propertyPath) { // If the property option was given, use it $valueObject = $this->propertyAccessor->getValue($model, $this->propertyPath); diff --git a/src/Form/Extension/Field/Type/FormTypeFieldExtension.php b/src/Form/Extension/Field/Type/FormTypeFieldExtension.php index 4a59f85e0d..fe083bda0c 100644 --- a/src/Form/Extension/Field/Type/FormTypeFieldExtension.php +++ b/src/Form/Extension/Field/Type/FormTypeFieldExtension.php @@ -200,7 +200,7 @@ public function configureOptions(OptionsResolver $resolver) * return the value related to FieldDescription, if the associated object does no * exists => a temporary one is created. * - * @param object $object + * @param object|null $object * * @return mixed */ @@ -209,7 +209,7 @@ public function getValueFromFieldDescription($object, FieldDescriptionInterface $value = null; if (!$object) { - return $value; + return null; } try { diff --git a/src/Maker/AdminMaker.php b/src/Maker/AdminMaker.php index ee60d304f5..65b1a54e99 100644 --- a/src/Maker/AdminMaker.php +++ b/src/Maker/AdminMaker.php @@ -176,7 +176,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen 'Controller' ); - $this->generateController($input, $io, $generator, $controllerClassNameDetails); + $this->generateController($io, $generator, $controllerClassNameDetails); $controllerClassFullName = $controllerClassNameDetails->getFullName(); } @@ -225,27 +225,23 @@ private function generateService( } private function generateController( - InputInterface $input, ConsoleStyle $io, Generator $generator, ClassNameDetails $controllerClassNameDetails ): void { - $controllerClassFullName = null; - if ($controllerClassNameDetails) { - $controllerClassFullName = $controllerClassNameDetails->getFullName(); - $generator->generateClass( - $controllerClassFullName, - sprintf('%s/AdminController.tpl.php', $this->skeletonDirectory), - [] - ); - $generator->writeChanges(); - $io->writeln(sprintf( - '%sThe controller class "%s" has been generated under the file "%s".', - PHP_EOL, - $controllerClassNameDetails->getShortName(), - $controllerClassFullName - )); - } + $controllerClassFullName = $controllerClassNameDetails->getFullName(); + $generator->generateClass( + $controllerClassFullName, + sprintf('%s/AdminController.tpl.php', $this->skeletonDirectory), + [] + ); + $generator->writeChanges(); + $io->writeln(sprintf( + '%sThe controller class "%s" has been generated under the file "%s".', + PHP_EOL, + $controllerClassNameDetails->getShortName(), + $controllerClassFullName + )); } private function generateAdmin( diff --git a/src/Menu/Provider/GroupMenuProvider.php b/src/Menu/Provider/GroupMenuProvider.php index 6dd1e87577..28d21074f6 100644 --- a/src/Menu/Provider/GroupMenuProvider.php +++ b/src/Menu/Provider/GroupMenuProvider.php @@ -85,11 +85,14 @@ public function __construct(FactoryInterface $menuFactory, Pool $pool, $checker */ public function get($name, array $options = []) { + /** + * @var array{ label: string, label_catalogue: string, icon: string, on_top?: bool, keep_open: bool, provider: string, items: list } + */ $group = $options['group']; $menuItem = $this->menuFactory->createItem($options['name']); - if (empty($group['on_top']) || false === $group['on_top']) { + if (!\array_key_exists('on_top', $group) || false === $group['on_top']) { foreach ($group['items'] as $item) { if ($this->canGenerateMenuItem($item, $group)) { $menuItem->addChild($this->generateMenuItem($item, $group)); diff --git a/src/Twig/Extension/SonataAdminExtension.php b/src/Twig/Extension/SonataAdminExtension.php index fef668a2b5..2d4553ea50 100644 --- a/src/Twig/Extension/SonataAdminExtension.php +++ b/src/Twig/Extension/SonataAdminExtension.php @@ -664,7 +664,7 @@ private function render( TemplateWrapper $template, array $parameters, Environment $environment - ): ?string { + ): string { $content = $template->render($parameters); if ($environment->isDebug()) { diff --git a/tests/Action/GetShortObjectDescriptionActionTest.php b/tests/Action/GetShortObjectDescriptionActionTest.php index 80b86a7d03..9270fb1af5 100644 --- a/tests/Action/GetShortObjectDescriptionActionTest.php +++ b/tests/Action/GetShortObjectDescriptionActionTest.php @@ -63,14 +63,15 @@ protected function setUp(): void public function testGetShortObjectDescriptionActionInvalidAdmin(): void { $this->expectException(NotFoundHttpException::class); + $code = 'sonata.post.admin'; $request = new Request([ - 'code' => 'sonata.post.admin', + 'code' => $code, 'objectId' => 42, 'uniqid' => 'asdasd123', ]); - $this->pool->getInstance('sonata.post.admin')->willReturn(null); + $this->pool->getInstance($code)->willThrow(\InvalidArgumentException::class); $this->admin->setRequest(Argument::type(Request::class))->shouldNotBeCalled(); ($this->action)($request); diff --git a/tests/Admin/AdminTest.php b/tests/Admin/AdminTest.php index fd01a34458..f737208ce7 100644 --- a/tests/Admin/AdminTest.php +++ b/tests/Admin/AdminTest.php @@ -2129,6 +2129,36 @@ public function testGetActionButtonsListCreateDisabled(): void $this->assertSame([], $admin->getActionButtons('list', null)); } + public function testCantAccessObjectIfNullPassed(): void + { + $admin = new PostAdmin('sonata.post.admin.post', 'NewsBundle\Entity\Post', 'Sonata\NewsBundle\Controller\PostAdminController'); + + $this->assertFalse($admin->canAccessObject('list', null)); + } + + public function testCantAccessObjectIfRandomObjectPassed(): void + { + $admin = new PostAdmin('sonata.post.admin.post', 'NewsBundle\Entity\Post', 'Sonata\NewsBundle\Controller\PostAdminController'); + $modelManager = $this->createMock(ModelManagerInterface::class); + $admin->setModelManager($modelManager); + + $this->assertFalse($admin->canAccessObject('list', new \stdClass())); + } + + public function testCanAccessObject(): void + { + $admin = new PostAdmin('sonata.post.admin.post', 'NewsBundle\Entity\Post', 'Sonata\NewsBundle\Controller\PostAdminController'); + $modelManager = $this->createMock(ModelManagerInterface::class); + $modelManager + ->method('getNormalizedIdentifier') + ->willReturn('identifier'); + $admin->setModelManager($modelManager); + $securityHandler = $this->createMock(SecurityHandlerInterface::class); + $admin->setSecurityHandler($securityHandler); + + $this->assertTrue($admin->canAccessObject('list', new \stdClass())); + } + /** * @covers \Sonata\AdminBundle\Admin\AbstractAdmin::configureBatchActions */ diff --git a/tests/Controller/HelperControllerTest.php b/tests/Controller/HelperControllerTest.php index d00c502188..e907570eb3 100644 --- a/tests/Controller/HelperControllerTest.php +++ b/tests/Controller/HelperControllerTest.php @@ -108,14 +108,15 @@ protected function setUp(): void public function testGetShortObjectDescriptionActionInvalidAdmin(): void { $this->expectException(NotFoundHttpException::class); + $code = 'sonata.post.admin'; $request = new Request([ - 'code' => 'sonata.post.admin', + 'code' => $code, 'objectId' => 42, 'uniqid' => 'asdasd123', ]); - $this->pool->getInstance('sonata.post.admin')->willReturn(null); + $this->pool->getInstance($code)->willThrow(\InvalidArgumentException::class); $this->admin->setRequest(Argument::type(Request::class))->shouldNotBeCalled(); $this->controller->getShortObjectDescriptionAction($request);