Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for arrays containing additional database values in list views #6200

Merged
merged 1 commit into from
Jul 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
jorrit marked this conversation as resolved.
Show resolved Hide resolved
----------------------------

.. 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
jorrit marked this conversation as resolved.
Show resolved Hide resolved
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.
jorrit marked this conversation as resolved.
Show resolved Hide resolved
*
* @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 {
jorrit marked this conversation as resolved.
Show resolved Hide resolved
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
jorrit marked this conversation as resolved.
Show resolved Hide resolved
* @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