Back-End

9 set, 2014

Criando uma API RESTful para o Symfony2 com o Apigility

Publicidade

O Apigility é um projeto de código aberto para simplificar a implementação de APIs HTTP em aplicativos PHP. O projeto foi implementado em Zend Framework 2, mas pode ser usado para criar uma API para qualquer aplicativo PHP. Para provar esse aspecto, mostro neste artigo como usar o Apigility para criar uma API RESTful para um projeto Symfony2 já existente.

Este caso de uso é uma extensão da prova de conceito do exemplo que foi apresentado no PHP Forum 2013 em Paris, durante a palestra “Symfony2 e Zend Framework 2: o time perfeito”, dada por Stefan Koopmanschap e por mim.

Gostaria de agradecer a Alessandro Nadalin pela sugestão sobre a parte de inicialização do Symfony2.

Instalação

Você pode instalar o aplicativo Symfony2 e a implementação da API no seguinte repositório github: https://github.com/ezimuel/apigility-symfony2-use-case. Esse repositório inclui o aplicativo Symfony2 na pasta symfony2app e a API do Apigility na pasta apigilityapi.

Você precisa instalar o aplicativo Symfony2 antes de começar a usar o Apigility. Para instalar o Symfony2, siga os passos relatados neste arquivo README.md.

Após a instalação, verifique se você consegue executar o aplicativo web em um navegador. Se estiver usando o PHP 5.4+, você poderá executá-lo usando a seguinte linha de comando, na pasta symfony2app:

php app/console server:run

Esse comando executa o servidor web interno do PHP em localhost com porta padrão 8000. Você pode verificar se o aplicativo está funcionando corretamente apontando seu browser para http://localhost:8000/post. Tente acrescentar algum post usando a interface web para preencher o banco de dados com alguns dados.

Se tudo funcionar bem, você pode instalar a implementação da API fornecida pelo Apigility. É necessário seguir as instruções relatadas neste arquivo README.md.

Após essa etapa, você já estará preparado para executar a API RESTful usando um servidor web. Se estiver utilizando PHP 5.4.8+, você pode usar o servidor web interno do PHP através do seguinte comando, na pasta apigilityapi:

php -S 0:8080 -t public/ public/index.php

Consumindo a API RESTful

As operações CRUD oferecidas pelo aplicativo web Symfony2 são expostas agora como API RESTful, graças ao Apigility. Você pode acessá-las usando as seguintes ações de HTTP:

GET    /post        get the entire list of posts
GET    /post[/:id]  get the post specified by id
POST   /post        create a new post
PUT    /post[/:id]  update a post
PATCH  /post[/:id]  partial update a post
DELETE /post[/:id]  delete the post specified by id

Por exemplo, é possível obter as listas de todos os posts de linha de comando usando a seguinte instrução HTTPie:

http GET http://localhost:8080/post

O resultado obtido deve ser assim:

HTTP/1.1 200 OK
Connection: close
Content-Type: application/hal+json
Host: localhost:8080
 
{
    "_embedded": {
        "post": [
            {
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/post/1"
                    }
                },
                "content": "test",
                "id": 1,
                "publish_date": {
                    "date": "2013-11-29 15:40:00",
                    "timezone": "Europe/Berlin",
                    "timezone_type": 3
                },
                "title": "test"
            },
            ...
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/post"
        }
    }
}

Como você pode ver, a resposta do corpo é representada no formato JSON HAL, o formato padrão do Apigility.

Uma das características mais legais do Apigility é o gerenciamento de erro. Por exemplo, se tentar executar um POST no recurso de /post sem os dados JSON relatados no corpo, você receberá a seguinte mensagem de erro:

http POST http://localhost:8080/post
 
HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/api-problem+json
Host: localhost:8080
 
{
    "detail": "You need a title and a content at least",
    "httpStatus": 400,
    "problemType": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
    "title": "Bad Request"
}

Esse erro é gerenciado diretamente pelo Apigility, e você não precisa fazer nada no código fonte da sua API.

