Бандл расширяет компонет Symfony http-foundation позволя выделить работу с API в отдельную инкапсулированную зону.
Предоставляется работа с контентом запроса в формате JSON
посредством ParameterBag
.
Архитектура бандла не допускает фатального падения в зоне API и всегда возвращает валидный ответ
с соответствующем кодом ошибки. Полный список кодов ошибок доступен в виде констант в классе
ApiException.
Для описания спецификации API обязательно использование Swagger 2 в одном из форматов:
- NelmioApiDocBundle.
- swagger-php.
- Использование файла конфигурации в формате
json
илиyaml
(yml
).
Откройте консоль и, перейдя в директорию проекта, выполните следующую команду для загрузки наиболее подходящей стабильной версии этого бандла:
composer require wakeapp/api-platform-bundle
Эта команда подразумевает что Composer установлен и доступен глобально.
После включите бандл добавив его в список зарегистрированных бандлов в app/AppKernel.php
файл вашего проекта:
<?php declare(strict_types=1);
// app/AppKernel.php
class AppKernel extends Kernel
{
// ...
public function registerBundles()
{
$bundles = [
// ...
new Linkin\Bundle\SwaggerResolverBundle\LinkinSwaggerResolverBundle(),
new Wakeapp\Bundle\ApiPlatformBundle\WakeappApiPlatformBundle(),
];
return $bundles;
}
// ...
}
Чтобы начать использовать бандл требуется определить создать и определить guesser
- объект содержащий правила
определения зоны API вашего проекта.
<?php
declare(strict_types=1);
namespace App\Guesser\ApiAreaGuesser;
use Symfony\Component\HttpFoundation\Request;
use Wakeapp\Bundle\ApiPlatformBundle\Guesser\ApiAreaGuesserInterface;
class ApiAreaGuesser implements ApiAreaGuesserInterface
{
/**
* {@inheritDoc}
*/
public function getApiVersion(Request $request): ?int
{
$apiVersionMatch = [];
preg_match('/^\/v([\d]+)\//i', $request->getPathInfo(), $apiVersionMatch);
if (empty($apiVersionMatch)) {
return null;
}
$apiVersion = (int) end($apiVersionMatch);
return $apiVersion;
}
/**
* {@inheritdoc}
*/
public function isApiRequest(Request $request): bool
{
return strpos($request->getPathInfo(), '/api') === 0;
}
}
Примечание: Если вы не используете autowire
то вам необходимо зарегистрировать ApiAreaGuesser
как сервис.
# app/config.yml
wakeapp_api_platform:
api_area_guesser_service: App\Guesser\ApiAreaGuesser
# app/config.yml
wakeapp_api_platform:
# полное имя класса DTO для стандартизации ответа
api_result_dto_class: Wakeapp\Bundle\ApiPlatformBundle\Dto\ApiResultDto
# идентификатор сервиса для определения зоны API
api_area_guesser_service: App\Guesser\ApiAreaGuesser
# идентификатор сервиса для глобального отлавливания ошибок и выдачи специализированных сообщений вместо 500
error_code_guesser_service: Wakeapp\Bundle\ApiPlatformBundle\Guesser\ApiErrorCodeGuesser
# Минимально допустимая версия API.
minimal_api_version: 1
# флаг для отладки ошибок - если установлен в true - ответ ошибки содержит trace.
response_debug: false
Использование функционала бандла начинается с создание контроллера и первого метода в указанной зоне API. В качестве примера рассмотрим возвращение простейшего профиля пользователя.
Для начала нам необходимо создать DTO возвращаемых данных:
<?php declare(strict_types=1);
namespace App\Dto;
use Swagger\Annotations as SWG;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverTrait;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverInterface;
/**
* @SWG\Definition(
* type="object",
* description="Profile info",
* required={"email", "firstName", "lastName"},
* )
*/
class ProfileResultDto implements DtoResolverInterface
{
use DtoResolverTrait;
/**
* @var string
*
* @SWG\Property(description="Profile email", example="test@gmail.com")
*/
protected $email;
/**
* @var string
*
* @SWG\Property(description="User's first name", example="John")
*/
protected $firstName;
/**
* @var string
*
* @SWG\Property(description="User's last name", example="Doe")
*/
protected $lastName;
/**
* @return string
*/
public function getEmail(): string
{
return $this->email;
}
/**
* @return string
*/
public function getFirstName(): string
{
return $this->firstName;
}
/**
* @return string
*/
public function getLastName(): string
{
return $this->lastName;
}
}
Теперь добавим контроллер с соответствующим методом. Примечание: в качестве примера реализации используется подключение NelmioApiDocBundle.
<?php declare(strict_types=1);
namespace App\Controller;
use App\Dto\ProfileResultDto;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\Routing\Annotation\Route;
use Wakeapp\Bundle\ApiPlatformBundle\Factory\ApiDtoFactory;
use Wakeapp\Bundle\ApiPlatformBundle\HttpFoundation\ApiResponse;
/**
* @Route("/api/profile")
*/
class ProfileController
{
/**
* Returns user profile
*
* @Route(methods={"GET"})
*
* @SWG\Response(
* response=ApiResponse::HTTP_OK,
* description="Successful result in 'data' offset",
* @Model(type=ProfileResultDto::class)
* )
*
* @param ApiDtoFactory $factory
*
* @return ApiResponse
*/
public function getProfile(ApiDtoFactory $factory): ApiResponse
{
// обработка данных
$resultDto = $factory->createApiDto(ProfileResultDto::class, [
'email' => 'test-user@mail.ru',
'firstName' => 'Test',
'lastName' => 'User',
]);
return new ApiResponse($resultDto);
}
}
Объект на DTO:
<?php declare(strict_types=1);
namespace App\Dto;
use Swagger\Annotations as SWG;
use Wakeapp\Bundle\ApiPlatformBundle\Dto\MagicAwareDtoResolverTrait;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverInterface;
/**
* @SWG\Definition(
* type="object",
* description="Update profile info",
* required={"firstName", "lastName"},
* )
*
* @method getEmail(): string
*/
class UpdateProfileEntryDto implements DtoResolverInterface
{
use MagicAwareDtoResolverTrait;
/**
* @var string
*/
protected $email;
/**
* @var string
*
* @SWG\Property(description="User's first name", example="John")
*/
protected $firstName;
/**
* @var string
*
* @SWG\Property(description="User's last name", example="Doe")
*/
protected $lastName;
/**
* @return string
*/
public function getFirstName(): string
{
return $this->firstName;
}
/**
* @return string
*/
public function getLastName(): string
{
return $this->lastName;
}
}
Контроллер:
<?php declare(strict_types=1);
namespace App\Controller;
use App\Dto\UpdateProfileEntryDto;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\Routing\Annotation\Route;
use Wakeapp\Bundle\ApiPlatformBundle\HttpFoundation\ApiResponse;
/**
* @Route("/api/profile")
*/
class ProfileController
{
/**
* Update user password
*
* @Route("/{email}", methods={"PATCH"})
*
* @SWG\Parameter(name="email", in="path", type="string", required=true, description="User email")
* @SWG\Parameter(name="body", in="body", @Model(type=UpdateProfileEntryDto::class), required=true)
*
* @param UpdateProfileEntryDto $entryDto
*
* @return ApiResponse
*/
public function getProfile(UpdateProfileEntryDto $entryDto): ApiResponse
{
return new ApiResponse($entryDto);
}
}
Чтобы описать формат обертки ответа Wakeapp\Bundle\ApiPlatformBundle\Dto\ApiResultDto
при помощи аннотаций
можно использовать следующий подход:
Шаг 1 Создайте собственный ответ сервера:
<?php declare(strict_types=1);
namespace App\Dto;
use Swagger\Annotations as SWG;
use Wakeapp\Bundle\ApiPlatformBundle\Dto\ApiResultDto as BaseApiResultDto;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverInterface;
/**
* @SWG\Definition(
* type="object",
* description="Common API response object template",
* required={"code", "message"},
* )
*/
class ApiResultDto extends BaseApiResultDto
{
/**
* @var int
*
* @SWG\Property(description="Response api code", example=0, default=0)
*/
protected $code = 0;
/**
* @var string
*
* @SWG\Property(description="Localized human readable text", example="Successfully")
*/
protected $message;
/**
* @var DtoResolverInterface|null
*
* @SWG\Property(type="object", description="Some specific response data or null")
*/
protected $data = null;
}
Шаг 2 Добавьте в конфигурацию:
# app/config.yml
wakeapp_api_platform:
api_result_dto_class: App\Dto\MyApiResultDto
Шаг 3 Добавьте описание к вашему методу:
<?php declare(strict_types=1);
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Wakeapp\Bundle\ApiPlatformBundle\HttpFoundation\ApiRequest;
use Wakeapp\Bundle\ApiPlatformBundle\HttpFoundation\ApiResponse;
use Wakeapp\Bundle\ApiPlatformBundle\Factory\ApiDtoFactory;
class ProfileController
{
/**
* ...
*
* @SWG\Parameter(name="username", in="query", type="string", required=true, description="User login")
* @SWG\Response(
* response=ApiResponse::HTTP_OK,
* description="Successful result in 'data' offset",
* @Model(type=ProfileResultDto::class)
* )
* @SWG\Response(response="default", @Model(type=ApiResultDto::class), description="Response wrapper")
*
* @param ApiRequest $apiRequest
* @param ApiDtoFactory $factory
*
* @return ApiResponse
*/
public function getProfile(ApiRequest $apiRequest, ApiDtoFactory $factory): ApiResponse
{
return new ApiResponse();
}
}