Util/Datatable.php
<?php

namespace Ali\DatatableBundle\Util;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\EntityManager;
use Ali\DatatableBundle\Util\Factory\Query\QueryInterface;
use Ali\DatatableBundle\Util\Factory\Query\DoctrineBuilder;
use Ali\DatatableBundle\Util\Formatter\Renderer;
use Ali\DatatableBundle\Util\Factory\Prototype\PrototypeBuilder;

class Datatable
{

    /** @var array */
    protected $_fixed_data = NULL;

    /** @var array */
    protected $_config;

    /** @var \Symfony\Component\DependencyInjection\ContainerInterface */
    protected $_container;

    /** @var boolean */
    protected $_has_action;

    /** @var boolean */
    protected $_has_renderer_action = false;

    /** @var array */
    protected $_multiple;

    /** @var \Ali\DatatableBundle\Util\Factory\Query\QueryInterface */
    protected $_queryBuilder;

    /** @var \Symfony\Component\HttpFoundation\Request */
    protected $_request;

    /** @var closure */
    protected $_renderer = NULL;

    /** @var array */
    protected $_renderers = NULL;

    /** @var Renderer */
    protected $_renderer_obj = null;

    /** @var boolean */
    protected $_search;

    /** @var array */
    protected $_search_fields = array();

    /** @var array */
    protected static $_instances = array();

    /** @var Datatable */
    protected static $_current_instance = NULL;

    /**
     * class constructor 
     * 
     * @param ContainerInterface $container 
     */
    public function __construct(ContainerInterface $container)
    {
        $this->_container        = $container;
        $this->_config           = $this->_container->getParameter('ali_datatable');
        $this->_request          = Request::createFromGlobals();
        self::$_current_instance = $this;
        $this->_applyDefaults();
    }

    /**
     * apply default value from datatable config
     * 
     * @return void
     */
    protected function _applyDefaults()
    {
        if (isset($this->_config['all']))
        {
            $this->_has_action = $this->_config['all']['action'];
            $this->_search     = $this->_config['all']['search'];
        }
    }

    /**
     * add join
     * 
     * @example:
     *      ->setJoin( 
     *              'r.event', 
     *              'e', 
     *              \Doctrine\ORM\Query\Expr\Join::INNER_JOIN, 
     *              'e.name like %test%') 
     * 
     * @param string $join_field
     * @param string $alias
     * @param string $type
     * @param string $cond
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function addJoin($join_field, $alias, $type = Join::INNER_JOIN, $cond = '')
    {
        $this->_queryBuilder->addJoin($join_field, $alias, $type, $cond);
        return $this;
    }

    /**
     * execute
     * 
     * @return JsonResponse
     */
    public function execute()
    {
        $request       = $this->_request;
        $iTotalRecords = $this->_queryBuilder->getTotalRecords();
        list($data, $objects) = $this->_queryBuilder->getData();
        $id_index      = array_search('_identifier_', array_keys($this->getFields()));
        $ids           = array();
        array_walk($data, function($val, $key) use ($id_index, &$ids) {
            $ids[$key] = $val[$id_index];
        });
        if (!is_null($this->_fixed_data))
        {
            $this->_fixed_data = array_reverse($this->_fixed_data);
            foreach ($this->_fixed_data as $item)
            {
                array_unshift($data, $item);
            }
        }
        if (!is_null($this->_renderer))
        {
            array_walk($data, $this->_renderer);
        }
        if (!is_null($this->_renderer_obj))
        {
            $this->_renderer_obj->applyTo($data, $objects);
        }
        if (!empty($this->_multiple))
        {
            array_walk($data, function($val, $key) use(&$data, $ids) {
                array_unshift($val, "<input type='checkbox' name='dataTables[actions][]' value='{$ids[$key]}' />");
                $data[$key] = $val;
            });
        }
        $output = array(
            "sEcho"                => intval($request->get('sEcho')),
            "iTotalRecords"        => $iTotalRecords,
            "iTotalDisplayRecords" => $iTotalRecords,
            "aaData"               => $data
        );
        return new JsonResponse($output);
    }

