PHP orientado a objeto com MySQL e AJAX - Seleção de estado e cidade

Publicado por Perfil removido em 09/03/2010

[ Hits: 20.888 ]

 


PHP orientado a objeto com MySQL e AJAX - Seleção de estado e cidade



Hoje vou dar uma pincelada sobre algo que eu queria abordar faz tempo, mas como me afastei do PHP por conta da correria do trabalho no dia-a-dia, demorei... Mas nunca é tarde.

O exemplo é bem simples e clássico, solução pra um problema e desejo antigo: carregar dados numa página web sem refresh. Eu sempre ouvia falar do AJAX e, apesar de ter começado minha carreira fazendo sites, só fui vê-lo em 2006, mas a primeiras coisas que estou fazendo com ele pra testar foram só recentemente. E agora que entendi quero compartilhar - eu já até estudei o Adobe Flex e cada um (Flex e AJAX) tem suas vantagens, mas a principal do AJAX é ser mais leve, pois usa os próprios bons e velhos HTML, XML e JavaScript, sem precisar da instalação de plugins como o do Flash Player que o Flex precisa.

Como eu disse, é um exemplo simples e clássico de seleção de cidades, onde numa caixa você seleciona o estado desejado e na de baixo a cidade numa lista criada dinamicamente com as cidades de cada estado selecionado.

Pra começar, vamos à criação do nosso banco de dados no MySQL - eu peguei parte das tabelas de um banco no qual estou trabalhando, bem simples, foi um dump mesmo que fiz no phpMyAdmin do meu servidor on-line, já com os dados pro exemplo. Veja:

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

CREATE DATABASE `cidades` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `cidades`;

