Desenvolvimento

19 set, 2016

Crie um aplicativo web móvel para ajudar cães de rua – Parte 01

Publicidade

Cães de rua em grandes cidades têm alto risco de lesão ou doença. Organizações dedicadas a ajudar animais abandonados dependem em parte dos alertas dos cidadãos para serem notificadas sobre os cães em perigo. Mas a informação que recebem é muitas vezes incompleta, prejudicando seus esforços para encontrar e ajudar os animais.

Recentemente, comecei a pensar sobre como a tecnologia pode ajudar a resolver esse problema. Imaginei uma aplicação móvel em que os cidadãos poderiam facilmente comunicar sobre animais feridos ou doentes para um banco de dados central, incluindo fotografias e localização por meio de GPS. Mas eu sabia que a configuração, a orquestração a manutenção dos vários componentes exigiriam tempo e esforço, e que eu também precisaria descobrir onde e como hospedar o aplicativo.

Nessa mesma época, eu ouvi sobre o developerWorks Premium. Esse programa pago inclui uma assinatura de 12 meses no IBM Bluemix (uma plataforma à qual já estava familiarizado devido a muitos tutoriais anteriores) e crédito mensal adicional para servidores de desenvolvimento e serviços hospedados, incluindo serviços de objetos e de armazenamento de dados. Eu percebi que esse programa me daria créditos suficientes para criar o protótipo do meu aplicativo. E a natureza da hospedagem dos serviços reduziria significativamente o esforço que eu, de outra forma, precisaria investir para lidar com as questões de segurança e configuração.

A instância Cloudant expõe seu conteúdo para uma interface REST, que torna mais fácil criar, apagar e procurar documentos armazenados através de solicitações HTTP padrão.

Neste artigo de duas partes, eu o conduzo através da construção de meu protótipo. O resultado final é o código que utiliza as capacidades de smartphones modernos, como GPS e câmeras, funciona bem em todos os dispositivos móveis, e pode ser facilmente estendido para outros cenários de uso.

Coletar dados de usuários móveis é apenas metade da solução. As agências de resgate também precisam de uma maneira de visualizar relatórios e fotos submetidas, e identificar locais especificados em um mapa. É por isso que a aplicação de exemplo também inclui um sistema de busca poderoso com integração ao mapa, totalmente otimizado para uso móvel.

Nos bastidores, o aplicativo organiza vários serviços Bluemix e de terceiros. Os serviços Bluemix são objetos de armazenamento, o que proporciona uma área de armazenamento seguro para as fotos enviadas; e Cloudant NoSQL DB, que fornece uma camada de dados NoSQL para os relatórios apresentados. O aplicativo também usa a API do Google Static Maps, que produz um mapa com um local específico identificado nele. No lado do cliente, você vai usar Bootstrap para criar uma interface de usuário amigável para dispositivos móveis para o aplicativo. No servidor, você vai usar o microframework Silex PHP para gerenciar o fluxo da aplicação. Você vai hospedar o aplicativo no Bluemix.

Você vai construir o código conforme você avançar no artigo. Como referência, você pode ver (ou obter) o meu código do projeto completo neste link. Se você quiser experimentar o aplicativo, acesse este link. Lembre que é uma demonstração pública, por isso tome cuidado para não fazer upload de informações confidenciais ou sensíveis nela. (Você também pode usar o botão System Reset do aplicativo para apagar todos os dados enviados.)

O que você vai precisar

1 – Criar uma aplicação stub

O primeiro passo é inicializar uma aplicação básica com o microframework Silex PHP e outras dependências. Você pode usar o Composer para baixar e instalar esses componentes facilmente.

1 – Salve o seguinte conteúdo em um arquivo de configuração Composer chamado $APP_ROOT/composer.json, onde $APP_ROOT é o diretório do projeto em seu ambiente de desenvolvimento:

