Dev. Software/ PHP

Implementando REST com PHP

rest_sample

Não faz muito tempo que eu escrevi sobre REST aqui no blog, e no post eu prometi que iria continuar com o assunto implementando serviços REST em diversas linguagens PHP, Java, C++, Python e até mesmo em Javascript.

Se você não sabe o que é REST, antes de continuar lendo o post, aconselho você ler o post que escrevi sobre o assunto, assim você não fica boiando no assunto.

Configurando o ambiente

Quem não tem um ambiente local para testar com PHP, sugiro um software que eu utilizo localmente o XAMPP. É open-source e grátis.
Existem outros no mercado que também faz o mesmo, só que eu gosto do XAMPP por que é prático.

Implementando o REST

Para implementar o REST você pode utilziar diversas bibliotecas espalhadas por ai, tem várias publicações e projetos que te levam à um atalho mais rápido para criar tal mecanismo. As que eu achei mais fantásticas foram essas:

O que eu acho mais fantástico nessas implementações são a abstração, eles basicamente deixam você livre para fazer o que quiser, a curva de aprendizagem também é bem mole.

O que um difere do outro?

Alguns recursos extras, por exemplo o Guzzle oferece uma arquitetura de plug-ins, onde você pode implementar suporte já fora da caixa em Cache, Oauth, etc. Já o Slim não tem tudo isso, porém oferece uma API mais simples de se dar.

Nesse exemplo eu vou usar o Slim Framework, baixe ele direto do site, depois de baixar descompacte-o no diretório raiz do XAMPP (htdocs), para ser mais direto.

rest-php

Na imagem acima, você ver que eu deixei só o diretório Slim, o arquivo .htdocs e o index.php