CREATE TABLE IF NOT EXISTS `cidades` (
  `id_cidade` int(11) NOT NULL auto_increment,
  `id_uf` int(11) NOT NULL,
  `nome` varchar(35) NOT NULL,
  `cep_principal` char(10) NOT NULL,
  `is_capital` tinyint(1) NOT NULL default '0',
  `sigla` varchar(3) NOT NULL,
  PRIMARY KEY  (`id_cidade`),
  UNIQUE KEY `sigla` (`sigla`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;

INSERT INTO `cidades` (`id_cidade`, `id_uf`, `nome`, `cep_principal`, `is_capital`, `sigla`) VALUES
(1, 1, 'Cuiabá', '78000-000', 1, 'CBA'),
(2, 1, 'Sinop', '78550-000', 0, 'SNP'),
(3, 1, 'Alta Floresta', '78580-000', 0, 'AFL'),
(4, 1, 'Lucas do Rio Verde', '78455-000', 0, 'LRV'),
(5, 1, 'Rondonópolis', '78700-000', 0, 'ROO'),
(6, 2, 'Cascavel', '85800-000', 0, 'CVL');

CREATE TABLE IF NOT EXISTS `paises` (
  `id_pais` int(11) NOT NULL auto_increment,
  `sigla` varchar(5) NOT NULL,
  `nome` varchar(30) NOT NULL,
  PRIMARY KEY  (`id_pais`),
  UNIQUE KEY `sigla` (`sigla`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

INSERT INTO `paises` (`id_pais`, `sigla`, `nome`) VALUES
(1, 'BRA', 'Brasil');

CREATE TABLE IF NOT EXISTS `uf` (
  `id_uf` int(11) NOT NULL auto_increment,
  `id_pais` int(11) NOT NULL,
  `sigla` char(2) NOT NULL,
  `nome` varchar(25) NOT NULL,
  PRIMARY KEY  (`id_uf`),
  UNIQUE KEY `sigla` (`sigla`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;

INSERT INTO `uf` (`id_uf`, `id_pais`, `sigla`, `nome`) VALUES
(1, 1, 'MT', 'Mato Grosso'),
(2, 1, 'PR', 'Paraná'),
(3, 1, 'MS', 'Mato Grosso do Sul');

Criado o banco, vamos ao PHP. Eu usei o próprio Gedit do Ubuntu (se você usa o ambiente KDE do Kubuntu - o qual eu também tenho instalado por curiosidade, gostei, achei legal e muito bonito, embora não seja tão prático como o GNOME - use o Kate, mas tem que verificar a compatibilidade com UTF-8).

Voltando ao assunto, o primeiro arquivo que criamos é o dados.php, que contêm a classe de conexão ao banco e seleção da base, conforme abaixo:

<?php
  class dados
  {
    //Propriedades/parâmetros do objeto de conexão
    public $host = "localhost";
    public $user = "cidades";
    public $senha = "cidades";
    public $db = "cidades";

    public function get_conexao()
    {
      //Conecta ao MySQL e seleciona o banco de dados
      $cnx = @mysql_connect($this->host, $this->user, $this->senha) or die("<pre>(!) Falha ao conectar ao banco de dados.</pre>");
      @mysql_select_db($this->db, $cnx) or die("<pre>(!) Falha ao selecionar banco de dados. ".str_replace('..', '.', mysql_error().'.')."</pre>");
    }
  }
?>

Eu usei funções nativas e simples do PHP na conexão pra ficar mais legível. Ah, e se você notou, no PHP a classe deve ter o mesmo nome do arquivo que a contém.

Bem, agora vamos criar o arquivo cidades_obj.php, que será a nossa classe de objeto que representa e guarda os dados de cada cidade selecionada. Note que as propriedades do objeto são um espelho dos campos do banco de dados pra, também, ficar mais legível:

<?php
  class cidades_obj
  {
    //Propriedades do objeto cidades
    public $id_cidade;
    public $id_uf;
    public $nome;
    public $cep_principal;
    public $is_capital;
    public $sigla;
  }
?>

Ok, agora vamos à classe que pega os dados no banco e joga numa lista de objetos (ou seja, a lista com cada cidade representada pelo objeto que criamos acima). Lista de objetos é pra ficar mais bonitinho, mas é um array mesmo. O nosso arquivo é o cidades.php. No início dele temos as propriedades que usaremos como parâmetros e na função get_cidades usamos as funções clássicas pra trabalhar com querys do MySQL no PHP:

<?php
  include_once("dados.php");
  include_once("cidades_obj.php");

  class cidades
  {
    //Propriedados/parâmetros do objeto de consulta de cidades
    public $id_cidade = 0;
    public $id_uf = 0;
    public $nome = '';
    public $cep_principal = '';
    public $is_capital = false;
    public $sigla = '';

    //Contador de resultados
    public $count = 0;

    public function get_cidades()
    {
      //Seleciona cidades no banco
      $sql = "SELECT * FROM cidades";
      $sql .= " WHERE id_cidade > 0";

      //Verifica parâmetros
      if ($this->id_cidade > 0) $sql .= " AND id_cidade = ".$this->id_cidade;
      if ($this->id_uf > 0) $sql .= " AND id_uf = ".$this->id_uf;
      if (strlen($this->nome) > 0) $sql.= " AND nome LIKE '%".$this->nome."%'";
      if (strlen($this->cep_principal) > 0) $sql.= " AND cep_principal = '".$this->cep_principal."'";
      if ($this->is_capital == true) $sql .= " AND is_capital";
      if (strlen($this->sigla) > 0) $sql.= " AND sigla = '".$this->sigla."'";

      $sql .= " ORDER BY nome ASC";

      //Conecta ao banco e abre a query
      $dados = new dados();
      $dados->get_conexao();
      $rs = mysql_query($sql);
      $this->count = 0;

      //Passa resultados pra um array do objeto cidades
      while ($reg = mysql_fetch_array($rs))
      {
        $cidade = new cidades_obj();
        $cidade->id_cidade = $reg['id_cidade'];
        $cidade->id_uf = $reg['id_uf'];
        $cidade->nome = $reg['nome'];
        $cidade->cep_principal = $reg['cep_principal'];
        $cidade->is_capital = $reg['is_capital'];
        $cidade->sigla = $reg['sigla'];
        $a[] = $cidade;
        $this->count++;
      }

      //Fecha conexão
      mysql_close();

      //Retorna lista de cidades
      return $a;
    }
  }
?>

Já temos o objeto que trará nossos resultados, agora vamos ler o array gerado pela classe acima e escrever o nosso XML no arquivo cidades.xml.php. Note que no início do arquivo instanciamos o objeto da classe acima e mais abaixo usamos a biblioteca DOMDocument do PHP5 pra gerar o XML:

<?php
  include_once("cidades.php");

  //Chama objeto cidades passando parâmetro por UF
  $cidades = new cidades();
  $cidades->id_uf = $_POST["id_uf"];
  $lst = $cidades->get_cidades();

  //Verifica se o array tem resultados
  if ($cidades->count > 0)
  {
    //Cria XML
    $xml = new DOMDocument("1.0", "UTF-8");
    $xml->preserveWhiteSpace = false;
    $xml->formatOutput = true;

    //Insere nó principal
    $root = $xml->createElement("cidades");

    //Varre o array
    foreach($lst as $city)
    {
      //Atribui variáveis pra criar campos com o valor de cada registro
      $id_uf = $xml->createElement("id_uf", $city->id_uf);
      $id_cidade = $xml->createElement("id_cidade", $city->id_cidade);
      $sigla = $xml->createElement("sigla", utf8_encode($city->sigla));
      $nome = $xml->createElement("nome", utf8_encode($city->nome));

      //Cria nó de registro
      $cidade = $xml->createElement("cidade");

      //Adiona campos com os valores
      $cidade->appendChild($id_uf);
      $cidade->appendChild($id_cidade);
      $cidade->appendChild($sigla);
      $cidade->appendChild($nome);

      //Adiciona o registro ao nó prncipal
      $root->appendChild($cidade);
    }

    //Fecha a TAG do nó principal
    $xml->appendChild($root);

    //Imprime o XML na tela
    Header("Content-Type: text/xml");
    echo $xml->saveXML();
  }
?>

O resultado gerado pela função acima é um arquivo XML mesmo como este:

<?xml version="1.0" encoding="UTF-8"?>
<cidades>
  <cidade>
    <id_uf>1</id_uf>
    <id_cidade>3</id_cidade>
    <sigla>AFL</sigla>
    <nome>Alta Floresta</nome>
  </cidade>
  <cidade>
    <id_uf>2</id_uf>
    <id_cidade>6</id_cidade>
    <sigla>CVL</sigla>
    <nome>Cascavel</nome>
  </cidade>
  <cidade>
    <id_uf>1</id_uf>
    <id_cidade>1</id_cidade>
    <sigla>CBA</sigla>
    <nome>Cuiabá</nome>
  </cidade>
  <cidade>
    <id_uf>1</id_uf>
    <id_cidade>4</id_cidade>
    <sigla>LRV</sigla>
    <nome>Lucas do Rio Verde</nome>
  </cidade>
  <cidade>
    <id_uf>1</id_uf>
    <id_cidade>5</id_cidade>
    <sigla>ROO</sigla>
    <nome>Rondonópolis</nome>
  </cidade>
  <cidade>
    <id_uf>1</id_uf>
    <id_cidade>2</id_cidade>
    <sigla>SNP</sigla>
    <nome>Sinop</nome>
  </cidade>
</cidades>

Por fim, vamos à nossa página com a seleção das cidades por estado. Eu dei ao arquivo o nome de idades.html.php seguindo o padrão que você deve ter percebido acima: ".xml.php" pra XML, "_obj.php" pra objeto (só criei com "_" por causa no nome da classe do objeto), só ".php" pra classe que pega os dados, e agora .html.php" pro arquivo que será visível mesmo - no meu ponto de vista este padrão ajuda a identificar pelo nome o propósito de de cada arquivo.

Bem, este último arquivo é o maior e mais complexo, onde vamos trabalhar com o AJAX mesmo, isso significa muito JavaScript, pois como o próprio nome já diz é Asynchronous Javascript And XML. Temos as funções todas no JavaScript para instanciar o AJAX, ler, fazer a consulta no nosso XML e imprimir os resultados na tela. Eis o código completo:

<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<html>
  <head>
    <title>Selecione a cidade</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <!-- ESTILOS DA PÁGINA -->
    <style type="text/css">
      body
      {
        background-color: #FFFFFF;
        font-family: arial, verdana, sans-serif;
        font-size: 10pt;
      }
      .Fonte8
      {
        font-size: 8pt;
      }
      .Fonte12
      {
        font-size: 12pt;
      }
    </style>

    <!-- CÓDIGOS DO AJAX -->
    <script language="JavaScript">
      function Dados(valor)
      {
        //Verifica se o navegador tem suporte a AJAX e qual o tipo de objeto AJAX ele usa
        try
        {
          ajax = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch(e)
        {
          try
          {
            ajax = new ActiveXObject("Msxml2.XMLHTTP");
          }
          catch(ex)
          {
            try
            {
              ajax = new XMLHttpRequest();
            }
            catch(exc)
            {
              alert("Este navegador não tem recursos para uso do AJAX!");
              ajax = null;
            }
          }
        }

        //Se tiver suporte a AJAX
        if(ajax)
        {
          //Deixa apenas o elemento 1 no option, os outros são excluídos
          document.forms[0].listCidades.options.length = 1;
          idOpcao  = document.getElementById("opcoes");

          if (valor == -1)
          {
            //Desabilita a lista de cidades
            document.forms[0].listCidades.disabled="disabled";
            idOpcao.innerHTML = "Selecione um estado na lista.";
          }
          else
          {
            //Define a chamada via POST ao XML criado pelo PHP com as cidades
            ajax.open("POST", "cidades.xml.php", true);
            ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

            ajax.onreadystatechange = function()
            {
              //Enquanto estiver processando emite a mensagem de aguarde
              if(ajax.readyState == 1)
              {
                idOpcao.innerHTML = "Aguarde...";
              }

              //Após ser processado chama função processXML que vai varrer os dados
              if(ajax.readyState == 4 )
              {
                if(ajax.responseXML)
                {
                  processXML(ajax.responseXML);
                }
                else
                {
                  //Caso não consiga ler o arquivo XML emite a mensagem e desabilita a lista de cidades
                  idOpcao.innerHTML = "Cidades não encontradas.";
                  document.forms[0].listCidades.disabled="disabled";
                }
              }
            }
          }

          //Passa o código do estado escolhido como parâmetro pro POST e envia
          var params = "id_uf=" + valor;
          ajax.send(params);
        }

        function processXML(obj)
        {
          //Pega a TAG cidade
          var dataArray = obj.getElementsByTagName("cidade");

          //Verifica o total de elementos contidos na TAG cidade
          if(dataArray.length > 0)
          {
            //Percorre o arquivo XML para extrair os dados
            for(var i = 0 ; i < dataArray.length ; i++)
            {
              var item = dataArray[i];

              //Contéudo dos campos no arquivo XML
              var sigla =  item.getElementsByTagName("sigla")[0].firstChild.nodeValue;
              var nome = item.getElementsByTagName("nome")[0].firstChild.nodeValue;

              idOpcao.innerHTML = "Selecione a cidade.";

              //Cria um novo option dinamicamente
              var novo = document.createElement("option");

              //Atribui um ID a esse elemento
              novo.setAttribute("id", "opcoes");

              //Atribui valor e texto
              novo.value = sigla;
              novo.text = sigla + " - " + nome;

              //Adiciona o novo elemento
              document.forms[0].listCidades.options.add(novo);
            }
            //Habilita a lista de cidades
            document.forms[0].listCidades.disabled="";
          }
          else
          {
            //Caso o XML volte vazio, exibe a mensagem abaixo e desabilita a lista de cidades
            idOpcao.innerHTML = "Cidades não encontradas.";
            document.forms[0].listCidades.disabled="disabled";
          }
        }
      }
    </script>

  </head>

  <body onload="document.forms[0].listCidades.disabled='disabled'">
    <span class="Fonte12"><b>Selecione a cidade</b></span><br />
    <span class="Fonte8">Tecnologia MySQL/OOP-PHP/AJAX</span>
    <br /><br />

    <form name="frmAjax">
      UF: 
      <select name="listEstados" onChange="Dados(this.value);">
        <option value="-1">Selecione o estado.</option>
        <option value="1">Mato Grosso</option>
        <option value="2">Paraná</option>
        <option value="3">Mato Grosso do Sul</option>
      </select>
      <br><br>
      Cidade: 
      <select name="listCidades" onChange="if (this.value != '0') { alert('Você selecionou ' + this.value + '.'); }">
        <option id="opcoes" value="0">Primeiro selecione o estado.</option>
      </select>
    </form>
    <a href="#" onclick="history.back()">Voltar</a>
</body>
</html>

O HTML é normal. Preste atenção aos nomes dos elementos pra poder chamá-los no JavaScript e às variáveis que armazenam os dados. Aquela verificação do suporte ao AJAX é por conta das diferenças de padrões - muita gente ainda não acordou pra vida e anida usa Microsoft Internet Explorer 6, e bem desatualizado, por isso nossa página pode não funcionar direito pra eles; além do que esta verificação é necessária porque o Windows Internet Explorer, me refiro já a algo mais considerável, que é o IE7 e o IE8, trabalha com AJAX via ActiveX, enquanto o Firefox, o Safari e o Chrome têm isso um pouco mais 'nativo' (não verifiquei o Opera).

Seguindo, nas últimas linhas da função Dados note que passamos o parâmetro com o código da UF que é verifiado la no $_POST do arquivo XML fazendo o nosso filtro. E na função proccessXML lemos o resultado retornado do XML chamado na função anterior, e preenchemos os valores da lista de cidades de acordo com eles.

Eu ignorei a tabela de países e criei a lista de estados fixa no HTML pra agilizar o exemplo.

Enfim, é bem resumido, mas espero que tenha sido proveitoso. Se você quiser como ficou o meu exemplo funcionando, clique aqui: http://pedro-araujo.com/tarefas/cidades.html.php

Referências:
Outras dicas deste autor

Programa inicializando com o sistema no Ubuntu (para iniciantes)

Quickcam Express no Ubuntu 9.04

cat como um editor de texto simples

Tuning PostgreSQL 9.1 com pgtune - Debian e derivados

Instalando uma interface gráfica para o pacman (gerenciador de pacotes do Arch Linux)

Leitura recomendada

Aviso de erro em arquivo de configuração do PHP

Tutorial de PHP-GTK2

PHP - Erro ao imprimir HTML ou JavaScript com ou sem variável

PHP - "Notice: Use of undefined constant..." [Resolvido]

Upload de arquivos maiores que 2MB em PHP

  

Comentários
[1] Comentário enviado por desv.carlos em 10/01/2011 - 12:12h

Muito bom o tutorial. É um dos poucos que achamos na internet e que realmente funciona.

[2] Comentário enviado por removido em 02/07/2011 - 11:37h

é o cara que faz jus ao nome "compartilhamento"
ta muito legal

[3] Comentário enviado por dastyler em 09/11/2013 - 05:53h

Olá Pedro,

Tentei implementar o seu código conforme o descrito na dica e deu erro de variavel indefinida no momento do retorno da função que traz os registros das cidades:

PHP Notice: Undefined variable: a in /var/www/html/select_php_ajax/classes/Cidades.php on line 58, referer: http://127.0.0.1/select_php_ajax/


[4] Comentário enviado por removido em 11/11/2013 - 09:00h

Cara, vou ter que dar uma olhada no código! Este exemplo é tão antigo que eu assim de cabeça não me lembro o que possa ser (deve ser uma variável não inicializada)! Mas "notice" não é um erro que impeça a execução, é apenas um alerta! Se você mudar a configuração de ERROR_REPORTING do PHP ou do próprio script ela deixa de aparecer.



Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts