Criando APIs com o Respect Framework

Jean Lucas de Carvalho

Desenvolvedor Frontend

2007

  • 16 anos
  • Primeiro site
  • www.letsgyn.com

Let's Gyn

HTML

>

PHP

  1. Cadastro de eventos
  2. Postagem de fotos
  3. Redimensionamento automático
  4. Inserção de logo automático
  5. ...

Ganhei TEMPO

APRENDI PHP

2009

  • Sistemas de Informação na UFG
  • 2 Estágios em desenvolvimento PHP
  • PHP puro
  • Wordpress
     

2012

  • Desenvolvedor Frontend no LabTIME/UFG
  • Código Frankenstein 
  • Migração para Bootstrap
  • Tempo para estudo
  • Frontend Moderno (Grunt, Yeoman, Bower, etc.)

Projeto IKKI

  • AngularJS
  • APIs REST

Google Developer BUS BRAZIL

  • AngularJS
  • APIs REST
  • Ionic Framework

PRESENTIO

  • WebApp
  • AngularJS
  • APIs REST
  • Facebook APIs
  • MongoDB

Sugestão de presentes para amigos baseado no perfil do facebook 

API

Application Programming Interface

UMA API, Vários Clientes

ResT

Por que REST?

  • Escalabilidade
  • Generalização
  • Independência
  • Latência
  • Segurança
  • Encapsulamento

Por que JSON?

  • Ubiquidade
  • Simplicidade
  • Legibilidade
  • Escalabilidade
  • Flexibilidade
  • Estilo de arquitetura
  • Não é um padrão rigoroso
  • Busca maximizar produtividade do desenvolvedor
  • É um problema de projeto
  • Mantenha o simples, simples

O que não Fazer

  • /getBeer
  • /getLocation
  • /SaveBeer
  • /getAllBeers
  • /getLoggedUserInfo
  • /getBeerTypes
  • /saveBeerType
  • /get
  • /upsertBeer
  • /beerState

URL Base simples e intuitiva

  • 2 URLs base por recurso
    • /beers
    • /beers/12
  • Não utilize verbos em suas URLs base
  • Use os métodos HTTP
    • ​POST
    • GET
    • PUT
    • DELETE

Escolhendo os substantivos das urls

  • Dê preferência a substantivos no plural
    • Foursquare
      • ​/checkins
    • ​Dropbox
      • /files
  • Utilize substantivos concretos e não os abstratos
    • ​Abstratos
      • ​/items
    • ​Concretos
      • ​/blogs
      • ​​/news

Simplifique as associações

  • GET /folders/523/files 
    • Retorna todos os arquivos pertencentes ao diretório informado
  • POST /folders/523/files 
    • Cria um novo arquivo no diretório informado 

Coloque a complexidade após o "?"

  • Atributos
    • GET /beers?type=pielsen&location=BR
  • Paginação
    • GET /beers?limit=10&offset=30
  • Busca Global​
    • GET /search?q=heineken
  • Busca por escopo
    • GET /users/546/beers?q=heineken

Trate os erros

  • Desenvolvedores aprendem a desenvolver através dos erros
  • Desenvolvedores dependem de erros bem projetados para os ajudar em situações críticas

Erros

  • Facebook

​HTTP Status Code: 200

{
    error: {
        message: "Malformed access token <token>",
        type: "OAuthException",
        code: "190"
    }
}

Erros

  • Twilio

​HTTP Status Code: 401

{
    status: "401",
    message: "Authenticate",
    code: 20003,
    info: "http://www.twilio.com/docs/errors/20003"
}

Boas Práticas

  • Use os códigos de estado do HTTP
  • Retorne as mensagens de erro mais verbosas
  • Comece usando os seguintes códigos:
    • ​200 OK
    • 400 Bad Request
    • 500 Internal Server Error

Ações

  • Use verbos no lugar de substantivos
    • GET /convert?from=EUR&to=RS&amount=200
  • ​Deixe claro em sua documentação que são ações e não recursos

PHP PURO

  • Métodos HTTP
  • Rotas
  • Organização de código
  • Complexidade
  • Segurança
  • etc.

Slim Framework

<?php

//instancie o objeto
$app = new \Slim\Slim();
 
//defina a rota
get('/', function () { 
  echo "Hello, World!"; 
}); 
//rode a aplicação Slim 
$app->run();

Slim Framework

<?php

$app->get('/users', function() use ($app) {

    # Renderiza a view
    $app->render('users.php');

});

$app->get('/users/:id', function($id) use ($app) {

	$profile = ModelUsers::getUserByID($id);

	# Renderiza a view com algum dado
	$app->render('profile.php', array(
   			'profile' => $profile,
			'view' => true
		)
	);
});

