Skip to content

Callable field services #245

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions docs/grid/field_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,197 @@ $field->setOptions([
```
{% endcode %}
{% endhint %}

## Callable

The Callable column aims to offer almost as much flexibility as the Twig column, but without requiring the creation of a template.

You simply need to specify a callable, which allows you to transform the `data` variable on the fly.

This field type has can be configured in two differents ways. Either you define a callable using the `callable` options or you can define a service with the `service` option.

### Using `callable` option

When defining callables in YAML, only string representations of callables are supported.
When configuring grids using PHP (as opposed to service grid configuration), both string and array callables are supported. However, closures cannot be used due to restrictions in Symfony's configuration (values of type "Closure" are not permitted in service configuration files).
By contrast, when configuring grids with service definitions, you can use both callables and closures.

Here are some examples of what you can do:

{% tabs %}
{% tab title="YAML" %}
{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %}
```yaml
sylius_grid:
grids:
app_user:
fields:
id:
type: callable
options:
callable: "callable:App\\Helper\\GridHelper::addHashPrefix"
label: app.ui.id
name:
type: callable
options:
callable: "callable:strtoupper"
label: app.ui.name
```
{% endcode %}
{% endtab %}
{% tab title="PHP" %}
{% code title="config/packages/sylius_grid.php" lineNumbers="true" %}
```php
<?php

use Sylius\Bundle\GridBundle\Builder\Field\CallableField;
use Sylius\Bundle\GridBundle\Builder\GridBuilder;
use Sylius\Bundle\GridBundle\Config\GridConfig;

return static function (GridConfig $grid): void {
$grid->addGrid(GridBuilder::create('app_user', '%app.model.user.class%')
->addField(
CallableField::create('id', 'App\\Helper\\GridHelper::addHashPrefix')
->setLabel('app.ui.id')
)
// or
->addField(
CallableField::create('id', ['App\\Helper\\GridHelper', 'addHashPrefix'])
->setLabel('app.ui.id')
)

->addField(
CallableField::create('name', 'strtoupper')
->setLabel('app.ui.name')
)
)
};
```
{% endcode %}

OR

{% code title="src/Grid/UserGrid.php" lineNumbers="true" %}
```php
<?php

declare(strict_types=1);

namespace App\Grid;

use App\Entity\User;
use Sylius\Bundle\GridBundle\Builder\Field\CallableField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface;

final class UserGrid extends AbstractGrid implements ResourceAwareGridInterface
{
public static function getName(): string
{
return 'app_user';
}

public function buildGrid(GridBuilderInterface $gridBuilder): void
{
$gridBuilder
->addField(
CallableField::create('id', GridHelper::addHashPrefix(...))
->setLabel('app.ui.id')
)
->addField(
CallableField::create('name', 'strtoupper')
->setLabel('app.ui.name')
)
->addField(
CallableField::create('roles' fn (array $roles): string => implode(', ', $roles))
->setLabel('app.ui.roles')
)
;
}

public function getResourceClass(): string
{
return User::class;
}
}
```
{% endcode %}
{% endtab %}
{% endtabs %}

### Using `service` option

For more complex tasks, the callable option may not be the most suited choice, especially when accessing a service is required to transform data. In such cases, you can use the `service` option to define a callable service.

If the service itself is callable, specifying the `service` option is sufficient. Alternatively, you can define a specific method within the service by using the `method` option.

Internally, Sylius uses a tagged locator to provide these services. To use a custom service, it must be tagged with `sylius.grid_field_callable_service`. As an alternative, you can use the `AsGridFieldCallableService` attribute on your service, which will automatically apply the required tag.

Before diving in examples, let's create a service that uses both a `PriceHelper` and `ChannelContextInterface` to retrieve the price of a product variant.

{% code title="src/Helper/ProductVariantHelper.php" lineNumbers="true" %}
```php
<?php

use Sylius\Bundle\CoreBundle\Templating\Helper\PriceHelper;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Grid\Annotation\AsGridFieldCallableService;

#[AsGridFieldCallableService]
final class ProductVariantHelper
{
public function __construct(
private PriceHelper $priceHelper,
private ChannelContextInterface $channelContext,
) {
}

public function getPrice($variant): string
{
return $this->priceHelper->getPrice($variant, [
'channel' => $this->channelContext->getChannel(),
]);
}
}
```
{% endcode %}

{% tabs %}
{% tab title="YAML" %}
{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %}
```yaml
sylius_grid:
grids:
app_product_variant:
fields:
price:
type: callable
path: .
options:
service: "App\Helper\ProductVariantHelper"
method: 'getPrice'
```
{% endcode %}
{% endtab %}
{% tab title="PHP" %}
{% code title="config/packages/sylius_grid.php" lineNumbers="true" %}
```php
<?php

use Sylius\Bundle\GridBundle\Builder\Field\CallableField;
use Sylius\Bundle\GridBundle\Builder\GridBuilder;
use Sylius\Bundle\GridBundle\Config\GridConfig;

return static function (GridConfig $grid): void {
$grid->addGrid(GridBuilder::create('app_product_variant', '%app.model.product_variant.class%')
->addField(
CallableField::createForService('price', \App\Helper\ProductVariantHelper, 'getPrice')
->setPath('.')
)
)
};
```
{% endcode %}
{% endtab %}
{% endtabs %}