.. 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: 

Auto-loading the routes
***********************

Ensure that those lines are present in your file `app/config/routing.yml`:


.. code-block:: yaml

   chill_cruds:
       resource: 'chill_main_crud_route_loader:load'
       type: service



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 <https://symfony.com/doc/current/bundles/prepend_extension.html>`_:

.. 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 %}
               <th>{{ 'Ordering'|trans }}</th>
               <th>{{ 'Label'|trans }}</th>
               <th>{{ 'Active'|trans }}</th>
               <th>&nbsp;</th>
           {% endblock %}

           {% block table_entities_tbody %}
           {# we customize the content of the table #}
           {% for entity in entities %}
               <tr>
                   <td>{{ entity.ordering }}</td>
                   <td>{{ entity|chill_entity_render_box }}</td>
                   <td>{{ entity.active }}</td>
                   <td>
                       <ul class="record_actions">
                           <li>
                               <a href="{{ chill_path_add_return_path('chill_crud_closing_motive_edit', { 'id': entity.id }) }}" class="sc-button bt-edit"></a>
                           </li>
                           <li>
                               <a href="{{ chill_path_add_return_path('chill_crud_closing_motive_new', { 'parent_id': entity.id } ) }}" class="sc-button bt-new">{{ 'closing_motive.new child'|trans }}</a>
                           </li>
                       </ul>
                   </td>
               </tr>
           {% 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.

