diff --git a/Datatable/Column/AbstractColumn.php b/Datatable/Column/AbstractColumn.php index 20fc01dd..f598addc 100644 --- a/Datatable/Column/AbstractColumn.php +++ b/Datatable/Column/AbstractColumn.php @@ -296,6 +296,39 @@ abstract class AbstractColumn implements ColumnInterface * @var bool */ protected $sentInResponse; + + /** + * If this column displays the total of its cells in its head + * Default: false. + * + * @var bool + */ + protected $computeTotal; + + /** + * Contains the eventually computed total of the column. + * + * @var mixed + */ + protected $total; + + /** + * If the column represents a real column in the database. + * + * @var bool + */ + protected $mapped; + + /** + * Force the association property. + */ + protected $isAssociation; + + /** + * The source of data, if different from title and no dql specified. + */ + protected $dataSource; + //------------------------------------------------- // Options //------------------------------------------------- @@ -326,6 +359,10 @@ public function configureOptions(OptionsResolver $resolver) 'type_of_field' => null, 'responsive_priority' => null, 'sent_in_response' => true, + 'compute_total' => false, + 'mapped' => true, + 'is_association' => false, + 'data_source' => null, ]); $resolver->setAllowedTypes('cell_type', ['null', 'string']); @@ -347,6 +384,10 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('type_of_field', ['null', 'string']); $resolver->setAllowedTypes('responsive_priority', ['null', 'int']); $resolver->setAllowedTypes('sent_in_response', ['bool']); + $resolver->setAllowedTypes('compute_total', ['bool']); + $resolver->setAllowedTypes('mapped', ['bool']); + $resolver->setAllowedTypes('is_association', ['bool']); + $resolver->setAllowedTypes('data_source', ['string', 'null']); $resolver->setAllowedValues('cell_type', [null, 'th', 'td']); $resolver->setAllowedValues('join_type', [null, 'join', 'leftJoin', 'innerJoin']); @@ -392,6 +433,10 @@ public function isAssociation() */ public function isToManyAssociation() { + if ($this->isAssociation) { + return true; + } + if (true === $this->isAssociation() && null !== $this->typeOfAssociation) { if (\in_array(ClassMetadataInfo::ONE_TO_MANY, $this->typeOfAssociation, true) || \in_array(ClassMetadataInfo::MANY_TO_MANY, $this->typeOfAssociation, true)) { return true; @@ -430,9 +475,9 @@ public function addDataToOutputArray(array &$row) /** * {@inheritdoc} */ - public function renderCellContent(array &$row) + public function renderCellContent(array &$row, array &$resultRow) { - $this->isToManyAssociation() ? $this->renderToMany($row) : $this->renderSingleField($row); + $this->isToManyAssociation() ? $this->renderToMany($row, $resultRow) : $this->renderSingleField($row, $resultRow); } /** @@ -976,8 +1021,6 @@ public function setTypeOfAssociation($typeOfAssociation) } /** - * Add a typeOfAssociation. - * * @param int $typeOfAssociation * * @return $this @@ -1028,4 +1071,122 @@ public function setSentInResponse($sentInResponse) return $this; } + + /** + * Get computeTotal. + * + * @return bool + */ + public function getComputeTotal() + { + return $this->computeTotal; + } + + /** + * Set sentIntResponse. + * + * @param bool $computeTotal + * + * @return $this + */ + public function setComputeTotal($computeTotal) + { + $this->computeTotal = $computeTotal; + + return $this; + } + + /** + * Get total. + */ + public function getTotal() + { + return $this->total; + } + + /** + * Set total. + * + * @param $total + * + * @return $this + */ + public function setTotal($total) + { + $this->total = $total; + + return $this; + } + + /** + * Get mapped. + * + * @return bool + */ + public function getMapped() + { + return $this->mapped; + } + + /** + * Set mapped. + * + * @param bool $mapped + * + * @return $this + */ + public function setMapped($mapped) + { + $this->mapped = $mapped; + + return $this; + } + + /** + * Get isAssociation. + * + * @return bool + */ + public function getIsAssociation() + { + return $this->isAssociation; + } + + /** + * Set isAssociation. + * + * @param bool $isAssociation + * + * @return $this + */ + public function setIsAssociation($isAssociation) + { + $this->isAssociation = $isAssociation; + + return $this; + } + + /** + * Get data source. + * + * @return string|null + */ + public function getDataSource() + { + return $this->isAssociation; + } + + /** + * Set data source. + * + * @param string|null $dataSource + * + * @return $this + */ + public function setDataSource($dataSource) + { + $this->dataSource = $dataSource; + + return $this; + } } diff --git a/Datatable/Column/ActionColumn.php b/Datatable/Column/ActionColumn.php index 507d6b95..8b2eb1b5 100644 --- a/Datatable/Column/ActionColumn.php +++ b/Datatable/Column/ActionColumn.php @@ -72,7 +72,7 @@ public function addDataToOutputArray(array &$row) /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { $parameters = []; $attributes = []; @@ -132,7 +132,7 @@ public function renderSingleField(array &$row) } } - $row[$this->getIndex()] = $this->twig->render( + $resultRow[$this->getIndex()] = $this->twig->render( $this->getCellContentTemplate(), [ 'actions' => $this->actions, @@ -149,7 +149,7 @@ public function renderSingleField(array &$row) /** * {@inheritdoc} */ - public function renderToMany(array &$row) + public function renderToMany(array &$row, array &$resultRow) { throw new Exception('ActionColumn::renderToMany(): This function should never be called.'); } diff --git a/Datatable/Column/ArrayColumn.php b/Datatable/Column/ArrayColumn.php index 725e51f4..5b254d4e 100644 --- a/Datatable/Column/ArrayColumn.php +++ b/Datatable/Column/ArrayColumn.php @@ -16,11 +16,11 @@ class ArrayColumn extends Column /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { $row[$this->data] = $this->arrayToString($row[$this->data] ?? []); - return parent::renderSingleField($row); + return parent::renderSingleField($row, $resultRow); } /** diff --git a/Datatable/Column/AttributeColumn.php b/Datatable/Column/AttributeColumn.php index 607493b0..3cd47201 100644 --- a/Datatable/Column/AttributeColumn.php +++ b/Datatable/Column/AttributeColumn.php @@ -37,10 +37,8 @@ class AttributeColumn extends AbstractColumn /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { - $renderAttributes = []; - $renderAttributes = \call_user_func($this->attributes, $row); $path = Helper::getDataPropertyPath($this->data); @@ -53,13 +51,13 @@ public function renderSingleField(array &$row) ] ); - $this->accessor->setValue($row, $path, $content); + $this->accessor->setValue($resultRow, $path, $content); } /** * {@inheritdoc} */ - public function renderToMany(array &$row) + public function renderToMany(array &$row, array &$resultRow) { $value = null; $path = Helper::getDataPropertyPath($this->data, $value); @@ -83,7 +81,7 @@ public function renderToMany(array &$row) $currentObjectPath ); - $this->accessor->setValue($row, $currentPath, $content); + $this->accessor->setValue($resultRow, $currentPath, $content); } } // no placeholder - leave this blank @@ -122,6 +120,8 @@ public function getColumnType() //------------------------------------------------- /** + * Config options. + * * @return $this */ public function configureOptions(OptionsResolver $resolver) @@ -140,6 +140,8 @@ public function configureOptions(OptionsResolver $resolver) } /** + * Get attributes. + * * @return Attributes[] */ public function getAttributes() @@ -148,6 +150,8 @@ public function getAttributes() } /** + * Set attributes. + * * @param Closure $attributes * * @throws Exception @@ -168,9 +172,11 @@ public function setAttributes($attributes) /** * Render template. * + * @param string|null $data + * * @return mixed|string */ - private function renderTemplate(?string $data) + private function renderTemplate($data) { return $this->twig->render( $this->getCellContentTemplate(), diff --git a/Datatable/Column/BooleanColumn.php b/Datatable/Column/BooleanColumn.php index 1e5423d6..0ef53acb 100644 --- a/Datatable/Column/BooleanColumn.php +++ b/Datatable/Column/BooleanColumn.php @@ -72,7 +72,7 @@ class BooleanColumn extends AbstractColumn /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { $path = Helper::getDataPropertyPath($this->data); @@ -83,7 +83,7 @@ public function renderSingleField(array &$row) $content = $this->renderTemplate($this->accessor->getValue($row, $path)); } - $this->accessor->setValue($row, $path, $content); + $this->accessor->setValue($resultRow, $path, $content); } return $this; @@ -92,7 +92,7 @@ public function renderSingleField(array &$row) /** * {@inheritdoc} */ - public function renderToMany(array &$row) + public function renderToMany(array &$row, array &$resultRow) { $value = null; $path = Helper::getDataPropertyPath($this->data, $value); @@ -115,7 +115,7 @@ public function renderToMany(array &$row) $content = $this->renderTemplate($this->accessor->getValue($row, $currentPath)); } - $this->accessor->setValue($row, $currentPath, $content); + $this->accessor->setValue($resultRow, $currentPath, $content); } } // no placeholder - leave this blank diff --git a/Datatable/Column/Column.php b/Datatable/Column/Column.php index 284e94f4..920d5d02 100644 --- a/Datatable/Column/Column.php +++ b/Datatable/Column/Column.php @@ -30,14 +30,14 @@ class Column extends AbstractColumn /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { $path = Helper::getDataPropertyPath($this->data); if ($this->accessor->isReadable($row, $path)) { if ($this->isEditableContentRequired($row)) { $content = $this->renderTemplate($this->accessor->getValue($row, $path), $row[$this->editable->getPk()]); - $this->accessor->setValue($row, $path, $content); + $this->accessor->setValue($resultRow, $path, $content); } } @@ -47,7 +47,7 @@ public function renderSingleField(array &$row) /** * {@inheritdoc} */ - public function renderToMany(array &$row) + public function renderToMany(array &$row, array &$resultRow) { $value = null; $path = Helper::getDataPropertyPath($this->data, $value); @@ -71,7 +71,7 @@ public function renderToMany(array &$row) $currentObjectPath ); - $this->accessor->setValue($row, $currentPath, $content); + $this->accessor->setValue($resultRow, $currentPath, $content); } } // no placeholder - leave this blank diff --git a/Datatable/Column/ColumnInterface.php b/Datatable/Column/ColumnInterface.php index 05a86c58..8955b540 100644 --- a/Datatable/Column/ColumnInterface.php +++ b/Datatable/Column/ColumnInterface.php @@ -76,21 +76,21 @@ public function addDataToOutputArray(array &$row); * Render images or any other special content. * This function works similar to the DataTables Plugin 'columns.render'. */ - public function renderCellContent(array &$row); + public function renderCellContent(array &$row, array &$resultRow); /** * Render single field. * * @return $this */ - public function renderSingleField(array &$row); + public function renderSingleField(array &$row, array &$resultRow); /** * Render toMany. * * @return $this */ - public function renderToMany(array &$row); + public function renderToMany(array &$row, array &$resultRow); /** * Get the template for the 'renderCellContent' function. diff --git a/Datatable/Column/DateTimeColumn.php b/Datatable/Column/DateTimeColumn.php index c5839132..64b7cb6e 100644 --- a/Datatable/Column/DateTimeColumn.php +++ b/Datatable/Column/DateTimeColumn.php @@ -48,7 +48,7 @@ class DateTimeColumn extends AbstractColumn /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { $path = Helper::getDataPropertyPath($this->data); @@ -59,7 +59,7 @@ public function renderSingleField(array &$row) $content = $this->renderTemplate($this->accessor->getValue($row, $path)); } - $this->accessor->setValue($row, $path, $content); + $this->accessor->setValue($resultRow, $path, $content); } return $this; @@ -68,7 +68,7 @@ public function renderSingleField(array &$row) /** * {@inheritdoc} */ - public function renderToMany(array &$row) + public function renderToMany(array &$row, array &$resultRow) { $value = null; $path = Helper::getDataPropertyPath($this->data, $value); @@ -91,7 +91,7 @@ public function renderToMany(array &$row) $content = $this->renderTemplate($this->accessor->getValue($row, $currentPath)); } - $this->accessor->setValue($row, $currentPath, $content); + $this->accessor->setValue($resultRow, $currentPath, $content); } } // no placeholder - leave this blank diff --git a/Datatable/Column/ImageColumn.php b/Datatable/Column/ImageColumn.php index a22bd372..b2079ffb 100644 --- a/Datatable/Column/ImageColumn.php +++ b/Datatable/Column/ImageColumn.php @@ -91,14 +91,14 @@ class ImageColumn extends AbstractColumn /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { $path = Helper::getDataPropertyPath($this->data); if ($this->accessor->isReadable($row, $path)) { $content = $this->renderImageTemplate($this->accessor->getValue($row, $path), '-image'); - $this->accessor->setValue($row, $path, $content); + $this->accessor->setValue($resultRow, $path, $content); } return $this; @@ -107,7 +107,7 @@ public function renderSingleField(array &$row) /** * {@inheritdoc} */ - public function renderToMany(array &$row) + public function renderToMany(array &$row, array &$resultRow) { // e.g. images[ ].fileName // => $path = [images] @@ -122,13 +122,13 @@ public function renderToMany(array &$row) foreach ($images as $key => $image) { $currentPath = $path.'['.$key.']'.$value; $content = $this->renderImageTemplate($this->accessor->getValue($row, $currentPath), '-gallery-image'); - $this->accessor->setValue($row, $currentPath, $content); + $this->accessor->setValue($resultRow, $currentPath, $content); } } else { // create an entry for the placeholder image $currentPath = $path.'[0]'.$value; $content = $this->renderImageTemplate(null, '-gallery-image'); - $this->accessor->setValue($row, $currentPath, $content); + $this->accessor->setValue($resultRow, $currentPath, $content); } } diff --git a/Datatable/Column/NumberColumn.php b/Datatable/Column/NumberColumn.php index f03396f5..70d8c3fa 100644 --- a/Datatable/Column/NumberColumn.php +++ b/Datatable/Column/NumberColumn.php @@ -47,7 +47,7 @@ class NumberColumn extends Column /** * {@inheritdoc} */ - public function renderSingleField(array &$row) + public function renderSingleField(array &$row, array &$resultRow) { $path = Helper::getDataPropertyPath($this->data); @@ -58,7 +58,7 @@ public function renderSingleField(array &$row) $content = $this->renderTemplate($this->accessor->getValue($row, $path)); } - $this->accessor->setValue($row, $path, $content); + $this->accessor->setValue($resultRow, $path, $content); } return $this; @@ -67,7 +67,7 @@ public function renderSingleField(array &$row) /** * {@inheritdoc} */ - public function renderToMany(array &$row) + public function renderToMany(array &$row, array &$resultRow) { $value = null; $path = Helper::getDataPropertyPath($this->data, $value); @@ -90,7 +90,7 @@ public function renderToMany(array &$row) $content = $this->renderTemplate($this->accessor->getValue($row, $currentPath)); } - $this->accessor->setValue($row, $currentPath, $content); + $this->accessor->setValue($resultRow, $currentPath, $content); } } // no placeholder - leave this blank diff --git a/Response/DatatableFormatter.php b/Response/DatatableFormatter.php index 41683a61..0c39c7f7 100644 --- a/Response/DatatableFormatter.php +++ b/Response/DatatableFormatter.php @@ -97,21 +97,23 @@ public function runFormatter(Paginator $paginator, DatatableInterface $datatable $row = \call_user_func($datatable->getLineFormatter(), $row); } + $resultRow = $row; + /** @var ColumnInterface $column */ foreach ($columns as $column) { // 3. Add some special data to the output array. For example, the visibility of actions. $column->addDataToOutputArray($row); // 4. Call Columns renderContent method to format row items (e.g. for images or boolean values) - $column->renderCellContent($row); + $column->renderCellContent($row, $resultRow); } foreach ($columns as $column) { if (! $column->getSentInResponse()) { - unset($row[$column->getDql()]); + unset($resultRow[$column->getDql()]); } } - $this->output['data'][] = $row; + $this->output['data'][] = $resultRow; } } diff --git a/Response/DatatableQueryBuilder.php b/Response/DatatableQueryBuilder.php index b7989fb7..d956e00c 100644 --- a/Response/DatatableQueryBuilder.php +++ b/Response/DatatableQueryBuilder.php @@ -389,61 +389,62 @@ private function initColumnArrays() $currentAlias = $currentPart; $metadata = $this->metadata; - if (true === $this->accessor->getValue($column, 'customDql')) { - $columnAlias = str_replace('.', '_', $data); - - // Select - $selectDql = preg_replace('/\{([\w]+)\}/', '$1', $dql); - $this->addSelectColumn(null, $selectDql.' '.$columnAlias); - // Order on alias column name - $this->addOrderColumn($column, null, $columnAlias); - // Fix subqueries alias duplication - $searchDql = preg_replace('/\{([\w]+)\}/', '$1_search', $dql); - $this->addSearchColumn($column, null, $searchDql); - } elseif (true === $this->accessor->getValue($column, 'selectColumn')) { - $parts = explode('.', $dql); - - while (\count($parts) > 1) { - $previousPart = $currentPart; - $previousAlias = $currentAlias; - - $currentPart = array_shift($parts); - $currentAlias = ($previousPart === $this->entityShortName ? '' : $previousPart.'_').$currentPart; - $currentAlias = $this->getSafeName($currentAlias); - - if (! \array_key_exists($previousAlias.'.'.$currentPart, $this->joins)) { - $this->addJoin($previousAlias.'.'.$currentPart, $currentAlias, $this->accessor->getValue($column, 'joinType')); + if ($this->accessor->getValue($column, 'mapped')) { + if (true === $this->accessor->getValue($column, 'customDql')) { + $columnAlias = str_replace('.', '_', $data); + + // Select + $selectDql = preg_replace('/\{([\w]+)\}/', '$1', $dql); + $this->addSelectColumn(null, $selectDql.' '.$columnAlias); + // Order on alias column name + $this->addOrderColumn($column, null, $columnAlias); + // Fix subqueries alias duplication + $searchDql = preg_replace('/\{([\w]+)\}/', '$1_search', $dql); + $this->addSearchColumn($column, null, $searchDql); + } elseif (true === $this->accessor->getValue($column, 'selectColumn')) { + $parts = explode('.', $dql); + + while (\count($parts) > 1) { + $previousPart = $currentPart; + $previousAlias = $currentAlias; + + $currentPart = array_shift($parts); + $currentAlias = ($previousPart === $this->entityShortName ? '' : $previousPart.'_').$currentPart; + + if (! \array_key_exists($previousAlias.'.'.$currentPart, $this->joins)) { + $this->addJoin($previousAlias.'.'.$currentPart, $currentAlias, $this->accessor->getValue($column, 'joinType')); + } + + $metadata = $this->setIdentifierFromAssociation($currentAlias, $currentPart, $metadata); } - $metadata = $this->setIdentifierFromAssociation($currentAlias, $currentPart, $metadata); - } - - $this->addSelectColumn($currentAlias, $this->getIdentifier($metadata)); - $this->addSelectColumn($currentAlias, $parts[0]); - $this->addSearchOrderColumn($column, $currentAlias, $parts[0]); - } else { - // Add Order-Field for VirtualColumn - if ($this->accessor->isReadable($column, 'orderColumn') && true === $this->accessor->getValue($column, 'orderable')) { - $orderColumn = $this->accessor->getValue($column, 'orderColumn'); - $orderParts = explode('.', $orderColumn); - if (\count($orderParts) < 2) { - $orderColumn = $this->entityShortName.'.'.$orderColumn; - } - $this->orderColumns[] = $orderColumn; + $this->addSelectColumn($currentAlias, $this->getIdentifier($metadata)); + $this->addSelectColumn($currentAlias, $parts[0]); + $this->addSearchOrderColumn($column, $currentAlias, $parts[0]); } else { - $this->orderColumns[] = null; - } + // Add Order-Field for VirtualColumn + if ($this->accessor->isReadable($column, 'orderColumn') && true === $this->accessor->getValue($column, 'orderable')) { + $orderColumn = $this->accessor->getValue($column, 'orderColumn'); + $orderParts = explode('.', $orderColumn); + if (\count($orderParts) < 2) { + $orderColumn = $this->entityShortName.'.'.$orderColumn; + } + $this->orderColumns[] = $orderColumn; + } else { + $this->orderColumns[] = null; + } - // Add Search-Field for VirtualColumn - if ($this->accessor->isReadable($column, 'searchColumn') && true === $this->accessor->getValue($column, 'searchable')) { - $searchColumn = $this->accessor->getValue($column, 'searchColumn'); - $searchParts = explode('.', $searchColumn); - if (\count($searchParts) < 2) { - $searchColumn = $this->entityShortName.'.'.$searchColumn; + // Add Search-Field for VirtualColumn + if ($this->accessor->isReadable($column, 'searchColumn') && true === $this->accessor->getValue($column, 'searchable')) { + $searchColumn = $this->accessor->getValue($column, 'searchColumn'); + $searchParts = explode('.', $searchColumn); + if (\count($searchParts) < 2) { + $searchColumn = $this->entityShortName.'.'.$searchColumn; + } + $this->searchColumns[] = $searchColumn; + } else { + $this->searchColumns[] = null; } - $this->searchColumns[] = $searchColumn; - } else { - $this->searchColumns[] = null; } } } diff --git a/Tests/Datatables/PostDatatable.php b/Tests/Datatables/PostDatatable.php index 06462486..9b8e5134 100644 --- a/Tests/Datatables/PostDatatable.php +++ b/Tests/Datatables/PostDatatable.php @@ -18,6 +18,7 @@ use Sg\DatatablesBundle\Datatable\Column\Column; use Sg\DatatablesBundle\Datatable\Column\DateTimeColumn; use Sg\DatatablesBundle\Datatable\Column\ImageColumn; +use Sg\DatatablesBundle\Datatable\Column\LinkColumn; use Sg\DatatablesBundle\Datatable\Column\NumberColumn; use Sg\DatatablesBundle\Datatable\Column\VirtualColumn; @@ -41,6 +42,9 @@ public function buildDatatable(array $options = []) ->add('id', Column::class, [ 'title' => 'Id', ]) + ->add('link', LinkColumn::class, [ + 'title' => 'Link', + ]) ->add('title', Column::class, [ 'title' => 'Title', ])