    /**
     * get datatable instance by id
     *  return current instance if null
     * 
     * @param string $id
     * 
     * @return \Ali\DatatableBundle\Util\Datatable .
     */
    public static function getInstance($id)
    {
        $instance = NULL;
        if (array_key_exists($id, self::$_instances))
        {
            $instance = self::$_instances[$id];
        }
        else
        {
            $instance = self::$_current_instance;
        }

        if (is_null($instance))
        {
            throw new \Exception('No instance found for datatable, you should set a datatable id in your
            action with "setDatatableId" using the id from your view ');
        }

        return $instance;
    }

    /**
     * get entity name
     * 
     * @return string
     */
    public function getEntityName()
    {
        return $this->_queryBuilder->getEntityName();
    }

    /**
     * get entity alias
     * 
     * @return string
     */
    public function getEntityAlias()
    {
        return $this->_queryBuilder->getEntityAlias();
    }

    /**
     * get fields
     * 
     * @return array
     */
    public function getFields()
    {
        return $this->_queryBuilder->getFields();
    }

    /**
     * get has_action
     * 
     * @return boolean
     */
    public function getHasAction()
    {
        return $this->_has_action;
    }

    /**
     * retrun true if the actions column is overridden by twig renderer
     * 
     * @return boolean
     */
    public function getHasRendererAction()
    {
        return $this->_has_renderer_action;
    }

    /**
     * get order field
     *
     * @return string
     */
    public function getOrderField()
    {
        return $this->_queryBuilder->getOrderField();
    }

    /**
     * get order type
     * 
     * @return string
     */
    public function getOrderType()
    {
        return $this->_queryBuilder->getOrderType();
    }

    /**
     * create raw prototype
     *
     * @param string $type
     * 
     * @return PrototypeBuilder 
     */
    public function getPrototype($type)
    {
        return new PrototypeBuilder($this->_container, $type);
    }

    /**
     * get query builder
     * 
     * @return QueryInterface
     */
    public function getQueryBuilder()
    {
        return $this->_queryBuilder;
    }

    /**
     * get search
     * 
     * @return boolean
     */
    public function getSearch()
    {
        return $this->_search;
    }

    /**
     * set entity
     * 
     * @param string $entity_name
     * @param string $entity_alias
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setEntity($entity_name, $entity_alias)
    {
        $this->_queryBuilder->setEntity($entity_name, $entity_alias);
        return $this;
    }

    /**
     * set entity manager
     * 
     * @param EntityManager $em
     * 
     * @return \Ali\DatatableBundle\Util\Datatable
     */
    public function setEntityManager(EntityManager $em)
    {
        $this->_queryBuilder = new DoctrineBuilder($this->_container, $em);
        return $this;
    }

    /**
     * set fields
     * 
     * @param array $fields
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setFields(array $fields)
    {
        $this->_queryBuilder->setFields($fields);
        return $this;
    }

    /**
     * Add a field
     * 
     * @param string $key
     * @param string $value
     * @return \Ali\DatatableBundle\Util\Datatable
     */
    public function addField($key, $value)
    {
        $fields       = $this->_queryBuilder->getFields() ? $this->_queryBuilder->getFields() : array();
        $fields[$key] = $value;
        $this->setFields($fields);
        return $this;
    }

    /**
     * Add an array of fields
     * 
     * @param array $fields
     * @return \Ali\DatatableBundle\Util\Datatable
     */
    public function addFields(array $fields)
    {
        $oldFields = $this->_queryBuilder->getFields() ? $this->_queryBuilder->getFields() : array();
        $newFields = array_merge($oldFields, $fields);
        $this->setFields($newFields);
        return $this;
    }

    /**
     * set has action
     * 
     * @param boolean $has_action
     * 
     * @return \Ali\DatatableBundle\Util\Datatable
     */
    public function setHasAction($has_action)
    {
        $this->_has_action = $has_action;
        return $this;
    }

    /**
     * set order
     * 
     * @param string $order_field
     * @param string $order_type
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setOrder($order_field, $order_type)
    {
        $this->_queryBuilder->setOrder($order_field, $order_type);
        return $this;
    }

    /**
     * set fixed data
     * 
     * @param null|array $data
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setFixedData($data)
    {
        $this->_fixed_data = $data;
        return $this;
    }

    /**
     * set query builder
     * 
     * @param QueryInterface $queryBuilder 
     */
    public function setQueryBuilder(QueryInterface $queryBuilder)
    {
        $this->_queryBuilder = $queryBuilder;
    }

    /**
     * set a php closure as renderer
     * 
     * @example:
     * 
     *  $controller_instance = $this;
     *  $datatable = $this->get('datatable')
     *       ->setEntity("AliBaseBundle:Entity", "e")
     *       ->setFields($fields)
     *       ->setOrder("e.created", "desc")
     *       ->setRenderer(
     *               function(&$data) use ($controller_instance)
     *               {
     *                   foreach ($data as $key => $value)
     *                   {
     *                       if ($key == 1)
     *                       {
     *                           $data[$key] = $controller_instance
     *                               ->get('templating')
     *                               ->render('AliBaseBundle:Entity:_decorator.html.twig',
     *                                       array(
     *                                           'data' => $value
     *                                       )
     *                               );
     *                       }
     *                   }
     *               }
     *         )
     *       ->setHasAction(true);
     * 
     * @param \Closure $renderer
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setRenderer(\Closure $renderer)
    {
        $this->_renderer = $renderer;
        return $this;
    }

    /**
     * set renderers as twig views
     * 
     * @example: To override the actions column
     * 
     *      ->setFields(
     *          array(
     *             "field label 1" => 'x.field1',
     *             "field label 2" => 'x.field2',
     *             "_identifier_"  => 'x.id'
     *          )
     *      )
     *      ->setRenderers(
     *          array(
     *             2 => array(
     *               'view' => 'AliDatatableBundle:Renderers:_actions.html.twig',
     *               'params' => array(
     *                  'edit_route'    => 'matche_edit',
     *                  'delete_route'  => 'matche_delete',
     *                  'delete_form_prototype'   => $datatable->getPrototype('delete_form')
     *               ),
     *             ),
     *          )
     *       )
     * 
     * @param array $renderers
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setRenderers(array $renderers)
    {
        $this->_renderers = $renderers;
        if (!empty($this->_renderers))
        {
            $this->_renderer_obj = new Renderer($this->_container, $this->_renderers, $this->getFields());
        }
        $actions_index = array_search('_identifier_', array_keys($this->getFields()));
        if ($actions_index != FALSE && isset($renderers[$actions_index]))
        {
            $this->_has_renderer_action = true;
        }
        return $this;
    }

    /**
     * set query where
     * 
     * @param string $where
     * @param array  $params
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setWhere($where, array $params = array())
    {
        $this->_queryBuilder->setWhere($where, $params);
        return $this;
    }

    /**
     * set query group
     * 
     * @param string $groupbywhere
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setGroupBy($groupby)
    {
        $this->_queryBuilder->setGroupBy($groupby);
        return $this;
    }

    /**
     * set search
     * 
     * @param bool $search
     * 
     * @return \Ali\DatatableBundle\Util\Datatable
     */
    public function setSearch($search)
    {
        $this->_search = $search;
        $this->_queryBuilder->setSearch($search);
        return $this;
    }

    /**
     * set datatable identifier
     * 
     * @param string $id
     * 
     * @return \Ali\DatatableBundle\Util\Datatable 
     */
    public function setDatatableId($id)
    {
        if (!array_key_exists($id, self::$_instances))
        {
            self::$_instances[$id] = $this;
        }
        else
        {
            throw new \Exception('Identifer already exists');
        }
        return $this;
    }

    /**
     * get multiple
     * 
     * @return array
     */
    public function getMultiple()
    {
        return $this->_multiple;
    }

    /**
     * set multiple
     * 
     * @example
     * 
     *  ->setMultiple('delete' => array ('title' => "Delete", 'route' => 'route_to_delete' ));
     * 
     * @param array $multiple
     * 
     * @return \Ali\DatatableBundle\Util\Datatable
     */
    public function setMultiple(array $multiple)
    {
        $this->_multiple = $multiple;
        return $this;
    }

    /**
     * get global configuration ( read it from config.yml under ali_datatable)
     * 
     * @return array
     */
    public function getConfiguration()
    {
        return $this->_config;
    }

    /**
     * get search field
     * 
     * @return array
     */
    public function getSearchFields()
    {
        return $this->_search_fields;
    }

    /**
     * set search fields
     * 
     * @example 
     * 
     *      ->setSearchFields(array(0,2,5))
     * 
     * @param array $search_fields
     * 
     * @return \Ali\DatatableBundle\Util\Datatable
     */
    public function setSearchFields(array $search_fields)
    {
        $this->_search_fields = $search_fields;
        return $this;
    }

}
