Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various base ColumnType improvements and tests #150

Merged
merged 10 commits into from
Jan 6, 2025
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
Loading