2
votes

Given the following example:

I want to implement a student report endpoint with API platform.

api/report/student

I have the following tables:

Student

id,
name,
address


Invoice

id,
description,
amount,
status,
student_id

I would like to work an API endpoint to return with the following information:

{
    data: [
       student_id: 1,
       student_name: john,
       outstanding_amount: 200 (Total of unpaid bill)
    ],
    [
       student_id: 2,
       student_name: mike,
       outstanding_amount: 300 (Total of unpaid bill)
    ]

}

Just wondering what would be the best practice to write an API endpoint for the report with API platform (https://api-platform.com).

The report endpoint requires sorting, joining tables and pagination etc.

What would be the best practice is to achieve this with the API platform? Is this something we can achieve with GraphQL or we need to bind it with controller action for doing this?

Thanks.

2
I don't think there are any official best practices for this, which will likely lead to either no answer or multiple opinion-based ones. Please instead start working on your task and come back with concrete questions when you encounter problems. The short answer to your question is: It's both possible to do with GraphQL or REST API and there are multiple ways to control how your entities are serialized. Which solution is "the best" depends entirely on your criteria and can not be generalized. - dbrumann
For the official best practice see the General Design Considerations, examples for your DataProvider are here. But if you want to reuse existing filters, see chapter 9 of my tutorial for an alternative. It uses REST but it should work with GraphQL if you add such operation and the operation name to \App\DataProvider\DayTotalsPerEmployeeCollectionDataProvider::supports. - MetaClass

2 Answers

1
votes

In fact, you have everything you need in API platform assuming you have the Core installed.

SORTING

For the sorting, if you take a look in the Filters section, you can use a filter called Order Filter which is basically a sorting filter.

==========

PAGINATION

You a bunch of features available when using Pagination. You can control the number of items per page or you can enable/disable pagination for a specific resource.

==========

JOINING TABLES

From what I understand, you need to have some custom result set from the database for your endpoint by doing JOIN for example. Creating Custom doctrine filters should help you to achieve this.

==========

Also, for more flexibility, it is possible to use Custom controller action. As dbrumann said, I don't think also there are any official best practices for this. You just use the available features. Of course you can also use GraphQL, but honestly I never used it. From what I see, it seems that it is a powerful tool. It is up to you.

0
votes

Simpelest solution that could possibly work:

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
 * @ApiResource(
 *     normalizationContext={"groups"={"student:read"}},
 *     attributes={"order"={"name", "address"}},
 *     itemOperations={
 *          "get",
 *          "put",
 *          "delete"
 *     },
 *     collectionOperations={
 *         "get",
 *          "post",
 *          "get_report"={
 *             "method"="GET",
 *             "path"="/students/report",
 *             "normalization_context"={"groups"={"student:report"}}
 *          }
 *     }
 * )
 * @ApiFilter(OrderFilter::class)
 * @ORM\Entity
 */
class Student
{
    /**
     * @var int
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"student:report"})
     */
    public $id;

    /**
     * @var string
     * @ORM\Column
     * @Assert\NotBlank
     * @Assert\Length(max=255)
     * @Groups({"student:read", "student:report"})
     */
    public $name;

    /**
     * @var string|null
     * @ORM\Column(nullable=true)
     * @Assert\Length(max=255)
     * @Groups({"student:read"})
     */
    public $address;

    /**
     * @var Collection
     * @ORM\OneToMany(targetEntity="App\Entity\Invoice", mappedBy="student")
     * @Groups({"student:read"})
     */
    public $invoices;

    public function __construct()
    {
        $this->invoices = new ArrayCollection();
    }

    /**
     * @return int|float
     * @Groups({"student:report"})
     */
    public function getOutstandingAmount()
    {
        $result = 0;
        foreach ($this->invoices as $invoice) {
            if ($invoice->getStatus() == Invoice::STATUS_OUTSTANDING) {
                $result += $invoice->getAmount();
            }
        }
        return $result;
    }
}

Property names in the output will not be prefixed with student_, if you want that, try DTO's.

Of course it would be better to have private properties and getters an setters, but that would not be the simpelest solution ;-)