Se você estiver acompanhando, saberá que estou escrevendo uma espécie de megassérie sobre desenvolvimento com GraphQL e Couchbase. Nos tutoriais anteriores, exploramos Como usar o GraphQL com Java, Como usar o GraphQL com o Node.jse Como usar o GraphQL com a Golang.
Depois de produzir conteúdo em todas essas linguagens, alguém da minha equipe me perguntou onde estava o PHP na mistura. A partir desse comentário, surgiu este tutorial sobre a criação de uma API com GraphQL e PHP usando Couchbase NoSQL como o camada de dados.
Antes de se dedicar muito a este tutorial de PHP e NoSQL GraphQL, há algumas premissas que devem ser atendidas:
- O Couchbase já deve ter sido instalado e configurado.
- O PHP deve ter sido instalado e configurado.
- O Composer e o PECL devem estar disponíveis para o download das dependências do projeto PHP.
Supondo que as condições acima sejam atendidas, podemos prosseguir com o desenvolvimento do nosso aplicativo.
Criação de um novo projeto com dependências do GraphQL e do Couchbase para PHP
A primeira etapa para o sucesso do nosso projeto é criar um e fazer o download das dependências necessárias. Em algum lugar de seu computador, crie um novo diretório e inclua um arquivo graphql.php arquivo. Usando o prompt de comando dentro desse diretório, execute o seguinte:
1 |
compositor exigir webonyx/graphql-php |
O comando acima instalará a dependência GraphQL que faz todo o trabalho pesado da nossa API. Em seguida, execute o seguinte comando em seu prompt de comando:
1 |
pecl instalar couchbase |
O comando acima instalará o Couchbase PHP SDK. Reiterando minhas suposições anteriores, você precisa ter Compositor e PECL disponíveis para você.
Mais informações sobre o SDK PHP do Couchbase e o Pacote GraphQL podem ser encontradas em cada uma de suas respectivas documentações.
Configuração do projeto para o desenvolvimento do GraphQL e do Couchbase
Com o projeto criado e o download das dependências, podemos trabalhar para inicializar nosso aplicativo. Abra o diretório graphql.php e inclua o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php require_once __DIR__ . '/vendor/autoload.php'; uso GraphQL\Tipo\Definição\Tipo de objeto; uso GraphQL\Tipo\Definição\Tipo; uso GraphQL\GraphQL; uso GraphQL\Tipo\Esquema; $cluster = novo CouchbaseCluster("couchbase://localhost"); $authenticator = novo \Couchbase\PasswordAuthenticator(); $authenticator->nome de usuário('exemplo')->senha('123456'); $cluster->autenticar($authenticator); $bucket = $cluster->openBucket('exemplo'); cabeçalho('Content-Type: application/json'); ?> |
No código acima, estamos fazendo referência à nossa dependência do Composer e incluindo algumas das classes GraphQL que planejamos usar. Sem realmente configurar o GraphQL ou usar o nosso banco de dados, precisamos estabelecer uma conexão com o nosso banco de dados.
Meu aplicativo PHP estava sendo executado localmente junto com minha instância do Couchbase. Certifique-se de preencher as lacunas quando se tratar de suas próprias informações de conexão com o Couchbase.
Definição de objetos GraphQL para gerenciamento de dados
Agora podemos nos concentrar em definir nosso modelo de dados, também conhecido como objetos GraphQL em nossa API. Neste exemplo específico, faremos referência a dados de Pokémon. Por esse motivo, podemos ter informações sobre um Pokemon específico, quais movimentos ele tem ou em qual videogame ele é encontrado. Essa será a base do nosso modelo de dados.
Vamos começar com o seguinte em nosso graphql.php usando este exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$moveType = novo Tipo de objeto([ "nome => "Mover, 'campos' => [ "nome => [ 'tipo' => Tipo::string() ], 'tipo' => [ 'tipo' => Tipo::string() ], "poder => [ 'tipo' => Tipo::int() ], ], ]); $pokemonType = novo Tipo de objeto([ "nome => "Pokémon, 'campos' => [ "nome => [ 'tipo' => Tipo::string() ], 'peso' => [ 'tipo' => Tipo::int() ], 'altura' => [ 'tipo' => Tipo::int() ], 'atributos' => [ 'tipo' => Tipo::listOf(Tipo::string()) ], "movimentos => [ 'tipo' => Tipo::listOf($moveType) ], ], ]); |
No código acima, temos dois objetos com as possíveis propriedades JSON, também chamadas de campos. Para o objeto $pokemonType
temos um campo que faz referência ao nosso outro objeto. O listOf
significa que podemos esperar o retorno de uma matriz de dados.
Os dois objetos que temos estão longe de ser tão complexos quanto poderiam ser, mas servem como um exemplo realista. No entanto, até o momento, eles não são nada mais do que um modelo. Precisamos ser capazes de preenchê-los com dados reais do nosso banco de dados.
Desenvolvimento de consultas para preencher objetos GraphQL
A próxima etapa é criar consultas a serem executadas em nosso banco de dados que preencherão nossos objetos GraphQL. Isso é feito por meio de consultas, que, no fim das contas, nada mais são do que outro objeto GraphQL que contém lógica.
Veja o código a seguir, por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$tipo de consulta = novo Tipo de objeto([ "nome => "Consulta, 'campos' => [ "pokemons => [ 'tipo' => Tipo::listOf($pokemonType), 'resolver' => função ($raiz, $argumentos) { global $balde; $consulta = CouchbaseN1qlQuery::fromString("SELECT example.* FROM example WHERE type = 'pokemon'"); $resultado = $balde->consulta($consulta); retorno $resultado->linhas; } ], 'pokemon' => [ 'tipo' => $pokemonType, 'args' => [ 'id' => Tipo::nonNull(Tipo::string()), ], 'resolver' => função ($raiz, $argumentos) { global $balde; $resultado = $balde->obter("pokemon-" . $argumentos['id']); retorno $resultado->valor; } ], ], ]); |
O código acima, que acabaria em seu graphql.php tem duas consultas possíveis. A primeira consulta chamada pokemons
retornará uma matriz do $pokemonType
modelo. O resolver
é o que de fato executa a lógica. A lógica para pokemons
executará uma consulta N1QL do Couchbase e retornará o resultado. O resultado da consulta N1QL é mapeado para cada um dos campos definidos em nosso objeto GraphQL.
Executando o pokemons
A consulta pode ser mais ou menos assim:
1 |
enrolar http://localhost:8080 -d '{"query": "query { pokemons{ name, height, attributes } }" }' |
O pokemon
A consulta é semelhante, mas não é a mesma. No pokemon
estamos exigindo uma consulta id
ser aprovado. Esse id
é usado para fazer uma pesquisa direta de um determinado documento NoSQL em vez de uma consulta NoSQL.
O pokemon
A consulta pode ser mais ou menos assim:
1 |
enrolar http://localhost:8080 -d '{"query": "query { pokemon(id: \"25\"){ name, height, attributes } }" }' |
O resultado dessa consulta será um único objeto em vez de uma matriz.
Manuseio de relacionamentos de modelo de forma simplista
Agora, digamos que quiséssemos algum tipo de relacionamento de dados. Até agora, presumimos que os dados de movimento e os dados de Pokémon existem no mesmo documento, mas e se tivéssemos dados divididos, mas relacionados?
Há duas abordagens para isso:
- Podemos fazer uma consulta JOIN, o que torna o gerenciamento de consultas potencialmente mais complexo.
- Podemos modularizar as consultas com base em cada campo, mantendo-as pequenas e eficientes.
Se você teve a oportunidade de ouvir meu podcast intitulado GraphQL para desenvolvimento de API que contou com a participação do cocriador do GraphQL, Lee Byron, você saberá que nenhum dos dois está errado e que tudo se resume à preferência. Vamos dar uma olhada na última opção, em que modularizamos as consultas e os objetos GraphQL.
Digamos que agora queiramos incluir os dados do jogo e que eles sejam um objeto separado como este:
1 2 3 4 5 6 |
$gameType = novo Tipo de objeto([ "nome => "Jogo, 'campos' => [ "nome => [ 'tipo' => Tipo::string() ], ], ]); |
Agora, digamos que os documentos do Pokémon em nosso banco de dados contenham um ID do jogo, mas não os dados reais. Semelhante a um relacionamento de chave primária e estrangeira, mas não de fato, pois se trata de NoSQL.
O que poderíamos fazer agora é o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$pokemonType = novo Tipo de objeto([ "nome => "Pokémon, 'campos' => [ "nome => [ 'tipo' => Tipo::string() ], 'peso' => [ 'tipo' => Tipo::int() ], 'altura' => [ 'tipo' => Tipo::int() ], 'atributos' => [ 'tipo' => Tipo::listOf(Tipo::string()) ], "movimentos => [ 'tipo' => Tipo::listOf($moveType) ], 'jogo' => [ 'tipo' => $gameType, 'resolver' => função ($raiz, $argumentos) { global $balde; $resultado = $balde->obter($raiz->jogo); retorno $resultado->valor; } ], ], ]); |
Observe que alteramos nosso $pokemonType
para incluir um campo de jogo, mas esse campo tem seu próprio resolver
função. Dentro dessa resolver
podemos acessar a função pai ou $root
que contém um ID do jogo, porque é isso que nosso pokemon
e pokemons
que as consultas nos forneceram. Usando o id, podemos obter os dados do jogo que existem em um documento separado e retorná-los.
Agora, nosso pokemons
é poupada de uma instrução JOIN. Não estou dizendo que uma instrução JOIN seja ruim, mas imagine se você tivesse alguns modelos de dados malucos que exigissem uma consulta de 50 linhas. Nesses casos, pode fazer sentido dividi-los em vez de tentar manter uma consulta enorme. Um fato interessante é que a operação do jogo no banco de dados não será chamada a menos que o campo do jogo seja solicitado em uma instrução Consulta GraphQL do front-end.
Reunindo o aplicativo
É hora de reunir o aplicativo. Até o momento, vimos apenas os modelos de dados e as consultas, mas não o configuramos para uso com o GraphQL.
Dê uma olhada no código agora completo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
<?php require_once __DIR__ . '/vendor/autoload.php'; uso GraphQL\Tipo\Definição\Tipo de objeto; uso GraphQL\Tipo\Definição\Tipo; uso GraphQL\GraphQL; uso GraphQL\Tipo\Esquema; $cluster = novo CouchbaseCluster("couchbase://localhost"); $authenticator = novo \Couchbase\PasswordAuthenticator(); $authenticator->nome de usuário('exemplo')->senha('123456'); $cluster->autenticar($authenticator); $bucket = $cluster->openBucket('exemplo'); $gameType = novo Tipo de objeto([ "nome => "Jogo, 'campos' => [ "nome => [ 'tipo' => Tipo::string() ], ], ]); $moveType = novo Tipo de objeto([ "nome => "Mover, 'campos' => [ "nome => [ 'tipo' => Tipo::string() ], 'tipo' => [ 'tipo' => Tipo::string() ], "poder => [ 'tipo' => Tipo::int() ], ], ]); $pokemonType = novo Tipo de objeto([ "nome => "Pokémon, 'campos' => [ "nome => [ 'tipo' => Tipo::string() ], 'peso' => [ 'tipo' => Tipo::int() ], 'altura' => [ 'tipo' => Tipo::int() ], 'atributos' => [ 'tipo' => Tipo::listOf(Tipo::string()) ], "movimentos => [ 'tipo' => Tipo::listOf($moveType) ], 'jogo' => [ 'tipo' => $gameType, 'resolver' => função ($root, $args) { global $bucket; $resultado = $bucket->obter($root->jogo); retorno $resultado->valor; } ], ], ]); $queryType = novo Tipo de objeto([ "nome => "Consulta, 'campos' => [ "pokemons => [ 'tipo' => Tipo::listOf($pokemonType), 'resolver' => função ($root, $args) { global $bucket; $query = CouchbaseN1qlQuery::fromString("SELECT example.* FROM example WHERE type = 'pokemon'"); $resultado = $bucket->consulta($query); retorno $resultado->linhas; } ], 'pokemon' => [ 'tipo' => $pokemonType, 'args' => [ 'id' => Tipo::nonNull(Tipo::string()), ], 'resolver' => função ($root, $args) { global $bucket; $resultado = $bucket->obter("pokemon-" . $args['id']); retorno $resultado->valor; } ], ], ]); $schema = novo Esquema([ 'consulta' => $queryType ]); $rawInput = file_get_contents('php://input'); 1TP4Entrada = json_decode($rawInput, verdadeiro); $query = 1TP4Entrada['consulta']; $variableValues = isset(1TP4Entrada['variáveis']) ? 1TP4Entrada['variáveis'] : nulo; tentar { $resultado = GraphQL::executeQuery($schema, $query, nulo, nulo, $variableValues); 1TP4Saída = $resultado->toArray(); } captura (\Exceção $e) { 1TP4Saída = [ 'erros' => [ [ 'mensagem' => $e->getMessage() ] ] ]; } cabeçalho('Content-Type: application/json'); eco json_encode(1TP4Saída); ?> |
A metade inferior do código foi extraída do código Documentação oficial do servidor PHP GraphQL. Essencialmente, estamos definindo nossas consultas como o esquema e recebendo qualquer entrada do usuário final. Essa entrada é usada em combinação com o esquema para produzir um resultado que é retornado ao usuário.
Conclusão
Você acabou de ver como criar uma API GraphQL com PHP e Couchbase como o banco de dados NoSQL. O GraphQL é uma ótima alternativa para a criação de APIs RESTful porque permite que você mantenha adequadamente um modelo de dados que pode ser consultado por meio de um frontend. Isso reduz a quantidade de solicitações HTTP, bem como a carga útil retornada em qualquer solicitação.
Se você quiser saber mais sobre como usar o Couchbase com PHP, confira um tutorial anterior que escrevi intitulado, Primeiros passos com NoSQL usando o Couchbase Server e PHP.