nbo/rest-api-bundle

Out of the box generic Symfony 5 REST API backend, transform any Doctrine entity and his relations onto API resources.

Installs: 157

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Forks: 0

Type:symfony-bundle


README

This bundle is a generic REST API, easily create and configure your API resources using your Doctrine entities.

Latest Stable Version Minimum PHP Version Build Status

Setup

$ composer req nbo/restapibundle

Documentation

Getting started

First you need to compose your api. Add a config/packages/rest_api.yml configuration onto your project.

Here's an example for a simple REST API, with a "user" and a "page" resources.

rest_api:
  default_limit: 10
  default_offset: 0
  default_order: {'id':'desc'}
  resources:
    user:
      classname: 'App\Entity\User'
      methods:
        get:
          authenticated: true
          roles: ['ROLE_ADMIN']
        post:
          authenticated: true
          roles: ['ROLE_ADMIN']
        put:
          authenticated: true
          roles: ['ROLE_ADMIN']
        delete:
          authenticated: true
          roles: ['ROLE_ADMIN']
    page:
      classname: 'App\Entity\Page'
      methods:
        get: ~
        post:
          authenticated: true
          roles: ['ROLE_CMS']
        put:
          authenticated: true
          roles: ['ROLE_CMS']
        delete:
          authenticated: true
          roles: ['ROLE_CMS']

In this example, all resource endpoints need an authenticated user with specific roles, except the GET /page which is public.

Use cases

Endpoint that require authenticated users

To restrict an endpoint to authenticated users, regardless of their roles, juste use the authenticated option.

Endpoint that require authenticated users and one or more specific roles

To restrict an endpoint to authenticated users with one or more specific roles, juste use the roles array option with the needed role(s).

User scoped resource

Sometimes, you need to scope a specific resource at a user level, where users can only see their own resources. Use the owner_restricted option.to do so.

Public endpoints

By default with no option, the endpoint is public. No authentication is needed.

Update your Doctrine entities

The next step is to transform your Doctrine entities onto API resources.

Here's an example of the User entity transformed onto an API resource.

<?php

namespace App\Entity;

use App\Entity\Traits\TimestampableEntity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use Nbo\RestApiBundle\Annotations\Resource;
use Nbo\RestApiBundle\Entity\AbstractResource;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @Resource(name="user")
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @ORM\HasLifecycleCallbacks()
 */
class User extends AbstractResource implements UserInterface
{
    const ROLE_ADMIN = 'ROLE_ADMIN';

    use TimestampableEntity;
    use SoftDeleteableEntity;

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     * @Groups("public")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     * @Groups("public")
     */
    private $email;

    /**
     * @ORM\Column(type="string", length=180)
     * @Groups("public")
     */
    private $displayName;

    /**
     * @ORM\Column(type="json")
     * @Groups("public")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type="string")
     * @Groups("private")
     */
    private $password;

    public function __toString()
    {
        return (string) $this->getDisplayName();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * @return string
     */
    public function getUsername(): string
    {
        return (string) $this->getEmail();
    }

    /**
     * @return mixed
     */
    public function getDisplayName(): ?string
    {
        return $this->displayName;
    }

    /**
     * @param mixed $displayName
     * @return User
     */
    public function setDisplayName($displayName = null)
    {
        $this->displayName = $displayName;
        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;
        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return (string) $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;
        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getSalt()
    {
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
    }
}

This is a simple User business object in a Symfony project. To turn it to an API resource, you just need to add the @Resource(name="your_api_resource_name") annotation.

Then you have to extends the AbstractResource or AbstractTranslatableResource abstract layers.

At this level, the API response is empty, to add data you need to add the @Groups("public") on the entity class members you want to expose.

This is it, your "user" resource REST API is now ready! An authenticated user with "ROLE_ADMIN" can now GET, POST, PUT or DELETE any user on "http://yourdomain/api/user" endpoint.

In a near future the @Group() annotation groups will become more flexible, to allow different levels of resource attributes access depending of the authenticated user roles.

Filter API resources

You can filter the API resources using the "q" query string parameter with the following filters:

Greater operator

Here we query all product resources, with a price greater than 150

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"price":{">":150}}

Lesser operator

Here we query all product resources, with a price lesser than 150

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"price":{"<":150}}

Equal operator

Here we query all product resources, with a price equal to 150

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"price":{"=":150}}

Different operator

Here we query all product resources, with a color different than yellow

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"color":{"!=":"yellow"}}

Is null operator

Here we query all product resources, with a category null

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"category":{"IS NULL":"null"}}

Is not null operator

Here we query all product resources, with a category not null

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"category":{"IS NOT NULL":"null"}}

LIKE operator

Here we query all product resources, with a name that start with "bag"

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"name":{"LIKE":"bag%"}}

Here we query all product resources, with a name that end with "bag"

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"name":{"LIKE":"%bag"}}

Here we query all product resources, with a name that contain "bag"

GET https://localhost:8000/api/product?page=1&limit=50&count=1&q={"name":{"LIKE":"%bag%"}}

Sort API resources

You can sort API resource by one or more fields, using the "sort" query parameter with the following syntax.

GET https://localhost:8000/api/media?page=1&limit=50&count=1&q={"platform":{"=":52}}&sort[updated]=desc&sort[otherfield]=asc

Count total API resources

To build a pagination system client side, you'll need to know for a given request how many API resources are available. To count API resources for a given request simply add the count query parameter.

GET http://localhost:8000/api/todo?page=5&limit=10&count=1

The API will add a "Count" response header, with the resources count for the given request.

Translatable resource

When your business object need to support i18n, you can GET, POST and PUT with a locale parameter:

GET http://localhost:8000/api/blog?page=5&limit=10

Become

GET http://localhost:8000/api/fr/blog?page=5&limit=10

To enable this behavior, you need to install the stof/doctrine-extensions-bundle, and update your Doctrine entity to extends the AbstractTranslatableResource layer and add the * @Gedmo\Translatable() annotation onto the translatable entity fields.

The stof/doctrine-extensions-bundle is the officially supported Doctrine extension bundle, but a future release will allow any Doctrine behavior extension.