Posts Tagged ‘PHP’

Mapeamento Objeto-Relacional (ORM)

1 \01UTC julho, 2008

Com o lançamento do PHP5, as esperanças de utilizar as técnicas da programação orientada à objetos (POO) no desenvolvimento de sistemas web cresceu para os adeptos da linguagem . A versão anterior do PHP já possuia recursos de orientação à objetos, mas estes eram muito limitados e alguns problemas chegavam a desencorajar seu uso. O PHP5 veio com um novo motor, o Zend Engine 2, que corrigiu esses problemas e suporta a maioria dos recursos esperados de uma linguagem orientada à objetos, como interfaces, métodos abstratos, herança, polimorfismo, dentre outros.

Apesar das linguagens modernas possuirem recursos da POO, os sistemas de gerenciamento de bases de dados (SGBD) são quase todos baseadas no modelo relacional, onde temos tabelas ao invés de objetos. E na maioria dos casos o que queremos é justamente guardar nossos objetos na base dados. Guardar instâncias de objetos em registros de tabelas é o objetivo das técnicas de mapeamento objeto-relacional (em inglês object-relational mapping, ORM).

Existem muitas diferenças entre os modelos relacional e orientado à objeto e a mais incômoda delas para quem desenvolve sistemas web é que tabelas não suportam herança.

Considere o seguinte exemplo: temos três classes, sendo uma delas abstrata e as outras duas concretas e extendendo a primeira. Mais palpável ainda: considere uma classe abstrata Pessoa e as classes concretas Fornecedor e Cliente que são filhas da classe Pessoa.

Como guardar essas informações no banco de dados?

Uma tabela por classe contreta

A solução mais imediata seria criar 2 tabelas, da seguinte forma:

Essa solução mais simples ignora completamente a hierarquia de classes e faz uma tabela por classe concreta sem relacionamentos.

  • Prós:
    • Todos os dados de um objeto estão em uma só tabela, facilitando a manipulação.
    • Buscas serão executadas de forma rápida.
    • Não há campos nulos.
  • Contras
    • Dificulta alterações na classe pai, pois os campos estão duplicados (ou triplicados, quadruplicados…).
    • Pode ser um grande problema se houver alguma classe com referência para a classe pai (veja próximo exemplo).

Em alguns casos talvez essa seja uma boa solução. Não é o caso se houver uma referência para a classe pai, como por exemplo:

UML - Referência à classe pai

Nesse caso a nossa primeira solução falha, pois não existe uma forma de relacionar Mensagem com Pessoa sem explicitar se é um Fornecedor ou um Cliente. Poderíamos claro, usar a chave estrangeira de todas as subclasses, mas isso seria um pesadelo no caso de muitos relacionamentos e/ou muitas subclasses. Precisamos de uma solução melhor.

Uma tabela por árvore de classes

Podemos utilizar 1 só tabela para todas as subclasses de Pessoa, solucionando o problema anterior:

Nesta solução temos 1 tabela para cada árvore de classes. O campo tipo irá determinar se o registro é um Cliente ou Fornecedor. O campo endereço ficará nulo no caso dos clientes e o campo telefone ficará nulo no caso dos fornecedores.

  • Prós:
    • Todas as informações de uma instância estão na mesma tabela, o que facilita a manipulação.
    • Permite referências à classe pai.
  • Contras:
    • Muitos campos nulos – pode ser muito deselegante para grandes árvores de classes.
    • Muitos registros na mesma tabela podem retardar as busca. Mesmo que indexemos o campo tipo teremos problemas, já que só é utilizado um índice por busca.
    • Registros muito grandes podem retardar as buscas.
    • Campo tipo extra.
    • Não permite referência específica às subclasses.

Note que essa solução falha se precisarmos ter uma referência para a subclasse. Se as mensagens, por exemplo, pudessem ser trocadas apenas entre clientes, não haveria como fazer essa restrição no banco de dados. E se nossa árvore de classes for grande a tabela se tornará um monstro com dezenas de campos nulos em cada registro. Precisamos ainda de uma solução melhor.

Uma tabela por classe

Podemos mapear cada classe para uma tabela, fazendo a representação da hereança por um relacionamento 1 para n:

Assim temos 1 tabela por classe, relacionando as classes de forma a mater a árvore e usando o campo tipo para indicar a subclasse. Na hora de buscar um registro, checa-se esse campo para saber qual é a classe e, logo, qual outra tabela consultar.

  • Prós:
    • Mantém as hierarquia das classes.
    • Não contém campos nulos.
    • Não quebra o encapsulamento de implementação da classe pai.
    • Permite fazer referências tanto a classe pai quanto às subclasses.
    • Registros pequenos, com poucos campos.
  • Contras:
    • Os atributos de um objeto estão espalhados em mais de uma tabela, tornando as buscas mais complexas.
    • As buscas podem ficar mais lentas pelo uso excessivo de joins.
    • Campo tipo extra.

Essa solução é mais completa, podendo ser utilizada em todas as situações. Além disso ela tem uma boa relação custo/benefício até para situações onde as outras soluções também são possíveis.

Conclusão

Recomendo utilizar a solução de uma tabela por classe sempre. O motivo é simples: mesmo que as outras soluções sejam aplicáveis em algumas situações, essas situações podem mudar na próxima alteração, forçando a implementar uma tabela por classe . Além disso, a perda de desempenho pode ser bastante minimizada pela utilização correta de índices.

