Skip to content
Draft
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
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ This library add Symfony Workflow component integration within Sonata Admin.
[WorkflowControllerTrait](src/Controller/WorkflowControllerTrait.php)
- a Controller :
[WorkflowController](src/Controller/WorkflowController.php)
- a Translator :
[TranslatorInterface](src/Translator/TranslatorInterface.php)


Installation
Expand Down Expand Up @@ -109,7 +111,7 @@ sonata_admin:
- admin.pull_request
```

> **note**: You may noticed that we also registered the controller
> **note**: You may have noticed that we also registered the controller
`Yokai\SonataWorkflow\Controller\WorkflowController` as a service.
It is important, because it needs the workflow registry service to work.

Expand Down Expand Up @@ -252,6 +254,60 @@ class PullRequestController extends CRUDController
}
```

Configure the translations
--------------------------

The bundle will sometime need to acquire translations.
A utility named `TranslatorInterface` comes with a default implementation, that is using sonata standards:
[StandardTranslator](src/Translator/StandardTranslator.php)

But you might want to change how these translations are generated.
For instance, you could have different translations keys for each workflow/transition.

You need to create a new implementation of this interface, for example:

```php
<?php

declare(strict_types=1);

namespace App;

use Sonata\AdminBundle\Admin\AdminInterface;
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Contracts\Translation\TranslatableInterface;

#[AsAlias(TranslatorInterface::class)]
final class CustomTranslator implements TranslatorInterface
{
public function transitionSuccessFlashMessage(
AdminInterface $admin,
WorkflowInterface $workflow,
object $object,
string $transition,
): TranslatableInterface {
return new TranslatableMessage(
message: "{$workflow->getName()}.{$transition}.success",
domain: 'workflows',
);
}

public function transitionErrorFlashMessage(
AdminInterface $admin,
WorkflowInterface $workflow,
object $object,
string $transition,
): TranslatableInterface {
return new TranslatableMessage(
message: "{$workflow->getName()}.{$transition}.error",
domain: 'workflows',
);
}
}
```



MIT License
Expand Down
59 changes: 24 additions & 35 deletions src/Controller/WorkflowControllerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@

namespace Yokai\SonataWorkflow\Controller;

use Psr\Container\ContainerInterface;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Exception\LockException;
use Sonata\AdminBundle\Exception\ModelManagerException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Workflow\Exception\InvalidArgumentException;
use Symfony\Component\Workflow\Exception\LogicException;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Contracts\Service\Attribute\Required;
use Yokai\SonataWorkflow\Translator\StandardTranslator;
use Yokai\SonataWorkflow\Translator\TranslatorInterface;