Se você quiser acrescentar um post válido, precisa especificar o título e o conteúdo como parâmetro JSON. Usando o HTTPie, você pode executar o seguinte comando:

http --json POST http://localhost:8080/post title=Foo content=Bar

A resposta HTTP será mais ou menos assim:

HTTP/1.1 201 Created
Connection: close
Content-Type: application/hal+json
Host: localhost:8080
Location: http://localhost:8080/post/4
 
{
    "_links": {
        "self": {
            "href": "http://localhost:8080/post/4"
        }
    },
    "content": "Bar",
    "id": 4,
    "publish_date": {
        "date": "2013-12-04 16:00:03",
        "timezone": "Europe/Berlin",
        "timezone_type": 3
    },
    "title": "Foo"
}

Basicamente, ao usar o Apigility, expusemos a peça modelo do projeto Symfony2 reutilizando o código original do aplicativo. Esse caso de uso pode ser interessante para projetos de Symfony2 existentes nos quais você deseja ter uma API RESTful com controle de versão, tratamento de erros, autenticação etc., pronta em minutos usando o Apigility.

A seguir, explicarei o procedimento que usamos para criar essa API.

Como usar o Apigility

O Apigility pode ser usado para criar uma API utilizando uma interface web simples (construída com AngularJS). Para executar essa interface do usuário, você precisa ativar o modo de desenvolvimento do Apigility executando o seguinte comando, dentro da pasta raiz do aplicativo esqueleto apigility:

php public/index.php development enable

Depois disso, você pode executar o Apigility configurando seu servidor web para apontar para a pasta pública do projeto. Se estiver utilizando PHP 5.4.8+, você pode usar o servidor web interno do PHP através do seguinte comando:

php -S 0:8080 -t public/ public/index.php

Se você apontar seu navegador para http://localhost:8080, deve aparecer a página de boas-vindas do Apigility.

apigility

Para criar uma nova API, clique no botão Criar Nova API (no topo/à direita na página). No nosso caso, criamos uma nova API ApiBlog.

Agora, escolha a seção ApiBlog (topo da página) e crie um novo serviço RESTful. Escolha a opção Serviço REST do menu à esquerda e clique no botão Criar Novo Serviço REST. Como o objetivo aqui é construir uma API para um projeto Symfony2 já existente, precisamos escolher um serviço Conectado por Código. Escolhemos o nome do Post para esse serviço.

create_new_service

Para criar um serviço REST, você precisa inserir algumas informações como a rota para comparar, os métodos HTTP permitidos para ENTIDADES e COLEÇÕES, o nome do Identificador etc. No nosso caso, escolhemos o seguinte conjunto:

  • Route to comparar: /post[/:id]
  • nome do Identificador: id
  • nome da Coleção: post

Usamos os valores padrão para os outros parâmetros.

Depois dessa etapa, é preciso personalizar a configuração do Apigility para usar a entidade Post fornecida pelo projeto Symfony2. A ideia é expor a entidade Post proveniente do aplicativo Symfony2 como recurso RESTful.

O primeiro passo é acrescentar ao composer.json do Apigility todas as dependências usadas pelo composer.json do aplicativo Symfony2. Desse modo, podemos reutilizar todas as classes existentes do projeto Symfony2 no Apigility.

O segundo passo é editar o arquivo config/module.config.php incluído na ApiBlog que acabamos de criar (todas as APIs produzidas pelo Apigility são enviadas como módulo ZF2 na pasta /module).

Você precisa alterar o valor da entidade zf-rest[‘entity-class’] para a entidade Symfony2 ‘Blog\\ExampleBundle\\Entity\\Post’ (linha 45 da nossa module.config.php) e o valor zf-hal [‘metadata_map’] de ‘ApiBlog\\V1\\Rest\\PostEntity’ para ‘Blog\\ExampleBundle\\Entity\\Post’. Você também precisa acrescentar o valor hydrator’ => ‘ClassMethods’ para esta entidade de Post (linha 72 da nossa module.config.php).

