cesurapp/api-bundle

Symfony Api Bundle

Installs: 228

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

Type:symfony-bundle

2.0.11 2024-09-19 21:28 UTC

README

App Tester Software License

This package allows you to expose fast api endpoints with Symfony.

Features:

  • Json request body transformer
  • Error messages are collected under a single format.
  • Language translation is applied to all error messages.
  • Custom cors header support
  • Automatic documentation generator (Thor)
  • Typescript client generator
  • Api DTO resolver
  • Doctrine filter & sorter resource
  • PhoneNumber, UniqueEntity, Username validator
  • Excel, Csv exporter (Sonata Export Bundle)

Install

Required Symfony 7

composer req cesurapp/api-bundle

Configuration: config/packages/api.yaml

api:
  exception_converter: false
  cors_header:
    - { name: 'Access-Control-Allow-Origin', value: '*' }
    - { name: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,PATCH,DELETE' }
    - { name: 'Access-Control-Allow-Headers', value: '*' }
    - { name: 'Access-Control-Expose-Headers', value: 'Content-Disposition' }
  thor:
    base_url: "%env(APP_DEFAULT_URI)%"
    global_config:
      authHeader:
        Content-Type: application/authheader
        Authorization: 'Bearer Token'
      query: []
      request: []
      header:
        Content-Type: application/header
        Accept: application/headaadsa
      response: []
      isAuth: true
      isPaginate: true
      isHidden: false

Generate TypeScript Client

View Documentation: http:://127.0.0.1:8000/thor

bin/console thor:extract ./path # Generate Documentation to Directory

Create Api Response

use \Cesurapp\ApiBundle\AbstractClass\ApiController;
use \Cesurapp\ApiBundle\Response\ApiResponse;
use \Cesurapp\ApiBundle\Thor\Attribute\Thor;
use \Symfony\Component\Routing\Annotation\Route;

class TestController extends ApiController {
    #[Thor(
        stack: 'Login|1',
        title: 'Login EndPoint',
        info: "Description",
        request: [
            'username' => 'string',
            'password' => 'string',
        ],
        response: [
            200 => ['data' => UserResource::class],
            BadCredentialsException::class,
            TokenExpiredException::class,
            AccessDeniedException::class
        ],
        dto: LoginDto::class, 
        isAuth: false, 
        isPaginate: false, 
        order: 0
    )]
    #[Route(name: 'Login', path: '/login', methods: ['POST'])]
    public function getMethod(LoginDto $loginDto): ApiResponse {
        return ApiResponse::create()
            ->setData(['custom-data'])
            ->setQuery('QueryBuilder')
            ->setHTTPCache(60)  // Enable HTTP Cache
            ->setPaginate()     // Enable QueryBuilder Paginator
            ->setHeaders([])    // Custom Header
            ->setResource(UserResource::class)
    }
    
    #[Thor(
        stack: 'Profile|2',
        title: 'Profile EndPoint',
        query: [
            'name' => '?string',
            'filter' => [
                'id' => '?int',
                'name' => '?string',
                'fullName' => '?string',
            ],
        ],
        response: [200 => ['data' => UserResource::class]],
        isAuth: true, 
        isPaginate: false, 
        order: 0
    )]
    #[Route(name: 'GetExample', path: '/get', methods: ['GET'])]
    public function postMethod(): ApiResponse {
        $query = $userRepo->createQueryBuilder('q');
        
        return ApiResponse::create()
            ->setQuery($query)
            ->setPaginate()     // Enable QueryBuilder Paginator
            ->setHeaders([])    // Custom Header
            ->setResource(UserResource::class)
    }
}

Create Api Resource

Filter and DataTable only work when pagination is enabled. Automatic TS columns are created for the table. Export is automatically enabled for all tables.

use \Cesurapp\ApiBundle\Response\ApiResourceInterface;

class UserResource implements ApiResourceInterface {
    public function toArray(mixed $item, mixed $optional = null): array {
        return [
            'id' => $object->getId(),
            'name' => $object->getName()
        ]
    }
    
    public function toResource(): array {
        return [
              'id' => [
                'type' => 'string', // Typescript Type -> ?string|?int|?boolean|?array|?object|NotificationResource::class|
                'filter' => static function (QueryBuilder $builder, string $alias, mixed $data) {}, // app.test?filter[id]=test
                'table' => [ // Typescript DataTable Types
                    'label' => 'ID',                     // DataTable Label
                    'sortable' => true,                  // DataTable Sortable Column   
                    'sortable_default' => true,          // DataTable Default Sortable Column
                    'sortable_desc' => true,             // DataTable Sortable DESC
                    'filter_input' => 'input',           // DataTable Add Filter Input Type -> input|number|date|daterange|checkbox|country|language
                   
                    // These fields are used in the backend. It doesn't transfer to the frontend. 
                    'exporter' => static fn($v) => $v,   // Export Column Template
                    'sortable_field' => 'firstName',     // Doctrine Getter Method
                    'sortable_field' => static fn (QueryBuilder $builder, string $direction) => $builder->orderBy('u.firstName', $direction),
                ],
            ],
            'created_at' => [
                'type' => 'string',
                'filter' => [
                    'from' => static function (QueryBuilder $builder, string $alias, mixed $data) {}, // app.test?filter[created_at][min]=test
                    'to' => static function (QueryBuilder $builder, string $alias, mixed $data) {}, // app.test?filter[created_at][max]=test
                ]
            ]
        ]   
    }

}

Using Filter

Filters are set according to the query parameter. Only matching records are filtered.

Sample request http://example.test/v1/userlist?filter[id]=1&filter[createdAt][min]=10.10.2023

Create Form Validation

Backend dates are stored in UTC ATOM format. In GET requests you get dates in ATOM format. In POST|PUT requests, send dates in ATOM format, converted to UTC.

use Cesurapp\ApiBundle\AbstractClass\ApiDto;
use Cesurapp\ApiBundle\Thor\Attribute\ThorResource;
use Symfony\Component\Validator\Constraints as Assert;

class LoginDto extends ApiDto {
    /**
     * Enable Auto Validation -> Default Enabled
     */
    protected bool $auto = true;
    
    /**
     * Form Fields
     */
    #[Assert\NotNull]
    public string|int|null|bool $name;

    #[Assert\Length(min: 3, max: 100)]
    public ?string $lastName;

    #[Assert\Length(min: 10, max: 100)]
    #[Assert\NotNull]
    public int $phone;

    #[Assert\NotNull]
    #[Assert\GreaterThan(new \DateTimeImmutable())]
    public \DateTimeImmutable $send_at;
    
    #[Assert\Optional([
        new Assert\Type('array'),
        new Assert\Count(['min' => 1]),
        new Assert\All([
            new Assert\Collection([
                'slug' => [
                    new Assert\NotBlank(),
                    new Assert\Type(['type' => 'string']),
                ],
                'label' => [
                    new Assert\NotBlank(),
                ],
            ]),
        ]),
    ])]
    #[ThorResource(data: [[
        'slug' => 'string',
        'label' => 'string|int|boolean',
    ]])]
    public ?array $data;
}