diff --git a/docs/.doctor-rst.yaml b/docs/.doctor-rst.yaml index 29257eb943..956db8838f 100644 --- a/docs/.doctor-rst.yaml +++ b/docs/.doctor-rst.yaml @@ -30,3 +30,7 @@ rules: #no_config_yaml: ~ #kernel_instead_of_app_kernel: ~ #no_app_console: ~ + +whitelist: + lines: + - '.. versionadded:: 3.x' diff --git a/docs/reference/action_list.rst b/docs/reference/action_list.rst index ea62dba29c..7f0ae14b13 100644 --- a/docs/reference/action_list.rst +++ b/docs/reference/action_list.rst @@ -578,5 +578,59 @@ Checkbox range selection You can check / uncheck a range of checkboxes by clicking a first one, then a second one with shift + click. +Displaying a non-model field +---------------------------- + +.. versionadded:: 3.x + + Support for displaying fields not part of the model class was introduced in version 3.x. + +The list view can also display fields that are not part of the model class. + +In some situations you can add a new getter to your model class to calculate +a field based on the other fields of your model:: + + // src/Entity/User.php + + public function getFullName(): string + { + return $this->getGivenName().' '.$this->getFamilyName(); + } + + // src/Admin/UserAdmin.php + + protected function configureListFields(ListMapper $listMapper) + { + $listMapper->addIdentifier('fullName'); + } + +In situations where the data are not available in the model or it is more performant +to have the database calculate the value you can override the ``configureQuery()`` Admin +class method to add fields to the result set. +In ``configureListFields()`` these fields can be added using the alias given +in the query. + +In the following example the number of comments for a post is added to the +query and displayed:: + + // src/Admin/PostAdmin.php + + protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface + { + $query = parent::configureQuery($query); + + $query + ->leftJoin('n.Comments', 'c') + ->addSelect('COUNT(c.id) numberofcomments') + ->addGroupBy('n'); + + return $query; + } + + protected function configureListFields(ListMapper $listMapper) + { + $listMapper->addIdentifier('numberofcomments'); + } + .. _`SonataDoctrineORMAdminBundle Documentation`: https://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/list_field_definition.html .. _`here`: https://github.com/sonata-project/SonataCoreBundle/tree/3.x/src/Form/Type diff --git a/src/Twig/Extension/SonataAdminExtension.php b/src/Twig/Extension/SonataAdminExtension.php index 5cf65daf71..8c1b74a660 100644 --- a/src/Twig/Extension/SonataAdminExtension.php +++ b/src/Twig/Extension/SonataAdminExtension.php @@ -184,14 +184,14 @@ public function getName() /** * render a list element from the FieldDescription. * - * @param object $object - * @param array $params + * @param object|array $listElement + * @param array $params * * @return string */ public function renderListElement( Environment $environment, - $object, + $listElement, FieldDescriptionInterface $fieldDescription, $params = [] ) { @@ -203,10 +203,12 @@ public function renderListElement( $environment ); + [$object, $value] = $this->getObjectAndValueFromListElement($listElement, $fieldDescription); + return $this->render($fieldDescription, $template, array_merge($params, [ 'admin' => $fieldDescription->getAdmin(), 'object' => $object, - 'value' => $this->getValueFromFieldDescription($object, $fieldDescription), + 'value' => $value, 'field_description' => $fieldDescription, ]), $environment); } @@ -238,6 +240,10 @@ public function output( * * @throws \RuntimeException * + * NEXT_MAJOR: Remove this method + * + * @deprecated This method is deprecated with no replacement since sonata-project/admin-bundle 3.x and will be removed in 4.0. + * * @return mixed */ public function getValueFromFieldDescription( @@ -245,6 +251,12 @@ public function getValueFromFieldDescription( FieldDescriptionInterface $fieldDescription, array $params = [] ) { + @trigger_error(sprintf( + 'The %s() method is deprecated since sonata-project/admin-bundle 3.x and will be removed in version 4.0.' + .' There is no replacement.', + __METHOD__ + ), E_USER_DEPRECATED); + if (isset($params['loop']) && $object instanceof \ArrayAccess) { throw new \RuntimeException('remove the loop requirement'); } @@ -695,4 +707,49 @@ private function getTemplateRegistry(string $adminCode): TemplateRegistryInterfa throw new ServiceNotFoundException($serviceId); } + + /** + * Extracts the object and requested value from the $listElement. + * + * @param object|array $listElement + * + * @throws \TypeError when $listElement is not an object or an array with an object on offset 0 + * + * @return array An array containing object and value + */ + private function getObjectAndValueFromListElement( + $listElement, + FieldDescriptionInterface $fieldDescription + ): array { + if (\is_object($listElement)) { + $object = $listElement; + } elseif (\is_array($listElement)) { + if (!isset($listElement[0]) || !\is_object($listElement[0])) { + throw new \TypeError(sprintf('If argument 1 passed to %s() is an array it must contain an object at offset 0.', __METHOD__)); + } + + $object = $listElement[0]; + } else { + throw new \TypeError(sprintf('Argument 1 passed to %s() must be an object or an array, %s given.', __METHOD__, \gettype($listElement))); + } + + if (\is_array($listElement) && \array_key_exists($fieldDescription->getName(), $listElement)) { + $value = $listElement[$fieldDescription->getName()]; + } else { + try { + $value = $fieldDescription->getValue($object); + } catch (NoValueException $e) { + // NEXT_MAJOR: throw the NoValueException. + @trigger_error( + 'Accessing a non existing value is deprecated' + .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.', + E_USER_DEPRECATED + ); + + $value = null; + } + } + + return [$object, $value]; + } } diff --git a/tests/Twig/Extension/SonataAdminExtensionTest.php b/tests/Twig/Extension/SonataAdminExtensionTest.php index 89ab2f20b1..ce993ee2cf 100644 --- a/tests/Twig/Extension/SonataAdminExtensionTest.php +++ b/tests/Twig/Extension/SonataAdminExtensionTest.php @@ -386,6 +386,68 @@ public function testRenderListElement(string $expected, string $type, $value, ar ); } + /** + * NEXT_MAJOR: Remove @expectedDeprecation. + * + * @group legacy + * @expectedDeprecation The Sonata\AdminBundle\Admin\AbstractAdmin::getTemplate method is deprecated (since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead). + */ + public function testRenderListElementWithAdditionalValuesInArray(): void + { + // NEXT_MAJOR: Remove this line + $this->admin + ->method('getTemplate') + ->with('base_list_field') + ->willReturn('@SonataAdmin/CRUD/base_list_field.html.twig'); + + $this->templateRegistry->getTemplate('base_list_field')->willReturn('@SonataAdmin/CRUD/base_list_field.html.twig'); + + $this->fieldDescription + ->method('getTemplate') + ->willReturn('@SonataAdmin/CRUD/list_string.html.twig'); + + $this->assertSame( + $this->removeExtraWhitespace('