Skip to content

Commit

Permalink
Base ColumnType improvements and tests (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kreyu authored Jan 6, 2025
1 parent 8fc43a6 commit da1e2dd
Show file tree
Hide file tree
Showing 6 changed files with 1,295 additions and 38 deletions.
22 changes: 22 additions & 0 deletions docs/src/reference/types/column/options/column.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ Sets the parameters used when translating the column header.
Sets the translation domain used when translating the column value.
Setting the option to `false` disables its translation.

### `value_translation_parameters`

- **type**: `array` or `callable` that returns an array
- **default**: `[]`

Sets the parameters used when translating the column value.

If given the callable, it will receive two arguments:
- column value, e.g. column (row) data formatted by the optional `formatter` option;
- column (row) data, e.g. value returned by property accessor or getter;

```php
// Assume the data table of User entities
$builder->addColumn('firstName', options: [
'value_translation_parameters' => function (string $firstName, User $user) {
return [...];
},
]);
```

The `ColumnValueView` will contain the resolved callable.

### `property_path`

- **type**: `null`, `false` or `string`
Expand Down
104 changes: 66 additions & 38 deletions src/Column/Type/ColumnType.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,17 @@ public function __construct(

public function buildColumn(ColumnBuilderInterface $builder, array $options): void
{
$sortPropertyPath = null;

if (true === $options['sort']) {
$sortPropertyPath = $builder->getName();
} elseif (is_string($options['sort'])) {
$sortPropertyPath = $options['sort'];
}

$builder
->setPropertyPath($options['property_path'] ?: null)
->setSortPropertyPath(is_string($options['sort']) ? $options['sort'] : null)
->setPropertyPath($options['property_path'] ?? $builder->getName() ?: null)
->setSortPropertyPath($sortPropertyPath)
->setPriority($options['priority'])
->setVisible($options['visible'])
->setPersonalizable($options['personalizable'])
Expand Down Expand Up @@ -77,14 +85,22 @@ public function buildValueView(ColumnValueView $view, ColumnInterface $column, a
$attr = $attr($normData, $rowData);
}

$translationParameters = $options['value_translation_parameters'];

if (is_callable($translationParameters)) {
$translationParameters = $translationParameters($normData, $rowData);
}

$view->vars = array_replace($view->vars, [
'name' => $column->getName(),
'column' => $view,
'row' => $view->parent,
'data_table' => $view->parent->parent,
'block_prefixes' => $this->getColumnBlockPrefixes($column, $options),
'data' => $view->data,
'value' => $view->value,
'translation_domain' => $options['value_translation_domain'] ?? $view->parent->parent->vars['translation_domain'] ?? null,
'translation_parameters' => $options['value_translation_parameters'] ?? [],
'translation_parameters' => $translationParameters ?? [],
'attr' => $attr,
]);
}
Expand All @@ -99,29 +115,27 @@ public function buildExportHeaderView(ColumnHeaderView $view, ColumnInterface $c
$options['export'] = [];
}

$options['export'] += [
'getter' => $options['getter'],
'property_path' => $options['property_path'],
'formatter' => $options['formatter'],
];
$options['export']['label'] ??= $options['label'] ?? StringUtil::camelToSentence($column->getName());
$options['export']['header_translation_domain'] ??= $options['header_translation_domain'] ?? $view->parent->parent->vars['translation_domain'] ?? false;
$options['export']['header_translation_parameters'] ??= $options['header_translation_parameters'] ?? [];

$label = $options['label'] ?? StringUtil::camelToSentence($column->getName());
$label = $options['export']['label'];

if ($this->translator) {
if ($label instanceof TranslatableInterface) {
$label = $label->trans($this->translator, $this->translator->getLocale());
$locale = null;

if (method_exists(TranslatableInterface::class, 'getLocale')) {
$locale = $this->translator->getLocale();
}

$label = $label->trans($this->translator, $locale);
} else {
$translationDomain = $options['export']['header_translation_domain']
?? $options['header_translation_domain']
?? $view->parent->parent->vars['translation_domain']
?? false;
$translationDomain = $options['export']['header_translation_domain'];
$translationParameters = $options['export']['header_translation_parameters'];

if ($translationDomain) {
$label = $this->translator->trans(
id: $label,
parameters: $options['header_translation_parameters'],
domain: $translationDomain,
);
$label = $this->translator->trans($label, $translationParameters, $translationDomain);
}
}
}
Expand All @@ -139,35 +153,49 @@ public function buildExportValueView(ColumnValueView $view, ColumnInterface $col
$options['export'] = [];
}

$options['export'] += [
'getter' => $options['getter'],
'property_path' => $options['property_path'],
'property_accessor' => $options['property_accessor'],
'formatter' => $options['formatter'],
];
$options['export']['getter'] ??= $options['getter'];
$options['export']['property_path'] ??= $options['property_path'];
$options['export']['property_accessor'] ??= $options['property_accessor'];
$options['export']['formatter'] ??= $options['formatter'];
$options['export']['value_translation_domain'] ??= $options['value_translation_domain'] ?? $view->parent->parent->vars['translation_domain'] ?? false;
$options['export']['value_translation_parameters'] ??= $options['value_translation_parameters'] ?? [];

$rowData = $view->parent->data;

$normData = $this->getNormDataFromRowData($rowData, $column, $options['export']);
$viewData = $this->getViewDataFromNormData($normData, $rowData, $column, $options['export']);

if ($this->translator && is_string($viewData)) {
$translationDomain = $options['export']['value_translation_domain']
?? $options['value_translation_domain']
?? $view->parent->parent->vars['translation_domain']
?? false;

if ($translationDomain) {
$viewData = $this->translator->trans(
id: $viewData,
parameters: $options['value_translation_parameters'],
domain: $translationDomain,
);
if ($this->translator && (is_string($viewData) || $viewData instanceof TranslatableInterface)) {
if ($viewData instanceof TranslatableInterface) {
$locale = null;

if (method_exists(TranslatableInterface::class, 'getLocale')) {
$locale = $this->translator->getLocale();
}

$viewData = $viewData->trans($this->translator, $locale);
} else {
$translationDomain = $options['export']['value_translation_domain'];
$translationParameters = $options['export']['value_translation_parameters'];

if (is_callable($translationParameters)) {
$translationParameters = $translationParameters($normData, $rowData);
}

if ($translationDomain) {
$viewData = $this->translator->trans(
id: $viewData,
parameters: $translationParameters,
domain: $translationDomain,
);
}
}
}

$view->data = $normData;
$view->value = $viewData;

$view->vars['data'] = $normData;
$view->vars['value'] = $viewData;
}

Expand Down Expand Up @@ -198,7 +226,7 @@ public function configureOptions(OptionsResolver $resolver): void
->setAllowedTypes('header_translation_domain', ['null', 'bool', 'string'])
->setAllowedTypes('header_translation_parameters', ['null', 'array'])
->setAllowedTypes('value_translation_domain', ['null', 'bool', 'string'])
->setAllowedTypes('value_translation_parameters', 'array')
->setAllowedTypes('value_translation_parameters', ['array', 'callable'])
->setAllowedTypes('block_name', ['null', 'string'])
->setAllowedTypes('block_prefix', ['null', 'string'])
->setAllowedTypes('sort', ['bool', 'string'])
Expand Down
129 changes: 129 additions & 0 deletions src/Test/Column/Type/ColumnTypeTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Test\Column\Type;

use Kreyu\Bundle\DataTableBundle\Column\ColumnFactoryInterface;
use Kreyu\Bundle\DataTableBundle\Column\ColumnInterface;
use Kreyu\Bundle\DataTableBundle\Column\ColumnRegistry;
use Kreyu\Bundle\DataTableBundle\Column\ColumnRegistryInterface;
use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnTypeInterface;
use Kreyu\Bundle\DataTableBundle\Column\Type\ResolvedColumnTypeFactory;
use Kreyu\Bundle\DataTableBundle\Column\Type\ResolvedColumnTypeFactoryInterface;
use Kreyu\Bundle\DataTableBundle\DataTableFactory;
use Kreyu\Bundle\DataTableBundle\DataTableFactoryInterface;
use Kreyu\Bundle\DataTableBundle\DataTableInterface;
use Kreyu\Bundle\DataTableBundle\DataTableRegistry;
use Kreyu\Bundle\DataTableBundle\DataTableRegistryInterface;
use Kreyu\Bundle\DataTableBundle\Query\ArrayProxyQuery;
use Kreyu\Bundle\DataTableBundle\Tests\Fixtures\Column\TestColumnFactory;
use Kreyu\Bundle\DataTableBundle\Type\DataTableType;
use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeFactory;
use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeFactoryInterface;
use PHPUnit\Framework\TestCase;

abstract class ColumnTypeTestCase extends TestCase
{
protected ColumnFactoryInterface $columnFactory;
protected ColumnRegistryInterface $columnRegistry;
protected ResolvedColumnTypeFactoryInterface $resolvedColumnTypeFactory;
protected DataTableFactoryInterface $dataTableFactory;
protected DataTableRegistryInterface $dataTableRegistry;
protected ResolvedDataTableTypeFactoryInterface $resolvedDataTableTypeFactory;
protected DataTableInterface $dataTable;

abstract protected function getTestedColumnType(): ColumnTypeInterface;

protected function createColumn(array $options = []): ColumnInterface
{
return $this->getColumnFactory()->create($this->getTestedColumnType()::class, $options);
}

protected function createNamedColumn(string $name, array $options = []): ColumnInterface
{
return $this->getColumnFactory()->createNamed($name, $this->getTestedColumnType()::class, $options);
}

protected function getColumnFactory(): ColumnFactoryInterface
{
return $this->columnFactory ??= $this->createColumnFactory();
}

protected function createColumnFactory(): ColumnFactoryInterface
{
$factory = new TestColumnFactory($this->getColumnRegistry());
$factory->setDataTable($this->getDataTable());

return $factory;
}

protected function getColumnRegistry(): ColumnRegistryInterface
{
return $this->columnRegistry ??= $this->createColumnRegistry();
}

protected function createColumnRegistry(): ColumnRegistryInterface
{
return new ColumnRegistry(
types: [$this->getTestedColumnType()],
typeExtensions: [],
resolvedTypeFactory: $this->getResolvedColumnTypeFactory(),
);
}

protected function getResolvedColumnTypeFactory(): ResolvedColumnTypeFactoryInterface
{
return $this->resolvedColumnTypeFactory ??= $this->createResolvedColumnTypeFactory();
}

protected function createResolvedColumnTypeFactory(): ResolvedColumnTypeFactoryInterface
{
return new ResolvedColumnTypeFactory();
}

protected function getDataTableRegistry(): DataTableRegistryInterface
{
return $this->dataTableRegistry ??= $this->createDataTableRegistry();
}

protected function createDataTableRegistry(): DataTableRegistryInterface
{
return new DataTableRegistry(
types: [new DataTableType()],
typeExtensions: [],
proxyQueryFactories: [],
resolvedTypeFactory: $this->getResolvedDataTableTypeFactory(),
);
}

protected function getDataTableFactory(): DataTableFactoryInterface
{
return $this->dataTableFactory ??= $this->createDataTableFactory();
}

protected function createDataTableFactory(): DataTableFactoryInterface
{
return new DataTableFactory($this->createDataTableRegistry());
}

protected function getResolvedDataTableTypeFactory(): ResolvedDataTableTypeFactoryInterface
{
return $this->resolvedDataTableTypeFactory ??= $this->createResolvedDataTableTypeFactory();
}

protected function createResolvedDataTableTypeFactory(): ResolvedDataTableTypeFactoryInterface
{
return new ResolvedDataTableTypeFactory();
}

protected function getDataTable(): DataTableInterface
{
return $this->dataTable ??= $this->createDataTable();
}

protected function createDataTable(): DataTableInterface
{
return $this->getDataTableFactory()->create(DataTableType::class, new ArrayProxyQuery([]));
}
}
41 changes: 41 additions & 0 deletions tests/Fixtures/Column/TestColumnFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Tests\Fixtures\Column;

use Kreyu\Bundle\DataTableBundle\Column\ColumnFactory;
use Kreyu\Bundle\DataTableBundle\Column\ColumnInterface;
use Kreyu\Bundle\DataTableBundle\Column\Type\ColumnType;
use Kreyu\Bundle\DataTableBundle\DataTableInterface;

/**
* Column factory that automatically sets the data table on created column.
*/
class TestColumnFactory extends ColumnFactory
{
private DataTableInterface $dataTable;

public function create(string $type = ColumnType::class, array $options = []): ColumnInterface
{
$column = parent::create($type, $options);
$column->setDataTable($this->dataTable);

return $column;
}

public function createNamed(string $name, string $type = ColumnType::class, array $options = []): ColumnInterface
{
$column = parent::createNamed($name, $type, $options);
$column->setDataTable($this->dataTable);

return $column;
}

public function setDataTable(DataTableInterface $dataTable): self
{
$this->dataTable = $dataTable;

return $this;
}
}
20 changes: 20 additions & 0 deletions tests/Fixtures/Model/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Tests\Fixtures\Model;

use Symfony\Contracts\Translation\TranslatableInterface;

class User
{
public function __construct(
public string|TranslatableInterface|null $firstName = null,
) {
}

public function getFirstNameUppercased(): string
{
return strtoupper($this->firstName);
}
}
Loading

0 comments on commit da1e2dd

Please sign in to comment.