Aplicação REST simples com Silex, parte I
Introdução
Nester artigo vamos realizar a criação de um simples aplicativo REST usando o micro framework Silex, mapeando operações CRUD através dos métodos HTTP GET, POST, PUT e DELETE.
O tutorial esta divido em 2 partes. Clique aqui para ir para a segunda parte.
Por que Silex?
Micro frameworks são interessantes para criação de APIs REST. E para ensinar conceitos de webservices REST escolhi o micro framework Silex. Vou citar abaixo algumas das vantagens pelo qual apoio a escolha do Silex:
- O Silex é baseado no Symfony, que permite a integração com diversos outros componentes deste framework. Sendo modular e instalando apenas o necessário.
- Há um grande número de pessoas por trás da comunidade do Silex.
- Suporte a longo prazo garantido.
- Um dos micro frameworks mais rápidos.
- Ideal tanto para pequenos quanto grandes projetos.
- Excelente documentação.
- Micro-frameworks permitem mais liberdade na maneira de trabalhar do que frameworks tradicionais.
Instalando Silex
A forma mais prática e recomendada de instalar o Silex é através do composer. Crie o arquivo composer.json com o conteudo:
{ "require": { "silex/silex": "~1.3" } }
Execute:
$ php composer.phar install
Database Schema
Vamos usar a seguinte estrutura de dados para nosso aplicativo:
CREATE DATABASE teste; -- ----------------------------------------------------- -- Table `teste`.`livros` -- ----------------------------------------------------- CREATE TABLE `teste`.`livros` ( `id` INT NOT NULL AUTO_INCREMENT, `titulo` VARCHAR(255) NOT NULL, `autor` VARCHAR(255) NOT NULL, `isbn` VARCHAR(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB; -- permissao de acesso GRANT ALL ON teste.* TO livros@localhost IDENTIFIED BY 'livros2016'; -- inserts USE teste; insert into livros (titulo, autor, isbn) VALUES('PHP Moderno', 'Josh Lockhart', '978-85-7522-428-1'); insert into livros (titulo, autor, isbn) VALUES('Web Services em PHP', 'Lorna Jane Mitchell', '978-85-7522-369-7'); insert into livros (titulo, autor, isbn) VALUES('Módulos para Magento', 'André Gugliotti', '978-85-7522-432-8');
Conteúdo da tabela livros:
mysql> select * from livros; +----+-----------------------+---------------------+-------------------+ | id | titulo | autor | isbn | +----+-----------------------+---------------------+-------------------+ | 1 | PHP Moderno | Josh Lockhart | 978-85-7522-428-1 | | 2 | Web Services em PHP | Lorna Jane Mitchell | 978-85-7522-369-7 | | 3 | Módulos para Magento | André Gugliotti | 978-85-7522-432-8 | +----+-----------------------+---------------------+-------------------+ 3 rows in set (0,00 sec)
Rotas
Nesta primeira parte, vamos criar apenas rotas de consulta de dados no silex. No caso, teremos duas rotas: Uma para consultar todos os livros, e outra para consultar apenas um determinado livro, informando o id.
GET /livros
Essa rota esta designada para trazer todos livros cadastrados através de uma requisição GET. Os dados serão retornados no formato JSON. Junto com a representação dos dados devemos retornar HTTP statuscode 200.
GET /livros/{id}
Através dessa requisição será possível consultar a informação de um determinado livro informando o id (campo identificador), através de uma requisição GET. Caso o id informado não exista na lista de livros, devemos retornar o statuscode HTTP 404. Caso exista, devemos retornar o statuscode HTTP 200, junto com a representação do recurso em formato JSON.
Configurando Webserver
Para utilizar o Silex é necessário uma pequena configuração de redirecionamento no WebServer (Apache, nginx, php webserver bulti-in, etc.). Realize a configuração de acordo com a documentação oficial do silex: http://silex.sensiolabs.org/doc/web_servers.html
Implementação
A partir do diretório onde executou o composer, crie o arquivo web/index.php, ele será nosso container app do Silex. A estrutura de diretório do nosso aplicativo exemplo ficará semelhante à abaixo:
|-web/ |----index.php |----.htaccess |-vendor/ |-composer.json
Aplicação mínima do Silex:
// web/index.php <?php // timezone date_default_timezone_set('America/Sao_Paulo'); require_once __DIR__ . '/../vendor/autoload.php'; $app = new Silex\Application(); $app->run();
Neste momento, ao acessar a aplicação será retornado página não encontrada, 404, pois não temos nenhuma rota configurada ainda.
Primeiramente vamos configurar o acesso ao banco de dados com PDO:
/* Connect to mysql atabase */ $dsn = 'mysql:dbname=teste;host=127.0.0.1;charset=utf8'; try { $dbh = new PDO($dsn, 'livros', 'livros2016'); } catch (PDOException $e) { echo 'Connection failed: ' . $e->getMessage(); }
Rotas:
- GET /livros
Retorna o conteúdo completo da tabela livros em formato JSON: (Por padrão o statuscode retornado é 200)
$app->get('/livros', function () use ($app, $dbh) { // consulta todos livros $sth = $dbh->prepare('SELECT id, titulo, autor, isbn FROM livros'); $sth->execute(); $livros = $sth->fetchAll(PDO::FETCH_ASSOC); return $app->json($livros); });
Nesta rota é possível consultar um determinado livro apenas, informando o ID correspondente. Caso o ID não for encontrado, dizemos ao silex para retornar 404 (page not found). Também fazemos a validação que o campo ID seja numérico usando o método assert():
$app->get('/livros/{id}', function ($id) use ($app, $dbh) { $sth = $dbh->prepare('SELECT id, titulo, autor, isbn FROM livros WHERE id=?'); $sth->execute([ $id ]); $livro = $sth->fetchAll(PDO::FETCH_ASSOC); if(empty($livro)) { // nao encontrado, 404 return new Response("Livro com id {$id} não encontrado para consulta!", 404); } return $app->json($livro); })->assert('id', '\d+');
Juntando tudo, temos o arquivo final index.php:
<?php // timezone date_default_timezone_set('America/Sao_Paulo'); // web/index.php require_once __DIR__ . '/../vendor/autoload.php'; $app = new Silex\Application(); /* Connect to mysql atabase */ $dsn = 'mysql:dbname=teste;host=127.0.0.1;charset=utf8'; try { $dbh = new PDO($dsn, 'livros', 'livros2016'); } catch (PDOException $e) { echo 'Connection failed: ' . $e->getMessage(); } /* Rotas */ $app->get('/livros', function () use ($app, $dbh) { // consulta todos livros $sth = $dbh->prepare('SELECT id, titulo, autor, isbn FROM livros'); $sth->execute(); $livros = $sth->fetchAll(PDO::FETCH_ASSOC); return $app->json($livros); }); $app->get('/livros/{id}', function ($id) use ($app, $dbh) { $sth = $dbh->prepare('SELECT id, titulo, autor, isbn FROM livros WHERE id=?'); $sth->execute([ $id ]); $livro = $sth->fetchAll(PDO::FETCH_ASSOC); if(empty($livro)) { // nao encontrado, 404 return new Response("Livro com id {$id} não encontrado para consulta!", 404); } return $app->json($livro); })->assert('id', '\d+'); $app->run();
Testando as Rotas
Para testar nossa implementação REST vamos usar o curl através da linha de comando. O curl facilita o envio de requisições POST, PUT e DELETE para nossa API. Uma ferramenta gráfica recomendada para testes de API também é o postman.
PS: O parâmetro -i do curl faz exibir os headers de resposta da requisição HTTP.
GET /livros
Retornando todos livros:
$ curl -i http://localhost/livros
HTTP/1.1 200 OK Cache-Control: no-cache Content-Length: 291 Content-Type: application/json [{"id":"1","titulo":"PHP Moderno","autor":"Josh Lockhart","isbn":"978-85-7522-428-1"},{"id":"2","titulo":"Web Services em PHP","autor":"Lorna Jane Mitchell","isbn":"978-85-7522-369-7"},{"id":"3","titulo":"M\u00f3dulos para Magento","autor":"Andr\u00e9 Gugliotti","isbn":"978-85-7522-432-8"}]
GET /livros/{id}
Retornando o livro com id = 2
$ curl -i http://localhost/livros/2
HTTP/1.1 200 OK Cache-Control: no-cache Content-Length: 100 Content-Type: application/json [{"id":"2","titulo":"Web Services em PHP","autor":"Lorna Jane Mitchell","isbn":"978-85-7522-369-7"}]
Requisitando um livro inexistente:
$ curl -i http://localhost/livros/10
HTTP/1.1 404 Not Found Cache-Control: no-cache Content-Length: 46 Content-Type: text/html; charset=UTF-8 Livro com id 10 não encontrado para consulta!
No caso acima poderíamos fazer o retorno da mensagem de erro em JSON ou usando um formato de erro especificado em http://tools.ietf.org/html/draft-nottingham-http-problem-07. Veremos mais detalhes em um tutorial futuro.
Continuação
Na parte II desta série vamos dar continuidade ao nosso aplicativo REST, incluíndo a possibilidade de inserir, atualizar e remover livros. Veremos também algumas sugestões e melhorias que você pode fazer no aplicativo afim de evoluir para melhores práticas.
Bom dia Douglas. Tudo bem? Parabéns pelo post.
Tenho uma api feita em silex, ela funciona muito bem em servidor local(wamp ou xampp), porém quando envio pra minha hospedagem web da erro. Imagino que seja alguma configuração no .htaccess
Testei nas hospedagens hostinger e hostgator e não funciona.
Pode me da alguma dica pra fazer minha api silex funcionar em uma hospedagem web compartilhada?
Olá Clebson,
Verifica a documentação do silex que fala sobre a configuração que deve ser feita nos servidores (apache ou nginx):
http://silex.sensiolabs.org/doc/master/web_servers.html
Você deve configurar pelo .htaccess mesmo! deve resolver..
Já segui a documentação, mas continua rodando apenas no servidor local.
Só complementar, adicionar após o require_once __DIR__ . ‘/../vendor/autoload.php’;
ADD: use Symfony\Componet\HttpFoudation\Response;