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);
    });
    
  • GET /livros/{id}
  • 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.

Comments

  1. By Clebson

    Reply

  2. By Clebson

    Reply

  3. Reply

Deixe um comentário

Follow

Get every new post on this blog delivered to your Inbox.

Join other followers: