Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

API Platform - Which approach should I use for creating custom operation without entity

I'm new to API Platform. I think it's great but I cannot find any example how to create custom endpoint that isn't based on any entity. There are a lot of examples based on an entity and usually they are all about CRUD. But what about custom operations?

I need to create custom search through database with some custom parameters which aren't related to any entity. E.g. I want to receive POST request something like this:

{
   "from": "Paris",
   "to": "Berlin"
}

This data isn't saved to db and I haven't entity for it. After I receive this data, there should be a lot of business logic including db queries through a lot of db tables and also getting data from external sources. Then, after the business logic is finished, I want to return back result which is also custom and isn't related to any entity. E.g.

{
    "flights": [/* a lot of json data*/],
    "airports": [/* a lot of json data*/],
    "cities": [/* a lot of json data*/],
    .......
}

So, I think I'm not the only on who does something similar. But I really cannot find a solution or best practices how to do this. In the documentation I've found at least three approaches and I cannot implement none of them. The best one, I guess the most suitable for me it is using Custom Operations and Controllers. But documentation says this one is not recommended. Also I think I should use DTOs for request and response, but for this approach I'm not sure I can use them.

The second one I found it's using Data Transfer Objects, but this approach requires an entity. According to the documentation, I should use DTOs and DataTransformers to convert DTO to an Entity. But I don't need entity, I don't need save it to db. I want just handle received DTO on my own.

The third one I guess it is using Data Providers, but I'm not sure it is suitable for my requirements.

So, the main question is which approach or best practice should I use to implement custom operation which isn't related to any entity. And it will be great use DTOs for request and response.

like image 605
Danylo Koniushenko Avatar asked Feb 04 '23 17:02

Danylo Koniushenko


1 Answers

You are not forced to use entities. Classes that are marked with @ApiResource annotation may not be entities. Actually, if your application is smarter than basic CRUD you should avoid marking entities as ApiResource.

Since you want to use POST HTTP method (which is for creating resource items) you can do something like this.

1) Define class describing search fields and which will be your @ApiResource

<?php
// src/ApiResource/Search.php 

namespace App\ApiResource;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Action\NotFoundAction;
use ApiPlatform\Core\Annotation\ApiProperty;
use App\Dto\SearchResult;

/**
 * @ApiResource(
 *     itemOperations={
 *         "get"={
 *             "controller"=NotFoundAction::class,
 *             "read"=true,
 *             "output"=false,
 *         },
 *     },
 *     output=SearchResult::class
 * )
 */
class Search
{
    /**
     * @var string
     * @ApiProperty(identifier=true)
     */
    public $from;

    /** @var string */
    public $to;
}

2) Define DTO that will represent the output

<?php
// src/Dto/SearchResult.php

namespace App\Dto;

class SearchResult
{
    public $flights;
    public $airports;
    public $cities;
}

3) Create class that will inplement DataPersisterInterface for handling business logic. It will be called by framework because you make POST request.

<?php
// src/DataPersister/SearchService.php

declare(strict_types=1);

namespace App\DataPersister;

use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\SearchResult;
use App\ApiResource\Search;

final class SearchService implements DataPersisterInterface
{
    public function supports($data): bool
    {
        return $data instanceof Search;
    }

    public function persist($data)
    {
        // here you have access to your request via $data
        $output = new SearchResult();
        $output->flights = ['a lot of json data'];
        $output->airports = ['a lot of json data'];
        $output->cities = ['inputData' => $data];
        return $output;
    }

    public function remove($data)
    {
        // this method just need to be presented
    }
}

That way you will recieve results based on request.

like image 159
Nikita U. Avatar answered May 09 '23 00:05

Nikita U.