Skip to content

Commit

Permalink
Add support for displaying additional values in list views
Browse files Browse the repository at this point in the history
  • Loading branch information
jorrit committed Jul 15, 2020
1 parent a7e05c3 commit a750669
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 5 deletions.
4 changes: 4 additions & 0 deletions docs/.doctor-rst.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ rules:
#no_config_yaml: ~
#kernel_instead_of_app_kernel: ~
#no_app_console: ~

whitelist:
lines:
- '.. versionadded:: 3.x'
54 changes: 54 additions & 0 deletions docs/reference/action_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
65 changes: 61 additions & 4 deletions src/Twig/Extension/SonataAdminExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
) {
Expand All @@ -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);
}
Expand Down Expand Up @@ -238,13 +240,23 @@ 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(
$object,
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');
}
Expand Down Expand Up @@ -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];
}
}
79 changes: 78 additions & 1 deletion tests/Twig/Extension/SonataAdminExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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('<td class="sonata-ba-list-field sonata-ba-list-field-" objectId="12345"> Extra value </td>'),
$this->removeExtraWhitespace($this->twigExtension->renderListElement(
$this->environment,
[$this->object, 'fd_name' => 'Extra value'],
$this->fieldDescription
))
);
}

/**
* NEXT_MAJOR: Remove @expectedDeprecation.
*
* @group legacy
* @expectedDeprecation Accessing a non existing value is deprecated since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.
*/
public function testRenderListElementWithNoValueException(): 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('getValue')
->willReturnCallback(static function (): void {
throw new NoValueException();
});

$this->assertSame(
$this->removeExtraWhitespace('<td class="sonata-ba-list-field sonata-ba-list-field-" objectId="12345"> </td>'),
$this->removeExtraWhitespace($this->twigExtension->renderListElement(
$this->environment,
$this->object,
$this->fieldDescription
))
);
}

/**
* @dataProvider getDeprecatedRenderListElementTests
* @group legacy
Expand Down Expand Up @@ -2261,6 +2323,11 @@ public function getDeprecatedTextExtensionItems(): iterable
];
}

/**
* NEXT_MAJOR: Remove this test.
*
* @group legacy
*/
public function testGetValueFromFieldDescription(): void
{
$object = new \stdClass();
Expand All @@ -2273,6 +2340,11 @@ public function testGetValueFromFieldDescription(): void
$this->assertSame('test123', $this->twigExtension->getValueFromFieldDescription($object, $fieldDescription));
}

/**
* NEXT_MAJOR: Remove this test.
*
* @group legacy
*/
public function testGetValueFromFieldDescriptionWithRemoveLoopException(): void
{
$object = $this->createMock(\ArrayAccess::class);
Expand All @@ -2288,7 +2360,7 @@ public function testGetValueFromFieldDescriptionWithRemoveLoopException(): void
}

/**
* NEXT_MAJOR: Change this test to expect a NoValueException instead.
* NEXT_MAJOR: Remove this test.
*
* @group legacy
* @expectedDeprecation Accessing a non existing value is deprecated since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.
Expand All @@ -2311,6 +2383,11 @@ public function testGetValueFromFieldDescriptionWithNoValueException(): void
$this->assertNull($this->twigExtension->getValueFromFieldDescription($object, $fieldDescription));
}

/**
* NEXT_MAJOR: Remove this test.
*
* @group legacy
*/
public function testGetValueFromFieldDescriptionWithNoValueExceptionNewAdminInstance(): void
{
$object = new \stdClass();
Expand Down

0 comments on commit a750669

Please sign in to comment.