{"id":23037152,"url":"https://github.com/it-bens/api-platform-resource-actions-bundle","last_synced_at":"2025-08-14T17:32:43.282Z","repository":{"id":56847445,"uuid":"525487860","full_name":"it-bens/api-platform-resource-actions-bundle","owner":"it-bens","description":"Bundle that can add actions to API Platform operations to deserialize payloads into command objects and pass them to the API Platform workflow.","archived":false,"fork":false,"pushed_at":"2023-12-15T11:58:14.000Z","size":200,"stargazers_count":3,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2023-12-15T12:55:30.772Z","etag":null,"topics":["api-platform","rest-api","symfony-bundle"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/it-bens.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-16T17:55:14.000Z","updated_at":"2023-04-22T10:54:49.000Z","dependencies_parsed_at":"2024-11-21T15:33:02.103Z","dependency_job_id":null,"html_url":"https://github.com/it-bens/api-platform-resource-actions-bundle","commit_stats":{"total_commits":50,"total_committers":2,"mean_commits":25.0,"dds":"0.020000000000000018","last_synced_commit":"b958ae8461c01621be2392deb6bb3c19244af198"},"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/it-bens%2Fapi-platform-resource-actions-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/it-bens%2Fapi-platform-resource-actions-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/it-bens%2Fapi-platform-resource-actions-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/it-bens%2Fapi-platform-resource-actions-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/it-bens","download_url":"https://codeload.github.com/it-bens/api-platform-resource-actions-bundle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229851790,"owners_count":18134288,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-platform","rest-api","symfony-bundle"],"created_at":"2024-12-15T17:29:27.431Z","updated_at":"2024-12-15T17:29:28.004Z","avatar_url":"https://github.com/it-bens.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# The API Platform Resource Action Bundle\n\n![Maintenance Status](https://img.shields.io/badge/Maintained%3F-yes-green.svg)\n[![Tests](https://github.com/it-bens/api-platform-resource-actions-bundle/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/it-bens/api-platform-resource-actions-bundle/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/it-bens/api-platform-resource-actions-bundle/branch/master/graph/badge.svg?token=P3RZM11FNV)](https://codecov.io/gh/it-bens/api-platform-resource-actions-bundle)\n\n## Motivation\n### Let's start with Symfony \u0026 API Platform :construction_worker:\nSymfony \u0026 API Platform are a great combination to easily create a REST API for a CRUD application.\nThe flexibility of a REST API is limited by design. Because the operations are generic, it's difficult to depict more specific operations.\n\nTypical ways of updating a models are to apply a full update operation (REST PUT) or to apply partial updates (REST PATCH).\nOften model updates are connected to certain requirements or side effects. The implementation of them often can become very complex.\n\nOne way to solve this problem is to use commands objects that are applied to the model. \nThe command implementations can be separated to check for requirements or to trigger side effects without writing longer and longer methods.\nUnfortunately, this concept collides with the simplicity of REST APIs implemented with API Platform. Custom operations can be implemented,\nbut this results in a lot of boilerplate code. API Platform could also populate a generic update-DTO and dispatch it via bus.\nBut the handler of this DTO would have to create the command objects and apply them. This would require a more or less complex logic,\nwhich is difficult to maintain and test.\n\n### API Platform resource actions to the rescue! :superhero:\nIn a REST API context the mentioned 'models' can be called 'resources'. An action refers to the mentioned commands but is more general.\n\nThis bundle provides the ability to attach actions to an API Platform resource operation via configuration.\nInternal a generic update DTO is used and later unpacked to create the desired action. After processing, the action object is dispatched via bus.\nIt can be handled from there like it came from any other source like form request or a console command.\nThis helps to keep API-logic-code out of your models and make them more agnostic about their data source.\n\n## Installation\nThe bundle can be installed via Composer:\n```bash\ncomposer require it-bens/api-platform-resource-actions-bundle\n```\nIf you're using Symfony Flex, the bundle will be automatically enabled (but has to be configured manually). \nFor older apps, enable it in your Kernel class.\n\n## Action configuration\nTo register an action, three things are required: a DTO, a configuration entry and a proper resource configuration in API Platform.\n\nLet's assume there is a command DTO like this:\n```php\nnamespace TheNamespace;\n\nclass AppendToPropertyOne {\n    public function __construct(\n        private TheNamespace\\TheEntity $entity, \n        private string $toAppend\n    ) {}\n}\n```\nThe command contains the entity it will be applied on and one more property.\n\nThe actions can be defined (and registered) via bundle configuration and/or class attributes.\nBoth sources can be combined, but an exception will be thrown if an action is defined more than once for a resource.\n\nThe bundle will check if the resources, used in the definitions, are registered in API Platform.\n\n### Action definition with configuration files\n```yaml\nitb_api_platform_resource_actions:\n   resources:\n      TheNamespace\\TheEntity:\n         increase-property-two:\n            command_class: TheNamespace\\AppendToPropertyOne\n            description: Appends a string to property one.\n```\nThe `resource` key refers to the API Platform resources.\nThe sub-key represents the action name (it will not be normalized into snake-case).\nThe description is optional and will be used for the OpenAPI documentation (blank descriptions result in exceptions).\n\n### Action definition with attributes\n```php\nnamespace TheNamespace;\n\nuse ApiPlatform\\Core\\Annotation\\ApiResource;\nuse ITB\\ApiPlatformResourceActionsBundle\\Attribute\\ResourceAction;\n\n#[ApiResource]\n#[ResourceAction(actionName: 'increase-property-two', commandClass: AppendToPropertyOne::class, description: 'Appends a string to property one.')]\nclass TheEntity {\n    ...\n}\n```\nThe `description` parameter is optional and can also be null.\n\nNormally this bundle will search for the attribute in all registered classes. The considered classes can be restricted by their namespace.\n```yaml\nitb_api_platform_resource_actions:\n  resources:\n    TheNamespace\\TheEntity:\n      increase-property-two:\n        command_class: TheNamespace\\AppendToPropertyOne\n        description: Appends a string to property one.\n```\n\n### Configuration of API Platform\nSo far this bundle won't do anything (except registering some services).\nThe actions can be used after an API Platform operation is configured to use the controller and the request DTO provided by this bundle.\nSub-Namespaces are considered as well.\n```yaml\nitb_api_platform_resource_actions:\n   attribute_namespaces: [ TheNamespace ]\n```\n\nOf course the configuration can be done via attributes as well.\n```php\nuse ITB\\ApiPlatformResourceActionsBundle\\Request\\Request;\nuse ITB\\ApiPlatformResourceActionsBundle\\Controller\\Controller;\nuse ITB\\ApiPlatformResourceActionsBundle\\Attribute\\ResourceAction;\n\n#[ApiResource(itemOperations: [\n    'patch' =\u003e [\n        'input': Request::class,\n        'controller': Controller::class\n    ]\n])]\n#[ResourceAction(...)]\nclass TheEntity {\n    ...\n}\n```\n\u003e :information_source: The `messenger` option has to be enabled, if the command should be handled there. Custom data persisters could be used as well.\n\n### Listing of configured actions via console\nAfter configuration, the resource actions can be displayed via Symfony console and filtered by a resource.\n```bash\nphp bin/console itb:api-platform-resource-actions:list-actions\n# or with filter by resource\nphp bin/console itb:api-platform-resource-actions:list-actions --resource=\"TheNamespace\\TheEntity\"\n```\nThe console will display a table with the columns \"API Platform resource\", \"action name\", \"command class\" and \"description\".\n\n## Action validation\nThis bundle can validate the created command manually. API Platform validates the input, but as this stage, the input object is generic.\nThat's why the command is validated in the controller. It's disabled by default and can be enabled in the bundle configuration.\n```yaml\nitb_api_platform_resource_actions:\n  validate_command: true\n```\n\n## The Open API documentation\n| ![\"The code is documentation enough\" button](docs/images/the-code-is-documentation-enough.png?raw=true \"The code is documentation enough - button\") |\n| :--: |\n| *Image provided by GetDigital (https://www.getdigital.de/geek-button-the-code-is-documentation-enough.html)* |\n\nWell NO! But here: maybe? :thinking:\n\nAPI Platform can automatically create an OpenAPI documentation. This bundle hooks into this process via decoration\nlike described here: https://api-platform.com/docs/core/openapi/. \n\nIt will add a table to the operation description like this:\n\n![OpenAPI documentation for operation with actions](docs/images/action-operation-with-documentation.png?raw=true \"OpenAPI documentation\")\n\nThe `Payload` column shows the properties of the command class. \nIf the class contains a property that has the same type as the API Platform resource, it will be removed from the list\n(because this is most likely the object, the command will be applied on).\n\n## Internal process\nThe process is closely coupled to API Platform and contains several steps:\n1. API Platform denormalizes the raw data into the generic `Request` DTO of this bundle.\n2. API Platform calls the `RequestTransformer` as a data transformer.\n   It injects the resource class and the resource object into the payload of the `Request` object.\n3. API Platform validates the `Request` object with the `ActionRequestValidator`.\n   It checks if the action is registered for the resource and if the payload contains the necessary data by using the `CommandFactory`.\n4. API Platform/The router calls the `Controller` and passes the `Request` object to it.\n5. The controller denormalizes the payload data into the command.\n6. The controller validates the command with the API Platform validator (if enabled).\n7. The controller returns the command to the default API Platform flow.\n\n## Current Limitations\n### Commands with two properties of the resource type\nThis bundle can handle the creation of a command that has two properties with the type of the related resources.\nIt uses the ITB `ReflectionConstructor` (https://github.com/it-bens/reflection-constructor),\nwhich can use a list of ignored property names when injecting the resource object into the payload for later denormalization.\n\nThere can be problems with this process if not all class properties are required or set by the constructor.\n(which would be bad practise anyway).\n\nFurthermore, the OpenAPI documentation of the payload properties ignores any property that has the same type as the resource.\nIn the case of two properties of that type, the bundle itself would work (like described) but the documentation would be incomplete.\n\n### Double Denormalization\nThe `Request` class is validated as a generic class by API Platform. \nThe `ActionRequestValidator` tries to denormalize the payload to the command object and catches denormalization exceptions.\nIf that validation passes, the denormalization is done again in the controller. \nTo minimize the performance impact of this double denormalization, a serializer cache should be used.\n\n### Commands without constructor\nThe `RequestTransformer` will look for a constructor argument that has the type of the resource.\nIf the command / DTO contains no constructor, it is assumed that the command requires no resource object.\n\nThis bundle uses the default Symfony serializer (created by the framework) for the denormalization.\nTherefore, this process is limited to the capabilities of the Symfony serializer(s).\n\n### Multiple operations with actions per resource\nThe operation for an action is identified by the resource configuration of API Platform.\nIt is assumed that an operation (of a specified resource) will use these actions if the 'input' key is set to `Request` \nand the controller is set to `Controller` (of this bundle).\nIf two or more operations of the same resource are configured like this, only the first one will be used.\n\nThe controller would still be called by API Platform for any other operation, and it would fail to find its corresponding actions.\n\n## Related Packages/Bundles\nBecause API Platform will always use the default bus to dispatch the command as a message, the usage of the\nMessage Bus Redirect Bundle (https://github.com/it-bens/message-bus-redirect-bundle) comes in handy.\n\n## Contributing\nI am really happy that the software developer community loves Open Source, like I do! ♥\n\nThat's why I appreciate every issue that is opened (preferably constructive)\nand every pull request that provides other or even better code to this package.\n\nYou are all breathtaking!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fit-bens%2Fapi-platform-resource-actions-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fit-bens%2Fapi-platform-resource-actions-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fit-bens%2Fapi-platform-resource-actions-bundle/lists"}