O terceiro e último passo é editar a classe PostResource do Apigility para acrescentar todas as ações RESTful específicas para cada método HTTP. Se quisermos expor todas as operações CRUD do aplicativo Symfony2, precisamos preencher os seguintes métodos:

  • create($data), POST /post
  • delete($id), DELETE /post[/:id]
  • fetch($id), GET /post[/:id]
  • fetchAll($params), GET /post
  • patch($id, $data), PATCH /post[/:id]
  • update($id, $data), PUT /post[/:id]

A ideia é inicializar o aplicativo Symfony2 (sem o gerenciamento de requisições HTTP) no construtor da classe PostResource e armazenar o serviço específico de que precisamos em uma variável protegida, a fim de empregá-lo em métodos específicos para recuperar dados da lógica de negócios do aplicativo Symfony2.

No nosso caso, o código que utilizamos para inicializar o aplicativo Symfony2 está relatado abaixo:

public function __construct()
{
    $symfonyApp = '/path/to/your/symfony2app';
    require_once $symfonyApp . '/app/AppKernel.php';
 
    $kernel = new \AppKernel('prod', true);
    $kernel->loadClassCache();
    $kernel->boot();
 
    $this->doctrine = $kernel->getContainer()
                             ->get('doctrine')
                             ->getManager();
}

O uso da classe AppKernel do Symfony2 é muito semelhante ao código empregado no arquivo web/app.php da distribuição padrão do Symfony.

No nosso caso, usamos o doctrine manage service porque nosso aplicativo Symfony2 empregava uma entidade simples do Doctrine como modelo. Dependendo do seu aplicativo Symfony2, é possível obter o serviço/objeto específico que você precisa consumir na API do contêiner do seu aplicativo ($kernel-> getContainer()).

Usando o $this->objeto doctrine, podemos reutilizar a Entidade Post do aplicativo Symfony2 em todos os métodos da API. Você pode ver como implementamos nossa classe PostResource aqui.

Veja que podemos retornar o objeto de entidade post como resultado em todos os métodos da API, pois especificamos o uso do hidratador ClassMethods em nosso arquivo de configuração config/module.config.php.

Conclusão

No nosso caso de uso, mostramos como reutilizar um aplicativo Symfony2 já existente e consumir a parte do Model para construir uma API RESTful. Decidimos inicializar o aplicativo Symfony2 porque desse modo estaremos de fato reutilizando código existente, especialmente se a parte do Model de seu aplicativo emprega um serviço diferente, e não apenas entidades do Doctrine.

Em relação ao desempenho dessa abordagem, posso dizer que está relacionado a implementações específicas do aplicativo Symfony2. De um modo geral, o desempenho da parte de inicialização do Symfony2 é bom graças ao mecanismo de carregamento “lazy” da estrutura. Claro, você pode reduzir o tempo da inicialização da API removendo os serviços e os componentes que não são necessários para a parte de Model.

Se estiver interessado em otimização, sugiro que dê uma olhada no app/AppKernel.php e no componente Symfony\Component\HttpKernel\Kernel do Symfony2.

Caso seu aplicativo Symfony2 empregue somente Doctrine como Model, você também pode usar diretamente o módulo ZF2 do Doctrine2 para criar a API, em vez de inicializar o aplicativo Symfony2. Recentemente, Tom Anderson desenvolveu uma ferramenta para criar uma API Apigility a partir de entidades de Doutrina no escopo. Você encontra esse projeto aqui.

O caso de uso apresentado neste artigo pode ser considerado uma primeira abordagem para o uso do Apigility para criar uma API para Symfony2. Tenho certeza de que veremos mais exemplos num futuro próximo.

Para ter mais informações sobre o Apigility e acompanhar as implementações futuras do projeto, você pode visitar a página apigility.org.

***

Artigo traduzido pela Redação iMasters com autorização do autor. Publicado originalmente em http://www.zimuel.it/create-api-symfony2-apigility/