Back-End

13 nov, 2012

Aprenda a usar o RESTful com PHP e Slim Framework

Publicidade

No artigo anterior, foi abordado um exemplo envolvendo um padrão de comunicação entre cliente e servidor. Aquele exemplo em tese não significava a princípio que estávamos implementando REST com todas suas regras, mesmo porque criamos apenas uma forma de responder requisições HTTP através de um formato único de resposta. Existe um padrão arquitetural que define o REST, que pode ser encontrado na wikipedia, e que não vamos a princípio abordar. O que precisamos saber é que, deste padrão arquitetural surge outro conceito chamado RESTful, que nada mais é que uma implementação do REST, de forma semelhante aos webservices.

Quando falamos em RESTful, estamos abordando uma forma de acesso a dados semelhante aos webservices, mas que obedecem a arquitetura REST formal. Este padrão pode ser compreendido da seguinte forma:

  • RESTful também obedece ao padrão arquitetural REST, então usa HTTP e responde em um formato conhecido (JSON);
  • RESTful aceita GET, POST, UPDATE, DELETE como métodos HTTP;
  • RESTful possui uma URI em forma de API, em conjunto com os métodos HTTP.

Se a teoria não forneceu uma dimensão sobre o RESTful, vamos à prática para que possamos exemplificar o processo.

Slim Framework

Existem dezenas de frameworks que implementam a arquitetura REST e vamos começar com o Slim Framework, que é bastante leve e prático, possuindo como principal característica a implementação RESTful. Se você acessar o site oficial, poderá ver todas as características que o framework possui.

Para fazer a instalação, acesse o site oficial, clique em “install now” e depois faça o download da Stable Release.  Após realizar o download, descompacte o framework no webroot do seu servidor web (aqui estou usando Wamp, então: c:\wamp\www). Após descompactar, teremos uma estrutura semelhante a seguinte:

E quando acessamos http://localhost/Slim, é apresentada a página Slim padrão:

A partir deste momento, poderemos criar a aplicação baseada neste framework.

Tabelas

Vamos criar uma aplicação simples para o cadastro de produtos (SlimProdutos), contendo duas tabelas (produtos e categorias), utilizando o seguinte esquema:

A SQL para que você possa gerar as tabelas é a seguinte:

