From e6bf77530b7cbab241cd323886ee0ccd28ef40ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 22 Nov 2019 13:45:30 +0100 Subject: [PATCH] work on basic CRUDController and improve configuration --- CRUD/Controller/CRUDController.php | 310 +++++++++++++++++-- CRUD/Routing/CRUDRoutesLoader.php | 2 +- Controller/AdminCountryCRUDController.php | 23 -- DependencyInjection/ChillMainExtension.php | 23 ++ DependencyInjection/Configuration.php | 5 +- Resources/views/CRUD/_edit_content.html.twig | 28 ++ Resources/views/CRUD/_edit_title.html.twig | 1 + Resources/views/CRUD/edit.html.twig | 10 + 8 files changed, 355 insertions(+), 47 deletions(-) create mode 100644 Resources/views/CRUD/_edit_content.html.twig create mode 100644 Resources/views/CRUD/_edit_title.html.twig create mode 100644 Resources/views/CRUD/edit.html.twig diff --git a/CRUD/Controller/CRUDController.php b/CRUD/Controller/CRUDController.php index 9153833e8..5e18cc47b 100644 --- a/CRUD/Controller/CRUDController.php +++ b/CRUD/Controller/CRUDController.php @@ -20,48 +20,46 @@ namespace Chill\MainBundle\CRUD\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Doctrine\ORM\QueryBuilder; use Chill\MainBundle\Pagination\PaginatorFactory; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; /** * * */ -abstract class CRUDController extends Controller +class CRUDController extends AbstractController { /** * * @var PaginatorFactory */ protected $paginatorFactory; - - abstract protected function getEntity(): string; - - abstract protected function orderingOptions(): array; + + /** + * The crud configuration + * + * This configuration si defined by `chill_main['crud']`. + * + * @var array + */ + protected $crudConfig; + + public function setCrudConfig(array $config) + { + $this->crudConfig = $config; + } protected function getDefaultOrdering(): array { return $this->orderingOptions(); } - protected function getTemplate($action): string - { - switch($action) { - case 'index': - return '@ChillMain\CRUD\index.html.twig'; - default: - throw new LogicException("action not supported: $action"); - } - } - - protected function getTemplateParameters($action): array - { - return []; - } - protected function processTemplateParameters($action): array { $configured = $this->getTemplateParameters($action); @@ -125,4 +123,274 @@ abstract class CRUDController extends Controller ], $this->processTemplateParameters('index')) ); } + + public function edit(Request $request, $id): Response + { + return $this->formEditAction('edit', $request, $id); + } + + protected function formEditAction($action, Request $request, $id, $formClass = null): Response + { + $entity = $this->getEntity($id, $request); + + if (NULL === $entity) { + throw $this->createNotFoundException(sprintf("The %s with id %s " + . "is not found"), $this->getCrudName(), $id); + } + + $this->checkACL($action, $entity); + + $form = $this->createFormFor($action, $entity, $formClass); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + + $this->addFlash('succes', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforeRedirect($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index', [ + 'id' => $entity->getId() + ]); + + } elseif ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity, + 'crud_name' => $this->getCrudName() + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + protected function formCreateAction($action, Request $request, $formClass = null): Response + { + $entity = $this->createEntity($request); + + $this->checkACL($action, $entity); + + $form = $this->createFormFor($action, $entity, $formClass); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPrePersist($action, $entity, $form, $request); + $em->persist($entity); + $this->onPostPersist($action, $entity, $form, $request); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + $this->getPaginatorFactory(); + $this->addFlash('succes', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforRedirect($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_'.$this->get, ['id' => $entity->getId()]); + + } elseif ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity + ]; + + return $this->render( + $this->getTemplateFor($action), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + /** + * get the instance of the entity with the given id + * + * @param string $id + * @return object + */ + protected function getEntity($id, Request $request): ?object + { + return $this->getDoctrine() + ->getRepository($this->getEntityClass()) + ->find($id); + } + + protected function getEntityClass(): string + { + return $this->crudConfig['class']; + } + + protected function getCrudName(): string + { + return $this->crudConfig['name']; + } + + protected function checkACL($entity, $action) + { + $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); + } + + protected function getRoleFor($action) + { + return $this->buildDefaultRole($action); + } + + protected function buildDefaultRole($action) + { + if (empty($this->crudConfig['base_role'])) { + throw new \LogicException(sprintf("the base role is not defined. You must define " + . "on or override %s or %s methods", __METHOD__, "getRoleFor")); + } + + return \strtoupper( + $this->crudConfig['base_role']. + '_'. + $action); + } + + protected function getFormClassFor($action) + { + return $this->crudConfig[$action]['form_class'] + ?? $this->crudConfig['form_class']; + } + + protected function createFormFor($action, $entity, $formClass = null) + { + $formClass = $formClass ?? $this->getFormClassFor($action); + + $form = $this->createForm($formClass, $entity); + $form->add('submit', SubmitType::class, [ + 'label' => $action + ]); + + return $form; + } + + protected function generateFormErrorMessage($action, FormInterface $form): string + { + $msg = 'This form contains errors'; + + return $this->getTranslator()->trans($msg); + } + + protected function generateFormSuccessMessage($action, $entity): string + { + switch ($action) { + case 'edit': + $msg = "The data have been successfully updated"; + break; + case 'new': + $msg = "The date have been successfully created"; + break; + default: + $msg = "Your request has been successfully executed"; + } + + return $this->getTranslator()->trans($msg); + } + + protected function generateTemplateParameter( + $action, + $entity, + Request $request, + array $defaultTemplateParameters = [] + ) { + return $defaultTemplateParameters; + } + + protected function createEntity(Request $request): object + { + $type = $this->getEntityClass(); + return new $type; + } + + protected function getTemplateFor($action, $entity, Request $request) + { + if (!empty($this->crudConfig[$action]['template'])) { + return $this->crudConfig[$action]['template']; + } + + switch ($action) { + case 'new': + return '@ChillMain/CRUD/new.html.twig'; + case 'edit': + return '@ChillMain/CRUD/edit.html.twig'; + case 'index': + return '@ChillMain/CRUD/index.html.twig'; + default: + throw new \LogicException("the view for action $action is not " + . "defined. You should override ".__METHOD__." to add this " + . "action"); + } + } + + protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onFormValid(object $entity, FormInterface $form, Request $request) + { + } + + protected function onBeforeRedirect(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function getPaginatorFactory(): PaginatorFactory + { + return $this->get(PaginatorFactory::class); + } + + protected function getTranslator(): TranslatorInterface + { + return $this->container->get('translator'); + } + + public static function getSubscribedServices() + { + return \array_merge( + parent::getSubscribedServices(), + [ + PaginatorFactory::class => PaginatorFactory::class, + 'translator' => TranslatorInterface::class, + ] + ); + } } diff --git a/CRUD/Routing/CRUDRoutesLoader.php b/CRUD/Routing/CRUDRoutesLoader.php index 669401ce9..adc5d6420 100644 --- a/CRUD/Routing/CRUDRoutesLoader.php +++ b/CRUD/Routing/CRUDRoutesLoader.php @@ -25,7 +25,7 @@ use Symfony\Component\Routing\RouteCollection; /** - * + * Load the route for CRUD * */ class CRUDRoutesLoader diff --git a/Controller/AdminCountryCRUDController.php b/Controller/AdminCountryCRUDController.php index 00ce5c9d8..310a36c60 100644 --- a/Controller/AdminCountryCRUDController.php +++ b/Controller/AdminCountryCRUDController.php @@ -17,27 +17,4 @@ class AdminCountryCRUDController extends CRUDController { $this->paginatorFactory = $paginator; } - - protected function getEntity(): string - { - return Country::class; - } - - protected function orderingOptions(): array - { - return [ - 'countryCode' => 'ASC' - ]; - } - - protected function getTemplateParameters($action): array - { - switch ($action) { - case 'index': - return [ - 'columns' => [ 'countryCode', 'name' ], - 'title' => 'Liste des pays' - ]; - } - } } diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php index 6e7c22581..92bc8ce9c 100644 --- a/DependencyInjection/ChillMainExtension.php +++ b/DependencyInjection/ChillMainExtension.php @@ -221,5 +221,28 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, ; $container->setDefinition('chill_main_crud_route_loader', $definition); + + $alreadyExistingNames = []; + + foreach ($config as $crudEntry) { + $controller = $crudEntry['controller']; + $name = $crudEntry['name']; + + // check for existing crud names + if (\in_array($name, $alreadyExistingNames)) { + throw new LogicException(sprintf("the name %s is defined twice in CRUD", $name)); + } + + if (!$container->has($controller)) { + $controllerDefinition = new Definition($controller); + $controllerDefinition->addTag('controller.service_arguments'); + $controllerDefinition->setAutoconfigured(true); + $container->setDefinition($controller, $controllerDefinition); + } + + $container->setParameter('chill_main_crud_config_'.$name, $crudEntry); + $container->getDefinition($controller) + ->addMethodCall('setCrudConfig', ['%chill_main_crud_config_'.$name.'%']); + } } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index c901fca40..089ed42dc 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -124,10 +124,11 @@ class Configuration implements ConfigurationInterface ->scalarNode('controller')->cannotBeEmpty()->isRequired()->end() ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_role')->defaultNull()->end() + ->scalarNode('form_class')->defaultNull()->end() ->arrayNode('actions') ->scalarPrototype()->end() - //->defaultValue(['index', 'new', 'edit', 'show', 'delete']) - //->ifEmpty()->thenInvalid() + ->defaultValue(['index', 'new', 'edit', 'show', 'delete']) ->end() ->end() ->end() diff --git a/Resources/views/CRUD/_edit_content.html.twig b/Resources/views/CRUD/_edit_content.html.twig new file mode 100644 index 000000000..f8afadcba --- /dev/null +++ b/Resources/views/CRUD/_edit_content.html.twig @@ -0,0 +1,28 @@ +
+ {% block crud_content_header %} +

{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }}

