Skip to content

Commit

Permalink
[SonataExtraBundle] AdminAction improvment (#285)
Browse files Browse the repository at this point in the history
[SonataExtraBundle] New ObjectActionExecutioner
[SonataExtraBundle] ObjectActionExecutioner ExecutionErrorEvent
[SonataExtraBundle] ObjectActionExecution rely on options/listener
[SonataExtraBundle] GenericFormHandler for both batch and entity custom form
  • Loading branch information
mpoiriert authored Aug 9, 2024
1 parent c588482 commit 83e8df4
Show file tree
Hide file tree
Showing 27 changed files with 1,032 additions and 324 deletions.
39 changes: 39 additions & 0 deletions app/src/Controller/Admin/AddRolesAminAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace App\Controller\Admin;

use App\Entity\User;
use App\Form\AddRolesForm;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\GenericFormHandler;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\ObjectActionExecutioner;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class AddRolesAminAction
{
public function __invoke(
ObjectActionExecutioner $objectActionExecutioner,
Request $request,
GenericFormHandler $genericFormHandler
): Response {
return $genericFormHandler
->execute(
$objectActionExecutioner,
$request,
AddRolesForm::class,
null,
function (User $user, array $data) use ($objectActionExecutioner): void {
$roles = $data['roles'];

$missingRoles = array_diff($roles, $user->getRoles());

if (\count($missingRoles) > 0) {
$user->setRoles(array_merge($user->getRoles(), $missingRoles));
$objectActionExecutioner->getAdmin()->update($user);
} else {
$objectActionExecutioner->skip('all-roles-already-set');
}
}
);
}
}
80 changes: 28 additions & 52 deletions app/src/Controller/Admin/MakeAdminAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,38 @@
namespace App\Controller\Admin;

use App\Entity\User;
use App\Sonata\Admin\UserAdmin;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\BatchIterator;
use Draw\Bundle\SonataExtraBundle\Notifier\Notification\SonataNotification;
use Sonata\AdminBundle\Admin\AdminInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\Event\ExecutionErrorEvent;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\ObjectActionExecutioner;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Notifier\NotifierInterface;

class MakeAdminAction
{
public function __invoke(UserAdmin $admin, User $user, NotifierInterface $notifier): Response
{
if ($this->addAdminRole($user)) {
$admin->update($user);
$notifier->send(SonataNotification::success('User is now an admin'));
} else {
$notifier->send(
(new SonataNotification('User already has the admin role'))
->setSonataFlashType('info')
public function __invoke(
ObjectActionExecutioner $objectActionExecutioner
): Response {
return $objectActionExecutioner
->execute(
[
'execution' => function (User $user) use ($objectActionExecutioner): void {
$currentRoles = $user->getRoles();

if (\in_array('ROLE_ADMIN', $currentRoles)) {
$objectActionExecutioner->skip('already-admin');

return;
}

$user->setRoles([
...$currentRoles,
'ROLE_ADMIN',
]);

$objectActionExecutioner->getAdmin()->update($user);
},
'onExecutionError' => function (ExecutionErrorEvent $event): void {
$event->setStopExecution(false);
},
]
);
}

return new RedirectResponse($admin->generateObjectUrl('show', $user));
}

private function addAdminRole(User $user): bool
{
$currentRoles = $user->getRoles();

if (\in_array('ROLE_ADMIN', $currentRoles)) {
return false;
}

$user->setRoles([
...$currentRoles,
'ROLE_ADMIN',
]);

return true;
}

/**
* @param BatchIterator<User> $batchIterator
*/
public function batch(BatchIterator $batchIterator, AdminInterface $admin): Response
{
foreach ($batchIterator->getObjects() as $object) {
if (!$this->addAdminRole($object)) {
$batchIterator->skip('already-admin');

continue;
}

$admin->update($object);
}

return new RedirectResponse($admin->generateUrl('list'));
}
}
7 changes: 6 additions & 1 deletion app/src/Entity/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,12 @@ public function getRoles(): array

public function setRoles(array $roles): static
{
$this->roles = $roles;
$this->roles = array_values(
array_filter(
$roles,
fn ($role) => \is_string($role) && '' !== $role && 'ROLE_USER' !== $role
)
);

return $this;
}
Expand Down
33 changes: 33 additions & 0 deletions app/src/Form/AddRolesForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;

class AddRolesForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add(
'roles',
ChoiceType::class,
[
'choices' => [
'ROLE_ADMIN' => 'ROLE_ADMIN',
'ROLE_USER' => 'ROLE_USER',
],
'multiple' => true,
'expanded' => true,
]
)
->add(
'submit',
SubmitType::class,
['label' => 'submit']
);
}
}
21 changes: 14 additions & 7 deletions app/src/Sonata/Admin/UserAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

namespace App\Sonata\Admin;

use App\Controller\Admin\AddRolesAminAction;
use App\Controller\Admin\MakeAdminAction;
use App\Entity\Tag;
use App\Entity\User;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\ActionableInterface;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\ActionableAdminInterface;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\AdminAction;
use Draw\Bundle\SonataExtraBundle\Doctrine\Filter\InFilter;
use Draw\Bundle\SonataExtraBundle\Form\Extension\Core\Type\SingleLineDateTimeType;
Expand Down Expand Up @@ -34,7 +35,7 @@
'pager_type' => 'simple',
]
)]
class UserAdmin extends AbstractAdmin implements ListPriorityAwareAdminInterface, ActionableInterface
class UserAdmin extends AbstractAdmin implements ListPriorityAwareAdminInterface, ActionableAdminInterface
{
public function getListFieldPriorityOptions(): array
{
Expand Down Expand Up @@ -83,6 +84,7 @@ protected function configureListFields(ListMapper $list): void
->add('childObject2')
->add('userTags', 'list')
->add('tags', 'list')
->add('roles', 'list')
->add('isLocked', 'boolean', ['inverse' => true]);
}

Expand Down Expand Up @@ -202,11 +204,16 @@ protected function configureFormFields(FormMapper $form): void
->end();
}

