api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.44k stars 868 forks source link

Custom controller action without re-defining default operations #142

Closed Drachenkaetzchen closed 8 years ago

Drachenkaetzchen commented 9 years ago

I have the need for a custom operation. However, it seems that I need to re-define default operations in the service configuration:

resource.partmeasurementunit.item_operation.custom_put:
        class:     "Dunglas\ApiBundle\Api\Operation\Operation"
        public:    false
        factory:   [ "@api.operation_factory", "createItemOperation" ]
        arguments:
             -    "@resource.partmeasurementunit"               # Resource
             -    [ "PUT" ]                 # Methods
             -    "/part_measurement_units/{id}/setDefault"           # Path
             -    "PartKeeprPartBundle:PartMeasurementUnit:setDefault"         # Controller
             -    "my_custom_route"                 # Route name
             -    # Context (will be present in Hydra documentation)
                  "@type":       "hydra:Operation"
                  "hydra:title": "A custom operation"
                  "returns":     "xmls:string"
    resource.partmeasurementunit.item_operation.get:
        class:     "Dunglas\ApiBundle\Api\Operation\Operation"
        public:    false
        factory:   [ "@api.operation_factory", "createItemOperation" ]
        arguments: [ "@resource.partmeasurementunit", "GET" ]

    resource.partmeasurementunit.item_operation.put:
        class:     "Dunglas\ApiBundle\Api\Operation\Operation"
        public:    false
        factory:   [ "@api.operation_factory", "createItemOperation" ]
        arguments: [ "@resource.partmeasurementunit", "PUT" ]

    resource.partmeasurementunit:
        parent:    "api.resource"
        arguments: [ "PartKeepr\\PartBundle\\Entity\\PartMeasurementUnit" ]
        tags:      [ { name: "api.resource" } ]
        calls:
            -       method:     "initItemOperations"
                    arguments: [ [ "@resource.partmeasurementunit.item_operation.custom_put", "@resource.partmeasurementunit.item_operation.get", "@resource.partmeasurementunit.item_operation.put"  ] ]
            -       method:    "initFilters"
                    arguments: [ [ "@resource.order_filter" ] ]
            -       method:    "initNormalizationContext"
                    arguments: [ { groups: [ "default" ] } ]
            -       method:    "initDenormalizationContext"
                    arguments:
                        - { groups: [ "default" ] }

In the service definition above, I had to re-define resource.partmeasurementunit.item_operation.get and resource.partmeasurementunit.item_operation.put to make it work. Is there any way to omit that and just define additional operations?

dunglas commented 9 years ago

Indeed all operations must be defined in that case (any suggestion to improve that are welcome). Creating a CompilerPass to automatize the building process is a solution.

theofidry commented 9 years ago

@dunglas this is due to having to pass by initItemOperations and not having an addItemOperation. At the moment, DunglasApiBundle\Api\Resource has:

And no adder. Maybe adding an adder for the initCollectionOperations() and initItemOperations() would be enough. It would be weird to have only two adders but any use case common enough to require another adder, unlike adding a custom operation.

dunglas commented 9 years ago

adders will be a step in the bad direction (Resource are kind of immutable object). And it will not help because operations are initialized in the ResourceCompilerPass. I was thinking to something like another tag such as <tag name="api.keep_operations" />.

soyuka commented 9 years ago

Thing is that we can't replace the ResourceCompilerPass so we'd have to remove ressources to create them again with new Operations on a custom ResourcePass (please stop me if I'm wrong). This is a lot of overload to add a custom route on every resources. I thought about addCollectionOperations or addItemOperations but as @dunglas said those are immutable objects, once they are init they can't be modified.

Would love to see a neat solution to this issue!

theofidry commented 9 years ago

haha yeah I guess the add is an ugly workaround, but I'm sure @dunglas will find this pretty neat solution your talking about ;)

dunglas commented 9 years ago

As soon as your own CompilerPass is executed before the ResourceCompilerPass from the bundle, the one from the bundle will do nothing.

soyuka commented 9 years ago

As soon as your own CompilerPass is executed before the ResourceCompilerPass from the bundle, the one from the bundle will do nothing.

It'll throw :

  [Symfony\Component\Config\Exception\FileLoaderLoadException]
  A Resource class already exists for "Acme\UserBundle\Entity\User" in .... 

Exception is located in the Resource.

Drachenkaetzchen commented 9 years ago

Do you people have a workaround or solution yet? My config.yml is becoming pretty messy ;)

Yes, I'll refactor the definition into their own bundles, but I wanted to wait until a solution is found to have less work.

dunglas commented 9 years ago

I'm working on something but it's not ready yet.

theofidry commented 9 years ago

In the meantime @felicitus you can always put the declarations for DunglasApiBundle in a separate file