Slim Framework

<?php

/**
 * Utilizando uma única rota para mapear múltiplos
 * métodos HTTP usando a função map()
 */
$app->map('/users/validate', function() {

})->via('GET', 'POST');

Slim Framework

<?php

/**
 * O Slim permite tratar mensagens de erro
 * e enviar a resposta HTTP apropriada
*/
$app->get('/denied', function() use ($app) {

	# Cria a mensagem de erro
	$errorData = array('error' => 'Permission Denied');

	# Retorna o status 403 do HTTP
        $app->render('error.php', $errorData, 403);

	# Podemos também enviar
	$app->halt(403, 'Permission Denied');

});

Respect Framework

Respect Framework

  • Micro-Framework para PHP 5.3+
  • Criado por Alexandre Gaigalas (alganet)
  • Mantido pela comunidade
  • Instalável via PEAR ou Composer

Respect Framework

<?php
use Respect\Rest\Router;

$roteador = new Router;
$roteador->get('/users/*', function($userName) {
   return 'Hello '. $userName;
});

Múltiplas rotas

<?php

$roteador->get(array('/user/*', '/users/*'), function($userName) {
    return 'Hello '. $userName;
});

Rota Única

<?php

/**
 * Utilizando uma única rota para mapear múltiplos
 * métodos HTTP usando a função any()
 */
$roteador->any('/users/*', function($id) {
    return "RestAPI";

    /* Você pode usar o $_SERVER['REQUEST_METHOD']
     * para verificar o tipo de requisição foi feita
     */
});

API de Cervejas

<?php

use Respect\Rest\Router;
use Respect\Config\Container;
use Respect\Validation\Validator as v;
use Respect\Relational\Mapper;
use Respect\Data\Collections\Collection;

/** 
 * Ler arquivo de configuração
 */
$config = new Container('config.ini');

/** 
 * Criar instância PDO com o SQLite usando as configs
 */
// diretório precisa ter permissão de escrita também
$mapper = new Mapper(new PDO($config->dsn));


// Criar instância do router
$router = new Router();

Módulos do Respect utilizados

  • Respect\Rest\Router;

  • Respect\Config\Container;

  • Respect\Validation\Validator as v;

  • Respect\Relational\Mapper;

  • Respect\Data\Collections\Collection;

<?php

// Rota para listar informações de uma cerveja ou todas
$router->get('/beers/*', function ($data) use ($mapper) {

    // Validar com negação se string esta preenchida
    if ( !isset($data) ) {
        $cervejas = $mapper->cervejas->fetchAll();
        header('HTTP/1.1 200 Ok');
        return $cervejas;
    }

    // tratar os dados
    $data = filter_var( $data, FILTER_SANITIZE_FULL_SPECIAL_CHARS );

    // validar conteúdo
    if ( v::not(v::alnum()->notEmpty())->validate($data) ) {
        header('HTTP/1.1 404 Not Found');
        return 'Não encontrada';
    }

    // buscar cerveja por id
    if ( v::int()->validate( $data ) ) {
        // buscar cerveja por id
        $cerveja = $mapper->cervejas[$data]->fetch();
    } else {
        // buscar cerveja pelo nome
        $cerveja = $mapper->cervejas(array( 'nome' => $data ))->fetch();
    }

    if ( !$cerveja ) {
        header('HTTP/1.1 404 Not Found');
        return 'Não encontrada'; 
    }

    header('HTTP/1.1 200 Ok');
    return $cerveja;
});
<?php

