Hoje!
PHP

Aprenda a usar o RESTful com PHP e Slim Framework

PorDaniel Schmitz em

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!

Deixe um comentário! 40

40 comentários

Comentários

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Comentando como Anônimo

  1. “Existe uma mudança drástica entre programar MVC e REST” Na minha opinião isso não tem nada a ver.
    Eu posso desenvolver uma app com arquitetura MVC e ainda servir REST. Essa comparação está totalmente equivocada. Você está comparando coisas que podem se complementar.

    1. Estou de acordo também… Uma coisa é uma coisa, outra coisa é outra coisa…

      “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.”

      Olha, mais um equívoco aqui… O intuito do MVC é justamente separar as camadas. Se implementado da maneira correta, nenhuma dado fica amarrado a sua visualização.

      De qualquer forma, seu artigo está muito bom, simples e direto ao ponto…

    2. Concordo Wesley,

      acredito que o MVC e o REST podem trabalhar juntos de forma bem satisfatória, basta usa-los da forma correta. Realmente podemos ter uma separação maior se não delegarmos ao controller o trabalho de renderizar views.
      Enfim, essa discussão pode ser bem técnica, mas Daniel parabéns pelo artigo, muita gente pode aprender o conceito de REST de uma forma intuitiva.

    3. O autor estar correto. Tbm não sei pq tanta paixão pelo padrão MVC, qual framework atual é totalmente baseado em somente prover os dados de maneies compatível com as tecnológicas? O Restfull vai dominar, precia apenas melhorar a segurança.

  2. O artigo é bacana, mas o autor se equivoca completamente quanto REST e MVC. Uma não exclui a outra. O papel do REST é desempenhado perfeitamente pelo Controller que pode muito bem usar uma camada Model.

    E a VIEW independe de framework, a diferença é que o seu controller no REST não está “delegando” a uma view a responsabilidade por tratar os dados, mas apenas entregando os dados, que pode ser consumida e tratada por uma view em JS, por exemplo.

    REST, pode inclusive ser consumido por outra aplicação já em MVC via WebServices.

  3. Concordo em parte com todos vocês: Wesley, Henrique e Marcelo. O MVC pode sim implementar um REST,, ambos podem trabalhar juntos. Mas o que acontece hoje com os programadores é que a maioria usa o controller para delegar a renderização da view, sendo que podem usar de forma mais eficiente, que é utilizando REST e separando mesmo as camadas, tanto que em um artigo futuro irei utilizar MVC no cliente e MVC no servidor, mas com REST “no meio”.

  4. Só uma observação. Pelo que me lembro (não sou especialista no assunto, nunca estudei a fundo), requisições para ATUALIZAR dados devem ser feitas com o método PUT

  5. Não há muita diferença em um CRUD, apenas a resposta…. se for assim todas aplicações que faço já utilizo RESETful pra DELETE (ajax envia o comando e retorna json)…

    Acho que a Autenticação que é importante ficou faltando…

  6. Bom o artigo, confesso que achei que fosse mais fundo, ficou quase um tuto do Slim, mas ficou muito bacana esse post.

    Lembrando que a maioria dos browsers suportam apenas GET e POST.
    Se quiserem testar REST servers no browser, recomendo o addon para Firefox: RESTClient (http://bit.ly/URNZac).

    Com relação a MVC x REST, concordo totalmente que os dois se complementam, eu uso MVC mas minha view é uma factory que implementa minha classe HttpResponse, assim eu posso “cuspir” JSON, XML, YAML, CSV, *PQP*… hehe

    Ou seja, o formato fica a gosto do cliente. O que acham dessa abordagem?

    Abraço.

  7. Pessoal,

    eu estava escrevendo o artigo sobre consumo do RESTful pelo jQuery e nos meus estudos

    aqui achei esse ótimo artigo http://coenraets.org/blog/2011/12/restful-services-with-

    jquery-php-and-the-slim-framework/ que é mais ou menos o que eu estava fazendo para

    vocês.

    O fato é que eu estou muito desmotivado em continuar a escrever em pt_br, não só pelos

    últimos artigos mas por outros fatores externos que de certa forma me fizeram mudar os planos tanto na iMasters quanto nos meus sites. A partir de agora, não irei mais produzir conteúdo em pt_br, nem aqui nem em nenhum lugar. Eu queria agradecer a todos e, de certa forma, pedir desculpas a quem estava esperando pelos próximos artigos. Mas o link que apresentei já resume todo o processo. Basta ler o código fonte. A todos um ótimo 2013!

    1. Bom, não entendo o porque da ‘revolta’, você vai achar 70 formas de fazer um bolo, cada um pega o que acha melhor. Uma coisa é você fazer da sua forma outra é copiar algum da internet. EU acho que ninguém desenvolve igual..

      EU acho que o problema não é fazer algo em pt_br ou em inglês e sim variar e adaptar com novas tendências e a nossa realidade. Pensou em fazer esse exemplo de RESTfull com Jquery usando frisbyjs para testar?

      Abraços a todos

      1. Daniel. Queria saber como eu usaria esse exemplo do slim, para inserir um produto através de um formulário simples. Não entendi como funciona curl? E so consegui fazer o exemplo ate a parte de lista categorias. Sou leigo no assunto, então desculpe pela pergunta idiota, rs.
        Exemplo:

        Cod. Produto:
        Nome:

      2. O formulário nao saiu, qdo eu mandei enviar minha opinião. Formulário simples, me refiro a doc html com uma tabela, com dois campos do tipo text e um submit.

  8. Otimo artigo. muito claro e objetivo, parabens.

    Só uma dúvida: e se eu precisar excluir vários registros selecionados por exemplo, como ficaria?

      1. Obrigado por responder, mas ai teria que ser na propria url, né? no caso meus ids são chaves de 32 chars

      2. Na url não, usando DELETE envia como raw body (pode ser JSON por exemplo).

        Ex:

        $.ajax({
        type: “DELETE”,
        url: rootUrl + “produto/deleteProdutos”,
        dataType: “json”,
        data: JSON.stringify({ids: [1,25,67,182,76,190]}),
        success: function() {
        //todo
        },
        error: function() {
        //todo
        }
        });

  9. Eu fiquei fascinado com o livro, mas ficou faltando algo que não encontro em lugar nenhum, nem no livro.

    Como faço para fazer o upload funcionar, eu tinha uma rotina muito boa com barra de progresso e tal, mas não consigo faze-la funcionar.

  10. Na inserção do produto, nao está chegando o POST, e com isso sempre retorna
    {
    “nome”: null,
    “preco”: null,
    “dataInclusao”: null,
    “idCategoria”: null,
    “id”: “0”
    }

    Será que houve alguma mudança no Slim para pegar o POST?

    1. Carlos, eu testei aqui usando o REST Console e todos os metodos funcionaram. Verifique o metodo POST do seu cliente esta sendo executado de forma correta, e passando um objeto JSON no body.

  11. Perfeito. Mas fica faltando implementar segurança(alguma forma de autenticação) e exemplo de upload. Alguém implementou isso ao exemplo e gostaria de partilhar?

  12. Daniel, estou utilizando o Slim para receber dados do Intel XDK, via ajax, mas os dados não chegam ao script php, tem usar mesmo a instrução :
    $request = \Slim\Slim::getInstance()->request();
    $data = $request->getBody(); ?

    dá para passar os dados por Array ou objeto?
    Como seria o recebimento?

    Estou enviando com ajax assim:
    $.ajax({
    type: ‘POST’,
    dataType: ‘json’,
    url: “http://localhost/cadclientes/api/cliente”,
    data: JSON.stringify( { nome: $(“#input_nome”).value(), fone: $(“#input_fone”).value(), email: $(“#input_email”).value() } ),
    contentType: “application/json; charset=utf-8”,
    success: function(data){
    alert(“Valor 1 “+data.nome+””);
    },
    });

    Agradeço pela sua atenção.

  13. Sou iniciante em PHP sei muito pouco, adorei o primeiro contato com MVC, mas agora no meu ambiente de trabalho me força a mudar, sou o único programador que foge do ambiente de trabalho onde nosso sistema foi desenvolvido, e todas as “coisas” que fogem da nossa linguagem sou eu quem tem que resolver, e agora me deparo com varias aplicações que usam REST e gostei muito do artigo de vocês, obrigado pelo compartilhamento de informações.

    1. Sua dúvida é um pouco complicada de responder, pois o REST não é preso a tecnologia, então depende do framework tanto do lado servidor quanto do lado cliente, porém não é diferente de uma requisição padrão como ocorreria no MVC.

      O que muda é que quem vai passar os parâmetros para o servidor é o javascript através do AJAX, se vc vai usar algum framework Javascript como Angular, React, VueJs, Aurelia etc ou Javascript puro é com você, mas o servidor vai esperar uma requisição, a novidade é que o servidor normalmente estera os verbos GET ou POST, mas com REST poderá passar os verbos PUT e DELETE também. O servidor vai esperar a chegada dos parâmetros tal como esperaria caso fosse apenas um POST de um FORM Html padrão, mas com uma diferença, ou vai vir JSON ou XML, sendo moderno será JSON; ao retornar informação, o servidor vai retornar do mesmo jeito. O que muda é que você não terá uma camada VIEW no servidor e sim um retorno em formato JSON onde o AJAX estará esperando a resposta dos parâmetros que passou.

      Não sei se fui claro, mas é isso.

    2. Olá diego, A requisição pode retornar um objeto contendo esses dois parâmetros, lembrando que toda requisição retorna um texto simples que na maioria das vezes está no formato json.

leia mais
Este projeto é mantido e patrocinado pelas empresas: