vendor/omines/datatables-bundle/src/DataTable.php line 360

Open in your IDE?
  1. <?php
  2. /*
  3.  * Symfony DataTables Bundle
  4.  * (c) Omines Internetbureau B.V. - https://omines.nl/
  5.  *
  6.  * For the full copyright and license information, please view the LICENSE
  7.  * file that was distributed with this source code.
  8.  */
  9. declare(strict_types=1);
  10. namespace Omines\DataTablesBundle;
  11. use Omines\DataTablesBundle\Adapter\AdapterInterface;
  12. use Omines\DataTablesBundle\Adapter\ResultSetInterface;
  13. use Omines\DataTablesBundle\Column\AbstractColumn;
  14. use Omines\DataTablesBundle\DependencyInjection\Instantiator;
  15. use Omines\DataTablesBundle\Exception\InvalidArgumentException;
  16. use Omines\DataTablesBundle\Exception\InvalidConfigurationException;
  17. use Omines\DataTablesBundle\Exception\InvalidStateException;
  18. use Omines\DataTablesBundle\Exporter\DataTableExporterManager;
  19. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  20. use Symfony\Component\HttpFoundation\JsonResponse;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Component\HttpFoundation\Response;
  23. use Symfony\Component\OptionsResolver\OptionsResolver;
  24. /**
  25.  * DataTable.
  26.  *
  27.  * @author Robbert Beesems <robbert.beesems@omines.com>
  28.  */
  29. class DataTable
  30. {
  31.     public const DEFAULT_OPTIONS = [
  32.         'jQueryUI' => false,
  33.         'pagingType' => 'full_numbers',
  34.         'lengthMenu' => [[102550, -1], [102550'All']],
  35.         'pageLength' => 10,
  36.         'displayStart' => 0,
  37.         'serverSide' => true,
  38.         'processing' => true,
  39.         'paging' => true,
  40.         'lengthChange' => true,
  41.         'ordering' => true,
  42.         'searching' => false,
  43.         'search' => null,
  44.         'autoWidth' => false,
  45.         'order' => [],
  46.         'searchDelay' => 400,
  47.         'dom' => 'lftrip',
  48.         'orderCellsTop' => true,
  49.         'stateSave' => false,
  50.         'fixedHeader' => false,
  51.     ];
  52.     public const DEFAULT_TEMPLATE '@DataTables/datatable_html.html.twig';
  53.     public const SORT_ASCENDING 'asc';
  54.     public const SORT_DESCENDING 'desc';
  55.     /** @var AdapterInterface */
  56.     protected $adapter;
  57.     /** @var AbstractColumn[] */
  58.     protected $columns = [];
  59.     /** @var array<string, AbstractColumn> */
  60.     protected $columnsByName = [];
  61.     /** @var EventDispatcherInterface */
  62.     protected $eventDispatcher;
  63.     /** @var DataTableExporterManager */
  64.     protected $exporterManager;
  65.     /** @var string */
  66.     protected $method Request::METHOD_POST;
  67.     /** @var array */
  68.     protected $options;
  69.     /** @var bool */
  70.     protected $languageFromCDN true;
  71.     /** @var string */
  72.     protected $name 'dt';
  73.     /** @var string */
  74.     protected $persistState 'fragment';
  75.     /** @var string */
  76.     protected $template self::DEFAULT_TEMPLATE;
  77.     /** @var array */
  78.     protected $templateParams = [];
  79.     /** @var callable */
  80.     protected $transformer;
  81.     /** @var string */
  82.     protected $translationDomain 'messages';
  83.     /** @var DataTableRendererInterface */
  84.     private $renderer;
  85.     /** @var DataTableState */
  86.     private $state;
  87.     /** @var Instantiator */
  88.     private $instantiator;
  89.     /**
  90.      * DataTable constructor.
  91.      */
  92.     public function __construct(EventDispatcherInterface $eventDispatcherDataTableExporterManager $exporterManager, array $options = [], Instantiator $instantiator null)
  93.     {
  94.         $this->eventDispatcher $eventDispatcher;
  95.         $this->exporterManager $exporterManager;
  96.         $this->instantiator $instantiator ?? new Instantiator();
  97.         $resolver = new OptionsResolver();
  98.         $this->configureOptions($resolver);
  99.         $this->options $resolver->resolve($options);
  100.     }
  101.     /**
  102.      * @return $this
  103.      */
  104.     public function add(string $namestring $type, array $options = [])
  105.     {
  106.         // Ensure name is unique
  107.         if (isset($this->columnsByName[$name])) {
  108.             throw new InvalidArgumentException(sprintf("There already is a column with name '%s'"$name));
  109.         }
  110.         $column $this->instantiator->getColumn($type);
  111.         $column->initialize($namecount($this->columns), $options$this);
  112.         $this->columns[] = $column;
  113.         $this->columnsByName[$name] = $column;
  114.         return $this;
  115.     }
  116.     /**
  117.      * Adds an event listener to an event on this DataTable.
  118.      *
  119.      * @param string   $eventName The name of the event to listen to
  120.      * @param callable $listener  The listener to execute
  121.      * @param int      $priority  The priority of the listener. Listeners
  122.      *                            with a higher priority are called before
  123.      *                            listeners with a lower priority.
  124.      *
  125.      * @return $this
  126.      */
  127.     public function addEventListener(string $eventName, callable $listenerint $priority 0): self
  128.     {
  129.         $this->eventDispatcher->addListener($eventName$listener$priority);
  130.         return $this;
  131.     }
  132.     /**
  133.      * @param int|string|AbstractColumn $column
  134.      * @return $this
  135.      */
  136.     public function addOrderBy($columnstring $direction self::SORT_ASCENDING)
  137.     {
  138.         if (!$column instanceof AbstractColumn) {
  139.             $column is_int($column) ? $this->getColumn($column) : $this->getColumnByName((string) $column);
  140.         }
  141.         $this->options['order'][] = [$column->getIndex(), $direction];
  142.         return $this;
  143.     }
  144.     public function createAdapter(string $adapter, array $options = []): static
  145.     {
  146.         return $this->setAdapter($this->instantiator->getAdapter($adapter), $options);
  147.     }
  148.     public function getAdapter(): AdapterInterface
  149.     {
  150.         return $this->adapter;
  151.     }
  152.     public function getColumn(int $index): AbstractColumn
  153.     {
  154.         if ($index || $index >= count($this->columns)) {
  155.             throw new InvalidArgumentException(sprintf('There is no column with index %d'$index));
  156.         }
  157.         return $this->columns[$index];
  158.     }
  159.     public function getColumnByName(string $name): AbstractColumn
  160.     {
  161.         if (!isset($this->columnsByName[$name])) {
  162.             throw new InvalidArgumentException(sprintf("There is no column named '%s'"$name));
  163.         }
  164.         return $this->columnsByName[$name];
  165.     }
  166.     /**
  167.      * @return AbstractColumn[]
  168.      */
  169.     public function getColumns(): array
  170.     {
  171.         return $this->columns;
  172.     }
  173.     public function getEventDispatcher(): EventDispatcherInterface
  174.     {
  175.         return $this->eventDispatcher;
  176.     }
  177.     public function isLanguageFromCDN(): bool
  178.     {
  179.         return $this->languageFromCDN;
  180.     }
  181.     public function getMethod(): string
  182.     {
  183.         return $this->method;
  184.     }
  185.     public function getName(): string
  186.     {
  187.         return $this->name;
  188.     }
  189.     public function getPersistState(): string
  190.     {
  191.         return $this->persistState;
  192.     }
  193.     /**
  194.      * @return DataTableState|null
  195.      */
  196.     public function getState()
  197.     {
  198.         return $this->state;
  199.     }
  200.     public function getTranslationDomain(): string
  201.     {
  202.         return $this->translationDomain;
  203.     }
  204.     public function isCallback(): bool
  205.     {
  206.         return (null === $this->state) ? false $this->state->isCallback();
  207.     }
  208.     /**
  209.      * @return $this
  210.      */
  211.     public function handleRequest(Request $request): self
  212.     {
  213.         switch ($this->getMethod()) {
  214.             case Request::METHOD_GET:
  215.                 $parameters $request->query;
  216.                 break;
  217.             case Request::METHOD_POST:
  218.                 $parameters $request->request;
  219.                 break;
  220.             default:
  221.                 throw new InvalidConfigurationException(sprintf("Unknown request method '%s'"$this->getMethod()));
  222.         }
  223.         if ($this->getName() === $parameters->get('_dt')) {
  224.             if (null === $this->state) {
  225.                 $this->state DataTableState::fromDefaults($this);
  226.             }
  227.             $this->state->applyParameters($parameters);
  228.         }
  229.         return $this;
  230.     }
  231.     public function getResponse(): Response
  232.     {
  233.         if (null === $this->state) {
  234.             throw new InvalidStateException('The DataTable does not know its state yet, did you call handleRequest?');
  235.         }
  236.         // Server side export
  237.         if (null !== $this->state->getExporterName()) {
  238.             return $this->exporterManager
  239.                 ->setDataTable($this)
  240.                 ->setExporterName($this->state->getExporterName())
  241.                 ->getResponse();
  242.         }
  243.         $resultSet $this->getResultSet();
  244.         $response = [
  245.             'draw' => $this->state->getDraw(),
  246.             'recordsTotal' => $resultSet->getTotalRecords(),
  247.             'recordsFiltered' => $resultSet->getTotalDisplayRecords(),
  248.             'data' => iterator_to_array($resultSet->getData()),
  249.         ];
  250.         if ($this->state->isInitial()) {
  251.             $response['options'] = $this->getInitialResponse();
  252.             $response['template'] = $this->renderer->renderDataTable($this$this->template$this->templateParams);
  253.         }
  254.         return new JsonResponse($response);
  255.     }
  256.     protected function getInitialResponse(): array
  257.     {
  258.         return array_merge($this->getOptions(), [
  259.             'columns' => array_map(
  260.                 function (AbstractColumn $column) {
  261.                     return [
  262.                         'data' => $column->getName(),
  263.                         'orderable' => $column->isOrderable(),
  264.                         'searchable' => $column->isSearchable(),
  265.                         'visible' => $column->isVisible(),
  266.                         'className' => $column->getClassName(),
  267.                     ];
  268.                 }, $this->getColumns()
  269.             ),
  270.         ]);
  271.     }
  272.     protected function getResultSet(): ResultSetInterface
  273.     {
  274.         if (null === $this->adapter) {
  275.             throw new InvalidStateException('No adapter was configured yet to retrieve data with. Call "createAdapter" or "setAdapter" before attempting to return data');
  276.         }
  277.         return $this->adapter->getData($this->state);
  278.     }
  279.     /**
  280.      * @return callable|null
  281.      */
  282.     public function getTransformer()
  283.     {
  284.         return $this->transformer;
  285.     }
  286.     public function getOptions(): array
  287.     {
  288.         return $this->options;
  289.     }
  290.     /**
  291.      * @param $name
  292.      * @return mixed|null
  293.      */
  294.     public function getOption($name): mixed
  295.     {
  296.         return $this->options[$name] ?? null;
  297.     }
  298.     public function setAdapter(AdapterInterface $adapter, array $options null): static
  299.     {
  300.         if (null !== $options) {
  301.             $adapter->configure($options);
  302.         }
  303.         $this->adapter $adapter;
  304.         return $this;
  305.     }
  306.     /**
  307.      * @return $this
  308.      */
  309.     public function setLanguageFromCDN(bool $languageFromCDN): self
  310.     {
  311.         $this->languageFromCDN $languageFromCDN;
  312.         return $this;
  313.     }
  314.     /**
  315.      * @return $this
  316.      */
  317.     public function setMethod(string $method): self
  318.     {
  319.         $this->method $method;
  320.         return $this;
  321.     }
  322.     /**
  323.      * @return $this
  324.      */
  325.     public function setPersistState(string $persistState): self
  326.     {
  327.         $this->persistState $persistState;
  328.         return $this;
  329.     }
  330.     /**
  331.      * @return $this
  332.      */
  333.     public function setRenderer(DataTableRendererInterface $renderer): self
  334.     {
  335.         $this->renderer $renderer;
  336.         return $this;
  337.     }
  338.     /**
  339.      * @return $this
  340.      */
  341.     public function setName(string $name): self
  342.     {
  343.         if (empty($name)) {
  344.             throw new InvalidArgumentException('DataTable name cannot be empty');
  345.         }
  346.         $this->name $name;
  347.         return $this;
  348.     }
  349.     /**
  350.      * @return $this
  351.      */
  352.     public function setTemplate(string $template, array $parameters = []): self
  353.     {
  354.         $this->template $template;
  355.         $this->templateParams $parameters;
  356.         return $this;
  357.     }
  358.     /**
  359.      * @return $this
  360.      */
  361.     public function setTranslationDomain(string $translationDomain): self
  362.     {
  363.         $this->translationDomain $translationDomain;
  364.         return $this;
  365.     }
  366.     /**
  367.      * @return $this
  368.      */
  369.     public function setTransformer(callable $formatter)
  370.     {
  371.         $this->transformer $formatter;
  372.         return $this;
  373.     }
  374.     /**
  375.      * @return $this
  376.      */
  377.     protected function configureOptions(OptionsResolver $resolver)
  378.     {
  379.         $resolver->setDefaults(self::DEFAULT_OPTIONS);
  380.         return $this;
  381.     }
  382. }