{
    "require": {
        "silex/silex": "*",
        "twig/twig": "*",
 
        "symfony/validator": "*",
        "guzzlehttp/guzzle": "*",
        "php-opencloud/openstack": "*"
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

2 – A partir da linha de comando do OS ou shell, execute o seguinte comando para baixar e instalar os componentes:

php composer.phar install

3 – Dentro do $APP_ROOT, crie um diretório chamado public (para todos os arquivos que serão acessados pela web), um diretório chamado views (para todos os arquivos de tela), e um arquivo config.php (para informações de configuração).

4 – Silex depende de reescrita da URL para “URLs amigáveis”, de modo que esse também é um bom momento para configurar as regras de regravação de URL do seu servidor web. Uma vez que o aplicativo irá eventualmente ser implantado em um servidor web Apache, crie um arquivo $APP_ROOT/.htaccess com o seguinte conteúdo:

<IfModule mod_rewrite.c>
    Options -MultiViews
    RewriteEngine On
    #RewriteBase /path/to/app
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [QSA,L]
</IfModule>

5 – Para tornar o acesso ao aplicativo ainda mais fácil, você também pode definir um novo host virtual em seu ambiente de desenvolvimento e indicar a sua raiz de documentos para $APP_ROOT/public. Este passo, embora opcional, é recomendável porque cria uma réplica mais próxima do ambiente de implementação do destino no Bluemix. Consulte a documentação do Apache Virtual Host para mais informações sobre como usar hosts virtuais.

  • Se você definir um novo host virtual chamado stray-assist.localhost (por exemplo), você será capaz de acessar o aplicativo por meio de uma URL como http://stray-assist.localhost/index (pode ser necessário atualizar o servidor DNS local da sua rede para ele saber sobre o novo host).
  • Se você decidir não usar uma máquina virtual e, em vez disso, armazenar seus arquivos em um diretório stray-assist/ sob a raiz de documentos do servidor web (por exemplo), você vai ser capaz de acessar seu aplicativo através de uma URL como http://localhost/stray-assist/public/index.

6 – Crie um script controller em $APP_ROOT/public/index.php, com o seguinte conteúdo:

<?php
// use Composer autoloader
require '../vendor/autoload.php';
 
// load configuration
require '../config.php';
 
// load classes
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints as Assert;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7;
use Silex\Application;
 
// initialize Silex application
$app = new Application();
 
// turn on application debugging
// set to false for production environments
$app['debug'] = true;
 
// load configuration from file
$app->config = $config;
 
// register Twig template provider
$app->register(new Silex\Provider\TwigServiceProvider(), array(
  'twig.path' => __DIR__.'/../views',
));
 
// register validator service provider
$app->register(new Silex\Provider\ValidatorServiceProvider());
 
// register session service provider
$app->register(new Silex\Provider\SessionServiceProvider());

Até agora, o código de script inicializa o framework do Silex e as classes de componentes necessários. O script também lê os dados de configuração a partir do arquivo de configuração do aplicativo, inicializa o template renderizador Twig, e o registra com o Silex.

7 – Continue o script adicionando chamadas de retorno do roteador para várias rotas e definindo a função -get() para solicitações GET, post() para solicitações POST, e assim por diante – a ser chamada quando cada rota corresponde a uma solicitação de entrada:

// index page handlers
$app->get('/', function () use ($app) {
  return $app->redirect($app["url_generator"]->generate('index'));
});
 
$app->get('/index', function () use ($app) {
  return $app['twig']->render('index.twig', array());
})
->bind('index');
 
// report submission form
$app->get('/report', function (Request $request) use ($app) {
  // todo
})
->bind('report');
 
// search form
$app->get('/search', function (Request $request) use ($app) {
  // todo
})
->bind('search');
 
// legal page handler
$app->get('/legal', function (Request $request) use ($app) {
 // todo
})
->bind('legal');
 
// reset handler
$app->get('/reset-system', function (Request $request) use ($app) {
  // todo  
})
->bind('reset-system');
 
$app->run();

A rota URL a ser correspondida é passada como o primeiro argumento para cada método HTTP; o segundo argumento é uma função que especifica as ações a serem tomadas quando a rota é correspondente a uma solicitação de entrada. Por exemplo, no seu script, o retorno de chamada /index chama o modelo da página principal do aplicativo, chamado de index.twig.

O script também configura uma chamada de retorno para a rota /route, que simplesmente redireciona para a rota /index. Da mesma forma, outras chamadas de retorno são definidas para as rotas, tais como /search e /report. Você vai preencher esses callbacks à medida que avançar no artigo.

8 – Como o bit final da preparação, crie uma UI simples baseada em Bootstrap com cabeçalho, rodapé e áreas de conteúdo. Aqui está um exemplo, que você vai usar para todas as views da aplicação indicadas nas listagens de código subsequentes:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Stray Assist</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->   
  </head>
  <body>
 
    <div class="container">
      <div class="panel panel-default">
        <div class="panel-heading clearfix">
          <h4 class="pull-left">Stray Assist</h4>
        </div>
      </div>
 
      {% for message in app.session.flashbag.get('success') %}
      <div class="alert alert-success">
        <strong>Success!</strong> {{ message }}
      </div>
      {% endfor %}      
 
      {% for message in app.session.flashbag.get('error') %}
      <div class="alert alert-danger">
        <strong>Error!</strong> {{ message }}
      </div>
      {% endfor %}
 
      <div>
        <!-- content body -->     
      </div>
    </div>
     
    <div class="container">
      <p class="text-center">
        <a href="{{ app.url_generator.generate('legal') }}" role="button" class="btn btn-default btn-sm">Legal</a>
        <a href="{{ app.url_generator.generate('legal') }}" role="button" class="btn btn-danger btn-sm">System Reset</a>
    </div>
     
  </body>
</html>

Com todo o trabalho de preparação fora do caminho, agora você pode começar a construir a aplicação.

2 – Crie a página de boas-vindas da aplicação

No passo 1, você populou as chamadas de retorno para as rotas / e /index com o código necessário para renderizar o template de índice. Agora, crie o script de view correspondente em $APP_ROOT/views/index.twig, usando o modelo Bootstrap do passo 1, e preencha a área de conteúdo com um menu básico:

<div class="panel panel-default">
  <div class="panel-heading">Report a stray dog near you that needs help.</div>
  <div class="btn-group btn-group-justified">
    <a role="button" class="btn btn-primary" href="{{ app.url_generator.generate('report') }}">Report</a>
  </div>
</div>
 
<div class="panel panel-default">
  <div class="panel-heading">Search for previous reports and track them on a map.</div>
  <div class="btn-group btn-group-justified">
    <a role="button" class="btn btn-primary" href="{{ app.url_generator.generate('search') }}">Search</a>
  </div>
</div>

Observe que o gerador de URL do Silex é usado para criar dinamicamente as URLs para as rotas /search e /report. Essa abordagem assegura que as rotas continuem válidas se o aplicativo for hospedado na raiz do domínio ou em um subdiretório da raiz do domínio.

Abra seu navegador e aponte para http://stray-assist.localhost/index (ou para a URL equivalente em seu ambiente de desenvolvimento) para ver a página de boas-vindas. A página inclui um botão para acessar a função de relatório e um botão para acessar a função de pesquisa:

app-1

3 – Crie a interface de relatório

Agora que todas as peças básicas estão trabalhando em harmonia, é hora de saltar para algo maior. Os usuários precisam de uma maneira de enviar relatórios sobre os animais feridos com que se deparam, e é importante que esses relatórios estejam estruturados e totalmente pesquisáveis usando vários critérios.

1 – Crie um script de visualização, $APP_ROOT/views/report.twig, e inicie com o seguinte código:

<div>
  <form method="post" action="{{ app.url_generator.generate('report') }}">
 
    <input type="hidden" name="latitude" value="{{ latitude }}" />
    <input type="hidden" name="longitude" value="{{ longitude }}" />

2 – Adicione as seguintes três seções principais:

  • Uma seção de Identificação, na qual os usuários inserem detalhes sobre o animal, tais como cor, sexo, idade e quaisquer marcas de identificação:
<div class="panel panel-default">
  <div class="panel-heading clearfix">
    <h4 class="pull-left">Identification</h4>
  </div>
  <div class="panel-body">
    <div class="form-group">
      <label for="color">Color</label>
      <input type="text" class="form-control" id="color" name="color" required="true"></input>
    </div>
    <div class="form-group">
      <label for="gender">Sex</label>
      <select name="gender" id="gender" class="form-control" required="true">
        <option value="male">Male</option>
        <option value="female">Female</option>
        <option value="unknown">Unknown</option>
 
      </select>
    </div>
    <div class="form-group">
      <label for="gender">Age</label>
      <select name="age" id="age" class="form-control" required="true">
        <option value="pup">Pup</option>
        <option value="adult">Adult</option>
        <option value="unknown">Unknown</option>
 
      </select>
    </div>
    <div class="form-group">
      <label for="identifiers">Identifying marks</label>
      <textarea name="identifiers" id="identifiers" class="form-control" rows="3"></textarea>
    </div>
  </div>
</div>
  • Uma seção de detalhes que tem uma área de descrição livre do problema ou lesão:
<div class="panel panel-default">
  <div class="panel-heading clearfix">
    <h4 class="pull-left">Details</h4>
  </div>
  <div class="panel-body">
    <div class="form-group">
      <label for="description">Description of injury</label>
      <textarea name="description" id="identifiers" class="form-control" rows="3" required="true"></textarea>
    </div>               
  </div>
</div>
  • Uma seção de identificação que pede o nome do usuário, número de telefone e endereço de e-mail:
<div class="panel panel-default">
      <div class="panel-heading clearfix">
        <h4 class="pull-left">Reporter</h4>
      </div>
      <div class="panel-body">
        <div class="form-group">
          <label for="name">Name</label>
          <input type="text" class="form-control" id="name" name="name" required="true"></input>
        </div>
        <div class="form-group">
          <label for="phone">Phone number</label>
          <input type="text" class="form-control" id="phone" name="phone" required="true"></input>
        </div>              
        <div class="form-group">
          <label for="email">Email address</label>
          <input type="email" class="form-control" id="email" name="email" required="true"></input>
        </div>              
        <div class="form-group">
          <button type="submit" name="submit" class="btn btn-primary">Submit</button>
        </div>          
      </div>
    </div>
  </form>
</div>

3 – Para localizar geograficamente (geotag) cada relatório com a localização atual do usuário, use a API de geolocalização do HTML5 que os navegadores mais modernos já têm incluída. Atualize o script de visualização, adicionando este código perto do topo:

   {% if not latitude and not longitude %}    
<script>
$(document).ready(function() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(handle_geo_query, handle_error);
  }
   
  function handle_geo_query(location) {
    window.location = '?latitude=' + location.coords.latitude + '&longitude=' + location.coords.longitude;
  }
   
  function handle_error(e) {
    alert('An error occurred during geolocation.');
  }
});
</script>       
{% endif %}