public function getActions(): iterable
public function getActions(): array
{
yield (new AdminAction('makeAdmin', true))
->setController(MakeAdminAction::class)
->setIcon('fa fa-user-plus')
->setBatchController(MakeAdminAction::class.'::batch');
return [
'makeAdin' => (new AdminAction('makeAdmin', true))
->setController(MakeAdminAction::class)
->setIcon('fa fa-user-plus')
->setBatchController(MakeAdminAction::class),
'addRoles' => (new AdminAction('addRoles', true))
->setController(AddRolesAminAction::class)
->setBatchController(AddRolesAminAction::class),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Draw\Bundle\SonataExtraBundle\ActionableAdmin;

use Draw\Bundle\SonataExtraBundle\ActionableAdmin\Extension\ActionableAdminExtension;
use Sonata\AdminBundle\Admin\AdminInterface;

interface ActionableAdminInterface extends AdminInterface
{
/**
* List of AdminAction available for this admin.
*
* Key is the action name.
*
* @see ActionableAdminExtension
*
* @return array<string,AdminAction>
*/
public function getActions(): array;
}

This file was deleted.

43 changes: 43 additions & 0 deletions packages/sonata-extra-bundle/ActionableAdmin/AdminActionLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Draw\Bundle\SonataExtraBundle\ActionableAdmin;

use Draw\Bundle\SonataExtraBundle\ActionableAdmin\Extension\ActionableAdminExtensionInterface;
use Sonata\AdminBundle\Admin\AdminInterface;

class AdminActionLoader
{
private array $actions = [];

/**
* @return array<string,AdminAction>
*/
public function getActions(AdminInterface $admin): array
{
if (!\array_key_exists($admin->getCode(), $this->actions)) {
$actions = $admin instanceof ActionableAdminInterface
? $admin->getActions()
: [];

foreach ($admin->getExtensions() as $extension) {
if (!$extension instanceof ActionableAdminExtensionInterface) {
continue;
}

$actions = $extension->getActions($actions);
}

array_walk(
$actions,
function (AdminAction $adminAction) use ($admin): void {
// Set default translation domain
$adminAction->setTranslationDomain($adminAction->getTranslationDomain() ?? $admin->getTranslationDomain());
}
);

$this->actions[$admin->getCode()] = $actions;
}

return $this->actions[$admin->getCode()];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,31 @@

namespace Draw\Bundle\SonataExtraBundle\ActionableAdmin\ArgumentResolver;

use Draw\Bundle\SonataExtraBundle\ActionableAdmin\BatchIterator;
use Draw\Bundle\SonataExtraBundle\ActionableAdmin\ObjectActionExecutioner;
use Sonata\AdminBundle\Request\AdminFetcherInterface;
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

class BatchIteratorValueResolver implements ValueResolverInterface
class ObjectActionExecutionerValueResolver implements ValueResolverInterface
{
public function __construct(
private AdminFetcherInterface $adminFetcher,
private BatchIterator $batchIterator
private ObjectActionExecutioner $objectActionExecutioner
) {
}

public function resolve(Request $request, ArgumentMetadata $argument): iterable
{

$type = $argument->getType();

if (null === $type) {
return [];
}

if (BatchIterator::class !== $type && !is_subclass_of($type, BatchIterator::class)) {
if (ObjectActionExecutioner::class !== $type && !is_subclass_of($type, ObjectActionExecutioner::class)) {
return [];
}

Expand All @@ -35,6 +36,20 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
return [];
}

if ($admin->hasSubject()) {
$action = $request->attributes->get('_actionableAdmin')['action'];

if (null === $action) {
return [];
}

return [$this->objectActionExecutioner->initialize(
target: $admin->getSubject(),
admin: $admin,
action: $action
)];
}

$action = $request->request->get('action');

if (null === $action) {
Expand All @@ -43,8 +58,8 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable

foreach ($request->attributes as $attribute) {
if ($attribute instanceof ProxyQuery) {
return [$this->batchIterator->initialize(
query: $attribute,
return [$this->objectActionExecutioner->initialize(
target: $attribute,
admin: $admin,
action: $action,
)];
Expand Down
Loading

0 comments on commit 83e8df4

Please sign in to comment.