diff --git a/docs/field_types.md b/docs/field_types.md index 8741e31d..ee427a2e 100644 --- a/docs/field_types.md +++ b/docs/field_types.md @@ -312,3 +312,89 @@ $field->setOptions([ // Your options here ]); ``` + +Callback +-------- + +The Callback 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 callback, which allows you to transform the 'data' variable on the fly. + +By default it uses the name of the field, but you can specify the path +alternatively. For example: + +
PHP + +```php +addGrid(GridBuilder::create('app_user', '%app.model.user.class%') + ->addField( + CallbackField::create('roles' fn (array $roles): string => implode(', ', $roles)) + ->setLabel('app.ui.roles') // # each filed type can have a label, we suggest using translation keys instead of messages + ->setPath('roles') + ) + ->addField( + CallbackField::create('status' fn (array $status): string => "$status", false) // the third argument allows to disable htmlspecialchars if set to false + ->setLabel('app.ui.status') // # each filed type can have a label, we suggest using translation keys instead of messages + ->setPath('status') + ) + ) +}; +``` + +OR + +```php +addField( + CallbackField::create('roles' fn (array $roles): string => implode(', ', $roles)) + ->setLabel('app.ui.roles') // # each filed type can have a label, we suggest using translation keys instead of messages + ->setPath('roles') + ) + ->addField( + CallbackField::create('status' fn (array $status): string => "$status", false) // the third argument allows to disable htmlspecialchars if set to false + ->setLabel('app.ui.status') // # each filed type can have a label, we suggest using translation keys instead of messages + ->setPath('status') + ) + ; + } + + public function getResourceClass(): string + { + return User::class; + } +} +``` + +
+ +This configuration will display each role of a customer separated with a comma. + diff --git a/src/Bundle/Builder/Field/CallbackField.php b/src/Bundle/Builder/Field/CallbackField.php new file mode 100644 index 00000000..b47c2a6d --- /dev/null +++ b/src/Bundle/Builder/Field/CallbackField.php @@ -0,0 +1,25 @@ +setOption('callback', $callback) + ->setOption('htmlspecialchars', $htmlspecialchars) + ; + } +} diff --git a/src/Bundle/Resources/config/services/field_types.xml b/src/Bundle/Resources/config/services/field_types.xml index ad91127b..9e158497 100644 --- a/src/Bundle/Resources/config/services/field_types.xml +++ b/src/Bundle/Resources/config/services/field_types.xml @@ -15,6 +15,12 @@ + + + + + + diff --git a/src/Component/FieldTypes/CallbackFieldType.php b/src/Component/FieldTypes/CallbackFieldType.php new file mode 100644 index 00000000..8eecfc81 --- /dev/null +++ b/src/Component/FieldTypes/CallbackFieldType.php @@ -0,0 +1,49 @@ +dataExtractor = $dataExtractor; + } + + public function render(Field $field, $data, array $options): string + { + $value = $this->dataExtractor->get($field, $data); + $value = call_user_func($options['callback'], $value); + + if ($options['htmlspecialchars'] !== true) { + return $value; + } + + return htmlspecialchars((string) $value); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setRequired('callback'); + $resolver->setAllowedTypes('callback', 'callable'); + + $resolver->setDefault('htmlspecialchars', true); + $resolver->setAllowedTypes('htmlspecialchars', 'bool'); + } +} diff --git a/src/Component/spec/FieldTypes/CallbackFieldTypeSpec.php b/src/Component/spec/FieldTypes/CallbackFieldTypeSpec.php new file mode 100644 index 00000000..954bc9ce --- /dev/null +++ b/src/Component/spec/FieldTypes/CallbackFieldTypeSpec.php @@ -0,0 +1,97 @@ +beConstructedWith($dataExtractor); + } + + function it_is_a_grid_field_type(): void + { + $this->shouldImplement(FieldTypeInterface::class); + } + + function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_callback_with_htmlspecialchars( + DataExtractorInterface $dataExtractor, + Field $field, + ): void { + $dataExtractor->get($field, ['foo' => 'bar'])->willReturn('bar'); + + $this->render($field, ['foo' => 'bar'], [ + 'callback' => fn (string $value): string => "$value", + 'htmlspecialchars' => true, + ])->shouldReturn('<strong>bar</strong>'); + } + + function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_callback_without_htmlspecialchars( + DataExtractorInterface $dataExtractor, + Field $field, + ): void { + $dataExtractor->get($field, ['foo' => 'bar'])->willReturn('bar'); + + $this->render($field, ['foo' => 'bar'], [ + 'callback' => fn (string $value): string => "$value", + 'htmlspecialchars' => false, + ])->shouldReturn('bar'); + } + + function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_function_callback( + DataExtractorInterface $dataExtractor, + Field $field, + ): void { + $dataExtractor->get($field, ['foo' => 'bar'])->willReturn('bar'); + + $this->render($field, ['foo' => 'bar'], [ + 'callback' => 'strtoupper', + 'htmlspecialchars' => true, + ])->shouldReturn('BAR'); + } + + function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_closure_callback( + DataExtractorInterface $dataExtractor, + Field $field, + ): void { + $dataExtractor->get($field, ['foo' => 'bar'])->willReturn('bar'); + + $this->render($field, ['foo' => 'bar'], [ + 'callback' => strtoupper(...), + 'htmlspecialchars' => true, + ])->shouldReturn('BAR'); + } + + function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_static_callback( + DataExtractorInterface $dataExtractor, + Field $field, + ): void { + $dataExtractor->get($field, ['foo' => 'bar'])->willReturn('BAR'); + + $this->render($field, ['foo' => 'bar'], [ + 'callback' => [self::class, 'callable'], + 'htmlspecialchars' => true, + ])->shouldReturn('bar'); + } + + static function callable($value) + { + return strtolower($value); + } +}