$router->post('/beers', function () use ($mapper) {
    
    //pega os dados via $_POST

    if ( !isset($_POST) || !isset($_POST['cerveja']) || v::not(v::arr())->validate($_POST['cerveja']) ) {
        header('HTTP/1.1 400 Bad Request');
        return 'Faltam parâmetros'; 
    }

    // Validar o input
    $validation = v::arr()                                                        // validar se é array                  
                 ->key('nome',   $rule = v::alnum()->notEmpty()->noWhitespace())  // validar a key 'nome' se não está vazia   
                 ->key('estilo', $rule)                                           // utilizando a mesma regra da key de cima      
                 ->validate($_POST['cerveja']);

    if ( !$validation ) {
        header('HTTP/1.1 400 Bad Request');
        return 'Faltam parâmetros'; 
    }

    // tratar os dados
    $cerveja         = new stdClass();
    $cerveja->nome   = filter_var($_POST['cerveja']['nome'],   FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $cerveja->estilo = filter_var($_POST['cerveja']['estilo'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);

    // buscar cerveja pelo nome para ver se já existe
    $check = $mapper->cervejas(array( 'nome' => $cerveja->nome ))->fetch();
    if ( $check ) {
        header('HTTP/1.1 409 Conflict');
        return 'Cerveja já existe no sistema'; 
    }

    // gravar nova cerveja
    $mapper->cervejas->persist($cerveja);
    $mapper->flush();

    // verificar se gravou
    if ( !isset($cerveja->id) || empty($cerveja->id) ) {
        header('HTTP/1.1 500 Internal Server Error');
        return 'Erro ao inserir cerveja';
    }
    
    //redireciona para a nova cerveja
    header('HTTP/1.1 201 Created');
    return 'Cerveja criada'; 
});
<?php

$router->put('/beers/*', function ($nome) use ($mapper) {

    //pega os dados
    parse_str(file_get_contents('php://input'), $data);

    if ( !isset($data) || !isset($data['cerveja']) || v::not(v::arr())->validate($data['cerveja']) ) {
        header('HTTP/1.1 400 Bad Request');
        return 'Faltam parâmetros'; 
    }

    // Validar o input
    $validation = v::arr()                                                        // validar se é array                  
                 ->key('nome',   $rule = v::alnum()->notEmpty()->noWhitespace())  // validar a key 'nome' se não está vazia   
                 ->key('estilo', $rule)                                           // utilizando a mesma regra da key de cima      
                 ->validate($data['cerveja']);

    if ( !$validation ) {
        header('HTTP/1.1 400 Bad Request');
        return 'Faltam parâmetros'; 
    }

    // tratar os dados
    $nome = filter_var( $nome, FILTER_SANITIZE_FULL_SPECIAL_CHARS );

    // validar conteúdo
    if ( v::not(v::alnum()->notEmpty())->validate($nome) ) {
        header('HTTP/1.1 404 Not Found');
        return 'Não encontrada';
    }

    // buscar cerveja pelo nome
    $cerveja = $mapper->cervejas(array( 'nome' => $nome ))->fetch();

    if ( !$cerveja ) {
        header('HTTP/1.1 404 Not Found');
        return 'Não encontrada'; 
    }

    // tratar os dados
    $newNome   = filter_var( $data['cerveja']['nome'],   FILTER_SANITIZE_FULL_SPECIAL_CHARS );
    $newEstilo = filter_var( $data['cerveja']['estilo'], FILTER_SANITIZE_FULL_SPECIAL_CHARS );

    //Persiste na base de dados ($mapper retorna objeto preenchido full)
    $cerveja->nome   = $newNome;
    $cerveja->estilo = $newEstilo;
    $mapper->cervejas->persist($cerveja);
    $mapper->flush();

    header('HTTP/1.1 200 Ok');
    return 'Cerveja atualizada';
});
<?php

$router->delete('/beers/*', function ($nome) use ($mapper) {

    // tratar os dados
    $nome = filter_var( $nome, FILTER_SANITIZE_FULL_SPECIAL_CHARS );

    // Validar com negação se string esta preenchida
    if ( !isset($nome) || v::not(v::alnum()->notEmpty())->validate($nome) ) {
        header('HTTP/1.1 400 Bad Request');
        return 'Faltam parâmetros'; 
    }

    // verificar se existe a cerveja pelo nome
    $cerveja = $mapper->cervejas(array( 'nome' => $nome ))->fetch();

    // BONUS - podemos buscar por id também 
    // $cerveja = $mapper->cervejas[$id]->fetch();
    
    if ( !$cerveja ) {
        header('HTTP/1.1 404 Not Found');
        return 'Não encontrada'; 
    }

    $mapper->cervejas->remove($cerveja);
    $mapper->flush();
    
    header('HTTP/1.1 200 Ok');
    return 'Cerveja removida';
});

Formatar resultado

<?php

$jsonRender = function ($data) {
    header('Content-Type: application/json');
    if ( v::string()->validate($data) ) {
        $data = array($data);
    }
    return json_encode($data,true);
};

$router->always('Accept', array('application/json' => $jsonRender));

Benchmarks

  • Silex
    • 2,533s por requisição
  • Slim
    • 0,873s por requisição
  • Flask
    • 0,567s por requisição
  • Respect\Rest
    • 0,395s por requisição
  • ​Restify (node.js)
    • ​0,231s por requisição

Saiba mais

  • http://respect.li/
  • https://github.com/Respect
  • http://www.restapitutorial.com/

Dúvidas?

Curso Angularjs na Prática

  • 24 horas
  • Futuring Escola
    (Próximo ao Parque Areião)
  • Sábados, de 4 de Outubro a 8 de Novembro
  • R$ 395,00

http://futuring.com.br

Obrigado

Criando APIs com o Respect Framework

By Jean Lucas de Carvalho

Criando APIs com o Respect Framework

  • 826