Search code examples
phpsymfonysonata-adminsonata

Adding a custom action to Sonata Admin


I have created a custom action that renders a small form at the bottom of my show template for orders. The form is a basic checkbox and a select field to with tow buttons. It works perfectly but the rendering is not right.

I know the way I render the show template is not 100% correct, because when it renders, the left hand side menu doesn't work anymore.

Here is my custom controller with action;

namespace Qi\Bss\FrontendBundle\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Qi\Bss\FrontendBundle\Crud\Crud;
use Qi\Bss\BaseBundle\Entity\Business\PmodOrder;
use Symfony\Component\HttpFoundation\RedirectResponse;

class PmodOrderController extends Controller
{   
    /**
     * @Route("/{id}/approve", name = "order_approve")
     * @Security("is_granted('IS_AUTHENTICATED_FULLY')")
     * @Method({"GET", "POST"})
     */
    public function approveAction(Request $request, $id){
        $em = $this->getDoctrine()->getManager();
        $order = $em->getRepository('QiBssBaseBundle:PmodOrder')->find($id);
        $approveForm = $this->createFormBuilder($order)
            ->add('requireApproval', 'checkbox', array('label' => 'Require second Approval', 'required' => false, 'mapped' => false))
            ->add('secondApprover', 'choice', array('choices' => Crud::enumStatus(), 'label' => 'User', 'required' => false))
            ->getForm();

        $approveForm->handleRequest($request);

        if ($approveForm->isSubmitted() && $approveForm->isValid()) {
            $secondApproval = $request->request->get('form');
            $approval = $approveForm->getData();

            if (isset($secondApproval['requireApproval'])) {
                $approval->setStatus(PmodOrder::STATUS_PARTLY_APPROVED);

                $em->persist($approval);
                $em->flush();

                return new RedirectResponse($this->container->get('router')->generate('admin_bss_base_business_pmodorder_show', array('id' => $order->getId())));
            } else {
                $approval->setSecondApprover(NULL);
                $approval->setStatus(PmodOrder::STATUS_APPROVED);

                $em->persist($approval);
                $em->flush();

                return new RedirectResponse($this->container->get('router')->generate('admin_bss_base_business_pmodorder_show', array('id' => $order->getId())));
            }            

        }

        return $this->render('QiBssFrontendBundle:PmodOrder:order_approve.html.twig', array(
            'order' => $order,
            'form' => $approveForm->createView(),
        ));
    }
}

What bothers me is the fact that I'm actually suppose to extend from Sonata's CRUDController. And when I do that I get an error;

An exception has been thrown during the rendering of a template ("There is no _sonata_admin defined for the controller Path\To\Controller\PmodOrderController and the current route ``")

And I am also aware that I'm actually suppose to use a return like return new RedirectResponse($this->admin->generateUrl('show'));

At this point I don't know what to do anymore. If somebody can please guide me how to extend correctly from CRUDController in my scenario, it would be really appreciated


Solution

  • Here an example, I don't know if it's the best solution but I hope that can help you :

    1- Create a custom CRUDcontroller :

    # CustomCRUDcontroller.php :
    class CustomCRUDDController extends Controller
    {   
        /**
         * Show action.
         *
         * @param int|string|null $id
         * @param Request         $request
         *
         * @return Response
         *
         * @throws NotFoundHttpException If the object does not exist
         * @throws AccessDeniedException If access is not granted
         */
        public function showAction($id = null)
        {
            $request = $this->getRequest();
            // DO YOUR LOGIC IN THE METHOD, for example :
            if(isset($request->get('yourFormParam'))){
                $this->doTheJob();
            }
    
            $id = $request->get($this->admin->getIdParameter());
    
            $object = $this->admin->getObject($id);
    
            if (!$object) {
                throw $this->createNotFoundException(sprintf('unable to find the object with id : %s', $id));
            }
    
            $this->admin->checkAccess('show', $object);
    
            $preResponse = $this->preShow($request, $object);
            if ($preResponse !== null) {
                return $preResponse;
            }
    
            $this->admin->setSubject($object);
    
            return $this->render($this->admin->getTemplate('show'), array(
                'action' => 'show',
                'object' => $object,
                'elements' => $this->admin->getShow(),
            ), null);
        }
    }
    

    2- Register it in admin.yml :

    # admin.yml :
    x.admin.x:
            class: Namespace\YourAdminClass
            arguments: [~, Namespace\Entity, Namespace:CustomCRUD]
            tags:
                - {name: sonata.admin, manager_type: orm, group: X, label: X}
    

    3- Create your own custom_show.html.twig (just a copy and paste of the original template base_show.html.twig located in the sonata-admin folder), here you can display extra elements to the view :

    # custom_show.html.twig :
    
    {% extends base_template %}
    
    {% import 'SonataAdminBundle:CRUD:base_show_macro.html.twig' as show_helper %}
    
    {% block actions %}
        {% include 'SonataAdminBundle:CRUD:action_buttons.html.twig' %}
    {% endblock %}
    
    {% block tab_menu %}
        {{ knp_menu_render(admin.sidemenu(action), {
            'currentClass' : 'active',
            'template': sonata_admin.adminPool.getTemplate('tab_menu_template')
        }, 'twig') }}
    {% endblock %}
    
    {% block show %}
        <div class="sonata-ba-view">
    
            {{ sonata_block_render_event('sonata.admin.show.top', { 'admin': admin, 'object': object }) }}
    
            {% set has_tab = (admin.showtabs|length == 1 and admin.showtabs|keys[0] != 'default') or admin.showtabs|length > 1 %}
    
            {% if has_tab %}
                <div class="nav-tabs-custom">
                    <ul class="nav nav-tabs" role="tablist">
                        {% for name, show_tab in admin.showtabs %}
                            <li{% if loop.first %} class="active"{% endif %}>
                                <a href="#tab_{{ admin.uniqid }}_{{ loop.index }}" data-toggle="tab">
                                    <i class="fa fa-exclamation-circle has-errors hide"></i>
                                    {{ admin.trans(name, {}, show_tab.translation_domain) }}
                                </a>
                            </li>
                        {% endfor %}
                    </ul>
    
                    <div class="tab-content">
                        {% for code, show_tab in admin.showtabs %}
                            <div
                                    class="tab-pane fade{% if loop.first %} in active{% endif %}"
                                    id="tab_{{ admin.uniqid }}_{{ loop.index }}"
                            >
                                <div class="box-body  container-fluid">
                                    <div class="sonata-ba-collapsed-fields">
                                        {% if show_tab.description != false %}
                                            <p>{{ show_tab.description|raw }}</p>
                                        {% endif %}
    
                                        {{ show_helper.render_groups(admin, object, elements, show_tab.groups, has_tab) }}
                                    </div>
                                </div>
                            </div>
                        {% endfor %}
                    </div>
                </div>
            {% elseif admin.showtabs is iterable %}
                {{ show_helper.render_groups(admin, object, elements, admin.showtabs.default.groups, has_tab) }}
            {% endif %}
    
        </div>
    
        {{ sonata_block_render_event('sonata.admin.show.bottom', { 'admin': admin, 'object': object }) }}
    {% endblock %}
    

    4- Then indicate to your adminController to display your custom_show template when the current route is "show" (instead of the default template base_show.html.twig) :

    # YourEntityAdminController.php :
    class YourEntityAdminController extends Controller
    {
        // allows you to chose your custom showAction template :
        public function getTemplate($name){
            if ( $name == "show" )
                return 'YourBundle:Admin:custom_show.html.twig' ;
            return parent::getTemplate($name);
        }
    
    }