Bordas arredondadas no CSS com uma forcinha do PHP

29 \29UTC junho, 2008

Bordas arredondadas parecem nunca sair de moda. Para a infelicidade dos webdesigners, adicionar mais de uma imagem de fundo a um elemento e a propriedade “border-radius” são exclusividades da versão 3 do CSS, que ainda não é suportada pelo navegador da Microsoft e por isso seu uso está fora de cogitação.

Mas nem tudo está perdido: uma ótima técnica que utiliza CSS2, descrita no artigo “Even More Rounded Corners”, permite mostrar bordas arredondadas utilizando apenas uma imagem PNG com a caixa inteira e uma porção de declarações “background-position” para fatiar virtualmente a imagem. Como todas as soluções compatíveis com o IE, ela necessita de marcação extra no HTML para funcionar. Na verdade, ela é bem compacta, vejam:

<div class="dialog">
<div class="content">
<div class="t"></div>
<!-- Seu contéudo vai aqui --></div>
<div class="b">
<div></div>
</div>
</div>

Mesmo compacta ela ainda causa, em menor intensidade, os mesmos já conhecidos problemas das marcações extras:

  • Dificuldade de manutenção: é necessário reproduzir a estrutura cada vez que quisermos uma caixa arredondada.
  • Perda de legibilidade: seu conteúdo ficará escondido num mar de DIVs inúteis.
  • Quebra de encapsulamento: tags com propósito unicamente visual (DIVs vazias!) tornam o código menos semântico e intuitivo.

Não sendo possível diminuir mais a quantidade de porcaria marcação inútil servida para o usuário, podemos ao menos reduzir algumas linhas do nosso código fonte no servidor, utilizando o PHP.

A solução aqui apresentada permitirá escrever no fonte:

<div class="dialog">
<!-- Seu conteúdo vai aqui --></div>

E ter como resultado o código “sujo” da técnica do “Even More Rounded Corners” na saída. O truque é fazer com que o PHP injete as divs necessárias e sirva para o usuário a marcação extra sem sujar o fonte, melhorando legibilidade, facilitando a manutenção e evitando em certo nível a quebra de encapsulamento. Para executar a mágica vamos usar o controle de buffer de saída do PHP e a extensão DOM, para manipular o HTML.

Primeiro, vamos precisar adicionar o seguinte código no início da página, fazendo com que ele execute antes do script emitir qualquer saída:

<?
// Captura a saída em um buffer.
ob_start();
?>

Isso garantirá que toda a saída do PHP seja guardada em um buffer, ao invés de ser enviada diretamente ao user agent.

O próximo e último passo é pegar toda a saída e injetar a marcação extra. Isso deve ser feito, é claro, no fim do script, depois de qualquer saída ter sido gerada e, conseqüentemente, guardada no buffer:

<?
// Lê o buffer de saída para uma variável string.
$saida = ob_get_contents();

ob_end_clean();

// Defina aqui a classe a ser considerada
// para caixas arredondadas.
$classe = "dialog";

// Carrega o documento na classe DOM.
$dom = new DomDocument("1.0");

// Detecta a codificação da página.
if (
  mb_detect_encoding(
  $saida . 'a' , 'UTF-8, ISO-8859-1' )
  ==
  "UTF-8"
  )
{
  $saida = utf8_decode($saida);
}
$dom->loadHTML($saida);

// Seleciona todas as DIVs
$divs = $dom->getElementsByTagName("div");

foreach ($divs as $div) {

  // Filtra as DIVs da classe selecionada.
  if (strstr($div->getAttribute("class"), $classe)) {

    // Cria os nós adicionais
    $content_div = $dom->createElement("div");
    $content_div->setAttribute("class", "content");

    $t_div = $dom->createElement("div");
    $t_div->setAttribute("class", "t");

    $b_div = $dom->createElement("div");
    $b_div->setAttribute("class", "b");

    $extra_div = $dom->createElement("div");

    // Cria a árvore correta
    $b_div->appendChild($extra_div);
    $content_div->appendChild($t_div);

    // Importa o conteúdo do div a ser arredondado
    // para dentro de 'content'.
    while ($div->hasChildNodes()) {
      $content_div->appendChild(
      $div->removeChild($div->firstChild)
      );
    }

    // Adiciona tudo de volta à div.
    $div->appendChild($content_div);
    $div->appendChild($b_div);
  }
}

$resultado = $dom->saveHTML();

echo $resultado;

?>

É claro que você não irá copiar e colar esse texto em todas as suas páginas. Você pode usar dois includes (um no começo do documento e outro ao final), e ainda, se você usa algum sistema de templates, fazer esses includes apenas no arquivo do template.

Podemos adaptar essa técnica para várias outras situações onde necessitamos de marcação extra. Podemos ainda adaptá-la facilmente para rodar no lado do cliente, uma vez que as funções DOM do PHP e do javascript são praticamente idênticas. O único porém deste último método é que não podemos garantir que o user agent suporte e esteja com o javascript habilitado e é então sujeitos a falhas, motivo pelo qual escolhi o método server-side.

Agora ninguém mais tem desculpa para fazer cara feia quando tiver que implementar aquele design todo arredondado!