[note color=”#005fff”]IMPORTANTE: O Arquivo de .htaccess existente no projeto tem as regras necessárias para criação de rotas das URLs necessárias, você precisa tê-lo para usar o Slim.[/note]

Ok, abra o arquivo index e você tem o seguinte conteúdo.

<?php
/**
 * Step 1: Require the Slim Framework
 *
 * If you are not using Composer, you need to require the
 * Slim Framework and register its PSR-0 autoloader.
 *
 * If you are using Composer, you can skip this step.
 */
require 'Slim/Slim.php';
 
\Slim\Slim::registerAutoloader();
 
/**
 * Step 2: Instantiate a Slim application
 *
 * This example instantiates a Slim application using
 * its default settings. However, you will usually configure
 * your Slim application now by passing an associative array
 * of setting names and values into the application constructor.
 */
$app = new \Slim\Slim();
 
/**
 * Step 3: Define the Slim application routes
 *
 * Here we define several Slim application routes that respond
 * to appropriate HTTP request methods. In this example, the second
 * argument for `Slim::get`, `Slim::post`, `Slim::put`, and `Slim::delete`
 * is an anonymous function.
 */
 
// GET route
$app->get('/', function () {
    $template = <<<EOT
<!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8"/>
            <title>Slim Framework for PHP 5</title>
            <style>
                html,body,div,span,object,iframe,
                h1,h2,h3,h4,h5,h6,p,blockquote,pre,
                abbr,address,cite,code,
                del,dfn,em,img,ins,kbd,q,samp,
                small,strong,sub,sup,var,
                b,i,
                dl,dt,dd,ol,ul,li,
                fieldset,form,label,legend,
                table,caption,tbody,tfoot,thead,tr,th,td,
                article,aside,canvas,details,figcaption,figure,
                footer,header,hgroup,menu,nav,section,summary,
                time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;}
                body{line-height:1;}
                article,aside,details,figcaption,figure,
                footer,header,hgroup,menu,nav,section{display:block;}
                nav ul{list-style:none;}
                blockquote,q{quotes:none;}
                blockquote:before,blockquote:after,
                q:before,q:after{content:'';content:none;}
                a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent;}
                ins{background-color:#ff9;color:#000;text-decoration:none;}
                mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold;}
                del{text-decoration:line-through;}
                abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help;}
                table{border-collapse:collapse;border-spacing:0;}
                hr{display:block;height:1px;border:0;border-top:1px solid #cccccc;margin:1em 0;padding:0;}
                input,select{vertical-align:middle;}
                html{ background: #EDEDED; height: 100%; }
                body{background:#FFF;margin:0 auto;min-height:100%;padding:0 30px;width:440px;color:#666;font:14px/23px Arial,Verdana,sans-serif;}
                h1,h2,h3,p,ul,ol,form,section{margin:0 0 20px 0;}
                h1{color:#333;font-size:20px;}
                h2,h3{color:#333;font-size:14px;}
                h3{margin:0;font-size:12px;font-weight:bold;}
                ul,ol{list-style-position:inside;color:#999;}
                ul{list-style-type:square;}
                code,kbd{background:#EEE;border:1px solid #DDD;border:1px solid #DDD;border-radius:4px;-moz-border-radius:4px;-webkit-border-radius:4px;padding:0 4px;color:#666;font-size:12px;}
                pre{background:#EEE;border:1px solid #DDD;border-radius:4px;-moz-border-radius:4px;-webkit-border-radius:4px;padding:5px 10px;color:#666;font-size:12px;}
                pre code{background:transparent;border:none;padding:0;}
                a{color:#70a23e;}
                header{padding: 30px 0;text-align:center;}
            </style>
        </head>
        <body>
            <header>
                <a href="http://www.slimframework.com"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHIAAAA6CAYAAABs1g18AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABRhJREFUeNrsXY+VsjAMR98twAo6Ao4gI+gIOIKOgCPICDoCjCAjXFdgha+5C3dcv/QfFB5i8h5PD21Bfk3yS9L2VpGnlGW5kS9wJMTHNRxpmjYRy6SycgRvL18OeMQOTYQ8HvIoJKiiz43hgHkq1zvK/h6e/TyJQXeV/VyWBOSHA4C5RvtMAiCc4ZB9FPjgRI8+YuKcrySO515a1hoAY3nc4G2AH52BZsn+MjaAEwIJICKAIR889HljMCcyrR0QE4v/q/BVBQva7Q1tAczG18+x+PvIswHEAslLbfGrMZKiXEOMAMy6LwlisQCJLPFMfKdBtli5dIihRyH7A627Iaiq5sJ1ThP9xoIgSdWSNVIHYmrTQgOgRyRNqm/M5PnrFFopr3F6B41cd8whRUSufUBU5EL4U93AYRnIWimCIiSI1wAaAZpJ9bPnxx8eyI3Gt4QybwWa6T/BvbQECUMQFkhd3jSkPFgrxwcynuBaNT/u6eJIlbGOBWSNIUDFEIwPZFAtBfYrfeIOSRSXuUYCsprCXwUIZWYnmEhJFMIocMDWjn206c2EsGLCJd42aWSyBNMnHxLEq7niMrY2qyDbQUbqrrTbwUPtxN1ZZCitQV4ZSd6DyoxhmRD6OFjuRUS/KdLGRHYowJZaqYgjt9Lchmi3QYA/cXBsHK6VfWNR5jgA1DLhwfFe4HqfODBpINEECCLO47LT/+HSvSd/OCOgQ8qE0DbHQUBqpC4BkKMPYPkFY4iAJXhGAYr1qmaqQDbECCg5A2NMchzR567aA4xcRKclI405Bmt46vYD7/Gcjqfk6GP/kh1wovIDSHDfiAs/8bOCQ4cf4qMt7eH5Cucr3S0aWGFfjdLHD8EhCFvXQlSqRrY5UV2O9cfZtk77jUFMXeqzCEZqSK4ICkSin2tE12/3rbVcE41OBjBjBPSdJ1N5lfYQpIuhr8axnyIy5KvXmkYnw8VbcwtTNj7fDNCmT2kPQXA+bxpEXkB21HlnSQq0gD67jnfh5KavVJa/XQYEFSaagWwbgjNA+ywstLpEWTKgc5gwVpsyO1bTII+tA6B7BPS+0PiznuM9gPKsPVXbFdADMtwbJxSmkXWfRh6AZhyyzBjIHoDmnCGaMZAKjd5hyNJYCBGDOVcg28AXQ5atAVDO3c4dSALQnYblfa3M4kc/cyA7gMIUBQCTyl4kugIpy8yA7ACqK8Uwk30lIFGOEV3rPDAELwQkr/9YjkaCPDQhCcsrAYlF1v8W8jAEYeQDY7qn6tNGWudfq+YUEr6uq6FZzBpJMUfWFDatLHMCciw2mRC+k81qCCA1DzK4aUVfrJpxnloZWCPVnOgYy8L3GvKjE96HpweQoy7iwVQclVutLOEKJxA8gaRCjSzgNI2zhh3bQhzBCQQPIHGaHaUd96GJbZz3Smmjy16u6j3FuKyNxcBarxqWWfYFE0tVVO1Rl3t1Mb05V00MQCJ71YHpNaMcsjWAfkQvPPkaNC7LqTG7JAhGXTKYf+VDeXAX9IvURoAwtTFHvyYIxtnd5tPkywrPafcwbeSuGVwFau3b76NO7SHQrvqhfFE8kM0Wvpv8gVYiYBlxL+fW/34bgP6bIC7JR7YPDubcHCPzIp4+cum7U6NlhZgK7lua3KGLeFwE2m+HblDYWSHG2SAfINuwBBfxbJEIuWZbBH4fAExD7cvaGVyXyH0dhiAYc92z3ZDfUVv+jgb8HrHy7WVO/8BFcy9vuTz+nwADAGnOR39Yg/QkAAAAAElFTkSuQmCC" alt="Slim"/></a>
            </header>
            <h1>Welcome to Slim!</h1>
            <p>
                Congratulations! Your Slim application is running. If this is
                your first time using Slim, start with this <a href="http://www.slimframework.com/learn" target="_blank">"Hello World" Tutorial</a>.
            </p>
            <section>
                <h2>Get Started</h2>
                <ol>
                    <li>The application code is in <code>index.php</code></li>
                    <li>Read the <a href="http://docs.slimframework.com/" target="_blank">online documentation</a></li>
                    <li>Follow <a href="http://www.twitter.com/slimphp" target="_blank">@slimphp</a> on Twitter</li>
                </ol>
            </section>
            <section>
                <h2>Slim Framework Community</h2>
 
                <h3>Support Forum and Knowledge Base</h3>
                <p>
                    Visit the <a href="http://help.slimframework.com" target="_blank">Slim support forum and knowledge base</a>
                    to read announcements, chat with fellow Slim users, ask questions, help others, or show off your cool
                    Slim Framework apps.
                </p>
 
                <h3>Twitter</h3>
                <p>
                    Follow <a href="http://www.twitter.com/slimphp" target="_blank">@slimphp</a> on Twitter to receive the very latest news
                    and updates about the framework.
                </p>
            </section>
            <section style="padding-bottom: 20px">
                <h2>Slim Framework Extras</h2>
                <p>
                    Custom View classes for Smarty, Twig, Mustache, and other template
                    frameworks are available online in a separate repository.
                </p>
                <p><a href="https://github.com/codeguy/Slim-Extras" target="_blank">Browse the Extras Repository</a></p>
            </section>
        </body>
    </html>
EOT;
    echo $template;
});
 
// POST route
$app->post('/post', function () {
    echo 'This is a POST route';
});
 
// PUT route
$app->put('/put', function () {
    echo 'This is a PUT route';
});
 
// DELETE route
$app->delete('/delete', function () {
    echo 'This is a DELETE route';
});
 
/**
 * Step 4: Run the Slim application
 *
 * This method should be called last. This executes the Slim application
 * and returns the HTTP response to the HTTP client.
 */
$app->run();

Mude-o para isso aqui.

<?php
require 'Slim/Slim.php'; // chamando a biblioteca
 
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim(); // registrando e inicializando o objeto no servidor
 
 
// GET route
$app->get('/', function () {
    $template = <<<EOT
        <h1>Implementando REST com slim framework</h1>
EOT;
    echo $template;
});
 
// POST route
$app->post('/post', function () {
    echo 'This is a POST route';
});
 
// PUT route
$app->put('/put', function () {
    echo 'This is a PUT route';
});
 
// DELETE route
$app->delete('/delete', function () {
    echo 'This is a DELETE route';
});
 
$app->run();

O que eu fiz foi remover os comentários excessivos que tinha no arquivo e coloquei uma mensagem customizada para mostrar que roda tranquilamente.

Toda vez que você for implementar um novo método no Slim Framework, basta você definir que tipo de comportamento ele possui e a ação que ele irá fazer.

Por exemplo quero criar um novo método chamado calcular.

$app->post('/calcular/:metodo',function($metodo) use ($app){
 
        $request = $app->request();
        $body = $request->post();
 
    switch($metodo){
            case 'somar':
                        echo $body['A'] + $body['B'];
            break;
            case 'dividir':
                        echo $body['A'] / $body['B'];
            break;
            case 'multiplicar':
                        echo $body['A'] * $body['B'];
            break;
            case 'diminuir':
                        echo $body['A'] - $body['B'];
            break;
    }
});

Eu posso usar as URIs com os métodos (/put, /post, /get, /delete).

E para testar meu serviço de REST como faço? Bom, você pode usar o cURL ou usar uma GUI para facilitar o processo, já que eu estou utilizando o navegador, eu vou adicionar uma extensão para o Google Chrome fazer isso. POSTMAN para nos salvar.

rest-gui

Na Webstore do google procure pelo POST MAN e você acha ele fácil, basta clicar em instalar e começar a testar.

rest-gui-demo

Coloco a URL, passo os parametros e voilá! Meu serviço de REST está implementado.

E como fica a requisição do servidor na aba de acesso? Vamos ver!

rest-php-call

Perfeito, eu posso criar vários serviços Web sem precisar escrever uma interface própria para testar, não existe mais desculpas esfarrapadas de esperar o Designer terminar a conversão de PSD para HTML/CSS para você começar a testar os serviços.

Esse é um belo exemplo simples de como implementar um serviço de REST. Agora vamos para um exemplo prático, digo mais completo.

Nesse exemplo usei o SQlite 3 para persistir os dados, você pode usar o que você quiser para persistência em PHP, o Slim não implica o uso de nenhum, já que dentro de suas chaves você pode escrever o que quiser.

[note color=”#007bff”]SQLite 3 só é suportado no PHP 5.4 em diante. Versões anteriores você pode usar o SQLite 2. Para saber qual versão que você tem use o php_info();[/note]

É bem simples o serviço de REST com uma API bem simplificada. O código abaixo contém as seguintes rotas que usaremos para o serviço.

  • GET /preload-inicial – Uso ele só 1 vez para carregar com dados falsos para ter algo para mostrar
  • GET /backup – Faz backup do banco
  • GET /clientes – Lista todos os clientes
  • GET /compras – Lista todas as compras do site
  • GET /compras/:id – Lista todas as compras de um cliente especifico pelo ID
  • PUT /cliente – Adicionar novos clientes
  • PUT /compra – Adicionar novas compras
  • POST /cliente/:id – Atualiza o cliente pelo ID
  • POST /compra/:id – Atualiza uma compra pelo ID
<?php
require 'Slim/Slim.php';
 
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
 
$response =array();
$db = new SQLite3('coisas.db');
 
 
// Uso essa função só para gerar dados fakes para ser usado no sistema
$app->get('/preload-inicial', function () use ($db,$response)
{       $value = rand(5,99);
        $db->exec('CREATE TABLE IF NOT EXISTS clientes(id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT);');
        $db->exec('CREATE TABLE IF NOT EXISTS compras(id INTEGER PRIMARY KEY, cliente_id INTEGER, valor INTEGER, compra_realizada DATETIME);');
        $db->query("INSERT OR IGNORE INTO clientes (nome) VALUES ('Igor Costa');");
        $db->query("INSERT OR IGNORE INTO clientes (nome) VALUES ('Elly Costa');");
        $db->query("INSERT OR IGNORE INTO clientes (nome) VALUES ('Leonardo Sobral');");
        $db->query("INSERT OR IGNORE INTO clientes (nome) VALUES ('Francisco Brianezi');");
        for($i=1;$i<=4;$i++){
            $db->query("INSERT INTO compras (valor,cliente_id,compra_realizada) VALUES ($value,$i,datetime());");
        }
 
});
// adicionar cliente
$app->put('/cliente', function () use($db,$response,$app) {
    $request = $app->request();
    $cliente = $request->put('nome');
    $response =$db->exec("INSERT INTO clientes (nome) VALUES ('$cliente');");
    echo $response;
});
// adicionar nova compra
$app->put('/compra', function () use($db,$response,$app) {
    $request = $app->request();
    $valor = $request->put('valor');
    $cliente = $request->put('cliente_id');
    $response = $db->query("INSERT INTO compras (valor,cliente_id,compra_realizada) VALUES ($valor,$cliente,datetime());");
    echo $response;
});
 
 
// Ediar cliente
 
$app->post('/cliente/:id',function($id) use($db,$response,$app){
    $request = $app->request();
    $nome = $request->post('nome');
    $response = $db->exec("UPDATE clientes SET nome='$nome' WHERE id=$id;");
    echo json_encode($response);
});
 
$app->post('/compra/:id',function($id) use($db,$response,$app){
    $request = $app->request();
    $valor = $request->post('valor');
    $cliente = $request->post('cliente_id');
    $response = $db->exec("UPDATE compras SET valor='$valor', cliente_id='$cliente' WHERE id=$id;");
    echo json_encode($response);
});
 
// lista os clientes
$app->get('/clientes',function()use ($db,$response,$app){
 
   $app->contentType('application/json');
   $tarefas = $db->query("SELECT * FROM clientes");
        while ($row = $tarefas->fetchArray(SQLITE3_ASSOC)) {
            array_push($response,$row);
        }
      echo json_encode($response);
 
});
// lista todas as compras
$app->get('/compras',function() use($db,$response,$app){
   $app->contentType('application/json');
   $tarefas = $db->query("SELECT clientes.id,clientes.nome,COUNT(*) as total_compras
                          FROM compras INNER JOIN clientes ON compras.cliente_id = clientes.id 
                          GROUP BY clientes.nome ORDER BY clientes.nome ASC");
        while ($row = $tarefas->fetchArray(SQLITE3_ASSOC)) {
            array_push($response,$row);
        }
      echo json_encode($response);
 
});
// lista todas as compras por id do cliente
$app->get('/compras/:id',function($id) use ($db,$response){
 
    $tarefas = $db->query("SELECT * FROM compras INNER JOIN clientes 
        ON compras.cliente_id = clientes.id  WHERE clientes.id = $id");
        while ($row = $tarefas->fetchArray(SQLITE3_ASSOC)) {
            array_push($response,$row);
        }
      echo json_encode($response); 
});
$app->get('/backup',function() use($app){
    $date = date("Y-m-d");
    exec('mkdir backups');
    $file = 'backups/backup-'.$date . '.sql';
    exec("sqlite3 coisas.db .dump > ".$file);
    echo readfile($file);
});
 
// Deleta cliente
$app->delete('/deleta/cliente/:id', function ($id) use ($db,$response,$app) {
 
    // para remover compras existentes do cliente
    $db->exec("DELETE FROM compras WHERE cliente_id=$id;");
    $response =$db->query("DELETE FROM clientes WHERE id=$id;");
    echo $response; 
});
// Deleta compra
$app->delete('/deleta/compra/:id', function ($id) {
    $response =$db->query("DELETE FROM compras WHERE id=$id;");
    echo $response; 
});
 
$app->run();

Para consumir esse serviço você pode usar a seguinte interface.

rest-gui-php-demo

Agora você já sabe como implementar um serviço em RESTful com PHP usando o Slim Framework. Na próxima competição que você participar use algo assim, é hiper rápido e você não gasta mais que 1 hora para implementar.

O uso do SQLite eu aconselho para coisas bem simples, listas simples e pouco à médio volume de dados. Não queria criar um ERP com SQLite que você está perdendo seu tempo.

Para maiores detalhes sobre o Slim Framework, visite a documentação do projeto. É simples e bem intuitiva.

O código fonte está disponível também no Github.

PHP

Classe em PHP para Geolocation

Tem coisa mais legal do que você compartilhar alguma coisa com uma outra comunidade ao qual você não faz parte.

Essa aqui é minha mínima contribuição para o pessoal do PHP que gostaria de transcodar um endereço e pegar suas coordenadas ou simplesmente a latitude e longitude.

A classe é hiper simples e de uso genérico, se você está em busca de coisas mais sofisticada eu aconselho você visitar esse site.

Como funciona?

 
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
 
<title>Geo Localização</title>
</head>
 
<body>
 
<?php
		include("GeoLocation.php");
 
        $geo = new GeoLocation();
       $resposta =  $geo->getGeoInfo("Sao Paulo,SP");
 
 
		print_r(var_dump($resposta)); // faz o output em Array do geo
 
 
 ?>
</body>
</html>

Código fonte da classe

 
<?php
date_default_timezone_set('America/Sao_Paulo');
 
/**
 
 Geolocation Class is stupid simple it's uses Google Maps Webservice to grab geo location or just simple
 Latitude and Logitude from requested address
 
   We have two simple methods getGeoInfo and getLatLong
 
    getGeoInfo - Returns all Data from requested address
	getLatLong - Returns only coordinates of Latitude and Longitude
 
 
	This class is provided by twitter.com/igorcosta
 
	For more info on Google geocoding visit http://code.google.com/apis/maps/documentation/geocoding
 */
class Geolocation {
 
 
			/**
			Return all geo information against requested address
			*/
 
			public function getGeoInfo($endereco)
			{
				$url = "http://maps.googleapis.com/maps/api/geocode/json?address=".urlencode($endereco). "&sensor=false&language=pt-BR";
				$retorno = false;
 
				if(!is_string($endereco))
					die('Precisa ser em String');
				else
					$resultado = file_get_contents($url);
 
					$retorno = json_decode($resultado);
 
				return $retorno;
			}
 
			/**
			 Status : "OK"  indicates that no errors occurred; the address was successfully parsed and at least one geocode was returned.
			 Status : "ZERO_RESULTS" indicates that the geocode was successful but returned no results.
			 Status : "OVER_QUERY_LIMIT" indicates that you are over your quota.
			 Status : "REQUEST_DENIED" indicates that your request was denied, generally because of lack of a sensor parameter.
			 Status : "INVALID_REQUEST" generally indicates that the query (address or latlng) is missing.
			*/
			public function getLatLong($endereco)
			{
				$url = "http://maps.googleapis.com/maps/api/geocode/json?address=".urlencode($endereco). "&sensor=false&language=pt-BR";
				$retorno = false;
 
					$resultado = file_get_contents($url);
					$retorno = json_decode($resultado);
					if($retorno->status == "OK")
					{
						foreach ($retorno->results as $item)
						{
							$loc = $item->geometry;
							foreach ($loc as $location)
							{
								$lat = $loc->location->lat;
								$long = $loc->location->lng;
							}
						}
						$coordenadas = array($lat,$long);
						$retorno = $coordenadas;
					}else{
						$resp['resposta'] = $retorno->status;
						$retorno = $resp;
 
					}
 
				return $retorno;
			}
 
}
?>