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.

Please follow and like us:

Comments

  1. By Clebson

    • mm By Douglas V. Pasqua

  2. By Clebson

Follow

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

Join other followers: