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 4519332
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 5 deletions.
52 changes: 52 additions & 0 deletions docs/reference/action_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -578,5 +578,57 @@ 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

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 offet 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 4519332

Please sign in to comment.