+ {% endblock crud_content_header %} + + {% block crud_content_form %} + {{ form_start(form) }} + {% for f in form if f.vars.name != 'submit' %} + {{ form_row(f) }} + {% endfor %} + + {% block crud_content_form_actions %} +
+
    + {% block content_form_actions_back %} +
  • + + {{ 'Cancel'|trans }} + + {% endblock %} +
  • {{ form_widget(form.submit, { 'attr': { 'class': 'sc-button bt-edit'} } ) }}
  • +
+
+ {% endblock %} + + {{ form_end(form) }} + {% endblock %} +
diff --git a/Resources/views/CRUD/_edit_title.html.twig b/Resources/views/CRUD/_edit_title.html.twig new file mode 100644 index 000000000..c98b7f2cd --- /dev/null +++ b/Resources/views/CRUD/_edit_title.html.twig @@ -0,0 +1 @@ +{{ 'crud.title.edit_of_%crud_name%'|trans({'%crud_name%' : crud_name }) }} diff --git a/Resources/views/CRUD/edit.html.twig b/Resources/views/CRUD/edit.html.twig new file mode 100644 index 000000000..ecd68ef02 --- /dev/null +++ b/Resources/views/CRUD/edit.html.twig @@ -0,0 +1,10 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block title %} +{% include('@ChillMain/CRUD/_edit_title.html.twig') %} +{% endblock %} + +{% block content %} +{% embed '@ChillMain/CRUD/_edit_content.html.twig' %} +{% endembed %} +{% endblock %}