Hi,
i made 6 custom endpoints (operations).
one example:
```
namespace App\Controller\APIOperation;
use App\Entity\Page;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
final class PageOperation extends Controller
{
/**
* @Route(
* name="postDuplicatePublication",
* path="/api/pages/{id}/action",
* methods={"POST"},
* defaults={
* "_api_resource_class"=Page::class,
* "_api_item_operation_name"="postDuplicatePublication"
* }
* )
*/
public function postDuplicatePublication(Page $page, ObjectNormalizer $normalizer, SerializerInterface $serializer, Request $request)
{
// my code... etc etc...
$data = $serializer->normalize($page, null, ['group' => 'page']);
return $data;
}
}
```
But that broke my IRI on orginal endpoints from API platform.
This is easyest way to create new route .
I need better example, API Platform documentation and web are nighmare (srry :( )
Hi,
I found a solution on the same problem. It's a bit hacky, but it solves it.
The IRI is generated based on the route you defined. As always in Symfony, the first route matching the requirements is used.
If you run the command "debug:router", you will see that your customs endpoints are defined before API Platform generated routes. My solution is to define api platform routes before our customs endpoints routes.
If you open the src/Kernel.php file, you will see the order to load routing files in the methods "configureRoutes". The files in the same folders are loaded in alphabetical order. That means that the config/routes/annotations.yaml is loaded before config/routes/api_platform.yaml.
In order to load config/routes/api_platform.yaml before, I renamed it config/routes/_api_platform.yaml.
Hi, thanks for reply.
Cool trick but i made my own too.
Example of my route with new custom-made annotation route params:
/**
* @Route(
* name="getOrganizationDatastoreTree",
* path="/api/organization/{id}/{entity}/datastore-tree",
* methods={"GET"},
* defaults={
* "_api_resource_class"=Organization::class,
* "_api_item_operation_name"="OrganizationTreeById",
*
* "_api_custom_collection_context"="DatastoreTree",
* "_api_custom_collection_entity"="DatastoreTree",
* "_api_custom_collection_id"="/api/datastore_trees",
* "_api_custom_collection_iri"="/api/datastore_trees",
* }
* )
*
* @param mixed $entity
*/
public function getOrganizationDatastoreTree(Organization $organization, $entity, ObjectNormalizer $normalizer, SerializerInterface $serializer)
{
$data = $serializer->normalize([$something], null, ['groups' => 'page_tree']); // very important to call
return $data;
}
And i have my custom collection/item normalizer who will replace default response with new params from annotation params
<?php
namespace App\Serializer;
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class CollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
protected $collectionNormalizer;
protected $resourceClassResolver;
private $_containerParams;
private $_container;
private $_requestStack;
public function __construct(
NormalizerInterface $collectionNormalizer,
ResourceClassResolverInterface $resourceClassResolver,
ContainerBag $containerParams,
ContainerInterface $container,
RequestStack $requestStack
)
{
$this->collectionNormalizer = $collectionNormalizer;
$this->resourceClassResolver = $resourceClassResolver;
$this->_containerParams = $containerParams;
$this->_container = $container;
$this->_requestStack = $requestStack;
}
public function supportsNormalization($data, $format = null)
{
return $this->collectionNormalizer->supportsNormalization($data, $format);
}
public function normalize($object, $format = null, array $context = [])
{
$data = $this->collectionNormalizer->normalize($object, $format, $context);
$routeParams = $this->_requestStack->getCurrentRequest()->attributes->get('_route_params');
if(!isset($context['api_sub_level']))
{
// COLLECTION
if(isset($data['@id']))
{
$data['@iri'] = $data['@id'];
}
if(isset($data['@context']))
{
$ex = explode('/', $data['@context']);
$data['@entity'] = end($ex);
}
// SKELETON OVERRIDE
if(isset($routeParams['_api_custom_collection_context']))
{
$data['@context'] = $routeParams['_api_custom_collection_context'];
}
if(isset($routeParams['_api_custom_collection_entity']))
{
$data['@entity'] = $routeParams['_api_custom_collection_entity'];
}
if(isset($routeParams['_api_custom_collection_id']))
{
$data['@id'] = $routeParams['_api_custom_collection_id'];
}
if(isset($routeParams['_api_custom_collection_iri']))
{
$data['@iri'] = $routeParams['_api_custom_collection_iri'];
}
}
return $data;
}
public function setNormalizer(NormalizerInterface $normalizer)
{
if ($this->collectionNormalizer instanceof NormalizerAwareInterface)
{
$this->collectionNormalizer->setNormalizer($normalizer);
}
}
}
And now i can set manually on every custom operation response params i want.
This should be fixed by adding some parameter in the ItemOperations configuration. Now it seems there's no way to create endpoints without parameters like GET /things/biggestor GET /users/me.
When the custom op is below the default, it never matches, and when it's above, it breaks the IRI. There could be some configuration parameter like "default" or "priority" on an operation, so that we can ensure the correct one gets picked for IRI generation.
Most helpful comment
Hi,
I found a solution on the same problem. It's a bit hacky, but it solves it.
The IRI is generated based on the route you defined. As always in Symfony, the first route matching the requirements is used.
If you run the command "debug:router", you will see that your customs endpoints are defined before API Platform generated routes. My solution is to define api platform routes before our customs endpoints routes.
If you open the src/Kernel.php file, you will see the order to load routing files in the methods "configureRoutes". The files in the same folders are loaded in alphabetical order. That means that the config/routes/annotations.yaml is loaded before config/routes/api_platform.yaml.
In order to load config/routes/api_platform.yaml before, I renamed it config/routes/_api_platform.yaml.