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

Publicado por Perfil removido em 09/03/2010

[ Hits: 24.134 ]

 


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

Instalando driver wireless Broadcom BCM4312 no Fedora, RHEL e CentOS Linux

Instalando o Mint Stick no Ubuntu 22.04 LTS

Epsxe no Linux

Instalando Basthop no Fedora

Kmess - messenger leve e fácil de usar

Leitura recomendada

Instalando PHP 5 no Conectiva Linux 10

Dica de livro para quem quer aprender PHP

Convertendo scripts em ASP para PHP

Xampp - Maneira fácil de se instalar um servidor LAMP (Apache, MySQL e PHP)

Usando os operadores -> e :: para classes

  

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