diff --git a/config/grid.yaml b/config/grid.yaml index 4525b2643..383f60622 100644 --- a/config/grid.yaml +++ b/config/grid.yaml @@ -60,9 +60,15 @@ services: Pimcore\Bundle\StudioBackendBundle\Grid\Service\ColumnConfigurationForRelationServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\Grid\Service\ColumnConfigurationForRelationService + Pimcore\Bundle\StudioBackendBundle\Grid\Service\PhpCodeTransformerLoaderInterface: + class: Pimcore\Bundle\StudioBackendBundle\Grid\Service\Loader\TaggedIteratorPhpCodeTransformerLoader + Pimcore\Bundle\StudioBackendBundle\Grid\Column\Resolver\ResolverTypeGuesserInterface: class: Pimcore\Bundle\StudioBackendBundle\Grid\Column\Resolver\ResolverTypeGuesser + Pimcore\Bundle\StudioBackendBundle\DataObject\Service\PhpCodeTransformerServiceInterface: + class: Pimcore\Bundle\StudioBackendBundle\DataObject\Service\PhpCodeTransformerService + # # Repository # @@ -417,3 +423,7 @@ services: Pimcore\Bundle\StudioBackendBundle\Grid\Column\Transformer\Translate: tags: [ 'pimcore.studio_backend.grid_transformer' ] + + Pimcore\Bundle\StudioBackendBundle\Grid\Column\Transformer\PhpCode: + tags: [ 'pimcore.studio_backend.grid_transformer' ] + diff --git a/doc/03_Grid.md b/doc/03_Grid.md index 948f71b68..ec799ea39 100644 --- a/doc/03_Grid.md +++ b/doc/03_Grid.md @@ -521,5 +521,47 @@ The `Translate` transformer translates a given value using Symfony’s Translato ] } } +... +``` + +#### PhpCode Transformer + +The `PhpCode` transformer delegates value transformation to a custom PHP class implementing the Pimcore\Bundle\StudioBackendBundle\Grid\Column\PhpCodeTransformerInterface. This allows developers to encapsulate complex transformation logic in reusable services. You can specify the class to use via its fully qualified name (phpClass) and pass custom arguments to control its behavior. This is ideal for advanced use cases where transformation logic depends on external services, dynamic configuration, or custom business rules. +--- + +**Available Configurations:** + +- `phpClass`: The fully qualified class name of the transformer to execute. Must implement PhpCodeTransformerInterface. + +--- + +**Example Configuration:** + +```json +{ + "key": "phpCodeTransformedName", + "locale": "en", + "type": "dataobject.advanced", + "config": { + "title": "PHP Code Transformed Name", + "advancedColumns": [ + { + "key": "simpleField", + "config": { + "field": "name" + } + } + ], + "transformers": [ + { + "key": "phpCode", + "config": { + "phpClass": "Pimcore\\Bundle\\StudioBackendBundle\\Grid\\Column\\Transformer\\ExamplePhpCodeTransformer", + } + } + ] + } +} + ... ``` \ No newline at end of file diff --git a/doc/05_Additional_Custom_Attributes.md b/doc/05_Additional_Custom_Attributes.md index 15753af76..73bd83586 100644 --- a/doc/05_Additional_Custom_Attributes.md +++ b/doc/05_Additional_Custom_Attributes.md @@ -156,4 +156,5 @@ final class AssetEvent extends AbstractPreResponseEvent - `pre_response.version` - `pre_response.website_settings.item` - `pre_response.workflow_details` -- `pre_response.notification_recipient` \ No newline at end of file +- `pre_response.notification_recipient` +- `pre_response.php_code_transformer` \ No newline at end of file diff --git a/src/DataObject/Controller/Grid/GetPhpCodeTransformerController.php b/src/DataObject/Controller/Grid/GetPhpCodeTransformerController.php new file mode 100644 index 000000000..247a5c5fd --- /dev/null +++ b/src/DataObject/Controller/Grid/GetPhpCodeTransformerController.php @@ -0,0 +1,78 @@ +value)] + #[Get( + path: self::PREFIX . '/data-objects/grid/transformers/services/phpcode', + operationId: 'data_object_get_phpcode_transformers', + description: 'data_object_get_phpcode_transformers_description', + summary: 'data_object_get_phpcode_transformers_summary', + tags: [Tags::DataObjectsGrid->value] + )] + #[SuccessResponse( + description: 'data_object_get_phpcode_transformers_success_response', + content: new CollectionJson(new GenericCollection(PhpCodeTransformer::class)) + )] + #[DefaultResponses([ + HttpResponseCodes::UNAUTHORIZED, + HttpResponseCodes::NOT_FOUND, + ])] + public function getPhpCodeTransformers(): JsonResponse + { + $collection = $this->phpCodeTransformerService->getPhpCodeTransformers(); + + return $this->getPaginatedCollection( + $this->serializer, + $collection->getItems(), + $collection->getTotalItems() + ); + } +} diff --git a/src/DataObject/Event/PreResponse/PhpCodeTransformerEvent.php b/src/DataObject/Event/PreResponse/PhpCodeTransformerEvent.php new file mode 100644 index 000000000..1f8aa8816 --- /dev/null +++ b/src/DataObject/Event/PreResponse/PhpCodeTransformerEvent.php @@ -0,0 +1,32 @@ +transformer); + } + + public function getTransformer(): PhpCodeTransformer + { + return $this->transformer; + } +} diff --git a/src/DataObject/Schema/PhpCodeTransformer.php b/src/DataObject/Schema/PhpCodeTransformer.php new file mode 100644 index 000000000..0754106a4 --- /dev/null +++ b/src/DataObject/Schema/PhpCodeTransformer.php @@ -0,0 +1,67 @@ +key; + } + + public function getLabel(): string + { + return $this->label; + } + + public function getClass(): string + { + return $this->class; + } +} diff --git a/src/DataObject/Service/PhpCodeTransformerService.php b/src/DataObject/Service/PhpCodeTransformerService.php new file mode 100644 index 000000000..f7ffcdecb --- /dev/null +++ b/src/DataObject/Service/PhpCodeTransformerService.php @@ -0,0 +1,67 @@ +getTransformerCollection($this->loader->getTransformers()); + } + + /** + * Converts an array of transformers into a Collection of PhpCodeTransformer DTOs + * + * @param iterable $transformers Raw transformer objects + * + */ + private function getTransformerCollection(iterable $transformers): Collection + { + $items = []; + + foreach ($transformers as $transformer) { + $item = new PhpCodeTransformer( + key: $transformer->getKey(), + label: $transformer->getName(), + class: get_class($transformer) + ); + + $this->eventDispatcher->dispatch( + new PhpCodeTransformerEvent($item), + PhpCodeTransformerEvent::EVENT_NAME + ); + + $items[] = $item; + } + + return new Collection(count($items), $items); + } +} diff --git a/src/DataObject/Service/PhpCodeTransformerServiceInterface.php b/src/DataObject/Service/PhpCodeTransformerServiceInterface.php new file mode 100644 index 000000000..4b3493fcb --- /dev/null +++ b/src/DataObject/Service/PhpCodeTransformerServiceInterface.php @@ -0,0 +1,24 @@ +findTaggedServiceIds(TaggedIteratorPhpCodeTransformerLoader::PHPCODE_TRANSFORMER_TAG), + + ] + ); + + foreach ($taggedServices as $environmentType) { + $this->checkInterface($environmentType, PhpCodeTransformerInterface::class); + } + } +} diff --git a/src/Exception/Api/PhpCodeTransformerCollectionException.php b/src/Exception/Api/PhpCodeTransformerCollectionException.php new file mode 100644 index 000000000..534496d7d --- /dev/null +++ b/src/Exception/Api/PhpCodeTransformerCollectionException.php @@ -0,0 +1,27 @@ +value, $message); + } +} diff --git a/src/Grid/Column/PhpCodeTransformerInterface.php b/src/Grid/Column/PhpCodeTransformerInterface.php new file mode 100644 index 000000000..b664dad79 --- /dev/null +++ b/src/Grid/Column/PhpCodeTransformerInterface.php @@ -0,0 +1,32 @@ +getName(), + sprintf( + 'Invalid "phpClass" configuration (must be a string) for %s transformer. ', + $this->getKey() + ) + ); + } + + //Check if class exists + $transformer = $this->resolver->resolve($phpClass); + + //Transform the entire value and return result + return $transformer->transform($value, $config); + } + + public function getName(): string + { + return 'PHP Code'; + } + + public function getKey(): string + { + return 'phpCode'; + } + + public function getDescription(): string + { + return 'Executes tagged PHP services on grid values.'; + } + + public function getConfigOptions(): array + { + $options = []; + + foreach ($this->resolver->getTransformers() as $executable) { + $options[] = [ + 'value' => get_class($executable), + 'label' => $executable->getKey(), + ]; + } + + return [ + 'phpClass' => [ + 'type' => 'select', + 'label' => 'PHP Transformer Class', + 'options' => $options, + ], + 'arguments' => [ + 'type' => 'keyValue', + 'label' => 'Arguments', + 'default' => [], + ], + ]; + } +} diff --git a/src/Grid/Service/Loader/TaggedIteratorPhpCodeTransformerLoader.php b/src/Grid/Service/Loader/TaggedIteratorPhpCodeTransformerLoader.php new file mode 100644 index 000000000..7c4bb43c4 --- /dev/null +++ b/src/Grid/Service/Loader/TaggedIteratorPhpCodeTransformerLoader.php @@ -0,0 +1,65 @@ + $transformers + */ + public function __construct( + #[TaggedIterator(self::PHPCODE_TRANSFORMER_TAG)] + private iterable $transformers, + ) { + } + + /** + * * @return array + */ + public function getTransformers(): array + { + $transformers = []; + foreach ($this->transformers as $transformer) { + $transformers[$transformer->getKey()] = $transformer; + } + + return $transformers; + } + + /** + * @throws InvalidArgumentException + */ + public function resolve(string $className): PhpCodeTransformerInterface + { + foreach ($this->transformers as $transformer) { + if (get_class($transformer) === $className) { + return $transformer; + } + } + + throw new InvalidArgumentException( + sprintf('No PhpCode transformer found for class "%s"', $className) + ); + } +} diff --git a/src/Grid/Service/PhpCodeTransformerLoaderInterface.php b/src/Grid/Service/PhpCodeTransformerLoaderInterface.php new file mode 100644 index 000000000..a4043ccd5 --- /dev/null +++ b/src/Grid/Service/PhpCodeTransformerLoaderInterface.php @@ -0,0 +1,36 @@ + + */ + public function getTransformers(): iterable; + + /** + * @throws InvalidArgumentException If no matching transformer is found + */ + public function resolve(string $className): PhpCodeTransformerInterface; +} diff --git a/src/PimcoreStudioBackendBundle.php b/src/PimcoreStudioBackendBundle.php index 55d68a257..6a9575d4e 100644 --- a/src/PimcoreStudioBackendBundle.php +++ b/src/PimcoreStudioBackendBundle.php @@ -26,6 +26,7 @@ use Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass\ListingFilterPass; use Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass\MercureTopicsProviderPass; use Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass\PatchAdapterPass; +use Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass\PhpCodeTransformerPass; use Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass\SettingsProviderPass; use Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass\TransformerPass; use Pimcore\Bundle\StudioBackendBundle\DependencyInjection\CompilerPass\UpdateAdapterPass; @@ -92,6 +93,7 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new MercureTopicsProviderPass()); $container->addCompilerPass(new FieldDefinitionResolverPass()); $container->addCompilerPass(new TransformerPass()); + $container->addCompilerPass(new PhpCodeTransformerPass()); $container->addCompilerPass(new DocumentTypeAdapterPass()); } diff --git a/translations/studio_api_docs.en.yaml b/translations/studio_api_docs.en.yaml index f30eaa11f..bcf4f5903 100644 --- a/translations/studio_api_docs.en.yaml +++ b/translations/studio_api_docs.en.yaml @@ -1408,3 +1408,8 @@ notification_get_unread_count_success_response: Count of unread notifications fo data_object_get_available_grid_columns_for_relation_description: Get all available grid columns for data objects Many to many relation field (defined in the visible columns) data_object_get_available_grid_columns_for_relation_summary: Get all available grid columns for data objects Many to many relation field data_object_get_available_grid_columns_success_for_relation_response: List of available grid columns for data objects Many to many relation field +data_object_get_phpcode_transformers_description: | + Returns a list of available PHPCode transformers used in grid rendering. + These transformers can be applied to grid columns to dynamically modify or format values using PHP logic. +data_object_get_phpcode_transformers_summary: List PHPCode transformers +data_object_get_phpcode_transformers_success_response: List of available PHPCode transformers