Normalmente, quando o código geotagging é executado em um navegador, uma caixa de diálogo aparece para pedir ao usuário uma autorização da localização atual:

app-2

O usuário deve permitir explicitamente a divulgação para que o aplicativo possa receber a localização atual, que geralmente é identificada através da torre de celular mais próxima ou do sistema de GPS do dispositivo. Se o usuário concorda, o script chama a função handle_geo_query, que lê a latitude e a longitude e recarrega a URL da página, desta vez acrescentando esses valores para a URL como parâmetros GET.

Se ocorrer um erro – por exemplo, se o usuário negar permissão para compartilhar o local ou a localização não puder ser identificada – a função handle_error é chamada para exibir uma caixa de alerta (você pode ajustar a mensagem de erro com base no tipo de erro). Nessa situação, o relatório é geotagged com a latitude 0 e longitude 0.

4 – Atualize o script controlador em $APP_ROOT/public/index.php, adicionando o seguinte código para a função stub de retorno de chamada existente para a rota /report:

<?php
 
// Silex application initialization  snipped
 
// report submission form
// get lat/long from browser and set as hidden form fields
$app->get('/report', function (Request $request) use ($app) {
  $latitude = $request->get('latitude');
  $longitude = $request->get('longitude');
  return $app['twig']->render('report.twig', array('latitude' => $latitude, 'longitude' => $longitude));
})
->bind('report');
 
