diff --git a/source/development/crud.rst b/source/development/crud.rst new file mode 100644 index 000000000..22be6e279 --- /dev/null +++ b/source/development/crud.rst @@ -0,0 +1,509 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _crud: + +CRUD +#### + +Chill provide an API to create a basic CRUD. + +One can follow those steps to create a CRUD for one entity: + +1. create your model and your form ; +2. configure the crud ; +3. customize the templates if required ; +4. customize some steps of the controller if required ; + + +An example with the ``ClosingMotive`` (PersonBundle) in the admin part of Chill: + + +Create your model +***************** + +Create your model on the usual way (in this example, ORM informations are stored in yaml file): + +.. code-block:: php + + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; + + use Doctrine\Common\Collections\Collection; + + /** + * ClosingMotive give an explanation why we closed the Accompanying period + */ + class ClosingMotive + { + /** + * @var integer + */ + private $id; + + /** + * @var array + */ + private $name; + + /** + * + * @var boolean + */ + private $active = true; + + /** + * + * @var self + */ + private $parent = null; + + /** + * child Accompanying periods + * + * @var Collection + */ + private $children; + + /** + * + * @var float + */ + private $ordering = 0.0; + + + // getters and setters come here + + } + +The form: + +.. code-block:: php + + namespace Chill\PersonBundle\Form; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolver; + use Chill\PersonBundle\Form\Type\ClosingMotivePickerType; + use Chill\MainBundle\Form\Type\TranslatableStringFormType; + use Symfony\Component\Form\Extension\Core\Type\CheckboxType; + use Symfony\Component\Form\Extension\Core\Type\NumberType; + + /** + * + * + */ + class ClosingMotiveType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('name', TranslatableStringFormType::class, [ + 'label' => 'Nom' + ]) + ->add('active', CheckboxType::class, [ + 'label' => 'Actif ?', + 'required' => false + ]) + ->add('ordering', NumberType::class, [ + 'label' => 'Ordre d\'apparition', + 'required' => true, + 'scale' => 5 + ]) + ->add('parent', ClosingMotivePickerType::class, [ + 'label' => 'Parent', + 'required' => false, + 'placeholder' => 'closing_motive.any parent', + 'multiple' => false, + 'only_leaf' => false + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', \Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive::class) + ; + } + } + +Configure the crud +****************** + +The crud is configured using the key ``crud`` under ``chill_main`` + +.. code-block:: yaml + + chill_main: + cruds: + - + # the class which is concerned by the CRUD + class: '\Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive::class' + # give a name for the crud. This will be used internally + name: closing_motive + # add a base path for the + base_path: /admin/closing-motive + # this is the form class + form_class: 'Chill\PersonBundle\Form\ClosingMotiveType::class' + # you can override the controller to configure some parts + # if you do not configure anything here, the default CRUDController will be used + controller: 'Chill\PersonBundle\Controller\AdminClosingMotiveController::class' + # this is a list of action you can configure + # by default, the actions `index`, `view`, `new` and `edit` are automatically create + # you can add more actions or configure some details about them + actions: + index: + # the default template for index is very poor, + # you will need to override it + template: '@ChillPerson/ClosingMotive/index.html.twig' + # the role required for this role + role: ROLE_ADMIN + new: + role: ROLE_ADMIN + # by default, the template will only show the form + # you can override it + template: '@ChillPerson/ClosingMotive/new.html.twig' + edit: + role: ROLE_ADMIN + template: '@ChillPerson/ClosingMotive/edit.html.twig' + +To leave the bundle auto-configure the ``chill_main`` bundle, you can `prepend the configuration of the ChillMain Bundle `_: + +.. code-block:: php + + namespace Chill\PersonBundle\DependencyInjection; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + + class ChillPersonExtension extends Extension implements PrependExtensionInterface + { + /** + * {@inheritDoc} + */ + public function load(array $configs, ContainerBuilder $container) + { + // skipped here + } + + + public function prepend(ContainerBuilder $container) + { + $this->prependCruds($container); + } + + protected function prependCruds(ContainerBuilder $container) + { + $container->prependExtensionConfig('chill_main', [ + 'cruds' => [ + [ + 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive::class, + 'name' => 'closing_motive', + 'base_path' => '/admin/closing-motive', + 'form_class' => \Chill\PersonBundle\Form\ClosingMotiveType::class, + 'controller' => \Chill\PersonBundle\Controller\AdminClosingMotiveController::class, + 'actions' => [ + 'index' => [ + 'template' => '@ChillPerson/ClosingMotive/index.html.twig', + 'role' => 'ROLE_ADMIN' + ], + 'new' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillPerson/ClosingMotive/new.html.twig', + ], + 'edit' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillPerson/ClosingMotive/edit.html.twig', + ] + ] + ] + ] + ]); + } + } + + + +Customize templates +******************* + +The current template are quite basic. You can override and extends them. + +For a better inclusion, you can embed them instead of extending them. + +For index. Note that we extend here the `admin` layout, not the default one: + +.. code-block:: html+jinja + + {% extends '@ChillMain/Admin/layout.html.twig' %} + + {% block admin_content %} + {% embed '@ChillMain/CRUD/_index.html.twig' %} + {# we customize the table headers #} + {% block table_entities_thead_tr %} + {{ 'Ordering'|trans }} + {{ 'Label'|trans }} + {{ 'Active'|trans }} +   + {% endblock %} + + {% block table_entities_tbody %} + {# we customize the content of the table #} + {% for entity in entities %} + + {{ entity.ordering }} + {{ entity|chill_entity_render_box }} + {{ entity.active }} + + + + + {% endfor %} + {% endblock %} + {% endembed %} + {% endblock %} + +For edit template: + +.. code-block:: html+jinja + + {% extends '@ChillMain/Admin/layout.html.twig' %} + + {% block title %} + {% include('@ChillMain/CRUD/_edit_title.html.twig') %} + {% endblock %} + + {% block admin_content %} + {% as we are in the admin layout, we override the admin content with the CRUD content %} + {% embed '@ChillMain/CRUD/_edit_content.html.twig' %} + {# we do not have "view" page. We empty the corresponding block #} + {% block content_form_actions_view %}{% endblock %} + {% endembed %} + {% endblock %} + +For new template: + +.. code-block:: html+jinja + + {% extends '@ChillMain/Admin/layout.html.twig' %} + + {% block title %} + {% include('@ChillMain/CRUD/_new_title.html.twig') %} + {% endblock %} + + {% block admin_content %} + {% embed '@ChillMain/CRUD/_new_content.html.twig' %} + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} + {% endblock %} + +Customize some steps in the controller +************************************** + +Some steps may be customized by overriding the default controller and some methods. Here, we will override the way the entity is created, and the ordering of the "index" page: + +* we will associate a parent ClosingMotive to the element if a parameter `parent_id` is found ; +* we will order the ClosingMotive by the ``ordering`` property + +.. code-block:: php + + namespace Chill\PersonBundle\Controller; + + use Chill\MainBundle\CRUD\Controller\CRUDController; + use Symfony\Component\HttpFoundation\Request; + use Chill\MainBundle\Pagination\PaginatorInterface; + + /** + * Controller for closing motives + * + */ + class AdminClosingMotiveController extends CRUDController + { + protected function createEntity($action, Request $request): object + { + // we first create an entity "the usual way" + $entity = parent::createEntity($action, $request); + + if ($request->query->has('parent_id')) { + // if we find the parent_id parameter, we add the corresponding + // parent to the newly created entity + $parentId = $request->query->getInt('parent_id'); + + $parent = $this->getDoctrine()->getManager() + ->getRepository($this->getEntityClass()) + ->find($parentId); + + if (NULL === $parent) { + throw $this->createNotFoundException('parent id not found'); + } + + $entity->setParent($parent); + } + + return $entity; + } + + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) + { + // by default, the query is an instance of QueryBuilder + /** @var \Doctrine\ORM\QueryBuilder $query */ + return $query->orderBy('e.ordering', 'ASC'); + } + } + +How-to and questions +******************** + +Which role is required for each action ? +======================================== + +By default, each action will use: + +1. the role defined under the action key ; +2. the base role as upper, with the action name appended: + + Example: if the base role is ``CHILL_BUNDLE_ENTITY``, the role will become: + + * ``CHILL_BUNDLE_ENTITY_VIEW`` for the ``view`` action ; + * ``CHILL_BUNDLE_ENTITY_INDEX`` for the ``index`` action. + +The entity will be passed to the role: + +* for the ``view`` and ``edit`` action: the entity fetched from database +* for the ``new`` action: the entity which is created (you can override default values using +* for index action (or if you re-use the ``indexAction`` method: ``null`` + +How to add some route and actions ? +=================================== + +Add them under the action key: + +.. code-block:: yaml + + chill_main: + cruds: + - + # snipped + actions: + myaction: ~ + +The method `myactionAction` will be called by the parameter. + +Inside this action, you can eventually call another internal method: + +* ``indexAction`` for a list of items ; +* ``viewAction`` for a view +* ``editFormAction`` for an edition +* ``createFormAction`` for a creation + +Example: + +.. code-block:: php + + namespace CSConnectes\SPBundle\Controller; + + use Chill\PersonBundle\CRUD\Controller\OneToOneEntityPersonCRUDController; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Chill\PersonBundle\Security\Authorization\PersonVoter; + use Symfony\Component\Form\FormInterface; + use Symfony\Component\HttpFoundation\Request; + use CSConnectes\SPBundle\Form\CSPersonPersonalSituationType; + use CSConnectes\SPBundle\Form\CSPersonDispositifsType; + use Symfony\Component\Security\Core\Role\Role; + use Symfony\Component\HttpFoundation\Response; + + class CSPersonController extends OneToOneEntityPersonCRUDController + { + public function personalSituationEdit(Request $request, $id) + { + return $this->formEditAction( + 'ps_situation_edit', + $request, + $id, + CSPersonPersonalSituationType::class + ); + } + + public function personalSituationView(Request $request, $id): Response + { + return $this->viewAction('ps_situation_view', $request, $id); + } + + } + +How to create a CRUD for entities associated to persons +======================================================= + +The bundle person provide some controller and template you can override, instead of the ones present in the mainbundle: + +* :code:`Chill\PersonBundle\CRUD\Controller\EntityPersonCRUDController` for entities linked with a one-to-may association to :code:`Person` class ; +* :code:`Chill\PersonBundle\CRUD\Controller\OneToOneEntityPersonCRUDController` for entities linked with a one-to-one association to :code:`Person` class. + +There are also template defined under ``@ChillPerson/CRUD/`` namespace. + +Those controller assume that: + +* the entity provide the method :code:`getPerson` and :code:`setPerson` ; +* the `index`'s id path will be the id of the person, and the ids in `view` and `edit` path will be the id of the entity ; + +This bundle also use by default the templates inside ``@ChillPerson/CRUD/``. + + +Reference +********* + +Configuration reference +======================= + + +.. code-block:: txt + + chill_main: + cruds: + + # Prototype + - + class: ~ # Required + controller: Chill\MainBundle\CRUD\Controller\CRUDController + name: ~ # Required + base_path: ~ # Required + base_role: null + form_class: null + actions: + + # Prototype + name: + + # the method name to call in the route. Will be set to the action name if left empty. + controller_action: null # Example: 'action' + + # the path that will be **appended** after the base path. Do not forget to add arguments for the method. Will be set to the action name, including an `{id}` parameter if left empty. + path: null # Example: /{id}/my-action + + # the requirements for the route. Will be set to `[ 'id' => '\d+' ]` if left empty. + requirements: [] + + # the role that will be required for this action. Override option `base_role` + role: null + + # the template to render the view + template: null + +Twig default block +================== + +This part should be documented. + diff --git a/source/development/index.rst b/source/development/index.rst index 245824c4e..b5d39e166 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -16,6 +16,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Install Chill for development Instructions to create a new bundle + CRUD (Create - Update - Delete) for one entity Routing Menus Forms