В Laravel из под капота не поддержки работы с GraphQL поэтому для работы с GraphQL в Laravel проекте самым подходящим пакетом будет Lighthouse (Документация: https://lighthouse-php.com/)
Установка
Устанавливается как и любой пакет в PHP через Composer
# Установка пакета
composer require nuwave/lighthouse
# Публикация конфигурации
php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider"
Основные понятия в схемах GraphQL:
Типы запроса:
- Query (Запрос) - для получения данных (аналог GET в REST)
- Mutation (Мутация) - для изменения данных (аналог POST/PUT/PATCH/DELETE)
- Subscription (Подписка) - для реального времени (WebSockets)
Схематика входных и выходных данных:
1. Input - будет использоваться для схематики входных данных
Пример:
input UserCreateInput {
name: String!
surname: String!
email: String!
}
2. Type - будет использовать
Пример:
type User {
"Unique primary key."
id: ID!
"Non-unique name."
name: String
"Non-unique surname"
surname: String
"Unique email address."
email: String!
}
Создание первой схемы
Все схемы имеют расширение .graphql. Они должны находится в корне проекта в каталоге graphql. Создайте свою первую схему в файле graphql/schema.graphql. Она будет базовой для всех остальных
type User {
"Unique primary key."
id: ID!
"Non-unique name."
name: String
"Non-unique surname"
surname: String
"Unique email address."
email: String!
}
input UserCreateInput {
name: String!
surname: String!
email: String!
}
extend type Query {
getUser(id: Int!): User
}
extend type Mutation {
createUser(input: CreateUserInput): User
}
Импорт схем
Чтобы схема не разрасталась их можно делить на файлы. Например для пользователя можно создать user.graphql, для статей post.graphql и тогда. Но Lighthouse по умолчанию не увидит созданные схемы и поэтому в главную схему (schema.graphql) их нужно импортировать следующей командой:
#import user.graphql
Директивы схем
Сейчас схема сама по себе ничего не делает. У запросов (query) и мутаций (muration) нету инструкций о том, как получить/создать пользователя. Для этого можно воспользоваться директивами.
Основные директивы Lighthouse
- @all - получить все записи
- @find - найти по ID
- @create - создать запись
- @update - обновить запись
- @delete - удалить запись
- @belongsTo - отношение "принадлежит"
- @hasMany - отношение "имеет много"
- @eq - фильтр "равно"
- @where - условия WHERE
Их их на самом деле большое множество. Также можно использовать laravel гуарды через директиву @guard. Использовать валидацию через @rules (например @rules(apply: ["exists:users,id"])) и так далее. Обо всем можно прочитать в документации
Дополним схему нужными директивами и заодно допишем другие CRUD методы
type User {
"Unique primary key."
id: ID!
"Non-unique name."
name: String
"Non-unique surname"
surname: String
"Unique email address."
email: String!
}
input UserCreateInput {
name: String!
surname: String!
email: String!
}
extend type Query {
"Получение конкретного пользователя"
getUser(id: ID! @eq): User @find
"Получение списка пользователей"
getUsers: [User!]! @all
}
extend type Mutation {
"Создание пользователя"
createUser(input: CreateUserInput): User @create
"Обновление пользователя"
updateUser(
id: ID!
title: String
content: String
): User @update
"Удаление пользователя"
deleteUser(id: ID!): User @delete
}
Теперь схема будет работать. Осталось только это проверить.
Проверка
Для проверки можно составить обычный Postman запрос. Эндпоинт по умолчанию /graphql, но его можно изменить в конфиге. Но создавать Postman запросы или писать автотесты при ознакомлении с пакетом будет не слишком удобно, поэтому рекомендую установить пакет mll-lab/laravel-graphiql (https://github.com/mll-lab/laravel-graphiql). Он позволяет получить по эндпоинту /graphiql песочницу в которой можно посмотреть все свои схемы и составлять запросы, что довольно удобно.
Готовый запрос на получение пользователя будет выглядить примерно так:
{
getUser(id: 1) {
id
name
surname
}
}
на создание в свою же очередь так
{
mutation {
createUser(input: {
name: "Иван",
surname: "Иванов",
email: "ivanov@ya.ru"
}) {
id
name
surname
}
}
Авторизация
Теперь поговорим о безопасности. Никто не получает данные с сервера без токена авторизации, но судя по текущей схеме авторизацией никто не занимается. Для тех, кто использует Sanctum в Laravel для авторизации, можно установить пакет daniel-de-wit/lighthouse-sanctum после чего появится возможность использовать Sanctum как освновной источник авторизации. Настраивать не сложно, просто опубликуйте конфиги и в полученном конфиге config/lighthouse-sanctum.php укажите нужный вам провайдер. А также настройте конфиг самого Sanctum.
Теперь в сами запросы нужно прокидывать токен авторизации в зависимости от настройки Sanctum. В самой же схеме все нужные эндпоинты нужно будет покрыть директивой @guard что будет выглядить примерно так:
extend type Query {
"Получение конкретного пользователя"
getUser(id: ID! @eq): User
@guard
@find
"Получение списка пользователей"
getUsers: [User!]!
@guard
@all
}
extend type Mutation @guard {
"Создание пользователя"
createUser(input: CreateUserInput): User @create
"Обновление пользователя"
updateUser(
id: ID!
title: String
content: String
): User @update
"Удаление пользователя"
deleteUser(id: ID!): User @delete
}
В примере показал, что можно вешать @guard как на все запросы так и только на конкретные.
Свои обработчики
А что если логики обработки схем недостаточно? Скорее всего так и будет! Если логики директив недостаточно нужно писать свои обработчики.
Для того, чтобы создать свой обработчик можно воспользоваться artisan командами пакета Lighthouse, где:
- Для создания Query нужно выполнить команду
php artisan lighthouse:query GetUser
- Для создания Mutation нужно выполнить команду
php artisan lighthouse:mutation CreateUser
Сами обработчики будут хранится в каталоге app/GraphQL/Queries в случае Query запросов и в app/GraphQL/Mutations в случае мутаций.
Сам код обработчика может выглядить примерно так
final class CreateUser
{
public function __invoke(mixed $_, array $args): User
{
return User::create($args['input']);
}
}
Lighthouse ищет автоматически обработчики связанные по тому же имени что и сам запрос. То есть если в схеме указано createUser, но директив на обработку нету, то Lighthouse будет искать обработчик с именем CreateUser и обрабатывать логику там.
Затем в метод __invoke данного обработчика него он передаст:
- $_ - частью какого подзапроса является сам запрос. Обычно приходит null если происходит обычный запрос
- $args - переданные аргументы
- $context - Объект класса GraphQLContext где будет лежать контекст всего запроса
- $resolveInfo - Объект класса ResolveInfo в котором можно посмотреть о том, какие поля от нас ожидают, чтобы обрабатывать только нужную информацию
Структура GraphQL каталога
Помимом квери и мутаций в данном каталоге еще есть такие подкаталоги как:
- Directives - содержит свои кастомные директивы. Например более глубокое кеширование, более детальные проверки безопасности и прочие необходимые обработки запроса
- Exceptions - свои кастомные экзепшены
- Resolvers - резолверы полей. Если в схеме есть какое-то поле, которое запрос не возвращает, то Lighthouse будет искать на него resolver именно в этом каталоге, чтобы понять как его нужно вывести
- Scalars - скалярные типы данных. Например, тип JSON для вывода произвольного ответа
- Validators - кастомные валидаторы данных. Если у какого-то запроса вы выставите директиву @validator, то он будет искать валидатор именно тут. Тут можно описать свой свод правил валидации для нужного вам метода