// other callbacks  snipped
 
$app->run();

A chamada de retorno verifica a URL da latitude e longitude obtida através da geolocalização, transfere esses valores para o script de visualização como variáveis de template, e renderiza o formulário (que você pode ver em http://stray-assist.localhost/report ou na URL equivalente em seu ambiente de desenvolvimento).

4 – Inicialize uma instância de banco Cloudant

Os usuários têm agora uma maneira de interagir com o aplicativo e enviar relatórios. O serviço Cloudant NoSQL DB do Bluemix torna mais fácil armazenar os relatórios em um banco de dados. As disposições relativas ao serviço de uma instância de banco de dados Cloudant vazia podem ser ligadas na sua aplicação. Essa instância de banco de dados expõe o seu conteúdo para uma interface REST, que torna mais fácil criar, apagar e procurar documentos armazenados através de solicitações HTTP padrão. O serviço padrão só oferece um contingente limitado de armazenamento gratuito, mas com a sua assinatura developerWorks Premium, você normalmente tem créditos suficientes para cobrir as necessidades de armazenamento de dados mais razoáveis.

1 – Faça login na sua conta Bluemix. No Console, selecione Dados e Análise (Data & Analytics) nos serviços disponíveis.

2 – Clique no botão + e, em seguida, selecione o serviço Cloudant NoSQL DB.

3 – Tenha certeza de que “Conectar ao campo” (Connect to field) está definido para Deixar desacoplado (Leave unbound) e que você está usando o plano compartilhado (como essa instância de serviço será executada inicialmente num estado desagregado, você pode desenvolver o aplicativo em um host separado, com a instância do serviço de banco de dados hospedado em Bluemix). Clique em Criar (Create).

4 – Clique em Iniciar (Launch) para inicializar a instância do serviço de banco de dados Cloudant.

5 – Na página de informações sobre o serviço, clique na guia Credenciais de Serviços (Service Credentials) para ver o nome do usuário e senha para a instância do serviço:

app-3

6 – Copie e cole os detalhes relevantes das credenciais de bloqueio JSON no arquivo de configuração do aplicativo em $APP_ROOT/config.php. Em seguida, na mesma página Cloudant, selecione a guia Gerenciar (Manage) e clique em Iniciar (Launch) para iniciar o painel Cloudant.

7 – Clique no botão Criar banco de dados (Create Database) na barra de menu superior, insira stray_assist como o nome do novo banco de dados e clique em Criar.

Sua instância de banco de dados Cloudant agora está ativa, e seu aplicativo está configurado para usá-la. Na próxima etapa, você usará esse banco de dados para armazenar relatórios de usuários.

5 – Valide e salve relatórios

Com o armazenamento de dados persistente no lugar, a sua tarefa agora é adicionar um processador de formulário que aceita e valida as submissões do usuário e os salva no banco de dados. Adicione todo o código deste passo a $APP_ROOT/public/index.php. O código define uma chamada de retorno para lidar com envios de formulários via POST.

O código do formulário começa com a coleta dos vários parâmetros de entrada – cor, sexo, idade, descrição e informações de contato -, e a validação de cada um usando vários validadores do Symfony:

<?php
 
// Silex application initialization  snipped
 
// initialize HTTP client
$guzzle = new GuzzleHttp\Client([
  'base_uri' => $app->config['settings']['db']['uri'] . '/',
]);
 
// report submission handler
$app->post('/report', function (Request $request) use ($app, $guzzle) {
  // collect input parameters
  $params = array(
    'color' => strip_tags(trim(strtolower($request->get('color')))),
    'gender' => strip_tags(trim($request->get('gender'))),
    'age' => strip_tags(trim($request->get('age'))),
    'identifiers' => strip_tags(trim($request->get('identifiers'))),
    'description' => strip_tags(trim($request->get('description'))),
    'email' => strip_tags(trim($request->get('email'))),
    'name' => strip_tags(trim($request->get('name'))),
    'phone' => (int)strip_tags(trim($request->get('phone'))),
    'latitude' => (float)strip_tags(trim($request->get('latitude'))),
    'longitude' => (float)strip_tags(trim($request->get('longitude')))
  );
   
  // define validation constraints
  $constraints = new Assert\Collection(array(
    'color' => new Assert\NotBlank(array('groups' => 'report')),
    'gender' => new Assert\Choice(array('choices' => array('male', 'female', 'unknown'
), 'groups' => 'report')),
    'age' => new Assert\Choice(array('choices' => array('pup', 'adult', 'unknown'
), 'groups' => 'report')),
    'description' => new Assert\NotBlank(array('groups' => 'report')),
    'email' =>  new Assert\Email(array('groups' => 'report')),
    'name' => new Assert\NotBlank(array('groups' => 'report')),
    'phone' => new Assert\Type(array('type' => 'numeric', 'groups' => 'report')),
    'latitude' => new Assert\Type(array('type' => 'float', 'groups' => 'report')),
    'longitude' => new Assert\Type(array('type' => 'float', 'groups' => 'report')),
    'identifiers' => new Assert\Type(array('type' => 'string', 'groups' => 'report'))
  ));
   
  // validate input and set errors if any as flash messages
  // if errors, redirect to input form
  $errors = $app['validator']->validate($params, $constraints, array('report'));
  if (count($errors) > 0) {
    foreach ($errors as $error) {
      $app['session']->getFlashBag()->add('error', 'Invalid input in field ' . $error->getPropertyPath());
    }
    return $app->redirect($app["url_generator"]->generate('report'));
  }

Se a entrada é válida, os valores são formatados em um documento JSON adequado para inserção no Cloudant:

// if input passes validation
  // produce JSON document with input values
  $doc = [
    'type' => 'report',
    'latitude' => $params['latitude'],
    'longitude' => $params['longitude'],
    'color' => $params['color'],
    'gender' => $params['gender'],
    'age' => $params['age'],
    'identifiers' => $params['identifiers'],
    'description' => $params['description'],
    'name' => $params['name'],
    'phone' => $params['phone'],
    'email' => $params['email']
    'datetime' => time()
  ];

O cliente Guzzle HTTP é então usado para formular e enviar um pedido POST que contém o documento JSON para a API Cloudant REST; isso cria e salva um novo documento no banco de dados Cloudant:

// save document to database
  // retrieve unique document identifier
  $response = $guzzle->post($app->config['settings']['db']['name'], [ 'json' => $doc ]);
  $result = json_decode($response->getBody());
  $id = $result->id;

Finalmente, o navegador do cliente é redirecionado de volta à página de índice do aplicativo com uma notificação de sucesso:

$app['session']->getFlashBag()->add('success', 'Report added.');
  return $app->redirect($app["url_generator"]->generate('index'));
});
 
// other callbacks  snipped
 
$app->run();

Para ver o processador de formulários em ação, tente adicionar um relatório através do aplicativo. Em seguida, navegue de volta para o console do Bluemix, inicie o painel Cloudant e visualize todos os documentos no seu banco de dados para ver um novo registro contendo os detalhes de seu relatório recém-adicionado:

app-4

6 – Inicialize uma instância Object Storage

O próximo recurso para adicionar ao sistema de comunicação é a capacidade de anexar fotos. Nesta etapa, você permite anexar fotografias com a ajuda do serviço Bluemix Object Storage. Ele facilita o armazenamento de dados não estruturados na nuvem. O serviço suporta a API OpenStack Swift e segue a hierarquia de três camadas do Swift para organizar os dados:

  • A unidade principal na hierarquia é uma conta. Contas correspondem aos usuários; para acessar uma conta, o usuário deve fornecer credenciais de autenticação.
  • Uma conta pode hospedar vários recipientes, que são o equivalente a pastas ou subpastas em um sistema de arquivos tradicional.
  • Cada recipiente pode armazenar vários objetos, que podem ser arquivos ou dados. Objetos podem ter metadados adicionais definidos pelo usuário. Normalmente, você pode armazenar um número ilimitado de objetos.

Essa descrição é apenas a ponta do iceberg. Para se ter uma base sólida nos conceitos do Swift (e, por extensão, Object Storage), dê uma olhada no livro OpenStack Swift, de Joe Arnold (O’Reilly, ISBN 978-1-4919-0082-6). Esse livro, juntamente com centenas de outros títulos de programação, está disponível através do Safari Books Online como parte do programa developerWorks Premium.

Para inicializar uma nova instância de serviço Object Storage no Bluemix:

1 –  Faça login na sua conta Bluemix. A partir do Console, selecione Armazenamento (Storage) na lista de serviços.

2 – Clique no botão + e, em seguida, selecione o serviço de armazenamento de objetos (Object Storage).

3 – Certifique-se de que Conectar com o campo (Connect to field) está definido para deixar desacoplado (Leave unbound), e escolha o plano gratuito ou o plano padrão (tal como acontece com a instância do serviço Cloudant no Passo 4, executar o serviço num estado desagregado torna possível o desenvolvimento do aplicativo em um host separado com o armazenamento de objetos hospedado em Bluemix). Clique em Criar.

4 – Depois que a instância de serviço é inicializada, clique na guia Credenciais de Serviços (Service credentials) para ver URL, região, nome de usuário, senha e outras credenciais para a instância de serviço:

app-5

Copie e cole os detalhes relevantes das credenciais de bloqueio JSON no arquivo de configuração do aplicativo em $APP_ROOT/config.php.

7 – Suporte fotografias anexas nos relatórios

Agora você está pronto para adicionar o suporte para envio de fotos no aplicativo:

1 – Edite o formulário de relatório em $APP_ROOT/views/report.twig, fazendo as duas adições a seguir (consulte o arquivo report.twig no meu projeto para ver onde no arquivo você deve adicionar esse novo código).

  • Atualizar a definição do formulário para suportar o upload de arquivos:
<form method="post" enctype="multipart/form-data" action="{{ app.url_generator.generate('report') }}">
    <input type="hidden" name="MAX_FILE_SIZE" value="300000000" />
  • Adicione um novo campo de upload de arquivo:
<div class="form-group">
  <label for="upload">Photo</label>
  <span class="btn btn-default btn-file">
    <input type="file" name="upload" />
  </span>
</div>

O pacote php-opencloud foi incluído no arquivo de dependências do Composer no passo 1, portanto, você já o tem instalado no seu sistema de desenvolvimento.

2 – Para interagir com a API Object Storage, a solução mais fácil é usar o php-opencloud, um SDK PHP para implantações baseadas em OpenStack. Esse SDK fornece um invólucro PHP em torno dos métodos da API Swift, então você deve simplesmente chamar o método apropriado – por exemplo, CreateContainer() ou listContainers() – e a biblioteca cliente cuida de formular o pedido e decodificar a resposta para você. Para usar php-opencloud, adicione o seguinte código – que utiliza o recipiente de dependência de injeção Silex para inicializar um novo cliente OpenStack – a $APP_ROOT/public/index.php:

<?php
 
// Silex application initialization  snipped
 
// initialize OpenStack client
$openstack = new OpenStack\OpenStack(array(
  'authUrl' => $app->config['settings']['object-storage']['url'],
  'region'  => $app->config['settings']['object-storage']['region'],
  'user'    => array(
    'id'       => $app->config['settings']['object-storage']['user'],
    'password' => $app->config['settings']['object-storage']['pass']
)));
$objectstore = $openstack->objectStoreV1();
 
// other callbacks  snipped
 
$app->run();

3 – Faça as seguintes três adições a index.php para atualizar a chamada de retorno para o manipulador /reportPOST (consulte o arquivo index.php no meu projeto para ver onde fazer essas adições).

'upload' => $request->files->get('upload')
  );

 