/**
*
Expand All @@ -26,13 +25,15 @@
trait WorkflowControllerTrait
{
private Registry $workflowRegistry;
private TranslatorInterface $translator;

/**
* @required Symfony DI autowiring
*/
public function setWorkflowRegistry(Registry $workflowRegistry): void
{
#[Required]
public function autowireWorkflowControllerTrait(
Registry $workflowRegistry,
TranslatorInterface|null $translator,
): void {
$this->workflowRegistry = $workflowRegistry;
$this->translator = $translator ?? new StandardTranslator();
}

public function workflowApplyTransitionAction(Request $request): Response
Expand Down Expand Up @@ -94,11 +95,7 @@ public function workflowApplyTransitionAction(Request $request): Response

$this->addFlash(
'sonata_flash_success',
$this->trans(
'flash_edit_success',
['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
'SonataAdminBundle'
)
$this->translator->transitionSuccessFlashMessage($this->admin, $workflow, $existingObject, $transition),
);
} catch (LogicException $e) {
throw new BadRequestHttpException(
Expand All @@ -111,6 +108,10 @@ public function workflowApplyTransitionAction(Request $request): Response
);
} catch (ModelManagerException $e) {
$this->handleModelManagerException($e);
$this->addFlash(
'sonata_flash_error',
$this->translator->transitionErrorFlashMessage($this->admin, $workflow, $existingObject, $transition),
);
} catch (LockException $e) {
$this->addFlash(
'sonata_flash_error',
Expand All @@ -132,30 +133,18 @@ public function workflowApplyTransitionAction(Request $request): Response
/**
* @throws InvalidArgumentException
*/
protected function getWorkflow(object $object): WorkflowInterface
final protected function getWorkflow(object $object): WorkflowInterface
{
$registry = $this->workflowRegistry ?? null;
if ($registry === null) {
try {
if (method_exists($this, 'get')) {
$registry = $this->get('workflow.registry');
} elseif (method_exists($this, 'getContainer')) {
$registry = $this->getContainer()->get('workflow.registry');
} else {
$registry = $this->container->get('workflow.registry');
}
} catch (ServiceNotFoundException $exception) {
throw new \LogicException(
'Could not find the "workflow.registry" service. ' .
'You should either provide it via setter injection in your controller service definition ' .
'or make it public in your project.',
0,
$exception
);
}
if (!isset($this->workflowRegistry)) {
throw new \LogicException('Workflow registry was not set on controller.');
}

return $registry->get($object);
return $this->workflowRegistry->get($object);
}

final protected function getWorkflowTranslator(): TranslatorInterface
{
return $this->translator ?? new StandardTranslator();
}

protected function preApplyTransition(object $object, string $transition): ?Response
Expand Down
48 changes: 48 additions & 0 deletions src/Translator/StandardTranslator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Yokai\SonataWorkflow\Translator;

use Sonata\AdminBundle\Admin\AdminInterface;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Contracts\Translation\TranslatableInterface;

final class StandardTranslator implements TranslatorInterface
{
public function transitionSuccessFlashMessage(
AdminInterface $admin,
WorkflowInterface $workflow,
object $object,
string $transition,
): TranslatableInterface {
return new TranslatableMessage(
message: 'flash_edit_success',
parameters: [
'%name%' => $this->escapeHtml($admin->toString($object)),
],
domain: 'SonataAdminBundle',
);
}

public function transitionErrorFlashMessage(
AdminInterface $admin,
WorkflowInterface $workflow,
object $object,
string $transition,
): TranslatableInterface {
return new TranslatableMessage(
message: 'flash_edit_error',
parameters: [
'%name%' => $this->escapeHtml($admin->toString($object)),
],
domain: 'SonataAdminBundle',
);
}

private function escapeHtml(string $string): string
{
return \htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE);
}
}
26 changes: 26 additions & 0 deletions src/Translator/TranslatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Yokai\SonataWorkflow\Translator;

use Sonata\AdminBundle\Admin\AdminInterface;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Contracts\Translation\TranslatableInterface;

interface TranslatorInterface
{
public function transitionSuccessFlashMessage(
AdminInterface $admin,
WorkflowInterface $workflow,
object $object,
string $transition,
): TranslatableInterface;

public function transitionErrorFlashMessage(
AdminInterface $admin,
WorkflowInterface $workflow,
object $object,
string $transition,
): TranslatableInterface;
}
9 changes: 8 additions & 1 deletion tests/Controller/WorkflowControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\StateMachine;
use Symfony\Contracts\Translation\TranslatableInterface;
use Yokai\SonataWorkflow\Tests\Fixtures\StubTranslator;
use Yokai\SonataWorkflow\Tests\PullRequest;
use Yokai\SonataWorkflow\Tests\PullRequestWorkflowController;
Expand Down Expand Up @@ -273,7 +274,9 @@ public function testWorkflowApplyTransitionActionSuccessHttp(): void
self::assertCount(0, $errors);
$successes = $this->flashBag->peek('sonata_flash_success');
self::assertCount(1, $successes);
self::assertSame('[trans]flash_edit_success[/trans]', $successes[0]);
$success = $successes[0];
self::assertInstanceOf(TranslatableInterface::class, $success);
self::assertSame('[trans]flash_edit_success[/trans]', $success->trans($this->container->get('translator')));
}

public function testWorkflowApplyTransitionActionPreApply(): void
Expand Down Expand Up @@ -312,6 +315,10 @@ private function controller(): PullRequestWorkflowController

$controller->setContainer($this->container);
$controller->configureAdmin($this->request);
$controller->autowireWorkflowControllerTrait(
$this->container->get('workflow.registry'),
null,
);

return $controller;
}
Expand Down
Loading