CREATE TABLE IF NOT EXISTS `SlimProdutos`.`Categorias` (
`id` INT NOT NULL AUTO_INCREMENT ,
`nome` VARCHAR(100) NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS `SlimProdutos`.`Produtos` (
`id` INT NOT NULL AUTO_INCREMENT ,
`nome` VARCHAR(100) NULL ,
`preco` DECIMAL(10,2) NULL ,
`dataInclusao` DATE NULL ,
`idCategoria` INT NOT NULL ,
PRIMARY KEY (`id`) ,
INDEX `fk_Produtos_Categorias_idx` (`idCategoria` ASC) ,
CONSTRAINT `fk_Produtos_Categorias`
FOREIGN KEY (`idCategoria` )
REFERENCES `SlimProdutos`.`Categorias` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;

Definindo a Api

Nossa primeira tarefa é programar o backend, onde teremos os métodos capazes de manipular dados. Antes de programar, lembre-se que estamos obedecendo ao RESTful, e que nele temos que montar uma Web API. A grosso modo, uma WebAPI é uma tabela de recursos que o servidor oferece. Um recurso é uma chamada HTTP que pode ser realizada através dos métodos GET, POST, PUT, DELETE. Para o nosso exemplo, iremos fornecer a seguinte API:

    • GET /produtos/    Retorna uma lista de todos os produtos cadastrados;
    • GET /produtos/<id>  Retorna o produto de acordo com a sua chave primária <id>;
    • POST /produtos/  Salva o objeto produto. Como não há id, gera um INSERT;
    • POST /produtos/<id>  Salva o objeto produto. Neste caso realiza um UPDATE. Poderia usar PUT também;
    • DELETE /produtos/<id> Apaga o objeto produto;
    • GET /categorias/ Obtém a lista de categorias.

Criando o projeto

Crie a pasta SlimProdutos no seu WebRoot (c:\wamp\www) e certifique-se que ela esteja no mesmo nível da pasta Slim. Copie o arquivo .htaccess da pasta Slim para SlimProdutos e crie o arquivo index.php, inicialmente com o seguinte código:
<?php
require '../Slim/Slim/Slim.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
$app->response()->header('Content-Type', 'application/json;charset=utf-8');
$app->get('/', function () {
echo "SlimProdutos";
});

$app->run();

Este é o esqueleto básico de uma aplicação RESTful com o framework Slim. Incluímos a biblioteca, instanciamos um objeto ($app) e mapeamos o “GET /” para exibir uma mensagem. Isso faz com que ao acessarmos http://localhost/SlimProdutos, a mensagem seja exibida.

Exibindo as categorias

Para exibirmos as categorias de um produto, teremos que alterar o código para que possamos mapear o “GET /categorias”, ler o banco de dados e exibir informações. Vamos supor que já foram adicionadas algumas categorias na tabela. Vamos criar uma função que retorna a conexão com o banco de dados e depois mapear pelo Slim o método para obter categorias:

<?php

require '../Slim/Slim/Slim.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
$app->response()->header('Content-Type', 'application/json;charset=utf-8');

$app->get('/', function () {
echo "SlimProdutos ";
});

$app->get('/categorias','getCategorias');

$app->run();

function getConn()
{
return new PDO('mysql:host=localhost;dbname=SlimProdutos',
'root',
'',
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
);

}

function getCategorias()
{
$stmt = getConn()->query("SELECT * FROM Categorias");
$categorias = $stmt->fetchAll(PDO::FETCH_OBJ);
echo "{categorias:".json_encode($categorias)."}";
}

As duas partes mais importantes deste código são:

  1. O mapeamento get de /categorais: $app->get(‘/categorias’,’getCategorias’);
  2. Na função getCategorias, o retorno json:   echo “{categorias:”.json_encode($categorias).”}”;

Ao testarmos o método, acessando http://localhost/SlimProdutos/categorias, poderemos ver a resposta. Algo do tipo:

{categorias:[{"id":"1","nome":"Eletr\u00f4nicos"},{"id":"2","nome":"Inform\u00e1tica"}]}

Incluindo produtos

Agora vamos incluir um produto. Após esta tarefa, conseguiremos executar duas operações básicas, que é consultar e inserir dados, sendo que as outras operações serão muito semelhantes. Começamos, então, mapeando a requisição no Slim, da seguinte forma:

$app->post('/produtos','addProduto');

Coloque este código abaixo do mapeamento “GET /categorias”. O novo mapeamento diz que se houver uma requisição “POST /produtos”, o método addProduto será chamado. Este método está descrito a seguir:

function addProduto()
{
$request = \Slim\Slim::getInstance()->request();
$produto = json_decode($request->getBody());
$sql = "INSERT INTO produtos (nome,preco,dataInclusao,idCategoria) values (:nome,:preco,:dataInclusao,:idCategoria) ";
$conn = getConn();
$stmt = $conn->prepare($sql);
$stmt->bindParam("nome",$produto->nome);
$stmt->bindParam("preco",$produto->preco);
$stmt->bindParam("dataInclusao",$produto->dataInclusao);
$stmt->bindParam("idCategoria",$produto->idCategoria);
$stmt->execute();
$produto->id = $conn->lastInsertId();
echo json_encode($produto);
}

Veja que o método é um pouco mais complexo que o primeiro, mas é de fácil compreensão. Inicialmente, obtemos a requisição POST para que possamos obter os dados que estão vindo do “formulário”. Colocamos formulário entre parêntesis porque, na verdade, a requisição não sabe de onde estes dados vieram, sabe apenas que existe um objeto JSON representando esse objeto e que este JSON está no corpo (“body”) da requisição.

Através do json_decode, nós pegamos o JSON do corpo da requisição ($request->getBody()) e o transformamos em um objeto armazenado na variável $produto. A partir da linha onde montamos o SQL, estamos preparando tudo para gerar o INSERT, utlizando PDO. Para finalizar, utilizamos o comando lastInsertId para retornar o último id gerado pelo INSERT, e retornamos novamente o objeto $produto, só que agora com o id preenchido.

Como testar se ainda não temos um cliente?

As aplicações RESTful são criadas de forma completamente separada da interface. Ainda não criamos uma. Sendo assim, como podemos testar a nossa aplicação se ainda não existe um “input”? Você pode usar uma ferramenta chamada curl para fazer a requisição ao servidor. No exemplo de inclusão de produtos, você poderia executar o seguinte comando:

curl -i -X POST -H 'Content-Type: application/json' -d '{"nome": "Produto 1", "idCategoria": "1"}' http://localhost/SlimProdutos/produtos

Para facilitar, recomendo utilizar uma app do Google Chrome chamada REST Console, muito útil para testes rápidos! Existem dezenas de aplicações com esta funcionalidade, fique a vontade em testá-las.

Obtendo um produto

Através do id de um produto, podemos obter suas informações. O mapeamento da api pode ser configurado pelo seguinte código:

$app->get('/produtos/:id','getProduto');

Repare que usamos uma variável :id que determina o campo id da tabela Produtos. Este :id será um parâmetro repassado para o método getProduto, conforme o código a seguir:

function getProduto($id)

{
$conn = getConn();
$sql = "SELECT * FROM produtos WHERE id=:id";
$stmt = $conn->prepare($sql);
$stmt->bindParam("id",$id);
$stmt->execute();
$produto = $stmt->fetchObject();

//categoria
$sql = "SELECT * FROM categorias WHERE id=:id";
$stmt = $conn->prepare($sql);
$stmt->bindParam("id",$produto->idCategoria);
$stmt->execute();
$produto->categoria = $stmt->fetchObject();

echo json_encode($produto);
}

Neste código, repassamos o :id do mapeamento para o parâmetro $id do método. Ele é usado no SQL de produtos para que se possa obter o registro da tabela. Veja que criamos a variável $produto como resultado da SQL e que depois realizamos uma segunda consulta ao banco de dados, para se obter a categoria do produto. O resultado final dessa consulta é um JSON semelhante ao exibido a seguir:

{
"id": "1",
"nome": "Produto1",
"preco": null,
"dataInclusao": null,
"idCategoria": "1",
"categoria": {
"id": "1",
"nome": "Eletr\u00f4nicos"
}
}

Editando o produto

De acordo com a API configurada, quando realizamos um POST repassando o id do produto, estamos salvando o objeto, ou seja, executando o comando update. O mapeamento Slim é o seguinte:

$app->post('/produtos/:id','saveProduto');

E o código:

function saveProduto($id)
{
$request = \Slim\Slim::getInstance()->request();
$produto = json_decode($request->getBody());
$sql = "UPDATE produtos SET nome=:nome,preco=:preco,dataInclusao=:dataInclusao,idCategoria=:idCategoria WHERE   id=:id";
$conn = getConn();
$stmt = $conn->prepare($sql);
$stmt->bindParam("nome",$produto->nome);
$stmt->bindParam("preco",$produto->preco);
$stmt->bindParam("dataInclusao",$produto->dataInclusao);
$stmt->bindParam("idCategoria",$produto->idCategoria);
$stmt->bindParam("id",$id);
$stmt->execute();

echo json_encode($produto);

}

Apagando o produto

Para apagar o produto, é enviado o método http DELETE, conforme o mapeamento:

$app->delete('/produtos/:id','deleteProduto');

E o código:

function deleteProduto($id)
{
$sql = "DELETE FROM produtos WHERE id=:id";
$conn = getConn();
$stmt = $conn->prepare($sql);
$stmt->bindParam("id",$id);
$stmt->execute();
echo "{'message':'Produto apagado'}";
}

Obtendo todos os produtos

Nossa última operação é obter todos os produtos cadastrados no banco. Vamos fazer de uma forma simples e sem paginação. O mapeamento da API que especificamos é a seguinte:

$app->get('/produtos','getProdutos');

E o código:

function getProdutos()
{
$sql = “SELECT *,Categorias.nome as nomeCategoria FROM Produtos,Categorias WHERE Categorias.id=Produtos.idCategoria”;
$stmt = getConn()->query($sql);
$produtos = $stmt->fetchAll(PDO::FETCH_OBJ);
echo “{\”produtos\”:”.json_encode($produtos).”}”;
}

Com os métodos prontos, nossa API especificada está pronta e pode ser consumida por qualquer cliente, e é isso que faremos no próximo artigo.

 Código Fonte

Conclusão (o porquê da evolução do “MVC para REST”)

Aqui começamos a enxergar a diferença de desenvolvimento da metodologia MVC e do REST. Se estivéssemos no MVC, iriamos criar o Model, o Controller e a View. Estaríamos “preso” a algum framework PHP, Ruby on Rails, ou qualquer outro que “amarra” os dados à sua visualização. É neste ponto que quero chegar e que quero que vocês entendam.

Existe uma mudança drástica entre programar MVC e REST. Neste artigo, estamos usando RESTful e criando uma API de acesso a dados sem ao menos nos preocuparmos com a visualização de dados. É algo que não existe por enquanto e em um breve futuro será assim. Iremos programar APIs e deixar a visualização de dados para depois. Estamos migrando do MVC para o REST, estamos evoluindo. Mesmo que MVC seja uma coisa e REST seja outra, estamos mudando o nosso paradigma de programação, assim como DESKTOP é uma coisa e WEB é outra, e, se em 1995 fosse dito que estávamos migrando de Desktop para Web, haveríamos críticas do tipo “desktop é uma coisa web que roda num browser e é outra”.

Minha convicção é que, em alguns anos, ainda haverá MVC mas a metodologia REST será empregada em conjunto para que haja uma separação muito forte entre dados e visualização, mesmo porque a quantidade de dispositivos de interface (web,desktop,mobile,tablet) estará muito variada e fatalmente o designer terá que desenvolver aplicações distintas para elas, consumindo a mesma fonte de dados.

No caso fictício da empresa no primeiro artigo desta série, ao criarmos uma api RESTful do sistema, estaríamos preparando o sistema para qualquer tipo  de mudança da camada de visualização, além de permitir que diversos tipos de views possam  consumir.

Até a próxima, pessoal!