'upload' => new Assert\Image(array('groups' => 'report'))
'file' => !is_null($params['upload']) ? trim($params['upload']->getClientOriginalName()) : '',

 

// if report includes photo
// create container in object storage service
// with name = document identifier
// and upload photo to it
if (!is_null($params['upload'])) {
  $container = $objectstore->createContainer(array(
    'name' => $id
  ));
  $stream = new Stream(fopen($params['upload']->getRealPath(), 'r'));
  $options = array(
    'name'   => trim($params['upload']->getClientOriginalName()),
    'stream' => $stream,
  );
  $container->createObject($options);  
}

Com essas alterações, o script verifica a existência de um carregamento de arquivo válido. Se o arquivo estiver presente, o script usa o cliente OpenStack para transferir o arquivo para a instância do serviço Object Storage.

Agora – uma vez que o relatório é salvo no banco de dados Cloudant como um novo documento – o método do cliente OpenStack createContainer() cria um novo recipiente que corresponde ao identificador do documento, e o arquivo enviado é salvo dentro desse recipiente através do método createObject() do cliente. O nome do arquivo de imagem é armazenado dentro do documento Cloudant como uma propriedade adicional.

4 – Para ver o suporte para anexos de foto em ação, tente adicionar um relatório através do aplicativo e anexar uma foto a ele usando o novo campo de carregamento de arquivo. Navegue de volta para o console Bluemix e lance a dashboard da instância de serviço Object Storage. Você pode ver uma nova pasta que contém sua foto carregada:

app-6

Conclusão

Neste ponto, você tem um protótipo funcional de um sistema que torna possível para os usuários denunciarem os animais lesionados – com geotagging e fotos.

Na segunda e última parte deste artigo, eu vou conduzir você através da construção de uma interface de pesquisa para o aplicativo e mapeamento de relatórios para endereços reais usando o Google Maps. Esses recursos são projetados para fazer relatórios mais acessíveis aos serviços de salvamento e ajudar a localizar rapidamente os registros por critérios de pesquisa ou palavras-chave. Finalmente, eu irei mostrar como implantar seu aplicativo em um ambiente seguro, robusto e escalável na nuvem Bluemix.

 

Recursos para download (pdf)

Tópicos relacionados:

***

Vikram Vaswani faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://www.ibm.com/developerworks/library/mo-assist-stray-dogs-1-premium/index.html