diff --git a/composer.json b/composer.json index f966e11dc..03a704cee 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "ext-json": "*", "ext-mbstring": "*", "psr/container": "^1.0|^2.0", - "yiisoft/arrays": "^2.0|^3.0", + "yiisoft/arrays": "^3.1", "yiisoft/data": "dev-master", "yiisoft/factory": "^1.0", "yiisoft/friendly-exception": "^1.0", diff --git a/src/BaseListView.php b/src/BaseListView.php index 2a520eb4e..ea0089b1b 100644 --- a/src/BaseListView.php +++ b/src/BaseListView.php @@ -18,6 +18,7 @@ use Yiisoft\Data\Reader\LimitableDataInterface; use Yiisoft\Data\Reader\OffsetableDataInterface; use Yiisoft\Data\Reader\ReadableDataInterface; +use Yiisoft\Data\Reader\Sort; use Yiisoft\Data\Reader\SortableDataInterface; use Yiisoft\Html\Html; use Yiisoft\Html\Tag\Div; @@ -30,12 +31,14 @@ use Yiisoft\Translator\TranslatorInterface; use Yiisoft\Validator\Result as ValidationResult; use Yiisoft\Widget\Widget; +use Yiisoft\Data\Reader\OrderHelper; use Yiisoft\Yii\DataView\Exception\DataReaderNotSetException; /** * @psalm-type UrlArguments = array * @psalm-type UrlCreator = callable(UrlArguments,array):string * @psalm-type PageNotFoundExceptionCallback = callable(PageNotFoundException):void + * @psalm-import-type TOrder from Sort */ abstract class BaseListView extends Widget { @@ -366,7 +369,9 @@ private function prepareDataReaderByParams( if ($dataReader->isSortable() && !empty($sort)) { $sortObject = $dataReader->getSort(); if ($sortObject !== null) { - $dataReader = $dataReader->withSort($sortObject->withOrderString($sort)); + $order = OrderHelper::stringToArray($sort); + $this->prepareOrder($order); + $dataReader = $dataReader->withSort($sortObject->withOrder($order)); } } @@ -377,6 +382,13 @@ private function prepareDataReaderByParams( return $dataReader; } + /** + * @psalm-param TOrder $order + */ + protected function prepareOrder(array &$order): void + { + } + /** * Return new instance with the header for the grid. * diff --git a/src/Column/Base/HeaderContext.php b/src/Column/Base/HeaderContext.php index 7f22a520e..b59b625ba 100644 --- a/src/Column/Base/HeaderContext.php +++ b/src/Column/Base/HeaderContext.php @@ -5,12 +5,14 @@ namespace Yiisoft\Yii\DataView\Column\Base; use Stringable; +use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Data\Paginator\PageToken; use Yiisoft\Data\Reader\Sort; use Yiisoft\Html\Html; use Yiisoft\Html\Tag\A; use Yiisoft\Translator\TranslatorInterface; use Yiisoft\Yii\DataView\BaseListView; +use Yiisoft\Data\Reader\OrderHelper; use Yiisoft\Yii\DataView\UrlConfig; use Yiisoft\Yii\DataView\UrlParametersFactory; @@ -22,11 +24,13 @@ final class HeaderContext /** * @internal * + * @psalm-param array $overrideOrderFields * @psalm-param UrlCreator|null $urlCreator */ public function __construct( private readonly ?Sort $originalSort, private readonly ?Sort $sort, + private readonly array $overrideOrderFields, private readonly ?string $sortableHeaderClass, private string|Stringable $sortableHeaderPrepend, private string|Stringable $sortableHeaderAppend, @@ -59,6 +63,8 @@ public function translate(string|Stringable $id): string */ public function prepareSortable(Cell $cell, string $property): array { + $originalProperty = $property; + $property = $this->overrideOrderFields[$property] ?? $property; if ($this->sort === null || $this->originalSort === null || !$this->sort->hasFieldInConfig($property)) { return [$cell, null, '', '']; } @@ -85,7 +91,7 @@ public function prepareSortable(Cell $cell, string $property): array UrlParametersFactory::create( $this->pageToken, $this->pageSize, - $this->getLinkSortValue($this->originalSort, $this->sort, $property), + $this->getLinkSortValue($this->originalSort, $this->sort, $property, $originalProperty), $this->urlConfig, ) ); @@ -98,8 +104,12 @@ public function prepareSortable(Cell $cell, string $property): array ]; } - private function getLinkSortValue(Sort $originalSort, Sort $sort, string $property): ?string - { + private function getLinkSortValue( + Sort $originalSort, + Sort $sort, + string $property, + string $originalProperty + ): ?string { $originalOrder = $originalSort->getOrder(); $order = $sort->getOrder(); @@ -141,12 +151,14 @@ private function getLinkSortValue(Sort $originalSort, Sort $sort, string $proper return null; } - $result = $sort->withOrder($order)->getOrderAsString(); - if (empty($result)) { + $resultOrder = $sort->withOrder($order)->getOrder(); + if (empty($resultOrder)) { return null; } - return $result; + return OrderHelper::arrayToString( + ArrayHelper::renameKey($resultOrder, $property, $originalProperty) + ); } private function isEqualOrders(array $a, array $b): bool diff --git a/src/Column/DataColumn.php b/src/Column/DataColumn.php index 2f9c71ae4..d1a877676 100644 --- a/src/Column/DataColumn.php +++ b/src/Column/DataColumn.php @@ -24,8 +24,6 @@ */ final class DataColumn implements ColumnInterface { - public readonly ?string $queryProperty; - /** * @var bool|callable|null * @psalm-var bool|FilterEmptyCallable|null @@ -39,7 +37,7 @@ final class DataColumn implements ColumnInterface */ public function __construct( public readonly ?string $property = null, - ?string $queryProperty = null, + public readonly ?string $field = null, public readonly ?string $header = null, public readonly bool $encodeHeader = true, public readonly ?string $footer = null, @@ -55,7 +53,6 @@ public function __construct( bool|callable|null $filterEmpty = null, private readonly bool $visible = true, ) { - $this->queryProperty = $queryProperty ?? $this->property; $this->filterEmpty = $filterEmpty; } diff --git a/src/Column/DataColumnRenderer.php b/src/Column/DataColumnRenderer.php index 8d4bcef6f..abc7ec09b 100644 --- a/src/Column/DataColumnRenderer.php +++ b/src/Column/DataColumnRenderer.php @@ -29,7 +29,7 @@ /** * @psalm-import-type FilterEmptyCallable from DataColumn */ -final class DataColumnRenderer implements FilterableColumnRendererInterface +final class DataColumnRenderer implements FilterableColumnRendererInterface, OverrideOrderFieldsColumnInterface { /** * @var bool|callable @@ -72,11 +72,11 @@ public function renderHeader(ColumnInterface $column, Cell $cell, HeaderContext } $cell = $cell->content($label); - if (!$column->withSorting || $column->queryProperty === null) { + if (!$column->withSorting || $column->property === null) { return $cell; } - [$cell, $link, $prepend, $append] = $context->prepareSortable($cell, $column->queryProperty); + [$cell, $link, $prepend, $append] = $context->prepareSortable($cell, $column->property); if ($link !== null) { $link = $link->content($label)->encode(false); } @@ -88,7 +88,7 @@ public function renderFilter(ColumnInterface $column, Cell $cell, FilterContext { $this->checkColumn($column); - if ($column->queryProperty === null || $column->filter === false) { + if ($column->property === null || $column->filter === false) { return null; } @@ -103,14 +103,14 @@ public function renderFilter(ColumnInterface $column, Cell $cell, FilterContext $content = [ $widget->withContext( new Context( - $column->queryProperty, - $context->getQueryValue($column->queryProperty), + $column->property, + $context->getQueryValue($column->property), $context->formId ) ), ]; - $errors = $context->validationResult->getAttributeErrorMessages($column->queryProperty); + $errors = $context->validationResult->getAttributeErrorMessages($column->property); if (!empty($errors)) { $cell = $cell->addClass($context->cellInvalidClass); $content[] = Html::div(attributes: $context->errorsContainerAttributes) @@ -123,11 +123,11 @@ public function renderFilter(ColumnInterface $column, Cell $cell, FilterContext public function makeFilter(ColumnInterface $column, MakeFilterContext $context): ?FilterInterface { $this->checkColumn($column); - if ($column->queryProperty === null) { + if ($column->property === null) { return null; } - $value = $context->getQueryValue($column->queryProperty); + $value = $context->getQueryValue($column->property); if ($value === null) { return null; } @@ -144,7 +144,7 @@ public function makeFilter(ColumnInterface $column, MakeFilterContext $context): $context->validationResult->addError( $error->getMessage(), $error->getParameters(), - [$column->queryProperty] + [$column->property] ); } return null; @@ -163,7 +163,7 @@ public function makeFilter(ColumnInterface $column, MakeFilterContext $context): $factory = $column->filterFactory; } - return $factory->create($column->queryProperty, $value); + return $factory->create($column->field ?? $column->property, $value); } public function renderBody(ColumnInterface $column, Cell $cell, DataContext $context): Cell @@ -244,4 +244,18 @@ private function checkColumn(ColumnInterface $column): void ); } } + + public function getOverrideOrderFields(ColumnInterface $column): array + { + $this->checkColumn($column); + + if ($column->property === null + || $column->field === null + || $column->property === $column->field + ) { + return []; + } + + return [$column->property => $column->field]; + } } diff --git a/src/Column/OverrideOrderFieldsColumnInterface.php b/src/Column/OverrideOrderFieldsColumnInterface.php new file mode 100644 index 000000000..70a21d8d3 --- /dev/null +++ b/src/Column/OverrideOrderFieldsColumnInterface.php @@ -0,0 +1,13 @@ + + */ + public function getOverrideOrderFields(ColumnInterface $column): array; +} diff --git a/src/GridView.php b/src/GridView.php index 55a8f4aa6..8cbcd65e2 100644 --- a/src/GridView.php +++ b/src/GridView.php @@ -7,6 +7,7 @@ use Closure; use Psr\Container\ContainerInterface; use Stringable; +use Yiisoft\Arrays\ArrayHelper; use Yiisoft\Data\Paginator\PaginatorInterface; use Yiisoft\Data\Reader\ReadableDataInterface; use Yiisoft\Data\Reader\Sort; @@ -25,6 +26,7 @@ use Yiisoft\Yii\DataView\Column\ColumnInterface; use Yiisoft\Yii\DataView\Column\ColumnRendererInterface; use Yiisoft\Yii\DataView\Column\FilterableColumnRendererInterface; +use Yiisoft\Yii\DataView\Column\OverrideOrderFieldsColumnInterface; use Yiisoft\Yii\DataView\Filter\Factory\IncorrectValueException; /** @@ -531,10 +533,18 @@ protected function renderItems(array $items, ValidationResult $filterValidationR $blocks[] = Html::colgroup()->columns(...$tags)->render(); } + $overrideOrderFields = []; + foreach ($columns as $i => $column) { + if ($renderers[$i] instanceof OverrideOrderFieldsColumnInterface) { + $overrideOrderFields = array_merge($overrideOrderFields, $renderers[$i]->getOverrideOrderFields($column)); + } + } + if ($this->headerTableEnabled) { $headerContext = new HeaderContext( $this->getSort($dataReader), $this->getSort($this->preparedDataReader), + $overrideOrderFields, $this->sortableHeaderClass, $this->sortableHeaderPrepend, $this->sortableHeaderAppend, @@ -665,6 +675,19 @@ protected function makeFilters(): array return [$filters, $validationResult]; } + protected function prepareOrder(array &$order): void + { + $columns = $this->getColumns(); + $renderers = $this->getColumnRenderers(); + foreach ($columns as $i => $column) { + if ($renderers[$i] instanceof OverrideOrderFieldsColumnInterface) { + foreach ($renderers[$i]->getOverrideOrderFields($column) as $from => $to) { + $order = ArrayHelper::renameKey($order, $from, $to); + } + } + } + } + private function prepareBodyAttributes(array $attributes, DataContext $context): array { foreach ($attributes as $i => $attribute) {