Escolar Documentos
Profissional Documentos
Cultura Documentos
Pablo Dall’Oglio
Novatec
Copyright © 2007, 2009, 2016, 2018 da Novatec Editora Ltda.
Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998. É proibida a reprodução
desta obra, mesmo parcial, por qualquer processo, sem prévia autorização, por escrito, do autor e
da Editora.
Editor: Rubens Prates
Editoração eletrônica: Carolina Kuwabata
Revisão gramatical: Marta Almeida de Sá
Capa: Pablo Dall’Oglio e Rodolpho Lopes
ISBN: 978-85-7522-696-4
Histórico de edições impressas:
Julho/2018 Quarta edição
Outubro/2017 Terceira reimpressão
Outubro/2016 Segunda reimpressão
Março/2016 Primeira reimpressão
Novembro/2015 Terceira edição (ISBN: 978-85-7522-465-6)
Abril/2009 Segunda edição (ISBN: 978-85-7522-200-3)
Setembro/2007 Primeira edição (ISBN: 978-85-7522-137-2)
Novatec Editora Ltda.
Rua Luís Antônio dos Santos 110
02460-000 – São Paulo, SP – Brasil
Tel.: +55 11 2959-6529
Email: novatec@novatec.com.br
Site: www.novatec.com.br
Twitter: twitter.com/novateceditora
Facebook: facebook.com/novatec
LinkedIn: linkedin.com/in/novatec
Assim, dedico este livro a você que não silencia.
A você que é contra o preconceito e a violência.
A você que reage perante a injustiça quando poderia simplesmente se omitir.
A você que ainda faz distinção entre o certo e o errado em uma sociedade em
que a ética e o caráter têm se tornado cada vez mais exíveis.
A você que tem valores e princípios e consegue vencer na vida sem passar por
cima de ninguém.
A você que tem a grandeza de torcer e se emocionar com a vitória alheia e de
querer o bem do próximo.
Dedico este livro aos meus, à minha família, ao meu amor, aos meus amigos.
Vocês são o que importa.
“O que me preocupa não é o grito dos maus. É o silêncio dos bons”.
M L K
Sumário
Sobre o autor
Agradecimentos
Nota do autor
Organização do livro
Capítulo 1 Introdução ao PHP
1.1 O que é o PHP?
1.2 Setup
1.2.1 Instalação
1.2.2 Con guração
1.3 Um programa PHP
1.3.1 Estrutura do código-fonte
1.3.2 Comentários
1.3.3 Comandos de saída (output)
1.4 Variáveis
1.4.1 Tipo booleano
1.4.2 Tipo numérico
1.4.3 Tipo string
1.4.4 Tipo array
1.4.5 Tipo objeto
1.4.6 Tipo recurso
1.4.7 Tipo misto
1.4.8 Tipo callback
1.4.9 Tipo NULL
1.5 Declarações de tipo
1.5.1 Tipagem estrita
1.6 Superglobais
1.7 Constantes
1.8 Operadores
1.8.1 Atribuição
1.8.2 Aritméticos
1.8.3 Relacionais
1.8.4 Lógicos
1.9 Estruturas de controle
1.9.1 IF
1.9.2 WHILE
1.9.3 FOR
1.9.4 SWITCH
1.9.5 FOREACH
1.9.6 CONTINUE
1.9.7 BREAK
1.10 Requisição de arquivos
1.11 Manipulação de funções
1.11.1 Criação
1.11.2 Variáveis globais
1.11.3 Variáveis estáticas
1.11.4 Passagem de parâmetros
1.11.5 Recursão
1.11.6 Funções anônimas
1.12 Manipulação de arquivos e diretórios
1.13 Manipulação de strings
1.13.1 Declaração
1.13.2 Concatenação
1.13.3 Caracteres de escape
1.13.4 Funções para manipulação de strings
1.14 Manipulação de arrays
1.14.1 Criando um array
1.14.2 Arrays associativos
1.14.3 Iterações
1.14.4 Acessando arrays
1.14.5 Arrays multidimensionais
1.14.6 Funções para manipulação de arrays
1.15 Frameworks
1.16 Ferramentas importantes
Capítulo 2 Fundamentos de orientação a objetos
2.1 Programação procedural
2.2 Orientação a objetos
2.3 Classe
2.4 Métodos
2.5 Métodos construtores e destrutores
2.6 Conversões de tipo
2.7 Relacionamentos entre objetos
2.7.1 Associação
2.7.2 Composição
2.7.3 Agregação
2.8 Herança
2.9 Polimor smo
2.10 Abstração
2.10.1 Classes abstratas
2.10.2 Classes nais
2.10.3 Métodos abstratos
2.10.4 Métodos nais
2.11 Encapsulamento
2.11.1 Public
2.11.2 Private
2.11.3 Protected
2.12 Membros da classe
2.12.1 Constantes
2.12.2 Atributos estáticos
2.12.3 Métodos estáticos
2.13 Funções para manipulação de objetos
2.14 Interfaces
2.15 Design patterns
2.15.1 Singleton
2.15.2 Facade
2.15.3 Adapter
Capítulo 3 Do estruturado à orientação a objetos
3.1 Introdução
3.2 Acesso à base de dados
3.2.1 Acesso nativo
3.2.2 Acesso orientado a objetos com PDO
3.3 Nível 1 – Procedural, um script por ação
3.3.1 Formulário de inserção
3.3.2 Listagem de registros
3.3.3 Edição de registros
3.3.4 Exclusão de registros
3.3.5 Problemas encontrados
3.4 Nível 2 – Agrupando ações comuns em scripts
3.4.1 Listagem de registros
3.4.2 Formulário de cadastro
3.5 Nível 3 – Separando o HTML com micro-templates
3.5.1 Formulário de cadastros
3.5.2 Listagem de registros
3.6 Nível 4 – Separando o acesso a dados com funções
3.6.1 Funções de acesso à base
3.6.2 Listagem de registros
3.6.3 Formulário
3.7 Nível 5 – Separando o acesso a dados com classes
3.7.1 Classe de acesso à base de dados
3.7.2 Listagem
3.7.3 Formulário
3.8 Nível 6 – Melhorando as conexões e a segurança
3.9 Nível 7 – Transformando páginas em classes de controle
3.9.1 Formulário de cadastro
3.9.2 Index
3.9.3 A listagem
Capítulo 4 Tópicos especiais em orientação a objetos
4.1 Tratamento de erros
4.1.1 Cenário proposto
4.1.2 Função die()
4.1.3 Retorno de ags
4.1.4 Tratamento de exceções
4.2 Métodos mágicos
4.2.1 Introdução aos métodos mágicos
4.2.2 Método __get()
4.2.3 Método __set()
4.2.4 Armazenando atributos em vetores
4.2.5 Métodos __isset() e __unset()
4.2.6 Método __toString()
4.2.7 Exemplo de uso de __get() e __set()
4.2.8 Método __clone()
4.2.9 Método __call()
4.3 Manipulação de XML com a SimpleXML
4.3.1 Classe SimpleXmlElement
4.3.2 Acessando atributos
4.3.3 Percorrendo elementos lhos
4.3.4 Acessando elementos lhos
4.3.5 Alterando o conteúdo do documento
4.3.6 Acessando elementos repetitivos
4.3.7 Acessando atributos de elemento
4.3.8 Percorrendo atributos de elementos
4.4 Manipulação de XML com DOM
4.4.1 Leitura de conteúdo
4.4.2 Manipulação de conteúdo
4.5 SPL
4.5.1 Manipulação de arquivos
4.5.2 Manipulação de las
4.5.3 Manipulação de pilhas
4.5.4 Percorrendo diretórios
4.5.5 Manipulando arrays
4.6 Re ection
4.6.1 Re ectionClass
4.6.2 Re ectionMethod
4.6.3 Re ectionProperty
4.6.4 Gerando documentação
4.7 Traits
4.8 Injeção de dependência
4.9 PSR
4.10 Namespaces
4.11 SPL Autoload
4.12 Composer
Capítulo 5 Persistência
5.1 Introdução
5.2 Gateways
5.2.1 Table Data Gateway
5.2.2 Active Record
5.2.3 Data Mapper
5.3 Conexões e transações
5.3.1 Classe para conexões com Factory Method
5.3.2 Classe para transações
5.3.3 Registro de log e strategy pattern
5.4 Active Record e Layer Supertype
5.4.1 De nição da classe Active Record
5.4.2 Novo objeto
5.4.3 Obter objeto
5.4.4 Alterar objeto
5.4.5 Clonar objeto
5.4.6 Excluir objeto
5.4.7 Encapsulamento
5.5 De nição de critérios
5.5.1 Query Object
5.6 Manipulação de coleções de objetos
5.6.1 Repository Pattern
5.6.2 Preparação dos dados
5.6.3 Carregar coleção de objetos
5.6.4 Alterar coleção de objetos
5.6.5 Excluir coleção de objetos
Capítulo 6 Apresentação e controle
6.1 Padrão MVC
6.2 Organização de namespaces e diretórios
6.3 SPL Autoloaders
6.3.1 Library Loader
6.3.2 Application Loader
6.3.3 Exemplo de uso
6.4 Padrões de controle
6.4.1 Page Controller
6.4.2 Front Controller
6.4.3 Remote Facade
6.5 Padrões de apresentação
6.5.1 Componentes
6.5.2 Template View
6.6 Criando componentes
6.6.1 Elementos HTML
6.6.2 Painéis
6.6.3 Caixas
6.6.4 Diálogo de mensagem e questionamento
6.6.5 Ações
6.7 Usando templates
6.7.1 Substituições simples
6.7.2 Substituições com repetições
Capítulo 7 Formulários e listagens
7.1 Formulários
7.1.1 Classe lógica para formulários
7.1.2 Classes para apresentação de formulários
7.1.3 Classes para campos de formulários
7.1.4 Exemplos
7.2 Listagens
7.2.1 Classe lógica para Datagrids
7.2.2 Classe para colunas da datagrid
7.2.3 Classe para apresentação da datagrid
7.2.4 Exemplos
Capítulo 8 Criando uma aplicação
8.1 Visão geral da aplicação
8.1.1 Index
8.1.2 Template
8.1.3 Iniciando o projeto
8.1.4 Modelo de classes
8.1.5 Modelo relacional
8.2 As classes de modelo
8.2.1 Código-fonte das classes
8.2.2 Testando as classes de modelo
8.3 Programa
8.3.1 Cadastro de pessoas
8.3.2 Criando traits para ações comuns
8.3.3 Cadastro de produtos
8.3.4 Cadastro de cidades
8.3.5 Manipulação de sessões
8.3.6 Registro de vendas
8.3.7 Relatório de contas
8.3.8 Relatório de produtos
8.3.9 Relatório de pessoas
8.3.10 Grá co de vendas por mês
8.3.11 Dashboard de vendas
8.3.12 Controle de login
8.4 Considerações nais
Sobre o autor
Minha história com PHP teve início no ano de 2000, quando z parte de
um grande projeto de sistema para gestão acadêmica, o SAGU (Sistema
Aberto de Gestão Uni cada). Na época, éramos pioneiros no uso de PHP
para o desenvolvimento de aplicações de negócio, e isso nos rendeu uma
visita do Rasmus1, criador da linguagem, em uma de suas passagens pelo
Brasil. Desde aquela época, trabalhei em inúmeros projetos e passei a
utilizar também o PHP-GTK para criar aplicações grá cas em PHP, o que
culminou no primeiro livro sobre PHP-GTK do mundo, publicado em
2004. Ao programar com GTK, precisei dominar a linguagem de
orientação a objetos, visto que seus componentes são representados por
classes. Com essas escolhas, eu passei a compreender de nitivamente os
conceitos de orientação a objetos.
No início de 2004, quei doido ao conhecer as novas características do
PHP5 relacionadas à orientação a objetos. Passei a estudar os novos
conceitos implementados, e tudo coincidiu com o período em que eu
estava estudando Design Patterns na disciplina de Engenharia de
Software na universidade. Nessa época, escrevi um artigo intitulado PHP5,
Orientação a objetos e Design Patterns, uma pequena contribuição minha para a
comunidade de PHP no Brasil, tendo em vista que a maioria das
referências na época estava em inglês. O artigo foi um sucesso, e, em
algumas semanas, milhares de pessoas já tinham feito o seu download.
Após anos trabalhando com PHP, fui desenvolvendo um conjunto de
classes que implementavam funcionalidades básicas para projetos novos
que eu iniciava, como conexão com bancos de dados, formulários e
listagens. Em junho de 2006, resolvi reunir essas classes de forma
consistente, sob a estrutura de um framework, para usar em meu TCC.
Foi justamente quando eu estava desenvolvendo um projeto que a equipe
da Univates gostou do framework e solicitou um treinamento para
aprender como utilizá-lo em seus projetos. Ao mesmo tempo, eu havia
sido convidado para projetar a arquitetura de um sistema de pesquisas
em saúde para o Ministério da Saúde. Com esses projetos, percebi
naquela época que aquele framework facilitava o ensino da orientação a
objetos para novatos. Foi quando decidi escrever este livro.
Em 2006, escrevi a primeira edição do livro. Era uma época de grandes
dúvidas sobre o meu futuro. A única certeza era que eu tinha
conhecimentos que poderiam auxiliar os outros. Passei o inverno inteiro
daquele ano escrevendo.
Como, de certa maneira, fui pioneiro ao escrever um livro mais complexo
sobre PHP no Brasil que abordasse padrões de projetos, de algum modo
ajudei a formar uma massa crítica de pessoas que hoje se especializaram
nesses assuntos e se tornaram evangelistas. É muito grati cante saber que
in uenciei algumas mentes, que por sua vez hoje in uenciam outras. Isso
é uma grande responsabilidade.
Quando escrevi a primeira edição, em 2006, tinha uma determinada
experiência de vida e uma visão de arquitetura de software. Hoje, o PHP
evoluiu bastante e eu também mudei minha forma de enxergar alguns
aspectos no desenvolvimento de software, a nal, muitos anos se
passaram. Nesses anos, trabalhei em mais de uma dezena de projetos,
pude aplicar alguns padrões conhecidos e descobri novos usos.
Amadureci minha visão sobre padrões, e agora posso oferecer exemplos
melhores e mais robustos. Após muitos anos em débito com a
comunidade, estou me redimindo agora ao atualizar este volume,
incrementando-o com novos padrões e novos exemplos de uso e
adequando-o a novas formas de resolver os mesmos problemas.
O objetivo deste livro é continuar a construir conhecimento de maneira
didática. Ele é voltado ao público em estágio inicial e intermediário que
pretenda aprender a usar orientação a objetos e padrões de projeto para
construir aplicações de maneira organizada. Não se preocupe se não
entender o livro completamente na primeira leitura. Não é uma obra para
ser lida uma vez e depois ser guardada. Espero que você leia-a mais vezes
como referência. Tampouco considero esta uma obra de nitiva. A
linguagem evolui, porém o livro foi escrito sobre conteúdos sólidos que
não vão mudar tão cedo.
Hoje muitos aprendem a programar com frameworks. Alguns não
distinguem mais o que é linguagem, framework, e o que é um padrão.
Quando aprendemos diretamente um framework, já não sabemos mais se
os problemas ocorrem por causa do mau uso da linguagem, do
desconhecimento do framework ou por não saber usar determinado
padrão. É preciso ter conhecimentos sólidos na linguagem, em Orientação
a Objetos e em padrões de projeto para ter produtividade, ser efetivo, não
esbarrar em problemas simples e não perder muito tempo para resolver
um problema. Para tudo na vida existe uma curva de aprendizado, e se há
uma coisa que devemos conhecer bem antes de nos aventurarmos com
um framework especí co é a Orientação a Objetos e os Design Patterns.
Espero que esta obra, então, lhe agregue conhecimento e lhe desperte
para novas ideias. Se isso acontecer, terá valido muito a pena ter escrito.
Não deixe de me escrever. Eu adoro receber emails com sugestões. Espero
contar com você para evoluir as ideias e, juntos, construirmos novas
edições ou mesmo novos livros.
Ao longo de vários anos, este livro serviu como base para a criação de
apresentações, aulas e vários outros conteúdos, mas nem sempre foi
indicado por aqueles que o utilizaram como referência. Portanto, você
encontrará muitos desses exemplos pela internet, mas nem sempre
aqueles que copiaram mantiveram os meus créditos. Permita-se usar
partes dos exemplos aqui apresentados para dar uma aula ou ensinar
colegas na empresa, desde que mantenha sempre os créditos de quem
criou o material.
Pablo Dall’Oglio
pablo@dalloglio.net
@pablodalloglio
linkedin.com/in/pablod
fb.com/pablodalloglio
1.2 Setup
1.2.1 Instalação
Neste capítulo vamos dar algumas dicas de instalação do PHP em
diferentes ambientes. Não faremos tutoriais completos e detalhados para
a instalação, pois instruções de instalação mudam conforme o
lançamento de novas versões de sistemas operacionais e do PHP. Assim,
colocá-las no livro seria um alto risco. Portanto, criaremos e indicaremos
tutoriais online e os manteremos (os tutoriais) atualizados.
Observação: Recomendo pelo menos PHP 7.2 ou superior para rodar os
exemplos deste livro. Alguns exemplos podem funcionar em versões inferiores, mas
alguns recursos específicos exigem pelo menos esta versão da linguagem.
phpinfo.php
<?php
phpinfo();
1.3.2 Comentários
Para comentar uma única linha:
// echo "a";
# echo "a";
Para comentar muitas linhas:
/* echo "a";
echo "b"; */
Resultado:
abc
var_dump
É uma função que imprime o conteúdo de uma variável de forma
detalhada, muito comum para executar um debug. Se o parâmetro for
um objeto, ele imprimirá todos os seus atributos; se for um array de
várias dimensões, imprimirá todas elas, com seus respectivos conteúdos
e os tipos de dados. Exemplo:
$vetor = array('Palio', 'Gol', 'Fiesta', 'Corsa');
var_dump($vetor);
Resultado:
array(4) {
[0]=> string(5) "Palio"
[1]=> string(3) "Gol"
[2]=> string(6) "Fiesta"
[3]=> string(5) "Corsa"
}
print_r
É uma função que imprime o conteúdo de uma variável de forma
detalhada, assim como a var_dump(), mas em um formato mais legível
para o programador, com os conteúdos alinhados e suprimindo os tipos
de dados. Exemplo:
$vetor = array('Palio', 'Gol', 'Fiesta', 'Corsa');
print_r($vetor);
Resultado:
Array (
[0] => Palio
[1] => Gol
[2] => Fiesta
[3] => Corsa
)
1.4 Variáveis
Variáveis são identi cadores utilizados para representar valores mutáveis e
voláteis que só existem durante a execução de um programa. São
armazenadas na memória RAM, e seu conteúdo é destruído após a
execução do programa. Para criar uma variável em PHP, precisamos
atribuir-lhe um nome de identi cação, sempre precedido pelo caractere
cifrão ($). Veja os exemplos a seguir:
<?php
$nome = "João";
$sobrenome = "da Silva";
echo "$sobrenome, $nome";
Resultado:
da Silva, João
Algumas dicas:
• Nunca inicie a nomenclatura de variáveis com números.
• Nunca utilize espaços em branco no meio do identi cador da variável.
• Nunca utilize caracteres especiais (! @ # % ^ & * / | [ ] { }) na
nomenclatura das variáveis.
• Evite criar variáveis com mais de 15 caracteres em virtude da clareza do
código-fonte.
• Nomes de variáveis devem ser signi cativos e transmitir a ideia de seu
conteúdo dentro do contexto no qual a variável está inserida.
• Use preferencialmente palavras em letras minúsculas separadas por
“_” ou somente as primeiras letras em maiúsculas quando da
ocorrência de mais palavras.
<?php
$codigo_cliente // exemplo de variável
$codigoCliente // exemplo de variável
Com exceção de nomes de classes e funções, o PHP é case sensitive, ou seja,
é sensível a letras maiúsculas e minúsculas. Tome cuidado ao declarar
variáveis. Por exemplo, a variável $codigo é tratada de forma totalmente
diferente da variável $Codigo.
Em alguns casos, precisamos ter em nosso código-fonte nomes de
variáveis que podem mudar de acordo com determinada situação. Nesse
caso, não só o conteúdo da variável é mutável, mas também seu nome.
Para isso o PHP implementa o conceito de variáveis variantes (variable
variables). Sempre que utilizarmos dois sinais de cifrão ($$) precedendo o
nome de uma variável, o PHP irá referenciar uma variável representada
pelo conteúdo da primeira. Neste exemplo, usamos esse recurso quando
declaramos a variável $nome (conteúdo de $variavel) contendo 'maria'.
<?php
// define o nome da variável
$variavel = 'nome';
// cria variável identificada pelo conteúdo de $variavel
$$variavel = 'maria';
// exibe variável $nome na tela
echo $nome; // resultado = maria
Quando uma variável é atribuída a outra, é criada uma nova área de
armazenamento na memória (exceto quando a variável representa um
objeto). Veja neste exemplo que, apesar de $b receber o mesmo conteúdo
de $a, após qualquer modi cação em $b, $a continua com o mesmo valor.
<?php
$a = 5;
$b = $a;
$b = 10;
echo $a; // resultado = 5
echo ' '; // espaço
echo $b; // resultado = 10
Para criar referência entre variáveis, ou seja, duas variáveis apontando
para a mesma região da memória, a atribuição deve ser precedida pelo
operador &. Assim, qualquer alteração em qualquer uma das variáveis
re ete na outra.
<?php
$a = 5;
$b = &$a;
$b = 10;
echo $a; // resultado = 10
echo ' '; // espaço
echo $b; // resultado = 10
Objetos são sempre copiados por referência, independentemente de
utilizarmos o operador & na operação de atribuição. Objetos planos
podem ser criados no PHP a partir da classe stdClass. Neste exemplo
demonstramos que objetos são copiados por referência. Assim, alterações
em um objeto implicam em alterações em sua réplica.
<?php
$a = new stdClass; // cria objeto
$a->nome = 'Maria'; // define atributo
$b = $a; // cria réplica
$b->nome = 'Joana'; // define atributo
print $a->nome; // resultado = Joana
echo ' '; // espaço
print $b->nome; // resultado = Joana
Resultado:
José da Silva
No programa a seguir, criamos uma variável numérica contendo o valor
91. Então, testamos se a variável é maior que 90. Esta comparação
também retorna um valor booleano (TRUE ou FALSE). O conteúdo da
variável $vai_chover é um booleano que será testado logo em seguida para
a impressão da string “Vai chover”.
<?php
$umidade = 91; // declara variável numérica
// testa se é maior que 90. Retorna um booleano
$vai_chover = ($umidade > 90);
// testa se $vai_chover é verdadeiro
if ($vai_chover) {
echo 'Vai chover';
}
Resultado:
Vai chover
Também são considerados valores falsos em comparações booleanas:
• Inteiro 0.
• Ponto utuante 0.0.
• Uma string vazia “” ou “0”.
• Um array vazio.
• Um objeto sem elementos.
• Tipo NULL.
Resultado:
stdClass Object (
[modelo] => Palio
[ano] => 2002
[cor] => Azul
)
Palio 2002 Azul
Resultado:
int(5)
string(5) "teste"
Além disso, o PHP tentará fazer conversões automáticas de tipo sempre
que for possível. Nos exemplos a seguir, usamos o operador de
concatenação (.) entre string e integer, e o resultado é uma string (teste5).
Em seguida, somamos duas strings que foram automaticamente
convertidas para numérico, resultando em (15).
<?php
var_dump('teste' . 5);
var_dump('5' + '10');
Resultado:
string(6) "teste5"
int(15)
Quando a conversão de tipos não puder ser feita naturalmente, como em (‘5’ +
‘x’), então uma Warning será emitida: A non-numeric value encountered.
Em se tratando de parâmetros de funções, podemos especi car
opcionalmente os tipos dos dados recebidos, como no exemplo a seguir,
em que adicionamos o tipo antes do nome do parâmetro ($peso, $altura).
Nestes casos, o PHP tentará converter o parâmetro ($peso, $altura) sempre
que possível para float, garantindo que ele seja daquele tipo. Então, se
passarmos valores float, int, ou até mesmo uma string numérica,
garantiremos que o parâmetro dentro da função seja um oat. No
exemplo a seguir, mesmo passando uma string, e um integer, dentro da
função, eles são recebidos como float.
<?php
function calcula_imc(float $peso, float $altura) {
var_dump($peso, $altura);
return $peso / ($altura*$altura);
}
var_dump(calcula_imc('75.1',2));
Resultado:
float(75.1)
float(2)
float(18.775)
Esta conversão não funciona em todos os casos. No caso de um método
que espera um parâmetro do tipo oat, se for passada uma string que não
possa ser convertida para oat diretamente, erros poderão ser emitidos.
No exemplo a seguir, temos um número com caracteres alfanuméricos
depois. A conversão, neste caso, ainda é possível, para 75.1, mas um Notice
é emitido.
var_dump(calcula_imc('75.1x',2));
Resultado:
Notice: A non well formed numeric value encountered
Caso venhamos a utilizar um parâmetro que não possa ser convertido
para float, como uma string não numérica, ou um array, então um erro
será emitido, como no exemplo a seguir:
var_dump(calcula_imc('abc123',2));
Resultado:
Fatal error: Uncaught TypeError: Argument 1 passed to calcula_imc() must
be of the type float, string given
Além de declarar o tipo do parâmetro, também é possível declarar o seu
retorno. No caso da função anterior, como ela envolve um cálculo entre
duas variáveis do tipo oat, o retorno naturalmente será um oat. Mas
podemos forçar o retorno para outro tipo também como um int, ou uma
string. No exemplo a seguir, o retorno está sendo forçado para integer.
<?php
function calcula_imc(float $peso, float $altura): int {
var_dump($peso, $altura);
return $peso / ($altura*$altura);
}
var_dump(calcula_imc(75, 1.85));
Resultado:
float(75)
float(1.85)
int(21)
Contudo, se você declarar um tipo de retorno como array, e internamente
a função retornar um float, um erro será lançado, pois a conversão entre
esses dois tipos não é possível:
<?php
function calcula_imc(float $peso, float $altura): array {
return $peso / ($altura*$altura);
}
var_dump(calcula_imc(75, 1.85));
Resultado:
Fatal error: Uncaught TypeError: Return value of calcula_imc() must be
of the type array, float returned
Também é possível de nir o tipo de retorno como void. Neste caso, a
função deve somente executar algo, mas não retornar. Caso ela retorne
algo, um erro fatal ocorrerá, como no exemplo a seguir.
<?php
function registra_log($mensagem): void {
file_put_contents('/tmp/system.log', $mensagem. "\n", FILE_APPEND);
return true;
}
registra_log('teste');
Resultado:
Fatal error: A void function must not return a value in ...
Como você poderia imaginar, não é qualquer palavra que é válida na
declaração de tipo. Você pode usar bool, oat, int, string, array, callable,
self e o nome de uma classe. No entanto não pode usar aliases como
integer ou boolean. Caso tente usar um destes, por exemplo, “boolean”,
quando o tipo correto é “bool”, um erro será lançado, pois o PHP achará
que você está especi cando o nome de uma classe (instance of boolean)
que, nesse caso, não existe.
<?php
function usar_tipo_errado(boolean $teste) {
var_dump($teste);
}
usar_tipo_errado(true);
Resultado:
Fatal error: Uncaught TypeError: Argument 1 passed to usar_tipo_errado()
must be an instance of boolean, boolean given
Resultado:
float(75)
float(1.85)
float(21.913805697589)
Fatal error: Uncaught TypeError: Argument 1 passed to calcula_imc() must
be of the type float, string given
1.7 Constantes
Uma constante é um valor que não sofre modi cações durante a execução
do programa. Ela é representada por um identi cador, assim como as
variáveis, com a exceção de que só pode conter valores escalares
(booleano, inteiro, ponto utuante e string) ou arrays. As regras de
nomenclatura de constantes são as mesmas regras das variáveis, exceto
pelo fato de as constantes não serem precedidas pelo sinal de cifrão ($) e
geralmente utilizarem nomes em letras maiúsculas.
MAXIMO_CLIENTES // exemplo de constante
Você pode de nir uma constante utilizando a função define(). Quando
uma constante é de nida, ela não pode mais ser modi cada ou anulada.
Exemplo:
<?php
define('MAXIMO_CLIENTES', 100);
echo MAXIMO_CLIENTES;
Resultado:
100
1.8 Operadores
1.8.1 Atribuição
Um operador de atribuição é usado para de nir o valor de uma variável.
O operador básico de atribuição é =, mas outros operadores podem ser
utilizados:
<?php
$var = 100;
$var += 5; // Soma 5 em $var;
$var -= 5; // Subtrai 5 em $var;
$var *=5; // Multiplica $var por 5;
$var /= 5; // Divide $var por 5;
print $var; // resultado: 100
Operadore
Descrição
s
++$var Pré-incremento. Incrementa $a em um e, então, retorna $a.
$var++ Pós-incremento. Retorna $a e, então, incrementa $a em um.
--$var Pré-decremento. Decrementa $a em um e, então, retorna
$a.
Operadore
Descrição
s
$var-- Pós-decremento. Retorna $a e, então, decrementa $a em
um.
<?php
$var = 100;
print $var++ . ' '; // retorna 100 e incrementa para 101
print ++$var . ' '; // incrementa para 102 e retorna
print $var-- . ' '; // retorna 102 e decrementa para 101
print --$var . ' '; // decrementa para 100 e retorna
Resultado:
100 102 102 100
1.8.2 Aritméticos
Operadores aritméticos são usados para se fazer cálculos matemáticos.
Operadore
Descrição
s
+ Adição
- Subtração
* Multiplicação
/ Divisão
% Módulo (resto da
divisão)
Em cálculos complexos, procure usar parênteses, sempre observando as
prioridades aritméticas. Por exemplo:
<?php
$a = 2;
$b = 4;
echo $a+3*4+5*$b . ' '; // resultado = 34
echo ($a+3)*4+(5*$b) . ' '; // resultado = 40
O PHP faz automaticamente a conversão de tipos em operações:
<?php
// declaração de uma string contendo 10
$a = '10';
// soma + 5
echo $a + 5;
Resultado:
15
1.8.3 Relacionais
Operadores relacionais são usados para estabelecer comparações entre
valores ou expressões, resultando sempre um valor boolean (TRUE ou
FALSE).
Comparadore
Descrição
s
== Igual. Resulta verdadeiro (TRUE) se as expressões forem iguais.
=== Idêntico. Resulta verdadeiro (TRUE) se as expressões forem iguais e tiverem o
mesmo tipo de dados.
!= ou <> Diferente. Resulta verdadeiro (TRUE) se as variáveis forem diferentes.
< Menor que.
> Maior que.
<= Menor que ou igual a.
>= Maior que ou igual a.
Veja a seguir alguns testes lógicos e seus respectivos resultados. No
primeiro caso, vemos a utilização errada do operador de atribuição = para
realizar uma comparação; o operador sempre retorna o valor atribuído.
<?php
if ($a = 5) {
echo 'essa operação atribui 5 à variável $a';
}
Resultado:
essa operação atribui 5 à variável $a
No exemplo a seguir, declaramos duas variáveis, uma integer e outra
string. Neste caso, vemos a utilização dos operadores de comparação == e
!=.
<?php
$a = 1234;
$b = '1234';
if ($a == $b) {
echo '$a e $b são iguais';
}
else if ($a != $b) {
echo '$a e $b são diferentes';
}
Resultado:
$a e $b são iguais
No próximo caso, além da comparação entre as variáveis, comparamos
também os tipos de dados das variáveis.
<?php
$c = 1234;
$d = '1234';
if ($c === $d) {
echo '$c e $d são iguais e do mesmo tipo';
}
if ($c !== $d) {
echo '$c e $d são de tipos diferentes';
}
Resultado:
$c e $d são de tipos diferentes
O PHP considera o valor zero como falso em comparações lógicas. Para
evitar erros semânticos em retorno de valores calculados por funções que
podem retornar tanto um valor booleano quanto um inteiro, podemos
utilizar as seguintes comparações:
<?php
$e = 0;
// testa a variável é FALSE
if ($e == FALSE) {
echo '$e é falso ';
}
// testa se a variável é um FALSE e do tipo boolean
if ($e === FALSE) {
echo '$e é FALSE e do tipo boolean ';
}
// testa se $e é igual a zero e do mesmo tipo que zero
if ($e === 0) {
echo '$e é zero mesmo ';
}
Resultado:
$e é falso $e é zero mesmo
1.8.4 Lógicos
Operadores lógicos são usados para combinar expressões lógicas entre si,
agrupando testes condicionais.
Operador Descrição
($a and E – Verdadeiro (TRUE) se tanto $a quanto $b forem verdadeiros.
$b)
($a or $b) OU – Verdadeiro (TRUE) se $a ou $b forem verdadeiros.
($a xor XOR – Verdadeiro (TRUE) se $a ou $b forem verdadeiros, de forma
$b) exclusiva.
(! $a) NOT – Verdadeiro (TRUE) se $a for FALSE.
($a && $b) E – Verdadeiro (TRUE) se tanto $a quanto $b forem verdadeiros.
($a || $b) OU – Verdadeiro (TRUE) se $a ou $b forem verdadeiros.
Resultado:
Não vou sair de casa
Já neste outro programa, caso uma variável seja TRUE e a outra seja FALSE
(OU exclusivo), será impresso no console “Vou pensar duas vezes antes de
sair”.
<?php
$vai_chover = FALSE;
$esta_frio = TRUE;
if ($vai_chover xor $esta_frio) {
echo "Vou pensar duas vezes antes de sair";
}
Resultado:
Vou pensar duas vezes antes de sair
1.9 Estruturas de controle
1.9.1 IF
O IF é uma estrutura de controle que introduz um desvio condicional, ou
seja, um desvio na execução natural do programa. Caso a condição dada
pela expressão seja satisfeita, então serão executadas as instruções do
bloco de comandos. Caso a condição não seja satisfeita, o bloco de
comandos será simplesmente ignorado. O comando IF pode ser lido
como “SE (expressão) ENTÃO { comandos... }”.
ELSE é utilizado para indicar um novo bloco de comandos delimitado por
{ }, caso a condição do IF não seja satisfeita. Pode ser lido como “caso
contrário”. A utilização do ELSE é opcional.
Veja na gura 1.2 um uxograma explicando o funcionamento do
comando IF.
Resultado:
não é igual
Quando não explicitamos o operador lógico em testes por meio do IF, o
comportamento-padrão do PHP é retornar TRUE sempre que a variável
tiver conteúdo válido.
<?php
$a = 'conteúdo';
if ($a) {
echo '$a tem conteúdo';
}
if ($b) {
echo '$b tem conteúdo';
}
Resultado:
$a tem conteúdo
Resultado:
parabéns, você foi promovido
parabéns, você foi promovido
O PHP nos oferece facilidades quando desejamos executar tarefas simples,
como realizar uma atribuição condicional a uma variável. A seguir você
confere um código tradicional que veri ca o estado de uma variável antes
de atribuir o resultado.
if ($valor_venda > 100) {
$resultado = 'muito caro';
}
else {
$resultado = 'pode comprar';
}
O mesmo código poderia ser escrito em uma única linha da seguinte
forma:
$resultado = ($valor_venda > 100) ? 'muito caro' : 'pode comprar';
A primeira expressão é a condição a ser avaliada; a segunda é o valor
atribuído caso esta seja verdadeira; e a terceira é o valor atribuído caso
esta seja falsa.
1.9.2 WHILE
O WHILE é uma estrutura de controle similar ao IF. De maneira similar,
tem uma condição para executar um bloco de comandos. A diferença
primordial é que o WHILE estabelece um laço de repetição, ou seja, o bloco
de comandos será executado de modo repetitivo enquanto a condição de
entrada dada pela expressão for verdadeira. Esse comando pode ser
interpretado como
“ENQUANTO (expressão) FAÇA {comandos... }.”.
A gura 1.3 procura explicar o uxo de funcionamento do comando WHILE.
Quando a expressão é avaliada como TRUE, o programa parte para a
execução de um bloco de comandos. Ao m da execução desse bloco de
comandos, o programa retorna ao ponto inicial da avaliação e, se a
expressão continuar verdadeira, o programa continua também com a
execução do bloco de comandos, constituindo um laço de repetições que
só é interrompido quando a expressão avaliada retorna um valor falso
(FALSE).
while (expressão) {
comandos;
}
Resultado:
1234
1.9.3 FOR
O FOR é uma estrutura de controle que estabelece um laço de repetição
baseado em um contador; é muito similar ao comando WHILE. O FOR é
controlado por um bloco de três comandos que estabelecem uma
contagem, ou seja, o bloco de comandos será executado um certo número
de vezes.
for (expr1; expr2; expr3) {
comandos
}
Parâmetro
Descrição
s
expr1 Valor inicial da variável contadora.
expr2 Condição de execução. Enquanto TRUE, o bloco de comandos será
executado.
expr3 Valor a ser incrementado após cada execução.
Exemplo:
<?php
for ($i = 1; $i <= 10; $i++) {
print $i;
}
Resultado:
12345678910
Procure utilizar nomes sugestivos para variáveis, mas, em alguns casos
especí cos, como em contadores, permita-se utilizar variáveis de apenas
uma letra, como no exemplo a seguir:
<?php
for ( $i = 0; $i < 5; $i++ ) {
for ( $j = 0; $j < 4; $j++ ) {
for ( $k = 0; $k < 3; $k++ ) {
// comandos...
}
}
}
Evite laços de repetição com muitos níveis de iteração. Como o próprio
Linus Torvalds já disse certa vez, se você estiver utilizando três níveis
encadeados ou mais, considere a possibilidade de revisar a lógica de seu
programa.
1.9.4 SWITCH
O comando switch é uma estrutura que simula uma bateria de testes
sobre uma variável. É similar a uma série de comandos IF sobre a mesma
expressão. Frequentemente é necessário comparar a mesma variável com
valores diferentes e executar uma ação especí ca em cada um desses
casos.
No uxograma da gura 1.4 vemos que, para cada teste condicional (CASE),
existe um bloco de comandos a ser executado caso a expressão avaliada
retorne verdadeiro (TRUE). Caso a expressão retorne falso (FALSE), o switch
parte para a próxima expressão a ser avaliada, até que não tenha mais
expressões para avaliar. Caso todas as expressões sejam falsas, o switch
executará o bloco de códigos representado pelo identi cador default.
Resultado:
i é igual a 1
O switch executa linha por linha até encontrar a ocorrência de break. Por
isso a importância do comando break, para evitar que os blocos de
comando seguintes sejam executados por engano. A cláusula default será
executada se nenhuma das expressões anteriores for veri cada.
<?php
$i = 1;
switch ($i) {
case 0:
print "i é igual a 0";
break;
case 1:
print "i é igual a 1";
break;
case 2:
print "i é igual a 2";
break;
default:
print "i não é igual a 0, 1 ou 2";
}
Resultado:
i é igual a 1
1.9.5 FOREACH
O foreach é um laço de repetição para iterações em arrays ou matrizes. É
um FOR simpli cado que decompõe um vetor ou uma matriz em cada um
de seus elementos por meio de sua cláusula AS.
foreach ($array as $valor) {
instruções
}
Exemplo:
<?php
$fruta = array("maçã", "laranja", "pera", "banana");
foreach ($fruta as $valor) {
print "$valor -";
}
Resultado:
maçã – laranja – pera – banana –
1.9.6 CONTINUE
A instrução continue, quando executada em um bloco de comandos
FOR/WHILE, ignora as instruções restantes até o fechamento em }. Dessa
maneira o programa segue para a próxima veri cação da condição de
entrada do laço de repetição.
1.9.7 BREAK
O comando break aborta a execução de blocos de comandos, como IF,
WHILE, FOR. Se estivermos em uma execução com muitos níveis de iteração e
quisermos abortar n níveis, a sintaxe deverá ser a seguinte:
while...
for...
break <quantidade de níveis>
1.10 Requisição de arquivos
Em linguagens de script como o PHP, frequentemente precisamos incluir
dentro de nossos programas outros arquivos com de nições de funções,
constantes, con gurações ou mesmo carregar um arquivo contendo a
de nição de uma classe. Para atingir esse objetivo no PHP, podemos fazer
uso de um dos seguintes comandos:
include <arquivo>
A instrução include() inclui e avalia o arquivo informado. Seu código
(variáveis, objetos e arrays) entra no escopo do programa, tornando-se
disponível a partir da linha em que a inclusão ocorre. Se o arquivo não
existir, será emitida uma mensagem de advertência (warning).
Exemplo:
tools.php
<?php
function quadrado($numero) {
return $numero * $numero;
}
teste.php
<?php
include 'tools.php'; // carrega arquivo com a função necessária
echo quadrado(4); // imprime o quadrado do número 4
Resultado:
16
require <arquivo>
Similar ao include. Difere somente na manipulação de erros. Enquanto o
include produz uma mensagem de advertência, o require produz um erro
fatal caso o arquivo não exista.
include_once <arquivo>
Funciona da mesma maneira que o comando include, a não ser que o
arquivo informado já tenha sido incluído. Neste caso, a operação não é
refeita (o arquivo é incluído apenas uma vez). É útil em casos em que o
programa passa mais de uma vez pela mesma instrução. Assim, evitará
sobreposições, redeclarações etc.
require_once <arquivo>
Funciona da mesma maneira que o comando require, a não ser que o
arquivo informado já tenha sido incluído. Neste caso, a operação não é
refeita (o arquivo é incluído apenas uma vez). É útil em casos em que o
programa passa mais de uma vez pela mesma instrução. Assim, podem-
se evitar sobreposições, redeclarações etc.
1.11.1 Criação
Para declarar uma função em PHP, usa-se o operador function seguido do
nome que desejamos de nir, sem espaços em branco e iniciando
obrigatoriamente com uma letra. Na mesma linha, digitamos a lista de
argumentos (parâmetros) que a função irá receber, separados por vírgula.
Em seguida, encapsulado por chaves {}, vem o código da função. No
nal, utiliza-se a cláusula return para retornar o resultado da função
(integer, string, array, objeto etc.).
<?php
function nome_da_funcao ($arg1, $arg2, $argN) {
$valor = $arg1 + $arg2 + $argN;
return $valor;
}
No exemplo a seguir, criamos uma função que calcula o índice de massa
corporal. Essa função recebe dois parâmetros ($peso e $altura) e retorna o
valor de nido pela fórmula.
<?php
function calcula_imc($peso, $altura) {
return $peso / ($altura * $altura);
}
echo calcula_imc(75, 1.85);
Resultado:
21.913805697589
Resultado:
percorreu 60 milhas
percorreu 120 milhas
percorreu no total 300 quilometros
Resultado:
percorreu mais 100 do total de 100
percorreu mais 200 do total de 300
percorreu mais 50 do total de 350
Resultado:
10
Para efetuar a passagem de parâmetros by reference para as funções, basta
utilizar o operador & na frente do parâmetro; isso faz com que as
transformações feitas pela função sobre a variável sejam válidas no
contexto externo à função.
<?php
function incrementa(&$variavel, $valor) {
$variavel += $valor;
}
$a = 10;
incrementa($a, 20);
echo $a;
Resultado:
30
O PHP permite de nir valores default para parâmetros. Reescreveremos a
função anterior, declarando o default de $valor como 40. Assim, se o
programador executar a função sem especi car o parâmetro, será
assumido o valor 40.
<?php
function incrementa(&$variavel, $valor = 40) {
$variavel += $valor;
}
$a = 10;
incrementa($a);
echo $a;
Resultado:
50
O PHP também permite de nir uma função com o número de
argumentos variáveis, ou seja, permite obtê-los de forma dinâmica,
mesmo sem saber quais são ou quantos são. Para saber quais são,
utilizamos a função func_get_args(); para saber a quantidade de
argumentos, utilizamos a função func_num_args().
<?php
function ola() {
$argumentos = func_get_args();
$quantidade = func_num_args();
for($n=0; $n<$quantidade; $n++) {
echo 'Olá ' . $argumentos[$n] . ', ';
}
}
ola('João', 'Maria', 'José', 'Pedro');
Resultado:
Olá João, Olá Maria, Olá José, Olá Pedro,
1.11.5 Recursão
O PHP permite chamadas de funções recursivamente. No caso a seguir,
criaremos uma função para calcular o fatorial de um número.
<?php
function fatorial($numero) {
if ($numero == 1)
return $numero;
else
return $numero * fatorial($numero -1);
}
echo fatorial(5) . "<br>\n";
echo fatorial(7) . "<br>\n";
Resultado:
120
5040
Resultado:
Jose da Conceicao
Array (
[0] => Jose da Conceicao
[1] => Jeferson Araujo
[2] => Felix Junior
[3] => Enio Muller
[4] => Angelo Onix
)
Resultado:
O próprio código-fonte
fwrite
Grava uma string no arquivo apontado pelo identi cador (handle) de
arquivo. Se o argumento comprimento (length) for informado, a gravação
será limitada a esse tamanho. Retorna o número de bytes gravados ou
FALSE em caso de erro.
int fwrite ( resource $handle , string $string [, int $length ] )
Exemplo:
<?php
$fp = fopen ("/tmp/file.txt", "w"); // abre o arquivo para gravação
// escreve no arquivo
fwrite ($fp, "linha 1" . PHP_EOL);
fwrite ($fp, "linha 2" . PHP_EOL);
fwrite ($fp, "linha 3" . PHP_EOL);
fclose ($fp); // fecha o arquivo
le_put_contents
Grava uma string (data) em um arquivo ( lename). Retorna a quantidade
de bytes gravados.
int file_put_contents ( string $filename , mixed $data [, ...] )
Exemplo:
<?php
echo file_put_contents('/tmp/teste.txt', "este \n é o conteúdo \n do
arquivo");
le_get_contents
Lê o conteúdo de um arquivo e retorna o conteúdo em forma de string.
string file_get_contents ( string $filename [, ...] )
Exemplo:
<?php
echo file_get_contents('/etc/mtab');
Resultado:
/dev/hda3 / ext3 rw 0 0
/dev/hda1 /windows ntfs rw 0 0
proc /proc proc rw 0 0
usbfs /proc/bus/usb usbfs rw 0 0
le
Lê um arquivo e retorna um array com seu conteúdo, de modo que cada
posição do array represente uma linha do arquivo. O nome do arquivo
pode conter o protocolo, como no caso de http://www.server.com/page.html.
Dessa forma, o arquivo remoto será lido.
array file ( string $filename [, ...] )
Exemplo:
<?php
$arquivo = file ("/tmp/file.txt");
foreach ($arquivo as $linha) {
print $linha;
}
Resultado:
linha 1 linha 2 linha 3
copy
Copia um arquivo para outro local/nome. Retorna TRUE caso tenha
sucesso e FALSE em caso de falhas.
bool copy ( string $source , string $dest [, ... ] )
Exemplo:
<?php
$origem = "/tmp/file.txt";
$destino = "/tmp/file2.txt";
if (copy($origem, $destino))
echo "Cópia efetuada";
else
echo "Cópia não efetuada";
Resultado:
Cópia efetuada
rename
Altera a nomenclatura de um arquivo ou diretório.
bool rename ( string $oldname , string $newname [, .. ] )
Exemplo:
<?php
$origem = "/tmp/file2.txt";
$destino = "/tmp/file3.txt";
if (rename($origem, $destino))
echo "Renomeação efetuada";
else
echo "Renomeação não efetuada";
Resultado:
Renomeação efetuada
unlink
Apaga um arquivo passado como parâmetro. Retorna TRUE em caso de
sucesso e FALSE em caso de falhas.
bool unlink ( string $filename [, resource $context ] )
Exemplo:
<?php
$arquivo = "/tmp/file3.txt";
if (unlink($arquivo))
echo "Arquivo apagado";
else
echo "Arquivo não apagado";
Resultado:
Arquivo apagado
le_exists
Veri ca a existência de um arquivo ou de um diretório.
bool file_exists ( string $filename )
Exemplo:
<?php
$arquivo = '/tmp/file2.txt';
if (file_exists($arquivo))
echo "Arquivo existente";
else
echo "Arquivo não existente";
Resultado:
Arquivo não existente
is_ le
Veri ca se a localização dada corresponde ou não a um arquivo.
bool is_file ( string $filename )
Exemplo:
<?php
$arquivo = '/tmp/file.txt';
if (is_file($arquivo))
echo "É um arquivo";
else
echo "Não é um arquivo";
Resultado:
É um arquivo
mkdir
Cria um diretório informando, entre outras opções, a sua permissão de
acesso.
bool mkdir ( string $pathname [, int $mode = 0777 [, bool $recursive =
false
[, resource $context ]]] )
Exemplo:
<?php
$dir = '/tmp/diretorio';
if (mkdir($dir, 0777))
echo "Diretório criado com sucesso";
else
echo "Diretório não criado";
Resultado:
Diretório criado com sucesso
rmdir
Apaga um diretório.
bool rmdir ( string $dirname [, resource $context ] )
Exemplo:
<?php
$dir = '/tmp/diretorio';
if (rmdir($dir))
echo "Diretório apagado com sucesso";
else
echo "Diretório não apagado";
Resultado:
Diretório apagado com sucesso
scandir
Lê o conteúdo de um diretório (arquivos e diretórios), retornando-o
como um array.
array scandir ( string $directory [, int $sorting_order =
SCANDIR_SORT_ASCENDING
[, resource $context ]] )
Exemplo:
<?php
$diretorio = '/';
if (is_dir($diretorio)) {
$linhas = scandir($diretorio);
foreach ($linhas as $linha) {
print $linha . '<br>' . PHP_EOL;
}
}
Resultado:
.
..
bin
boot
dev
etc
...
Resultado:
Aqui nesta área
você poderá escrever
textos com múltiplas linhas.
1.13.2 Concatenação
Para concatenar strings, pode-se utilizar o operador “.” ou colocar
múltiplas variáveis dentro de strings duplas “”, visto que seu conteúdo é
interpretado.
<?php
$fruta = 'maçã';
echo $fruta .' é a fruta de adão<br>' . PHP_EOL; // resultado: maçã é a
fruta de adão
echo "{$fruta} é a fruta de adão<br>" . PHP_EOL; // resultado: maçã é a
fruta de adão
O PHP realiza automaticamente a conversão entre tipos, como neste
exemplo de concatenação entre uma string e um número:
<?php
$a = 1234;
echo 'O salário é ' . $a . '<br>' . PHP_EOL;
echo "O salário é $a" . '<br>' . PHP_EOL;
Resultado:
O salário é 1234
O salário é 1234
Observação: use aspas duplas para declarar strings somente quando for
necessário avaliar seu conteúdo, evitando, assim, tempo de processamento
desnecessário.
Resultado:
CONVERTENDO PARA MAIUSCULO
convertendo para minusculo
substr
Retorna uma parte de uma string. O primeiro parâmetro representa a
string original, o segundo representa o início do corte, e o terceiro, o
tamanho do corte. Se o comprimento (length) for negativo, conta n caracteres
antes do nal.
string substr ( string $string , int $start [, int $length ] )
Exemplo:
<?php
print substr("Americana", 1) . '<br>'. PHP_EOL;
print substr("Americana", 1, 3) . '<br>'. PHP_EOL;
print substr("Americana", 0, -1) . '<br>'. PHP_EOL;
print substr("Americana", -2) . '<br>'. PHP_EOL;
Resultado:
mericana
mer
American
na
strpad
Preenche uma string com outra string, dentro de um tamanho especí co,
podendo preencher com caracteres à esquerda, à direita ou centralizado.
string str_pad ( string $input , int $pad_length [, string $pad_string =
" "
[, int $pad_type = STR_PAD_RIGHT ]] )
Exemplo:
<?php
$texto = "The Beatles";
print str_pad($texto, 20, "*", STR_PAD_LEFT) . "<br>\n";
print str_pad($texto, 20, "*", STR_PAD_BOTH) . "<br>\n";
print str_pad($texto, 20, "*") . "<br>\n";
Resultado:
*********The Beatles
****The Beatles*****
The Beatles*********
str_repeat
Repete uma string por determinada quantidade de vezes.
string str_repeat ( string $input , int $multiplier )
Exemplo:
<?php
$txt = ".oO0Oo.";
print str_repeat($txt, 5) . "\n";
Resultado:
.oO0Oo..oO0Oo..oO0Oo..oO0Oo..oO0Oo.
strlen
Retorna o comprimento de uma string.
int strlen ( string $string )
Exemplo:
<?php
$txt = "O Rato roeu a roupa do rei de roma";
print 'O comprimento é: ' . strlen($txt) . "\n";
Resultado:
O comprimento é: 34
str_replace
Substitui uma string (primeiro parâmetro) por outra (segundo
parâmetro) dentro de um dado contexto (terceiro parâmetro).
mixed str_replace (mixed $search , mixed $replace , mixed $subject [,
int &$count ])
Exemplo:
<?php
print str_replace('Rato', 'Leão', 'O Rato roeu a roupa do rei de Roma');
Resultado:
O Leão roeu a roupa do rei de Roma
Observação: a chave pode ser string ou integer não negativo; o valor pode ser
de qualquer tipo.
1.14.3 Iterações
Os arrays podem ser iterados no PHP pelo operador FOREACH, que percorre
cada uma das posições do array. Exemplo:
$frutas = array();
$frutas['cor'] = 'vermelha';
$frutas['sabor'] = 'doce';
$frutas['formato'] = 'redondo';
$frutas['nome'] = 'maçã';
foreach ($frutas as $chave => $fruta) {
echo "$chave => $fruta <br>\n";
}
Resultado:
cor => vermelha
sabor => doce
formato => redondo
nome => maçã
1.14.4 Acessando arrays
As posições de um array podem ser acessadas a qualquer momento, e
algumas operações podem ser executadas sobre elas.
<?php
$contato = array();
$contato['nome'] = 'Pablo';
$contato['empresa'] = 'Adianti';
$contato['peso'] = 73;
// alterações
$contato['nome'] .= ' Dall Oglio';
$contato['empresa'] .= ' Solutions';
$contato['peso'] += 2;
// debug
print_r($contato);
$comidas = array();
$comidas[] = 'Lasanha';
$comidas[] = 'Pizza';
$comidas[] = 'Macarrão';
// alterações
$comidas[1] = 'Pizza Calabresa';
// debug
print_r($comidas);
Resultado:
Array (
[nome] => Pablo Dall Oglio
[empresa] => Adianti Solutions
[peso] => 75
)
Array (
[0] => Lasanha
[1] => Pizza Calabresa
[2] => Macarrão
)
Resultado:
=> modelo Palio
- característica cor => azul
- característica potência => 1.0
- característica opcionais => Ar Cond.
=> modelo Corsa
- característica cor => cinza
- característica potência => 1.3
- característica opcionais => MP3
=> modelo Gol
- característica cor => branco
- característica potência => 1.0
- característica opcionais => Metalica
Resultado:
Array (
[0] => ciano
[1] => verde
[2] => azul
[3] => vermelho
[4] => amarelo
)
array_shift
Remove um elemento do início de um array.
mixed array_shift ( array &$array )
array_pop
Remove um valor do nal de um array.
mixed array_pop ( array &$array )
Exemplo:
<?php
$a = array("ciano", "verde", "azul", "vermelho", "amarelo");
array_shift($a);
array_pop($a);
print_r($a);
Resultado:
Array (
[0] => verde
[1] => azul
[2] => vermelho
)
array_reverse
Recebe um array e retorna-o na ordem inversa.
array array_reverse ( array $array [, bool $preserve_keys ] )
Exemplo:
<?php
$a[0] = 'verde';
$a[1] = 'amarelo';
$a[2] = 'vermelho';
$a[3] = 'azul';
$b = array_reverse($a, true);
print_r($b);
Resultado:
Array (
[3] => azul
[2] => vermelho
[1] => amarelo
[0] => verde
)
array_merge
Mescla dois ou mais arrays. Um array é adicionado ao nal do outro. O
resultado é um novo array. Se ambos os arrays tiverem conteúdo
indexado pela mesma chave, o segundo irá se sobrepor ao primeiro.
array array_merge (array nome_array1, array nome_array2 [, array ...])
Exemplo:
<?php
$a = array("verde", "azul");
$b = array("vermelho", "amarelo");
$c = array_merge($a, $b);
print_r($c);
Resultado:
Array (
[0] => verde
[1] => azul
[2] => vermelho
[3] => amarelo
)
array_keys
Retorna as chaves (índices) de um array. Se o segundo parâmetro for
indicado, a função retornará apenas índices que apontam para um
conteúdo igual ao parâmetro.
array array_keys ( array $input [, mixed $search_value [, bool $strict
]] )
array_values
Retorna somente os valores de um array, ignorando suas chaves.
array array_values ( array $input )
count
Retorna a quantidade de elementos de um array.
int count (array nome_array)
Exemplo:
<?php
$exemplo = array('cor' => 'vermelho', 'volume' => 5,
'animal'=>'cachorro');
print_r(array_keys($exemplo));
print_r(array_values($exemplo));
print 'Quantidade: ' . count($exemplo);
Resultado:
Array (
[0] => cor
[1] => volume
[2] => animal
)
Array (
[0] => vermelho
[1] => 5
[2] => cachorro
)
Quantidade: 3
in_array
Veri ca se um array contém um determinado valor.
bool in_array ( mixed $needle , array $haystack [, bool $strict ] )
Exemplo:
<?php
$a = array('refrigerante', 'cerveja', 'vodca', 'suco natural');
if (in_array('suco natural', $a)) {
echo 'suco natural encontrado';
}
Resultado:
suco natural encontrado
sort
Ordena um array pelo seu valor, sem manter a associação de índices.
Para ordem reversa, utilize rsort().
bool sort ( array &$array [, int $sort_flags ] )
Exemplo:
<?php
$a = array('refrigerante', 'cerveja', 'vodca', 'suco natural');
sort($a);
print_r($a);
Resultado:
Array (
[0] => cerveja
[1] => refrigerante
[2] => suco natural
[3] => vodca
)
asort
Ordena um array pelo seu valor, mantendo a associação de índices. Para
ordenar de forma reversa, use arsort().
bool asort ( array &$array [, int $sort_flags ] )
Exemplo:
<?php
$a[0] = 'verde';
$a[1] = 'amarelo';
$a[2] = 'vermelho';
$a[3] = 'azul';
asort($a);
print_r($a);
Resultado:
Array (
[1] => amarelo
[3] => azul
[0] => verde
[2] => vermelho
)
ksort
Ordena um array pelos seus índices. Para ordem reversa, utilize krsort().
int ksort ( array &$array [, int $sort_flags ] )
Exemplo:
<?php
$carro['potência'] = '1.0';
$carro['cor'] = 'branco';
$carro['modelo'] = 'celta';
$carro['opcionais'] = 'ar quente';
ksort($carro);
print_r($carro);
Resultado:
Array (
[cor] => branco
[modelo] => celta
[opcionais] => ar quente
[potência] => 1.0
)
explode
Converte uma string em um array, quebrando os elementos por meio de
um separador (delimiter).
array explode ( string $delimiter , string $string [, int $limit ] )
implode
Converte um array em uma string, agrupando os elementos do array por
meio de um elemento cola (glue).
string implode ( string $glue , array $pieces )
Exemplo:
<?php
$string = "10/05/2015";
print_r( explode("/", $string) ); // string → array
$array = [ 10, 05, 2015 ];
print implode('/', $array); // array → string
Resultado:
Array (
[0] => 10
[1] => 05
[2] => 2015
)
10/5/2015
1.15 Frameworks
Um framework é uma arquitetura pré-montada para construção de
softwares com nalidades especí cas. Em um software, um framework
geralmente é um conjunto de classes de infraestrutura utilizado para
simpli car a construção de aplicações. Geralmente trazem
implementações de padrões de projeto para resolver problemas comuns
relacionados à engenharia de software. Esperamos que, ao nal da leitura
do livro, você esteja apto a aprender a utilização de um framework para
criar aplicações. Alguns frameworks são:
• Adianti Framework: https://www.adianti.com.br/framework
• Laravel: https://laravel.com
• Symphony: https://symfony.com
• Zend Framework: https://framework.zend.com/
É aconselhável você acessar cada um dos frameworks, estudar suas
principais características e compará-los para descobrir qual é o mais
adequado à sua realidade de desenvolvimento. Neste livro, construiremos
um mini-framework para ns didáticos. Com o desenvolvimento deste
conhecimento, você terá muito mais propriedade para usar outros
frameworks de mercado.
procedural.php
<?php
function total_debitos_cliente($id_cliente) {}
function registra_venda($id_cliente, $data) {}
function registra_venda_item($id_venda, $item) {}
function consulta_estoque($produto) {}
function consulta_valor($produto) {}
function gera_financeiro($id_cliente, $total, $parcelas) {}
function conclui_venda($id_cliente, $parcelas, $itens) {
if (total_debitos_cliente($id_cliente) > 0) {
return FALSE;
}
$id_venda = registra_venda( $id_cliente, date('Y-m-d') );
if ($itens) {
$total = 0;
foreach ($itens as $item) {
if (consulta_estoque($item) <= 0) {
return FALSE;
}
$total += consulta_valor($item);
registra_venda_item( $id_venda, $item );
}
}
gera_financeiro( $id_cliente, $total, $parcelas);
}
conclui_venda(5, 5, array(1,2,3));
Agora que visualizamos um exemplo de rotina procedural, podemos tirar
algumas conclusões sobre as características desse tipo de programa:
• Um procedimento não retém estado – Um procedimento (função) isolado, tal
como total_debitos_cliente(), não retém um estado, ou seja, ele recebe
somente parâmetros de entrada, faz um processamento e devolve uma
resposta.
A função não armazena informações para uso futuro.
• Não implica organização modular – O paradigma procedural não leva o
desenvolvedor a organizar o programa de maneira modular, ou seja,
agrupando funcionalidades em comum. As funcionalidades relativas a
uma venda, bem como as relativas ao nanceiro, constituem módulos
diferentes; logo, deveriam estar segregadas. A organização no
paradigma procedural depende mais de o desenvolvedor organizar as
funções em arquivos. O paradigma não o conduz a uma organização
nesse sentido.
• Não leva a uma organização de nomes – O paradigma procedural não leva o
desenvolvedor a uma organização de nomes de funções. Muitas vezes,
ca a cargo do desenvolvedor elaborar padrões de nomenclatura para
funções.
• Não facilita o reúso – Como o paradigma procedural não leva o
desenvolvedor a organizar o programa de maneira modular, tampouco
a uma organização de nomes, ele terá di culdade para identi car
procedimentos já construídos e reaproveitá-los. Poderão ocorrer, então,
situações em que ele escreva uma funcionalidade repetidamente apenas
porque não localizou a implementação já existente.
• Mais suscetível a mudanças – Como o programador poderá ter di culdade
para localizar e identi car procedimentos, bem como a dependência
entre eles, um programa procedural é mais suscetível a mudanças.
Quando uma função for alterada, o programador terá di culdade para
saber quais são as funções que dependem dela direta ou indiretamente.
Como vimos, um programa procedural pode acabar virando uma colcha
de retalhos caso o desenvolvedor não crie bons padrões para torná-lo
modularizado, bem como padrões para nomenclatura de procedimentos,
visando facilitar a localização e a identi cação das funções.
2.3 Classe
Vários objetos podem ser construídos durante a execução de uma
aplicação. Para construir objetos, antes, precisamos declarar formalmente
sua estrutura. Cada objeto segue uma estrutura que de ne quais serão
seus atributos e qual será seu comportamento. Essa estrutura chama-se
classe. Uma classe pode gerar muitos objetos. Cada objeto terá atributos
próprios que os distinguirão uns dos outros. Começaremos a entender a
estrutura de um objeto demonstrando como ele contém seus atributos.
Para declarar uma classe, utilizamos a palavra-chave class. Para declarar
os atributos, utilizamos a palavra-chave public, protected ou private no
início da classe, seguida do nome dos atributos. Por motivos didáticos,
neste exemplo, vamos programar do “jeito errado”. Mas o que seria o
“jeito errado”? É quando trabalhamos com atributos públicos. Um
atributo é público porque ele pode ser acessado tanto de dentro da classe
quanto de fora dela, e isso pode levar a alguns problemas que serão
explicados em seguida. A gura 2.3 demonstra o que estamos criando:
um objeto cujos atributos (representados pelos pequenos cubos) são
livremente acessados pela aplicação.
objeto1.php
<?php
class Produto {
public $descricao;
public $estoque;
public $preco;
}
$p1 = new Produto;
$p1->descricao = 'Chocolate';
$p1->estoque = 10;
$p1->preco = 7;
$p2 = new Produto;
$p2->descricao = 'Café';
$p2->estoque = 20;
$p2->preco = 4;
// output objeto inteiro
var_dump($p1);
var_dump($p2);
Resultado:
object(Produto)#1 (3) {
["descricao"]=> string(9) "Chocolate"
["estoque"]=> int(10)
["preco"]=> int(7)
}
object(Produto)#2 (3) {
["descricao"]=> string(5) "Café"
["estoque"]=> int(20)
["preco"]=> int(4)
}
Apesar de serem simples de entender e utilizar, atributos públicos não são
uma boa prática de programação, pois podem levar um objeto a
apresentar estados inconsistentes. Imagine uma situação em que o
programador atribui um valor que não é condizente com o que é
esperado, como no exemplo a seguir. Neste caso, tanto o atributo estoque
quanto o atributo preco receberam valores inconsistentes que podem
provocar falhas futuras, como no momento de envolver o objeto Produto
em cálculos.
$p1 = new Produto;
$p1->descricao = 'Chocolate';
$p1->estoque = 'Dez unidades';
$p1->preco = 'R$ 7,00';
2.4 Métodos
Um objeto deve proteger o acesso ao seu conteúdo, aos seus atributos. A
gura 2.4 procura demonstrar como um objeto deve proteger o acesso ao
seu conteúdo. Veja que no interior da gura temos pequenos cubos que
procuram demonstrar os atributos de um objeto. Já na fronteira do objeto
temos pequenos círculos que procuram demonstrar pontos de acesso ao
objeto. Esses pontos de acesso devem receber requisições “externas” ao
objeto, processá-las e depois modi car o estado interno (atributos) de um
objeto. Então um objeto deve proteger os seus atributos por meio desses
pontos de acesso.
objeto2.php
<?php
class Produto {
public $descricao;
public $estoque;
public $preco;
public function aumentarEstoque( $unidades ) {
if (is_numeric($unidades) AND $unidades >= 0) {
$this->estoque += $unidades;
}
}
public function diminuirEstoque( $unidades ) {
if (is_numeric($unidades) AND $unidades >= 0) {
$this->estoque -= $unidades;
}
}
public function reajustarPreco( $percentual ) {
if (is_numeric($percentual) AND $percentual >= 0) {
$this->preco *= (1 + ($percentual/100));
}
}
}
$p1 = new Produto;
$p1->descricao = 'Chocolate';
$p1->estoque = 10;
$p1->preco = 8;
print "O estoque de {$p1->descricao} é {$p1->estoque} <br>\n";
print "O preço de {$p1->descricao} é {$p1->preco} <br>\n";
$p1->aumentarEstoque(10);
$p1->diminuirEstoque(5);
$p1->reajustarPreco(50);
print "O estoque de {$p1->descricao} é {$p1->estoque} <br>\n";
print "O preço de {$p1->descricao} é {$p1->preco} <br>\n";
Resultado:
O estoque de Chocolate é 10
O preço de Chocolate é 8
O estoque de Chocolate é 15
O preço de Chocolate é 12
Ao criar os métodos, adicionamos comportamento à classe, que passa a
oferecer essas funcionalidades ao mundo externo à classe. Dentro de cada
método, podemos ter regras de validação dos parâmetros, como zemos
ao veri car se determinado parâmetro era válido (numérico e positivo).
Em cada caso, podemos ainda gerar uma mensagem de erro se o
parâmetro for inválido.
Apesar de adicionarmos métodos à classe, o objeto ainda não está
protegendo o acesso aos seus atributos, pois pudemos alterar os atributos
estoque e preco diretamente sem passar por método algum. Ainda
podemos de nir valores inconsistentes para eles. Para corrigir isso e fazer
com que a classe proteja seus atributos, é importante alterarmos a
declaração deles de public para private. Ao alterarmos a declaração dos
atributos, torna-se impossível de nir valores arbitrários a eles, gerando
um erro sempre que a tentativa ocorrer.
objeto3.php
<?php
class Produto {
private $descricao;
private $estoque;
private $preco;
}
$p1 = new Produto;
$p1->descricao = 'Chocolate';
$p1->estoque = 10;
$p1->preco = 8;
Resultado:
Resultado: Fatal error: Cannot access private property
Produto::$descricao in objeto3.php on line 10
Como não poderemos mais de nir os valores dos atributos de maneira
arbitrária, precisaremos de um meio para fazer isso. Para tal, podemos
criar métodos de acesso (setters e getters), ou seja, métodos que permitam a
gravação e a leitura dos atributos utilizando alguma regra de validação.
No exemplo a seguir, vamos reescrever a classe Produto para adicionar os
métodos de acesso; o método setDescricao() irá veri car se o parâmetro
recebido é string e setEstoque() irá veri car se o parâmetro é numérico e
positivo. Essa técnica funciona porque os métodos são public, ou seja, são
acessíveis de fora da classe. Entretanto os atributos são private, ou seja,
somente podem ser acessados de dentro da própria classe (a partir de
métodos).
Observação: é importante notar que nem sempre precisaremos criar métodos
setters e getters para todos os atributos de uma classe. O PHP oferece recursos
avançados como os métodos mágicos (vistos mais adiante), que facilitam muito a
vida do programador.
objeto4.php
<?php
class Produto {
private $descricao;
private $estoque;
private $preco;
public function setDescricao($descricao) {
if (is_string($descricao)) {
$this->descricao = $descricao;
}
}
public function getDescricao() {
return $this->descricao;
}
public function setEstoque($estoque) {
if (is_numeric($estoque) AND $estoque > 0) {
$this->estoque = $estoque;
}
}
public function getEstoque() {
return $this->estoque;
}
}
$p1 = new Produto;
$p1->setDescricao('Chocolate');
$p1->setEstoque(10);
print 'Descrição: '. $p1->getDescricao() . '<br>'.PHP_EOL;
print 'Estoque: ' .$p1->getEstoque() . '<br>'.PHP_EOL;
Resultado:
Descrição: Chocolate
Estoque: 10
objeto5.php
<?php
class Produto {
private $descricao;
private $estoque;
private $preco;
public function __construct($descricao, $estoque, $preco) {
if (is_string($descricao)) {
$this->descricao = $descricao;
}
if (is_numeric($estoque) AND $estoque > 0) {
$this->estoque = $estoque;
}
if (is_numeric($preco) AND $preco > 0) {
$this->preco = $preco;
}
}
public function getDescricao() {
return $this->descricao;
}
public function getEstoque() {
return $this->estoque;
}
public function getPreco() {
return $this->preco;
}
}
$p1 = new Produto('Chocolate', 10, 5);
print 'Descrição: '. $p1->getDescricao() . '<br>'.PHP_EOL;
print 'Estoque: ' . $p1->getEstoque() . '<br>'.PHP_EOL;
print 'Preço: ' . $p1->getPreco() . '<br>'.PHP_EOL;
Resultado:
Descrição: Chocolate
Estoque: 10
Preço: 5
O PHP também implementa o conceito de método destrutor. Um
destrutor é um método especial com o nome __destruct() (convenção da
linguagem) executado automaticamente quando o objeto é desalocado da
memória, o que pode acontecer em algumas circunstâncias, como:
quando atribuímos o valor NULL ao objeto; quando utilizamos a função
unset() sobre o objeto ou, em última instância, quando o programa é
nalizado, e então todos os objetos são desalocados automaticamente.
Como exemplos de utilização, o método destrutor pode ser usado para
nalizar conexões, apagar arquivos temporários e desfazer outras
operações criadas durante o ciclo de vida do objeto.
No exemplo a seguir construiremos novamente um objeto da classe
Produto. Para isso, utilizaremos um método construtor simpli cado para
receber os parâmetros e consolidá-los como atributos do objeto. No
método construtor exibiremos uma mensagem de debug. O método
destrutor também irá exibir uma mensagem de debug neste caso para que
saibamos em que momento ele é executado. Neste exemplo ele será
executado automaticamente quando usarmos o método unset(). Se não
tivéssemos usado o método unset(), os objetos seriam todos destruídos ao
nal da execução do programa.
objeto6.php
<?php
class Produto {
private $descricao;
private $estoque;
private $preco;
public function __construct($descricao, $estoque, $preco) {
$this->descricao = $descricao;
$this->estoque = $estoque;
$this->preco = $preco;
print "CONSTRUÍDO: Objeto {$descricao}, estoque {$estoque}, preco
{$preco}<br>\n";
}
public function __destruct() {
print "DESTRUÍDO: Objeto {$this->descricao}, estoque {$this-
>estoque},
preco {$this->preco}<br>\n";
}
}
$p1 = new Produto('Chocolate', 10, 5);
unset($p1);
$p2 = new Produto('Café', 100, 7);
unset($p2);
Resultado:
CONSTRUÍDO: Objeto Chocolate, estoque 10, preco 5
DESTRUÍDO: Objeto Chocolate, estoque 10, preco 5
CONSTRUÍDO: Objeto Café, estoque 100, preco 7
DESTRUÍDO: Objeto Café, estoque 100, preco 7
stdclass.php
<?php
$produto = new StdClass;
$produto->descricao = 'Chocolate Amargo';
$produto->estoque = 100;
$produto->preco = 7;
print_r($produto);
Resultado:
stdClass Object (
[descricao] => Chocolate Amargo
[estoque] => 100
[preco] => 7
)
Podemos também realizar operações de conversão de tipo (casting), como
criar um objeto a partir de um vetor e vice-versa, por meio da utilização
de um operador que utiliza o tipo de destino entre parênteses. Neste
exemplo, vamos declarar um objeto $produto com alguns atributos. Em
seguida, vamos convertê-lo em array ($vetor1) por meio da operação de
casting (array). Depois, criaremos o $vetor2, um array gerado pela sintaxe
resumida de produção de vetores (utilizando parênteses []). Então, vamos
convertê-lo em object ($produto2) por meio da operação de casting (object).
casting.php
<?php
$produto = new StdClass;
$produto->descricao = 'Chocolate Amargo';
$produto->estoque = 100;
$produto->preco = 7;
$vetor1 = (array) $produto;
print $vetor1['descricao'] . "<br>\n";
$vetor2 = [ 'descricao' => 'Café', 'estoque' => 100, 'preco' => 7 ];
$produto2 = (object) $vetor2;
print $produto2->descricao . "<br>\n";
Resultado:
Chocolate Amargo
Café
Outra possibilidade que o PHP nos oferece é utilizar variáveis variáveis
para declarar as propriedades de um objeto. Neste exemplo, criaremos um
vetor simples com alguns índices de nidos, como descricao, estoque e
preco. Em seguida, vamos declarar um objeto ($objeto) vazio (stdClass).
Então percorreremos o vetor ($produto) acessando a chave e o valor de
cada posição a cada iteração. Dentro do laço de repetição, preencheremos
os atributos do objeto com a sintaxe ($objeto->$chave). Neste caso a
variável $chave será traduzida pelo seu conteúdo (descricao, estoque, preco)
a cada passada do loop. Note que a única diferença para a atribuição
normal é a presença do cifrão ($) na frente da variável de propriedade.
objarray.php
<?php
$produto = array();
$produto['descricao'] = 'Chocolate Amargo';
$produto['estoque'] = 100;
$produto['preco'] = 7;
$objeto = new stdClass;
foreach ($produto as $chave => $valor) {
$objeto->$chave = $valor;
}
print_r($objeto);
Resultado:
stdClass Object (
[descricao] => Chocolate Amargo
[estoque] => 100
[preco] => 7
)
2.7.1 Associação
Associação é a relação mais comum entre objetos. Na associação, um
objeto faz uma referência a outro objeto. Essa referência funciona como
um apontamento em que um objeto terá um atributo que apontará para a
posição da memória em que o outro objeto se encontra, podendo
executar seus métodos. A forma mais comum de implementar uma
associação é ter um objeto como atributo de outro.
Conceitualmente, há uma associação entre um produto e seu fabricante
em que um produto está relacionado a um fabricante e, por sua vez, um
fabricante pode produzir diferentes produtos; a gura 2.5 procura ilustrar
esse relacionamento por meio do diagrama de classes.
classes/Fabricante.php
<?php
class Fabricante {
private $nome;
private $endereco;
private $documento;
public function __construct( $nome, $endereco, $documento ) {
$this->nome = $nome;
$this->endereco = $endereco;
$this->documento = $documento;
}
public function getNome() {
return $this->nome;
}
}
A associação entre as classes Produto e Fabricante se estabelece a partir da
classe Produto. A classe Produto terá como atributos: descricao, estoque,
preco e fabricante. Enquanto os primeiros são atributos escalares
(variáveis escalares são as que contêm integer, float, string ou boolean), o
atributo fabricante é, na verdade, um objeto, ou seja, ele apontará para
um objeto da classe Fabricante. Para estabelecer o vínculo (associação)
entre as duas classes, criaremos os métodos setFabricante() e
getFabricante(), sendo o primeiro responsável por receber um objeto da
classe Fabricante e armazená-lo internamente na propriedade $this-
>fabricante e o segundo responsável por retornar essa propriedade.
classes/Produto.php
<?php
class Produto {
private $descricao;
private $estoque;
private $preco;
private $fabricante;
public function __construct($descricao, $estoque, $preco) {
$this->descricao = $descricao;
$this->estoque = $estoque;
$this->preco = $preco;
}
public function getDescricao() {
return $this->descricao;
}
public function setFabricante( Fabricante $f ) {
$this->fabricante = $f;
}
public function getFabricante() {
return $this->fabricante;
}
}
Uma vez de nidas as classes, podemos utilizá-las. No próximo exemplo,
importaremos as classes (require_once) para em seguida criar um objeto
de cada: Produto e Fabricante. A partir dos objetos $p1 e $f1 criados, a
associação é estabelecida entre os dois objetos no momento da execução
do método setFabricante(), que recebe a instância $f1 e a armazena no
atributo $this->fabriante do objeto Produto. Por m serão impressos
alguns atributos para demonstrar a relação.
associacao.php
<?php
require_once 'classes/Fabricante.php';
require_once 'classes/Produto.php';
// criação dos objetos
$p1 = new Produto('Chocolate', 10, 7);
$f1 = new Fabricante('Chocolate Factory', 'Willy Wonka Street',
'1234985235');
// associação
$p1->setFabricante( $f1 );
print 'A descrição é ' . $p1->getDescricao() . "<br>\n";
print 'O fabriante é ' . $p1->getFabricante()->getNome() . "<br>\n";
Resultado:
A descrição é Chocolate
O fabricante é Chocolate Factory
2.7.2 Composição
A composição é uma relação entre objetos de duas classes conhecida
como relação todo/parte. O relacionamento tem esse nome porque
conceitualmente um objeto (todo) contém outros objetos (partes). A
composição permite combinar diferentes tipos de objeto em um objeto
mais complexo.
Na composição, o objeto “todo” é responsável pela criação e destruição de
suas “partes”. O objeto “todo” realmente “contém” a(s) instância(s) de
suas partes. Na composição, quando o objeto “todo” é destruído, suas
“partes” também são justamente por terem sido criadas pelo objeto
“todo”. Um produto (ex.: televisão) apresenta muitas características (cor,
peso, tamanho, potência). Conceitualmente, as características são
especí cas de cada produto. Assim, um produto é composto de
características. No próximo exemplo, demonstraremos o relacionamento
de composição por meio da relação entre a classe Produto (todo) e a classe
Caracteristica (partes). Veja uma composição na gura 2.6.
classes/Caracteristica.php
<?php
class Caracteristica {
private $nome;
private $valor;
public function __construct( $nome, $valor ) {
$this->nome = $nome;
$this->valor = $valor;
}
public function getNome() {
return $this->nome;
}
public function getValor() {
return $this->valor;
}
}
Em seguida, alteraremos a classe Produto para queela tenha os métodos
para compor características. Inicialmente, precisaremos declarar um
atributo $caracteristicas, que será um vetor interno que armazenará uma
ou mais instâncias contendo objetos da classe Caracteristica.
Escreveremos o método addCaracteristica(), que receberá os parâmetros
$nome e $valor e criará internamente um objeto da classe Caracteristica
para armazenar esses dois dados. O objeto criado será adicionado ao vetor
($this->caracteristicas). Já o método getCaracteristicas() simplesmente
retornará o vetor de características.
Observação: iremos suprimir os atributos e os métodos já definidos da classe.
classes/Produto.php
<?php
class Produto {
// ...
private $caracteristicas;
public function addCaracteristica( $nome, $valor ) {
$this->caracteristicas[] = new Caracteristica($nome, $valor);
}
public function getCaracteristicas() {
return $this->caracteristicas;
}
}
Agora vamos demonstrar como utilizar a composição. Inicialmente vamos
importar as classes necessárias (require_once) para o escopo principal do
programa. Em seguida, iremos criar o objeto $p1 da classe Produto com
alguns atributos iniciais. Cada vez que acionarmos o método
addCaracteristica(), passando o nome e o valor da característica, será
criado internamente um objeto da classe Caracteristica contendo o nome
e valor. Observe que esses objetos criados não existem fora da classe
Produto. Dessa forma, ao excluirmos o objeto $p1 (Produto) da memória
com um comando unset(), os objetos da classe Caracteristica também
serão excluídos, visto que eles só existem no escopo interno da classe.
Esse é o conceito de composição, no qual um objeto (todo) contém uma
ou mais instâncias de outro objeto (parte), sendo responsável pela sua
criação e também por sua destruição.
composicao.php
<?php
require_once 'classes/Produto.php';
require_once 'classes/Caracteristica.php';
// criação dos objetos
$p1 = new Produto('Chocolate', 10, 7);
// composição
$p1->addCaracteristica( 'Cor', 'Branco' );
$p1->addCaracteristica( 'Peso', '2.6 kg' );
$p1->addCaracteristica( 'Potência', '20 watts RMS' );
print 'Produto: ' . $p1->getDescricao() . "<br>\n";
foreach ($p1->getCaracteristicas() as $c) {
print ' Característica: ' . $c->getNome() . ' - ' . $c->getValor() . "
<br>\n";
}
Resultado:
Produto: Chocolate
Característica: Cor - Branco
Característica: Peso - 2.6 kg
Característica: Potência - 20 watts RMS
2.7.3 Agregação
Agregação também é um tipo de relação entre objetos todo/parte. Na
agregação, um objeto agrega outro objeto, ou seja, torna um objeto
externo parte de si mesmo pela utilização de um dos seus métodos. Assim
o objeto “todo” poderá utilizar funcionalidades do objeto agregado. Nessa
relação, um objeto poderá agregar uma ou muitas instâncias de outro
objeto. Para agregar muitas instâncias, a forma mais simples é utilizar
arrays. Criamos um array como atributo da classe, e o papel desse array é
armazenar inúmeras instâncias de outro objeto.
Na agregação, diferentemente da composição, o objeto “todo” recebe as
instâncias de objetos “parte” já prontas, ou seja, ele não é responsável por
sua criação ou destruição. Assim as instâncias dos objetos “parte” são
criadas fora da classe “todo” e agregadas (armazenadas internamente) por
meio de um método de agregação. Quando o objeto “todo” for excluído
da memória, as instâncias das “partes” não serão excluídas, visto que elas
não pertencem exclusivamente a um objeto “todo”.
O relacionamento entre uma cesta de compras e seus produtos pode ser
visto como uma agregação. Esse relacionamento é todo/parte, pois,
enquanto a cesta é o “todo”, os produtos são suas “partes”. No entanto,
diferentemente da composição, as “partes” não são exclusivas do “todo”.
Dessa forma, um produto poderá fazer parte de diferentes cestas. Uma
Cesta pode ser formada por inúmeros produtos (instâncias da classe
Produto). Na gura 2.7 temos o relacionamento entre as classes Cesta e
Produto.
classes/Cesta.php
<?php
class Cesta {
private $time;
private $itens;
public function __construct( ) {
$this->time = date('Y-m-d H:i:s');
$this->itens = array();
}
public function addItem( Produto $p ) {
$this->itens[] = $p;
}
public function getItens() {
return $this->itens;
}
public function getTime() {
return $this->time;
}
}
Agora iremos demonstrar o relacionamento de agregação na prática. Para
isso, inicialmente vamos importar as classes necessárias (require_once).
Em seguida, instanciaremos um objeto da classe Cesta. Depois, iremos
agregar por três vezes os objetos da classe Produto dentro da Cesta ($c1) por
meio do método addItem(). Veja que, diferentemente da composição, na
agregação o método que cria as “partes” já recebe as instâncias prontas,
externas ao escopo da classe. Assim, no escopo principal do programa
temos quatro objetos em memória ($c1, $p1, $p2, $p3) e, ao excluirmos a
cesta $c1, as demais instâncias de Produto continuarão existindo, podendo
inclusive fazer parte de outras cestas de compras.
agregacao.php
<?php
require_once 'classes/Cesta.php';
require_once 'classes/Produto.php';
// criação da cesta
$c1 = new Cesta;
// agregação dos produtos
$c1->addItem( $p1 = new Produto('Chocolate', 10, 5) );
$c1->addItem( $p2 = new Produto('Café', 100, 7) );
$c1->addItem( $p3 = new Produto('Mostarda', 50, 3) );
foreach ($c1->getItens() as $item) {
print 'Item: ' . $item->getDescricao() . "<br>\n";
}
Resultado:
Item: Chocolate
Item: Café
Item: Mostarda
Caso em algum momento o programador não respeite a regra de passar
um objeto da classe Produto ao método addItem(), uma mensagem de erro
será emitida:
Fatal error: Argument 1 passed to Cesta::addItem() must be an instance of
Produto, instance of XYZ given...
2.8 Herança
A utilização da orientação a objetos nos guia em direção a uma
organização maior da estrutura de código-fonte, mas um dos maiores
benefícios que encontramos na utilização desse paradigma é o reúso. A
possibilidade de reutilizar partes de código já de nidas é o que nos dá
mais agilidade, além de eliminar a necessidade de eventuais duplicações
ou reescritas de código.
Quando falamos em herança, a primeira imagem que nos aparece na
memória é a de uma árvore genealógica com avós, pais, lhos, e com as
características que são transmitidas geração após geração. O que devemos
levar em consideração sobre herança em orientação a objetos é o
compartilhamento de atributos e comportamentos entre as classes de
uma mesma hierarquia (árvore). As classes inferiores da hierarquia
automaticamente herdam propriedades e métodos das classes superiores,
chamadas superclasses.
Esse recurso tem uma aplicabilidade muito grande, visto que é
relativamente comum termos de criar novas funcionalidades em software.
Utilizando a herança, em vez de criarmos uma estrutura totalmente nova
(uma classe), podemos reaproveitar uma estrutura já desenvolvida que
nos forneça uma base abstrata para a criação, provendo recursos básicos e
comuns.
Para demonstrar o relacionamento de herança, utilizaremos os conceitos
de conta, conta-corrente e conta poupança. Em um sistema bancário há
diferentes tipos de conta e existem similaridades entre elas. Ambas as
contas – poupança e conta-corrente – têm atributos em comum, como
número da agência, da conta e o saldo, porém uma conta-corrente ainda
terá um limite. Ambas as classes têm funcionalidades em comum, como
depositar valores e retirar valores. Porém alguns desses métodos podem
ter uma leve diferenciação entre uma conta e outra. Enquanto o ato de
depositar valores é igual, o ato de retirar valores é diferente, pois uma
conta-corrente tem um limite a ser observado além do seu saldo real.
Dessa forma, criaremos três classes: Conta, ContaCorrente e ContaPoupanca. A
primeira classe será a classe pai, e as outras duas, classes lhas. A classe
pai terá as funcionalidades em comum e, nas classes lhas,
implementaremos as funcionalidades especí cas. A gura 2.8 demonstra
o relacionamento de herança entre as classes propostas.
Figura 2.8 – Relacionamento de herança.
A classe Conta terá um método construtor para de nir alguns atributos
iniciais. Além disso, terá também o método depositar() para incrementar
o saldo, o método getSaldo() para retornar o saldo atual e o método
getInfo() para retornar algumas informações da conta. Mas por que ela
não tem um método retirar()? Porque o método retirar() é diferente
entre as classes ContaCorrente e ContaPoupanca, portanto será implementado
nas classes lhas.
Vimos anteriormente que, ao declarar um atributo como private, ele ca
protegido do escopo externo da classe. Porém, em um relacionamento de
herança, precisaremos acessar os atributos da classe a partir das classes
lhas. Para que isso seja possível, declaramos o atributo como protected,
que permite que ele seja acessado de dentro da classe, e também de suas
lhas, mas não de fora da classe. Na sequência, veremos com mais
detalhes as diferenças entre public, protected e private.
classes/Conta.php
<?php
class Conta {
protected $agencia;
protected $conta;
protected $saldo;
public function __construct($agencia, $conta, $saldo) {
$this->agencia = $agencia;
$this->conta = $conta;
if ($saldo >= 0) {
$this->saldo = $saldo;
}
}
public function getInfo() {
return "Agência: {$this->agencia}, Conta: {$this->conta}";
}
public function depositar($quantia) {
if ($quantia > 0) {
$this->saldo += $quantia;
}
}
public function getSaldo() {
return $this->saldo;
}
}
A partir do momento em que temos a classe pai Conta criada, podemos
aproveitá-la para criar classes derivadas como ContaCorrente e
ContaPoupanca. Inicialmente criaremos a classe ContaPoupanca. Para criar
uma classe lha, usaremos o operador extends na declaração da classe. O
operador extends permite indicar que a classe que está sendo criada irá
herdar características da classe pai, como atributos e métodos.
A classe ContaPoupanca terá uma implementação própria do método
retirar(), que por sua vez é de simples implementação, pois é preciso
somente veri car se a conta contém saldo su ciente em relação à quantia
a ser retirada. Esse método retornará true (retirada ok) ou false (retirada
não permitida).
classes/ContaPoupanca.php
<?php
class ContaPoupanca extends Conta {
public function retirar($quantia) {
if ($this->saldo >= $quantia) {
$this->saldo -= $quantia;
}
else {
return false; // retirada não permitida
}
return true; // retirada permitida
}
}
Agora podemos declarar a classe ContaCorrente, que também é lha de
Conta.
A classe ContaCorrente também contém um método retirar() diferente,
visto que uma conta-corrente pode ter um limite, além do seu saldo real.
Para de nirmos o limite, recriaremos o método construtor, acrescentando
um novo parâmetro ($limite). Para não precisarmos de nir totalmente o
método construtor, vamos chamar o método construtor já existente na
classe pai (parent::__construct()), que tratará por sua vez de armazenar os
atributos recebidos. O método retirar() também foi sobrescrito para
veri car se a retirada está dentro do saldo real, além de levar em
consideração o limite da conta.
classes/ContaCorrente.php
<?php
class ContaCorrente extends Conta {
protected $limite;
public function __construct($agencia, $conta, $saldo, $limite) {
parent::__construct($agencia, $conta, $saldo);
$this->limite = $limite;
}
public function retirar($quantia) {
if ( ($this->saldo + $this->limite) >= $quantia ) {
$this->saldo -= $quantia; // retirada permitida
}
else {
return false; // retirada não permitida
}
return true;
}
}
poli.php
<?php
require_once 'classes/Conta.php';
require_once 'classes/ContaPoupanca.php';
require_once 'classes/ContaCorrente.php';
// criação dos objetos
$contas = array();
$contas[] = new ContaCorrente(6677, "CC.1234.56", 100, 500);
$contas[] = new ContaPoupanca(6678, "PP.1234.57", 100);
// percorre as contas
foreach ($contas as $key => $conta) {
print "Conta: {$conta->getInfo()} <br>\n";
print " Saldo atual: {$conta->getSaldo()} <br>\n";
$conta->depositar(200);
print " Depósito de: 200 <br>\n";
print " Saldo atual: {$conta->getSaldo()} <br>\n";
if ($conta->retirar(700)) {
print " Retirada de: 700 <br>\n";
}
else {
print " Retirada de: 700 (não permitida)<br>\n";
}
print " Saldo atual: {$conta->getSaldo()} <br>\n";
}
Resultado:
Conta: Agência: 6677, Conta: CC.1234.56
Saldo atual: 100
Depósito de: 200
Saldo atual: 300
Retirada de: 700
Saldo atual: -400
Conta: Agência: 6678, Conta: PP.1234.57
Saldo atual: 100
Depósito de: 200
Saldo atual: 300
Retirada de: 700 (não permitida)
Saldo atual: 300
2.10 Abstração
No paradigma de orientação a objetos se prega o conceito da “abstração”.
De acordo com o dicionário Priberam, “abstrair” é separar mentalmente,
considerar isoladamente, simpli car, alhear-se. Para construir um sistema
orientado a objetos, não devemos projetar o sistema como se fosse uma
grande peça monolítica; devemos separá-lo em partes, concentrando-nos
nas peças mais importantes e ignorando os detalhes (em um primeiro
momento) para podermos construir peças bem-de nidas que possam ser
reaproveitadas depois, formando uma estrutura hierárquica.
classes/conta.php
<?php
abstract class Conta {
// ...
}
classe_abstrata.php
<?php
require_once 'classes/Conta.php';
$conta = new Conta;
Resultado:
Fatal error: Cannot instantiate abstract class Conta...
classes/ContaPoupanca.php
<?php
final class ContaPoupanca extends Conta {
// ...
}
classe_ nal.php
<?php
require_once 'classes/Conta.php';
require_once 'classes/ContaPoupanca.php';
class ContaPoupancaUniversitaria extends ContaPoupanca {
// ...
}
Resultado:
Fatal error: Class ContaPoupancaUniversitaria may not inherit from final
class (ContaPoupanca)...
classes/Conta.php
<?php
abstract class Conta {
// ...
abstract function retirar($quantia);
}
metodo_abstrato.php
<?php
require_once 'classes/Conta.php';
class ContaSalario extends Conta {
// ...
}
Resultado:
Fatal error: Class ContaSalario contains 1 abstract method and must
therefore be
declared abstract or implement the remaining methods (Conta::retirar)...
classes/ContaCorrente.php
<?php
class ContaCorrente extends Conta {
// ...
public final function retirar($quantia) {
// …
}
}
metodo_ nal.php
<?php
require_once 'classes/Conta.php';
require_once 'classes/ContaCorrente.php';
class ContaCorrenteEspecial extends ContaCorrente {
public function retirar($quantia) {
$this->saldo -= $quantia;
}
}
Resultado:
Fatal error: Cannot override final method ContaCorrente::retirar() in...
2.11 Encapsulamento
Um dos recursos mais interessantes na orientação a objetos é o
encapsulamento, um mecanismo que provê proteção de acesso aos
membros internos de um objeto. Lembre-se de que uma classe tem
responsabilidade sobre os atributos que ela contém. Dessa forma, há
certas propriedades de uma classe que devem ser tratadas exclusivamente
por métodos dela mesma, que são implementações projetadas para
manipular essas propriedades da forma correta. As propriedades não
devem ser acessadas diretamente de fora do escopo de uma classe, pois
dessa forma a classe não fornece mais garantias sobre os atributos que ela
contém, perdendo, assim, a responsabilidade sobre eles.
Na gura 2.9, vemos a representação grá ca de um objeto. As
propriedades são os pequenos blocos no núcleo do objeto; os métodos
são os círculos maiores na parte externa. Essa gura foi construída para
demonstrar que os métodos devem ser projetados de forma a proteger
suas propriedades, fornecendo interfaces para a manipulação destas.
2.11.1 Public
Demonstrar a visibilidade public é uma tarefa simples, pois um atributo
declarado como public pode ser alterado de qualquer parte, ou seja, tanto
de dentro da classe quanto de fora do escopo dela. Um atributo público é
potencialmente perigoso, pois permite que possamos gravar valores
inconsistentes em atributos. Neste exemplo vamos declarar uma classe
Pessoa com três atributos. Ao instanciarmos um objeto da classe, podemos
de nir os atributos livremente, inclusive armazenando um valor
inconsistente em nascimento. Podemos também de nir atributos em
tempo de execução, como faremos com telefone.
Observação: ao criarmos atributos em tempo de execução, estes são tratados
automaticamente como public pelo PHP. Até o PHP4, o operador var era usado
para declarar atributos públicos.
public.php
<?php
class Pessoa {
public $nome;
public $endereco;
public $nascimento;
}
$p1 = new Pessoa;
$p1->nome = 'Maria da Silva';
$p1->endereco = 'Rua Bento Gonçalves';
$p1->nascimento = '01 de Maio de 2015';
$p1->telefone = '(51) 1234-5678'; // definida em tempo de execução
print_r($p1);
Resultado:
Pessoa Object (
[nome] => Maria da Silva
[endereco] => Rua Bento Gonçalves
[nascimento] => 01 de Maio de 2015
[telefone] => (51) 1234-5678
)
2.11.2 Private
Como vimos, um atributo public permite que possamos armazenar
valores inconsistentes em um atributo. Portanto é preciso impedir que
isso ocorra em algumas situações. Para protegermos os atributos de um
objeto, podemos marcá-los como private. Um atributo private só poderá
ser alterado dentro da própria classe. Para permitir a alteração de valores,
a técnica indicada é criar um método público para isso. Esse método pode
então validar os valores antes de atribuí-los ao objeto.
Para demonstrar a visibilidade private, criaremos a classe Pessoa e
marcaremos a maioria das propriedades como private. Dessa forma elas
só poderão ser alteradas por métodos da mesma classe. Em seguida,
tentaremos de nir valores aos atributos private. Veremos que um erro
ocorrerá já na primeira tentativa.
private1.php
<?php
class Pessoa {
private $nome;
private $endereco;
private $nascimento;
}
$p1 = new Pessoa;
$p1->nome = 'Maria da Silva';
$p1->endereco = 'Rua Bento Gonçalves';
$p1->nascimento = '01 de Maio de 2015';
Resultado:
Fatal error: Cannot access private property Pessoa::$nome...
Veja que o PHP resulta em um erro de acesso à propriedade, como
esperado. Entretanto, se não pudermos alterar o valor de um atributo
private dessa forma, como faremos? Por meio da criação de métodos
públicos que receberão os valores por parâmetro antes de armazená-los
como atributos do objeto. Neste exemplo, criaremos o método
setNascimento() para atribuir a propriedade nascimento. Antes de atribuir a
propriedade, esse método irá validar a data por meio da função
checkdate(), que terá de estar no formato yyyy-mm-dd.
private2.php
<?php
class Pessoa {
private $nome;
private $endereco;
private $nascimento;
public function __construct($nome, $endereco) {
$this->nome = $nome;
$this->endereco = $endereco;
}
public function setNascimento($nascimento) {
$partes = explode('-', $nascimento);
if (count($partes)==3) {
if (checkdate ( $partes[1] , $partes[2] , $partes[0] )) {
$this->nascimento = $nascimento;
return true;
}
return false;
}
return false;
}
}
$p1 = new Pessoa('Maria da Silva', 'Rua Bento Gonçalves');
if ($p1->setNascimento('01 de Maio de 2015')) {
print "Atribuiu 01 de Maio de 2015<br>\n";
}
else {
print "Não atribuiu 01 de Maio de 2015<br>\n";
}
if ($p1->setNascimento('2015-12-30')) {
print "Atribuiu 2015-12-30<br>\n";
}
else {
print "Não atribuiu 2015-12-30<br>\n";
}
Resultado:
Não atribuiu 01 de Maio de 2015
Atribuiu 2015-12-30
2.11.3 Protected
Vimos que a visibilidade public é permissiva demais e acaba
possibilitando que cometamos falhas. A visibilidade private protege a
classe, exigindo a criação de métodos de acesso. Entretanto, em uma
estrutura de herança, a visibilidade private nem sempre é a mais
adequada, pois não permite que a classe lha acesse os atributos. Nesses
casos é preciso usar a visibilidade protected. A visibilidade protected
de ne que um atributo pode ser acessado de dentro da classe em que ele é
de nido, bem como pelas classes lhas. Para demonstrar a visibilidade
protected, vamos declarar as classes Pessoa e Funcionario. A classe Pessoa
conterá só um atributo: nome. Já a classe Funcionario terá mais dois: cargo e
salario. A classe Funcionario conterá um método contrata() para de nir o
cargo e o salário e um método getInfo() para retornar algumas
informações, como nome e salário, em forma de string concatenada. Ao
nal do programa, instanciaremos um Funcionario e de niremos seu
cargo e seu salário. Contudo, ao executarmos o método getInfo(), a string
não trará o nome dele.
Isso ocorrerá porque o nome estará marcado como private na classe pai.
Para que as classes lhas também possam acessar o atributo nome, este
deverá estar marcado como protected na classe pai, cando também
disponível para as classes lhas.
protected.php
<?php
class Pessoa {
private $nome;
public function __construct($nome) {
$this->nome = $nome;
}
}
class Funcionario extends Pessoa {
private $cargo, $salario;
public function contrata($c, $s) {
if (is_numeric($s) AND $s > 0) {
$this->cargo = $c;
$this->salario = $s;
}
}
public function getInfo() {
return "Nome: {$this->nome}, Salário: {$this->salario}";
}
}
$p1 = new Funcionario('Maria da Silva');
$p1->contrata( 'Auxiliar administrativo', 1600 );
print $p1->getInfo();
Resultado:
Notice: Undefined property: Funcionario::$nome in ...
Nome: , Salário: 1600
protected.php (correção)
<?php
class Pessoa {
protected $nome;
// ...
}
2.12.1 Constantes
Como vimos no capítulo 1, o PHP permite de nir constantes globais.
Agora veremos que ele também permite de nir constantes de classe. Uma
constante de classe é contida por sua classe, ou seja, podemos ter
diferentes constantes de mesmo nome, porém pertencendo a classes
diferentes. Neste exemplo, veremos como se dá a declaração de uma
constante dentro de uma classe pelo operador const.
Constantes de classe podem ser utilizadas para declarar valores imutáveis
que somente fazem sentido dentro de uma classe. No exemplo a seguir
de niremos um vetor de gêneros contendo as descrições para os gêneros
masculino e feminino.
O método construtor receberá as informações de nome e gênero.
Enquanto o método getNome() retornará o nome, o método
getNomeGenero() retornará o nome do gênero correspondente, que será
obtido a partir do array contido na constante GENEROS. A constante poderá
ainda ser acessada de modo externo ao contexto da classe por meio da
sintaxe Classe::CONSTANTE, e dentro da classe pela sintaxe self::CONSTANTE.
O operador self representará a própria classe.
constante_classe.php
<?php
class Pessoa {
private $nome;
private $genero;
const GENEROS = array('M'=>'Masculino', 'F'=> 'Feminino');
public function __construct($nome, $genero) {
$this->nome = $nome;
$this->genero = $genero;
}
public function getNome() {
return $this->nome;
}
public function getNomeGenero() {
return self::GENEROS[$this->genero];
}
}
$p1 = new Pessoa('Maria da Silva', 'F');
$p2 = new Pessoa('Carlos Pereira', 'M');
print 'Nome: '. $p1->getNome() . "<br>\n";
print 'Genero: '. $p1->getNomeGenero(). "<br>\n";
print 'Nome: '. $p2->getNome() . "<br>\n";
print 'Genero: '. $p2->getNomeGenero(). "<br>\n";
print_r(Pessoa::GENEROS);
Resultado:
Nome: Maria da Silva
Genero: Feminino
Nome: Carlos Pereira
Genero: Masculino
Array (
[M] => Masculino
[F] => Feminino
)
propriedade_estatica.php
<?php
class Software {
private $id;
private $nome;
public static $contador;
function __construct($nome) {
self::$contador ++;
$this->id = self::$contador;
$this->nome = $nome;
print "Software {$this->id} - {$this->nome} <br>\n";
}
}
// cria novos objetos
new Software('Dia');
new Software('Gimp');
new Software('Gnumeric');
echo 'Quantidade criada = ' . Software::$contador;
Resultado:
Software 1 - Dia
Software 2 - Gimp
Software 3 - Gnumeric
Quantidade criada = 3
metodo_estatico.php
<?php
class Software {
private $id;
private $nome;
private static $contador;
function __construct($nome) {
self::$contador ++;
$this->id = self::$contador;
$this->nome = $nome;
print "Software {$this->id} - {$this->nome} <br>\n";
}
public static function getContador() {
return self::$contador;
}
}
// cria novos objetos
new Software('Dia');
new Software('Gimp');
new Software('Gnumeric');
echo 'Quantidade criada = ' . Software::getContador();
Resultado:
Software 1 - Dia
Software 2 - Gimp
Software 3 - Gnumeric
Quantidade criada = 3
Resultado:
Array
(
[0] => setSalario
[1] => getSalario
[2] => setNome
[3] => getNome
)
get_object_vars
Retorna um vetor com os conteúdos das propriedades públicas de um
objeto. São valores dinâmicos que se alteram de acordo com o ciclo de
vida do objeto.
array get_object_vars ( object $object )
Exemplo:
<?php
class Funcionario {
public $nome;
public $salario;
public $departamento;
}
$jose = new Funcionario;
$jose->nome = 'José da Silva';
$jose->salario = 2000;
$jose->departamento = 'Financeiro';
print_r(get_object_vars($jose));
Resultado:
Array
(
[nome] => José da Silva
[salario] => 2000
[departamento] => Financeiro
)
get_class
Retorna o nome da classe à qual um objeto pertence.
string get_class ([ object $object = NULL ] )
get_parent_class
Retorna o nome da classe ancestral (classe pai). Se o parâmetro for um
objeto, retorna o nome da classe ancestral da classe à qual o objeto
pertence. Se o parâmetro for uma string, retorna o nome da classe
ancestral da classe passada como parâmetro.
string get_parent_class ([ mixed $object ] )
is_subclass_of
Indica se determinado objeto ou determinada classe são derivados de
outra classe.
bool is_subclass_of (mixed $object, string $class_name [, bool
$allow_string = TRUE])
Exemplo:
<?php
class Funcionario {
public $nome;
public $salario;
public $departamento;
}
class Estagiario extends Funcionario {
public $bolsa;
}
$jose = new Funcionario;
$joao = new Estagiario;
echo get_class($jose) . ' '; // resultado: Funcionario
echo get_class($joao) . ' '; // resultado: Estagiario
echo get_parent_class($joao) . ' '; // resultado: Funcionario
echo get_parent_class('Estagiario') . ' '; // resultado: Funcionario
echo "<br>\n"; // quebra de linha
if (is_subclass_of($joao, 'Funcionario')) {
echo "Classe do objeto João é derivada de Funcionario";
}
echo "<br>\n"; // quebra de linha
if (is_subclass_of('Estagiario', 'Funcionario')) {
echo "Classe Estagiario é derivada de Funcionario";
}
Resultado:
Funcionario Estagiario Funcionario Funcionario
Classe do objeto João é derivada de Funcionario
Classe Estagiario é derivada de Funcionario
method_exists
Veri ca se determinado objeto contém o método descrito. Podemos
veri car a existência de um método antes de executar por engano um
método inexistente.
bool method_exists ( mixed $object , string $method_name )
Exemplo:
<?php
class Funcionario {
public $nome;
public $salario;
public $departamento;
function setSalario() {}
function getSalario() {}
}
$jose = new Funcionario;
if (method_exists($jose, 'setNome')) {
echo 'Objeto Jose contém o método setNome()';
}
if (method_exists($jose, 'setSalario')) {
echo 'Objeto Jose contém o método setSalario()';
}
Resultado:
Objeto Jose contém o método setSalario()
call_user_func
Executa uma função ou um método de uma classe passado como
parâmetro. Para executar uma função, basta passar seu nome como uma
string, e para executar um método de um objeto, basta passar o
parâmetro como um array contendo na posição 0 o objeto e na posição 1
o método a ser executado. Para executar métodos estáticos, basta passar
o nome da classe na posição 0 do array.
mixed call_user_func ( callback $function [, mixed $parameter [, mixed
$... ]] )
Exemplo:
<?php
function apresenta($nome) {
echo "Olá senhor {$nome}! <br>\n";
}
// execução de função
call_user_func('apresenta', 'Pablo');
// declaração de classe
class Pessoa {
public static function apresenta($nome) {
echo "Olá senhor {$nome}! <br>\n";
}
}
// chamada de método estático
call_user_func(array('Pessoa', 'apresenta'), 'Pablo');
// chamada de método de objeto
$obj = new Pessoa;
call_user_func(array($obj, 'apresenta'), 'Pablo');
Resultado:
Olá senhor Pablo!
Olá senhor Pablo!
Olá senhor Pablo!
2.14 Interfaces
Interfaces são um recurso utilizado na orientação a objetos para diminuir
o acoplamento entre as partes do sistema. Mas o que é acoplamento? O
acoplamento representa o quanto um módulo (classe, componente) do
sistema está dependente de outro. Em linhas gerais, buscamos desenvolver
um software com baixo acoplamento, ou seja, um software em que suas
partes não dependam de muitas outras para funcionar. Isso porque um
software com alto acoplamento di culta a reusabilidade. É complexo
reutilizar uma parte de um software sabendo que esta (parte) depende de
inúmeras outras partes para executar o seu trabalho.
É impossível criar um sistema com zero por cento de acoplamento, pois
diferentes partes do software precisam conversar umas com as outras.
Entretanto podemos tornar as relações de dependência mais explícitas e
facilitar a troca (intercâmbio) de componentes. Para isso, utilizamos
interfaces.
Uma interface representa a de nição de uma fronteira entre dois
diferentes componentes (classes). A de nição dessa fronteira é
importante, pois é ela que rege a comunicação entre os dois componentes,
estabelecendo um contrato. Interface em programação é um tipo abstrato
de dados. Uma interface não irá conter dados (atributos) ou mesmo
código-fonte, porém irá conter a de nição de um conjunto de métodos e
seus parâmetros.
A interface de ne uma fronteira de comunicação entre dois componentes,
um contrato, mas ela não contém implementação. Nessa comunicação há
pelo menos uma classe que fornece (implementa) os métodos de nidos
na interface e outra classe que consome (depende de) os métodos
de nidos. As classes “concordam” com essa comunicação (contrato) em
tempo de execução. A gura 2.10 representa essa dependência no formato
UML. À esquerda, temos uma classe que depende que outra classe lhe
forneça dois métodos: metodo1() e metodo2(). Qualquer classe que atender
a esse “contrato” pode ser utilizada por ela. À direita, temos a classe que
fornece esses métodos. Este diagrama nos diz que qualquer classe que
implementar a interface pode ser utilizada pela classe da esquerda. O
diagrama não nos diz explicitamente qual método da classe da esquerda
precisa dessas funcionalidades.
classes/Produto.php
<?php
class Produto {
private $descricao;
private $estoque;
private $preco;
// ...
public function __construct($descricao, $estoque, $preco) {
$this->descricao = $descricao;
$this->estoque = $estoque;
$this->preco = $preco;
}
public function getPreco() {
return $this->preco;
}
Um orçamento é composto de itens. A princípio, poderemos orçar
produtos. Então vamos criar uma classe Orcamento que terá uma agregação
com Produto. A classe Orcamento contém um método adiciona() que recebe
um objeto da classe Produto, bem como a quantidade a ser adicionada.
Cada produto adicionado é acrescido ao nal do vetor $itens. A classe
Orcamento também contém o método calculaTotal(), que retorna o total de
itens incluídos no orçamento.
classes/Orcamento.php
<?php
class Orcamento {
private $itens;
public function adiciona(Produto $produto, $qtde) {
$this->itens[] = array($qtde, $produto);
}
public function calculaTotal() {
$total = 0;
foreach ($this->itens as $item) {
$total += ($item[0] * $item[1]->getPreco());
}
return $total;
}
}
Agora vamos dar um exemplo de uso para a classe Orcamento em que será
criado um orçamento; em seguida serão inseridos alguns produtos e ao
nal será exibido o total do orçamento.
orcamento.php
<?php
require_once 'classes/Orcamento.php';
require_once 'classes/Produto.php';
$o = new Orcamento;
$o->adiciona( new Produto('Máquina de café', 10, 299), 1 );
$o->adiciona( new Produto('Barbeador elétrico', 10, 170), 1 );
$o->adiciona( new Produto('Barra de chocolate', 10, 7), 3 );
print $o->calculaTotal();
Resultado:
490
Vamos fazer algumas considerações. Em primeiro lugar, a classe Orcamento
utiliza no método calculaTotal() o método getPreco() da classe Produto,
ou seja, ela sabe muito a respeito da classe Produto, tendo a certeza de que
a classe Produto contém esse método. Isso caracteriza um forte
acoplamento do tipo estático. Em segundo lugar, a classe Orcamento
somente funciona com Produto. Mas e se futuramente for possível fazer
orçamento com outras coisas que não necessariamente produtos, tais
como serviços? Precisamos tornar a classe Orcamento mais exível,
podendo fazer orçamento não somente de produtos, mas também de
outros itens orçáveis, ou seja, que possam fazer parte de um orçamento.
Não precisamos forçar para que a classe Orcamento aceite somente objetos
da classe Produto. Podemos de nir uma interface de comunicação. Essa
interface permitirá que a classe Orcamento possa receber objetos de outras
classes também. Desde que esses objetos concordem com essa interface.
Vamos então declarar uma interface de comunicação chamada
OrcavelInterface, ou seja, interface para elementos orçáveis. Como a classe
Orcamento somente precisa do método getPreco() para fazer o orçamento, é
esse método que colocaremos nessa interface. A gura 2.11 representa a
interface de comunicação que estamos criando. De um lado é indicado
que as classes Produto e Servico implementam, ou seja, fornecem a
implementação do conteúdo da interface (método getPreco()). Por outro
lado é indicado que a classe Orcamento consome ou necessita de algum
objeto que forneça aquela interface.
classes/OrcavelInterface.php
<?php
interface OrcavelInterface {
public function getPreco();
}
Agora, vamos alterar as classes Produto e Servico para indicar que ambas
implementam, ou seja, fornecem a implementação da classe
OrcavelInterface. Para isso é indispensável que ambas contenham a
implementação do método getPreco(); caso contrário, uma falha de
execução ocorrerá. Para indicar que uma classe fornecerá uma interface,
usaremos a palavra-chave implements entre a classe e o nome da interface
que ela implementa. Uma classe pode implementar várias interfaces, que
devem ser separadas por vírgula.
classes/Produto.php
<?php
class Produto implements OrcavelInterface {
private $descricao;
private $estoque;
private $preco;
// ...
public function __construct($descricao, $estoque, $preco) {
$this->descricao = $descricao;
$this->estoque = $estoque;
$this->preco = $preco;
}
public function getPreco() {
return $this->preco;
}
// ...
classes/Servico.php
<?php
class Servico implements OrcavelInterface {
private $descricao;
private $preco;
public function __construct($descricao, $preco) {
$this->descricao = $descricao;
$this->preco = $preco;
}
public function getPreco() {
return $this->preco;
}
// ...
}
Podemos também reescrever a classe Orcamento, mais especi camente seu
método adiciona(), para indicar que esse método não precisa mais que o
parâmetro recebido seja necessariamente do tipo Produto, mas sim do tipo
OrcavelInterface. Isso mesmo, uma interface também é um tipo. Como
indicamos a interface como parâmetro, signi ca que podemos em tempo
de execução passar como parâmetro qualquer objeto que implemente essa
interface (por exemplo, Produto, Servico). Essa nova relação entre Orcamento
e OrcavelInterface caracteriza um acoplamento do tipo dinâmico, pois é
de nida em tempo de execução. O acoplamento dinâmico é preferível ao
estático, demonstrado anteriormente. As vantagens do uso de interfaces
não param por aí. Além de diminuir o acoplamento, permite que
acrescentemos novas classes que possam ser incluídas em um orçamento,
sem a necessidade de reescrever a classe Orcamento. Basta criar uma nova
classe que implemente a interface requerida OrcavelInterface.
classes/Orcamento.php
<?php
class Orcamento {
private $itens;
public function adiciona(OrcavelInterface $produto, $qtde) {
$this->itens[] = array($qtde, $produto);
}
public function calculaTotal() {
$total = 0;
foreach ($this->itens as $item) {
$total += ($item[0] * $item[1]->getPreco());
}
return $total;
}
}
Agora vamos reescrever o exemplo de uso da classe Orcamento para criar
um orçamento que aceite tanto produtos quanto serviços. Veja que
adicionaremos objetos das classes Produto e Servico ao método adiciona(),
e o método calculaTotal() apresentará o total geral.
orcamento2.php
<?php
require_once 'classes/Orcamento.php';
require_once 'classes/OrcavelInterface.php';
require_once 'classes/Produto.php';
require_once 'classes/Servico.php';
$o = new Orcamento;
$o->adiciona( new Produto('Máquina de café', 10, 299), 1 );
$o->adiciona( new Produto('Barbeador elétrico', 10, 170), 1 );
$o->adiciona( new Produto('Barra de chocolate', 10, 7), 3 );
$o->adiciona( new Servico('Corte de grama', 20), 1 );
$o->adiciona( new Servico('Corte de barba', 20), 1 );
$o->adiciona( new Servico('Limpeza do apto', 50), 1 );
print $o->calculaTotal();
Resultado:
580
Caso em algum momento tivéssemos passado um objeto de outra classe
para o método adiciona(), teríamos uma mensagem de erro como esta a
seguir, em que CLASSE representa a classe errada passada para o método:
Fatal error: Argument 1 passed to Orcamento::adiciona() must implement
interface OrcavelInterface, instance of CLASSE given, called in ...
Sempre que não implementarmos um método exigido por sua interface,
como o método getPreco() na classe Servico, um erro será gerado, pois, ao
usarmos o operador implements, a classe obriga-se a implementar os
métodos indicados pela interface:
Fatal error: Class Servico contains 1 abstract method and must therefore
be declared abstract or implement the remaining methods
(OrcavelInterface::getPreco) in ...
2.15.1 Singleton
No desenvolvimento de aplicações, muitas vezes, é necessário
compartilhar algumas informações no domínio da aplicação, tornando-as
visíveis dentro de diferentes contextos, em diferentes classes no sistema.
Nessas ocasiões, somos tentados a usar um recurso chamado variáveis
globais – que são fáceis de utilizar e disponibilizam seu conteúdo de
forma indiscriminada a toda aplicação. Isso signi ca que qualquer código
de qualquer classe pode alterar uma variável global, armazenando valores
inconsistentes ou mesmo excluindo todo o seu conteúdo, e ainda, às
vezes, outras partes da aplicação esperam que esta contenha determinada
informação que não existe mais.
Os motivos descritos são apenas alguns dos quais tornam o uso de
variáveis globais condenável dentro do paradigma orientado a objetos,
por violar alguns conceitos fundamentais como o encapsulamento. Para
esses casos, existe uma solução já estudada e catalogada – o pattern
Singleton. Esse pattern permite que um objeto que disponível para toda
a aplicação, porém sem perder o encapsulamento.
Portanto um Singleton permite que se crie um, e apenas um, objeto de
determinada classe e se compartilhe esse objeto com diferentes lugares
(métodos) da mesma aplicação, como um recurso global. A estrutura do
Singleton garante que haja uma única instância desse objeto e que essa
instância possa ser obtida a qualquer momento. Em primeiro lugar,
precisamos garantir que somente seja possível “fabricar” um, e apenas
um, objeto da classe para que não haja dois objetos por aplicação,
causando “duas versões” da verdade.
Essa solução, que parece mágica à primeira vista, é relativamente simples.
Para que só exista uma instância desse objeto disponível na aplicação, seu
método construtor é marcado como private. Dessa forma, o único local
de onde poderá se instanciar um objeto Singleton estará dentro da
própria classe. Assim, sempre que alguém tentar instanciar um objeto de
fora dessa classe, obterá o seguinte erro:
Fatal error: Call to private Singleton::__construct() from invalid
context in...
Como não poderemos criar novos objetos dessa classe por meio do
operador new, precisaremos criar outra forma para instanciar um, e apenas
um, objeto. Faremos isso por meio de um método estático chamado
getInstance() que será responsável por criar uma instância da primeira
vez que for chamado e somente retornar a mesma instância em todas as
outras vezes que for chamado. Para ter um controle e retornar a mesma
instância, esse método armazenará a instância criada em uma tributo
estático (neste caso, chamada $instance). A gura 2.12 ilustra a estrutura
básica de um Singleton.
application.ini
timezone = "America/Sao_Paulo"
language = "en"
application = "livro"
Agora criaremos uma classe para gerenciar as preferências. As preferências
precisam ser globais, pois podem ser requisitadas de diferentes pontos da
aplicação. Para controlar as preferências, criaremos a classe Preferencias.
Ela será um Singleton, pois poderemos criar somente um objeto dessa
classe, mesmo em sucessivas tentativas. Seu método construtor será
privado, e ela terá um método getInstance() que criará um objeto da
primeira vez que for chamado e retornará o mesmo objeto nas vezes
posteriores em que for executado. Seu método construtor (new) será
executado somente na primeira vez que executarmos o getInstance().
Nesse momento o arquivo de con guração application.ini será lido por meio
da função parse_ini_file(), que irá interpretá-lo e gerar um vetor
armazenado no atributo $this->data.
A partir desse momento, de qualquer parte da aplicação, se precisarmos
do objeto Preferencias, basta usarmos o método
Preferencias::getInstance(). Mesmo executando esse método inúmeras
vezes, ele retornará sempre o mesmo objeto. Então poderemos usar
métodos como getData() para ler uma variável de con guração ou
setData() para armazenar ou substituir uma variável, bem como save()
para gravar o arquivo INI novamente em disco.
classes/Preferencias.php
<?php
class Preferencias {
private $data;
private static $instance;
private function __construct() {
$this->data = parse_ini_file('application.ini');
}
public static function getInstance() {
if (empty(self::$instance)) {
self::$instance = new self;
}
return self::$instance;
}
public function setData($key, $value) {
$this->data[$key] = $value;
}
public function getData($key) {
return $this->data[$key];
}
public function save() {
$string = '';
if ($this->data) {
foreach ($this->data as $key => $value) {
$string .= "{$key} = \"{$value}\" \n";
}
}
file_put_contents('application.ini', $string);
}
}
Agora vamos usar a classe Preferencias. Para isso, vamos usar o método
getInstance() pela primeira vez, retornando o objeto $p1 recém-criado.
Então vamos efetuar a leitura da variável language por meio do getData().
Em seguida alteraremos o valor da variável language por meio do
setData(). Para demonstrar que o getInstance() sempre retorna na verdade
o mesmo objeto, usaremos o getInstance() novamente retornando um
objeto $p2. Se tivéssemos utilizado o método getData(), poderíamos
pensar que ele retornaria o valor da variável language lida do arquivo
(“en”), mas, como o objeto $p2 na verdade é o mesmo objeto que $p1,
ambos retornam o mesmo conteúdo da variável language na leitura (“pt”).
Por m, se quisermos alterar de fato o arquivo, precisaremos chamar o
método save() ao nal da execução.
singleton.php
<?php
require_once 'classes/Preferencias.php';
// obtém uma instância
$p1 = Preferencias::getInstance();
print 'A linguagem é: '. $p1->getData('language') . "<br>\n";
$p1->setData('language', 'pt');
print 'A linguagem é: '. $p1->getData('language') . "<br>\n";
// obtém a mesma instância
$p2 = Preferencias::getInstance();
print 'A linguagem é: '. $p2->getData('language') . "<br>\n";
// Descomentar para gravar o valor
// $p1->save();
Resultado:
A linguagem é: en
A linguagem é: pt
A linguagem é: pt
2.15.2 Facade
São raras as vezes em que desenvolvemos um sistema sem precisar usar
bibliotecas de terceiros. Geralmente utilizamos bibliotecas de terceiros
para executar atividades especí cas para as quais alguém já criou uma
classe ou um componente que faz bem o trabalho. Nesses casos, em vez
de desenvolver, basta usufruir de tal funcionalidade. Um sistema de
gestão empresarial, por exemplo, tem funcionalidades como gerenciar
compras, vendas, pedidos, entre outras. Mas para algumas funções, como
emitir nota scal, gerar um boleto ou usar um serviço de cobrança, já
existem bibliotecas que podem ser integradas na solução, o que evita que
sejam desenvolvidas novamente.
Sempre que formos integrar uma biblioteca em nossa aplicação, devemos
ter alguns cuidados no sentido de facilitar a manutenção do software.
Essa biblioteca e a evolução dela não estão sob nosso controle (é um
trabalho de terceiros). Dessa forma, sempre que ela tiver novas versões ou
correções de bugs, precisaremos atualizá-la em nosso software. E é nesse
momento que podemos ter complicações, dependendo da maneira que
essa biblioteca tiver sido integrada ao nosso software. Se as chamadas à
biblioteca de terceiros estiverem espalhadas por vários lugares da
aplicação, a atualização será naturalmente complexa. Se as chamadas
estiverem concentradas em um único arquivo, a atualização será bem
mais fácil.
O design pattern Facade existe justamente para nos “blindar” em relação
a bibliotecas externas e também a códigos legados, tornando a
manutenção e suas atualizações mais fáceis, uma vez que ele nos leva a
criar uma camada que busca isolar funcionalidades externas em relação à
nossa aplicação.
A gura 2.13 mostra a estrutura básica de um Facade. Nesse padrão,
geralmente há um “subsistema”, ou seja, um conjunto de classes ou
componentes que não queremos que seja utilizado diretamente pela
aplicação. Nesse cenário, criamos uma “fachada”, ou uma classe Facade,
que nessa gura tem exatamente esse nome, mas que na prática terá um
nome relacionado ao problema a que se propõe resolver. A classe Facade
será uma classe sob nosso controle que concentrará as chamadas ao
“subsistema”, ou seja, se constituirá em um ponto central de onde as
chamadas partem. Ao usar esse design pattern, sempre que nossa
aplicação precisar de alguma funcionalidade presente no “subsistema”,
fará chamadas à classe Facade, não ao subsistema. Isso facilita a
manutenção do software, pois, sempre que precisarmos fazer alguma
manutenção ou atualização na estrutura do “subsistema”, precisaremos
apenas alterar a classe Facade, e não várias chamadas espalhadas em
diversos lugares diferentes de nossa aplicação.
pagseguro.php
<?php
// ...
require_once "App/Lib/PagSeguro/PagSeguroLibrary.php";
$paymentRequest = new PagSeguroPaymentRequest();
$paymentRequest->setCurrency("BRL");
// item
$item = new PagSeguroItem;
$item->setId( $product->id );
$item->setDescription( $product->description );
$item->setQuantity( $data->amount );
$item->setAmount( $product->price );
$paymentRequest->addItem($item);
// endreço
$address = new PagSeguroAddress;
$address->setPostalCode( $customer->postal );
$address->setStreet( $customer->address );
$address->setNumber( $customer->number );
$address->setDistrict( $customer->neighborhood );
$address->setCity( $customer->city );
$address->setState( $customer->state );
$paymentRequest->setShippingAddress($address);
// cliente
$sender = new PagSeguroSender;
$sender->setName( trim($customer->name) );
$sender->setEmail( trim($customer->email) );
$paymentRequest->setSender($sender);
$paymentRequest->setRedirectUrl("$host/confirmation.html");
$credentials = new PagSeguroAccountCredentials($ini['account'],
$ini['token']);
$url = $paymentRequest->register($credentials);
// ...
facade.php
<?php
class PagSeguroFacade {
private $request;
public function __construct( $currency ) {
$this->request = new PagSeguroPaymentRequest();
$this->request->setCurrency( $currency );
}
public function addItem($product, $amount) {
$item = new PagSeguroItem;
$item->setId( $product->id );
$item->setDescription( $product->description );
$item->setQuantity( $amount );
$item->setAmount( $product->price );
$this->request->addItem($item);
}
public function setCustomer($customer) {
$address = new PagSeguroAddress;
$address->setPostalCode( $customer->postal );
$address->setStreet( $customer->address );
$address->setNumber( $customer->number );
$address->setDistrict( $customer->neighborhood );
$address->setCity( $customer->city );
$address->setState( $customer->state );
$this->request->setShippingAddress($address);
$sender = new PagSeguroSender;
$sender->setName( trim($customer->name) );
$sender->setEmail( trim($customer->email) );
$this->request->setSender($sender);
}
public function process() {
// ...
}
}
A partir do momento que temos a classe Facade (PagSeguroFacade),
podemos utilizá-la em diversos pontos da aplicação com sentimento de
culpa reduzido, pois, mesmo que diversos pontos da aplicação dependam
da Facade, apenas a classe Facade tem dependências do recurso externo.
Assim, quando este sofrer alterações, teremos um único ponto da
aplicação para nos preocupar.
appfacade.php
<?php
$ps = new PagSeguroFacade('BRL'); // chamada à Facade
$product = new stdClass;
$product->id = 5;
$product->description = 'Pendrive';
$product->price = 10;
$ps->addItem($product, 3);
// ...
Após veri carmos o exemplo de utilização, podemos concluir que uma
classe Facade:
• torna uma biblioteca de software mais fácil de ser utilizada,
compreendida e testada;
• torna o nosso código que usa a biblioteca mais legível;
• reduz as dependências de código externo, visto que todos irão usar a
Facade;
• facilita a substituição de componentes de software, delimitando
melhor seus limites.
Observação: Facade não é o único design pattern que permite criar uma
interface simplificada para acessar outro objeto. Os padrões Adapter e Decorator
também podem ser utilizados.
2.15.3 Adapter
Adapter também é um Design Pattern que privilegia a manutenção e a
evolução de um software. Como vimos no exemplo anterior (Facade),
raras são as vezes em que não utilizamos bibliotecas de terceiros; em
alguns casos, precisamos tentar isolá-las de nossa aplicação (Facade),
outras vezes, precisamos adaptar um conjunto de métodos (uma interface)
para que este que de acordo com o esperado em nossa aplicação.
Nesta seção vamos falar sobre o design pattern Adapter, que trata da
adaptação (ou conversão) da interface de um objeto. Geralmente esse
design pattern é utilizado sempre que temos dois conjuntos de classes que
não podem operar conjuntamente, pois há incompatibilidade de
interfaces (chamadas de métodos). Imagine uma classe que espera que
outra tenha métodos em determinado formato que ela não tem. Nesses
casos, podemos aplicar o design pattern adapter para fazer uma conversão.
A gura 2.14 demonstra como é a estrutura de um design pattern Adapter.
Basicamente temos os papéis: do Adaptado, classe original que gostaríamos
de converter para um novo formato; do Adaptador, classe que utiliza a
classe original (Adaptado) por meio de composição ou de herança; e do
Cliente, nosso próprio código (aplicação), que, em vez de utilizar o
Adaptado, usa o Adaptador para compatibilizar as chamadas de métodos.
Para compreender melhor a aplicação do design pattern é importante
acompanhar o seguinte exemplo de problema. Imagine que sua aplicação
durante muitos anos tenha utilizado uma biblioteca externa para envio de
e-mails. Vamos chamá-la de OldEmailService. Depois de um longo tempo
você percebeu que a OldEmailService já não supria mais as necessidades
por não permitir recursos como anexar arquivos, autenticar usando SMTP,
entre outros. Então você procurou na internet outra biblioteca e
encontrou a maravilhosa PHPMailer. Entretanto você percebeu que os
métodos da PHPMailer (IsSMTP, MsgHTML, AddAttachment) eram diferentes
dos métodos da biblioteca que você utilizava (setUseSmtp, setHtmlBody,
addAttach) e percebeu também que iria ter trabalho para adaptar todas as
chamadas, visto que existiam diversos pontos do seu sistema que já
enviavam email.
classes/PHPMailerAdapter.php
<?php
class PHPMailerAdapter {
private $pm;
public function __construct() {
$this->pm = new PHPMailer;
$this->pm-> CharSet = 'utf-8';
}
public function setDebug($bool) {
$this->pm-> SMTPDebug = $bool;
}
public function setFrom($from, $name) {
$this->pm-> From = $from;
$this->pm-> FromName = $name;
}
public function setSubject($subject) {
$this->pm-> Subject = $subject;
}
public function setTextBody($body) {
$this->pm-> Body = $body;
$this->pm-> IsHTML(false);
}
public function setHtmlBody($html) {
$this->pm-> MsgHTML($html);
}
public function addAddress($address, $name = '') {
$this->pm-> AddAddress($address, $name);
}
public function addAttach($path, $name = '') {
$this->pm-> AddAttachment($path, $name);
}
public function setUseSmtp($auth = true) {
$this->pm-> IsSMTP();
$this->pm-> SMTPAuth = $auth;
}
public function setSmtpHost($host, $port = 25) {
$this->pm-> Host = $host;
$this->pm-> Port = $port;
$this->pm-> SMTPSecure = "ssl";
}
public function setSmtpUser($user, $pass) {
$this->pm-> Username = $user;
$this->pm-> Password = $pass;
}
public function send() {
if (!$this->pm-> Send()) {
if ($this->pm-> SMTPDebug) {
throw new Exception('E-mail not sent: ' . $this->pm-
>ErrorInfo);
}
else {
throw new Exception('E-mail not sent');
}
}
return TRUE;
}
}
Para utilizar a classe recém-criada PHPMailerAdapter diretamente, basta
importarmos a PHPMailer original, bem como sua adaptação
PHPMailerAdapter. Em seguida chamaremos os métodos da
PHPMailerAdapter, que por sua vez irá adaptar essas chamadas ao formato
da PHPMailer.
enviaemail.php
<?php
require_once 'PHPMailer.php';
require_once 'classes/PHPMailerAdapter.php';
$mail = new PHPMailerAdapter;
$mail->setUseSmtp();
$mail->setSmtpHost('smtp.gmail.com', 465);
$mail->setSmtpUser('pablo@dalloglio.net', 'minhasenha');
$mail->setFrom('pablo@dalloglio.net', 'Pablo Dall Oglio');
$mail->addAddress('destinatario@gmail.com', 'Destinatário');
$mail->setSubject('Oi Cara');
$mail->setHtmlBody('<b>Isso é um <i>teste</i></b>');
$mail->send();
Vamos reconstruir o cenário do problema que mostramos no início desta
seção. Tínhamos uma aplicação que dependia da classe OldEmailService,
que enviava emails. Agora temos a classe PHPMailerAdapter, que adapta as
chamadas da PHPMailer ao formato da antiga OldEmailService. Vamos
imaginar uma classe que executa operações com clientes chamada
ClienteService. Suponhamos que ela tenha um método
informaInadimplentes() que receba como parâmetro um objeto de
transporte de emails (OldEmailService) e execute sobre ele algumas
operações como setHtmlBody(), addAddress() e send(). Para passarmos a
utilizar a PHPMailer, basta substituir as ocorrências de OldEmailService por
PHPMailerAdapter. Veja que a PHPMailerAdapter irá adaptar as chamadas, e o
código cliente dessa classe (informaInadimplentes) não precisará ser
totalmente reescrito para utilizar a nova biblioteca. Vale lembrar que
nesses casos é importante ter uma interface para formalizar o que existe
em comum entre OldEmailService e PHPMailerAdapter. O código a seguir é
só um exemplo, não é funcional.
adapter.php
<?php
class ClienteService {
public static function informaInadimplentes( $mailer ) {
$inadimplentes = Cliente::getInadimplentes();
foreach ($inadimplentes as $cliente) {
$mailer->setHtmlBody();
$mailer->addAddress( $cliente->email);
$mailer->setHtmlBody("Hei <b>$cliente->nome</b>, cumpra com suas
obrigações");
$mailer->send();
}
}
}
// substituir:
// ClienteService::informaInadimplentes( new OldEmailService );
// por:
ClienteService::informaInadimplentes( new PHPMailerAdapter );
A tragédia da vida é que nos tornamos velhos cedo demais e sábios tarde demais.
B F
Apresentar de imediato um programa totalmente orientado a objetos, desde o
acesso a dados até a interface, seria uma curva de aprendizagem muito
acentuada. Portanto, desenvolvemos um método que irá, por meio de sete
etapas, demonstrar a reconstrução de um programa totalmente no modo
estruturado, em um programa totalmente orientado a objetos.
3.1 Introdução
O objetivo deste capítulo é demonstrar a evolução de um programa PHP
desde sua concepção, usando o método estruturado, até a utilização do
método orientado a objetos. Para demonstrar esta evolução entre um
paradigma de desenvolvimento e outro, vamos construir algumas rotinas
como um formulário e uma listagem de pessoas, com funcionalidades como
inserção, atualização, listagem e exclusão de registros. A princípio, vamos
construir estes programas de maneira estruturada, o que é considerado um
método não elegante e desatualizado de desenvolver sistemas. Após esta
construção inicial, esses programas passarão por uma série de melhorias
(refatorações), sendo que em cada etapa um certo tipo de melhoria será
introduzido. Ao nal de sete etapas, teremos um programa melhor, mais
organizado e orientado a objetos. Mas por que foi escolhida esta metodologia
em vez de apresentar ao leitor logo o programa ideal, utilizando uma série de
padrões reconhecidos como sendo os corretos? Em seguida, você verá a
explicação para isso.
Quando começamos a aprender nossa primeira linguagem de programação,
não estamos muito preocupados em seguir os melhores padrões, pois só
queremos conseguir construir um programa que realmente funcione. Neste
estágio, fazer o programa listar os registros, salvar dados, excluir e editar
corretamente é tudo o que buscamos. O mesmo vale para outras áreas da
vida. Quando buscamos nos exercitar (por exemplo, correr), não buscamos ser
um atleta olímpico e quebrar recordes, só queremos correr. Se algum dia
chegássemos a um nível extraordinário, seria ótimo, mas sabemos que temos
um longo caminho a percorrer. Quando estamos na pré-escola, somos
apresentados à aritmética básica (soma, subtração, multiplicação, divisão), e
não a equações complexas como a fórmula de Bhaskara. Um dia, chegamos lá,
mas antes precisamos aprender a fazer algo muito mais básico.
Algo semelhante acontece com a programação; primeiro, precisamos aprender
coisas muito fundamentais, para em seguida aperfeiçoarmos esse método e
fazer algo melhor, algo mais complexo. Se nos apresentassem a fórmula de
Bhaskara na pré-escola, não teríamos embasamento para sua compreensão.
Isso também ocorre com conceitos avançados de programação. Quando o
desenvolvedor não tem experiências anteriores que permitam que ele faça
associações com esses conceitos, não há aprendizado, somente uma tentativa
de memorização, ou de repetição de padrões.
Na programação, assim como em outras áreas da vida, o desenvolvimento do
conhecimento se dá pela construção, e a construção é algo gradual, não direto.
É preciso desenvolver um método simpli cado e aperfeiçoar esse método.
Então, quando estamos aprendendo, geralmente seguimos o método mais
simples, aquele que funciona. Mesmo que algumas pessoas nos digam que
esse não é o melhor método, se funciona, é a conta. É por esse motivo que a
maioria das pessoas aprende PHP estruturado antes do orientado a objetos,
porque o estruturado é mais fácil de entender.
Um programa orientado a objetos é um passo além do estruturado. Um
programa orientado a objetos engloba abstrações, reutilizações, componentes,
conceitos que precisam ser ensinados. Um programa estruturado abrange
menos conceitos a ser absorvidos para a sua construção. Por isso geralmente é
a primeira escolha.
Depois de aprender a desenvolver de maneira estruturada, alguns
desenvolvedores se questionam: “Mas o que tem de errado com esse jeito que
eu z?” ou “Mas o que eu poderia melhorar?”. E muitos acabam aprendendo
melhores formas de fazer a mesma coisa e acabam automaticamente na
orientação a objetos. Já outros desenvolvedores preferem a zona de conforto
da programação estruturada. E um terceiro grupo opta por criar um atalho
para a construção do conhecimento e ir direto para o aprendizado de um
framework, que já diz como fazer uma série de coisas.
O melhor caminho é, sem dúvida, o caminho da construção do
conhecimento. Aquele caminho em que o desenvolvedor se questiona e
melhora incrementalmente. Os dois outros caminhos são perigosos. Ao
carmos na programação estruturada, estaremos deixando de nos bene ciar
de uma série de vantagens que a orientação a objetos tem para nos oferecer.
Vou procurar mostrar em seguida quais são estes pontos. Ao criar um atalho
no caminho com a adoção de um framework numa fase inicial do
aprendizado, deixamos de aprender conceitos fundamentais. Dessa forma,
camos limitados àquilo que o framework nos oferece, passamos a ter
di culdades de executar alguma atividade fora da curva e, quando algo dá
errado, temos di culdade para descobrir o que é sem recorrer a terceiros.
Quando utilizamos uma arquitetura pronta (framework), esta também pode
apresentar falhas, e você não terá as habilidades necessárias para detectar essas
falhas, ou até mesmo consertá-las, o que também é um risco grande.
Em qualquer situação, só o conhecimento salva. É preciso compreender o que
se usa para poder ir além. O único caminho é construir esse conhecimento de
forma gradual, passo a passo. Para ajudar na construção desse conhecimento,
geralmente recorremos a livros. Escrever livros não é uma tarefa fácil. Lembro-
me de que, nas primeiras edições deste livro, havia uma necessidade de
mostrar a melhor forma de fazer programas orientados a objetos. No entanto
nem todos estão prontos para conhecer essa forma, pois, na maioria das vezes,
faltam alguns fundamentos. Apresentar uma forma perfeita nal não resolve o
problema. Por isso muitos leitores liam o livro mais de uma vez, ou várias
vezes, até absorver o conhecimento da melhor forma.
Desta vez, nos utilizaremos de um método diferente. Em vez de apresentar
uma maneira correta de fazer as coisas logo de início, mostrarei o caminho.
Para isso foi preciso relembrar como era o desenvolvimento nos primeiros dias
de aprendizado de PHP, para, então, ao longo de pequenas melhorias e
substituições, chegar a uma maneira melhor, mais organizada, mais orientada
a objetos, e acredito que consegui fazer isso por meio de sete passos.
Os passos desta metodologia estão descritos a seguir.
1. Procedural, um script por ação – Nesta etapa, construíremos um
programa bastante básico, estruturado, com PHP, HTML e SQL
misturados, em que cada ação (incluir, editar, excluir, listar) é um script
separado.
2. Agrupando ações comuns em scripts – Nesta etapa, reuniremos ações
comuns em torno de menos scripts (incluir junto com editar, listar junto
com excluir) e passaremos a trabalhar com o conceito de script e ação
realizada.
3. Separando o HTML com micro-templates – Nesta etapa, trabalharemos
com o HTML em arquivos à parte, chamados de templates. Com isso,
separaremos o visual do resto do programa.
4. Separando o acesso a dados com funções – Nesta etapa, separaremos
todas as funções que mexem com banco de dados em um arquivo (ainda
estruturado), contendo uma série de funções (insere_pessoa,
exclui_pessoa). Separaremos as funções de persistência do programa
principal.
5. Separando o acesso a dados com classes – Nesta etapa, transformaremos
as funções de acesso à base de dados em classes de modelo com métodos de
manipulação de dados. Introduziremos a biblioteca PDO e começaremos a
trabalhar com exceções. Nossa camada de banco já está utilizando classes.
6. Melhorando as conexões e a segurança – Nesta etapa, refatoraremos nossa
classe de banco de dados, para que esta não apresente mais os dados de
conexão (banco, usuário e senha) explicitamente no código, e
aumentaremos a segurança ao trabalhar com prepared statements a m de
evitar ataques como SQL injection.
7. Transformando páginas em classes de controle – Nesta etapa,
refatoraremos nossos programas principais, que ainda são estruturados, e
transformaremos estes em classes de controle (por exemplo, PessoaForm,
PessoaList), em que cada ação do usuário será identi cada por um método.
Também alteraremos o uxo da aplicação e então teremos um único
arquivo (index.php) por onde passarão todas as rotas.
A temática desta construção será um programa de manipulação de pessoas.
Para isso, precisaremos de funcionalidades como inserção, alteração, listagem
e exclusão dessas pessoas. Em cada nível construído, serão apresentados os
problemas remanescentes e mostraremos quais foram resolvidos. No nal você
cará com uma sensação de aprendizado, pois terá visto a evolução ocorrer
aos poucos.
É isso que queremos.
Antes, porém, abordaremos como se dá a conexão à base de dados, tanto de
maneira procedural, quanto orientada a objetos, por meio da biblioteca PDO.
dados/pgsql_insere.php
<?php
// abre conexão com Postgres
$conn = pg_connect("host=localhost port=5432 dbname=livro user=postgres
password=");
// insere vários registros
pg_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (1, 'Érico
Veríssimo')");
pg_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (2, 'John
Lennon')");
pg_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (3, 'Mahatma
Gandhi')");
pg_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (4, 'Ayrton
Senna')");
pg_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (5, 'Charlie
Chaplin')");
pg_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (6, 'Anita
Garibaldi')");
pg_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (7, 'Mário
Quintana')");
// fecha a conexão
pg_close($conn);
No próximo exemplo, o programa se conectará ao mesmo banco de dados do
exemplo anterior, chamado livro, localizado em localhost. Em seguida, irá
selecionar código e nome de famosos existentes nesse banco de dados,
exibindo-os na tela.
dados/pgsql_lista.php
<?php
// abre conexão com Postgres
$conn = pg_connect("host=localhost port=5432 dbname=livro user=postgres
password=");
// define consulta que será realizada
$query = 'SELECT codigo, nome FROM famosos';
// envia consulta ao banco de dados
$result = pg_query($conn, $query);
if ($result) {
// percorre resultados da pesquisa
while ($row = pg_fetch_assoc($result)) {
echo $row['codigo'] . ' - ' . $row['nome'] . "<br>\n";
}
}
// fecha a conexão
pg_close($conn);
Resultado:
1 - Érico Veríssimo
2 - John Lennon
3 - Mahatma Gandhi
4 - Ayrton Senna
5 - Charlie Chaplin
6 - Anita Garibaldi
7 - Mário Quintana
Nos exemplos anteriores, você deve ter notado quais funções precisamos
utilizar para nos conectarmos a um banco de dados PostgreSQL e executar
comandos SQL. Basicamente, temos o pg_connect() para abrir uma conexão,
pg_query() para executar uma instrução SQL e pg_close() para fechar uma
conexão, entre outros comandos. No PHP, as funções nativas de acesso a
banco de dados são especí cas para cada sistema gerenciador de banco de
dados (SGBD). Isso quer dizer que as funções de acesso e consulta a um banco
de dados MySQL são diferentes de um Oracle, um DB2, entre outros. Esse
cenário começa a mudar com a biblioteca PDO, apresentada no capítulo 3,
que fornece uma interface uni cada para acesso a diversos bancos de dados.
No exemplo seguinte, procuramos repetir o mesmo exemplo de inserção de
dados em um banco, anteriormente em PostgreSQL, agora em MySQL. Veja
que o exemplo é bastante similar, porém a nomenclatura das funções
utilizadas e a ordem dos parâmetros são outras. Para o MySQL, utilizamos
basicamente as funções mysqli_connect() para conectar ao banco,
mysqli_query() para enviar a instrução SQL para o banco e mysqli_close() para
fechar a conexão com o banco de dados. Neste exemplo, estamos conectando
o programa a um banco de dados livro localizado localmente (127.0.0.1), com
o usuário root e a senha mysql. Primeiro, criaremos a base de dados livro e,
depois, criaremos a tabela de famosos.
# mysql
mysql> create database livro;
Query OK, 1 row affected (0.00 sec)
mysql> use livro
Database changed
mysql> CREATE TABLE famosos (codigo integer, nome varchar(40));
Query OK, 0 rows affected (0.01 sec)
dados/mysql_insere.php
<?php
// abre conexão com o MySQL
$conn = mysqli_connect('127.0.0.1', 'root', 'mysql', 'livro');
// insere vários registros
mysqli_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (1, 'Érico
Veríssimo')");
mysqli_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (2, 'John
Lennon')");
mysqli_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (3, 'Mahatma
Gandhi')");
mysqli_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (4, 'Ayrton
Senna')");
mysqli_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (5, 'Charlie
Chaplin')");
mysqli_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (6, 'Anita
Garibaldi')");
mysqli_query($conn, "INSERT INTO famosos (codigo, nome) VALUES (7, 'Mário
Quintana')");
// fecha a conexão
mysqli_close($conn);
Assim como reescrevemos o exemplo para inserção de dados de PostgreSQL
para MySQL, iremos agora reescrever o programa de listagem de dados,
responsável por obter os registros do banco de dados e exibi-los em tela.
dados/mysql_lista.php
<?php
// abre conexão com o MySQL
$conn = mysqli_connect('127.0.0.1', 'root', 'mysql', 'livro');
// define a consulta que será realizada
$query = 'SELECT codigo, nome FROM famosos';
// envia consulta ao banco de dados
$result = mysqli_query($conn, $query);
if ($result) {
// percorre resultados da pesquisa
while ($row = mysqli_fetch_assoc($result)) {
echo $row['codigo'] . ' - ' . $row['nome'] . "<br>\n";
}
}
mysqli_close($conn); // fecha a conexão
Resultado:
1 - Érico Veríssimo
2 - John Lennon
3 - Mahatma Gandhi
4 - Ayrton Senna
5 - Charlie Chaplin
6 - Anita Garibaldi
7 - Mário Quintana
dados/pdo_insere.php
<?php
try {
// instancia objeto PDO, conectando no PostgreSQL
$conn = new
PDO('pgsql:dbname=livro;user=postgres;password=;host=localhost');
// executa uma série de instruções SQL
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (1, 'Érico
Veríssimo')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (2, 'John
Lennon')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (3, 'Mahatma
Gandhi')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (4, 'Ayrton
Senna')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (5, 'Charlie
Chaplin')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (6, 'Anita
Garibaldi')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (7, 'Mário
Quintana')");
// fecha a conexão
$conn = null;
}
catch (PDOException $e) {
// caso ocorra uma exceção, exibe na tela
print "Erro!: " . $e->getMessage() . "\n";
}
No próximo exemplo, o programa se conectará ao banco de dados livro. Em
seguida, selecionará código e nome dos famosos que compõem esse banco de
dados e exibirá na tela. Veja como os resultados serão percorridos via laço de
repetições (FOREACH) por meio de uma simples iteração. Cada linha (resultset) do
resultado é retornada para dentro do array $row, indexado pelos nomes das
colunas do SELECT ("codigo", "nome") e também por índices numéricos que
representam as posições das colunas (0, 1, ...). Novamente, o controle de erros
é realizado por tratamento de exceções. Caso alguma exceção seja gerada, a
sua mensagem será exibida na tela pelo bloco catch.
dados/pdo_lista.php
<?php
try {
// instancia objeto PDO, conectando no PostgreSQL
$conn = new
PDO('pgsql:dbname=livro;user=postgres;password=;host=localhost');
// executa uma instrução SQL de consulta
$result = $conn->query("SELECT codigo, nome FROM famosos");
if ($result) {
// percorre os resultados via iteração
foreach($result as $row) {
// exibe os resultados
echo $row['codigo'] . ' - ' . $row['nome'] . "<br>\n";
}
}
// fecha a conexão
$conn = null;
}
catch (PDOException $e) {
print "Erro!: " . $e->getMessage() . "<br>";
}
Resultado:
1 - Érico Veríssimo
2 - John Lennon
3 - Mahatma Gandhi
4 - Ayrton Senna
5 - Charlie Chaplin
6 - Anita Garibaldi
7 - Mário Quintana
Podemos também retornar os dados de uma consulta por meio da função
fetch(), que aceita como parâmetro o “estilo de fetch”. De acordo com o estilo,
o retorno poderá ser um dos relacionados a seguir:
Parâmetros Descrição
PDO::FETCH_ASSO Retorna um array indexado pelo nome da coluna.
C
PDO::FETCH_NUM Retorna um array indexado pela posição numérica da coluna.
PDO::FETCH_BOTH Retorna um array indexado pelo nome e pela posição numérica da
coluna.
PDO::FETCH_OBJ Retorna um objeto anônimo (stdClass), de modo que cada coluna seja
acessada como uma propriedade.
No exemplo a seguir, repetiremos o programa que lista os mesmos dados que
o exemplo anterior. A diferença é que agora utilizaremos a função fetch() para
iterar os resultados. O estilo de fetch desejado será o PDO::FETCH_OBJ, que
retorna os dados da consulta em forma de objeto. Veja que agora a variável
$row é um objeto e cada coluna retornada (codigo, nome) é acessada como uma
propriedade desse objeto.
dados/pdo_lista_obj.php
<?php
try {
// instancia objeto PDO, conectando no PostgreSQL
$conn = new
PDO('pgsql:dbname=livro;user=postgres;password=;host=localhost');
// executa uma instrução SQL de consulta
$result = $conn->query("SELECT codigo, nome FROM famosos");
if ($result) {
// percorre os resultados via fetch()
while ($row = $result->fetch(PDO::FETCH_OBJ)) {
// exibe os dados na tela, acessando o objeto retornado
echo $row->codigo . ' - '. $row->nome . "<br>\n";
}
}
// fecha a conexão
$conn = null;
}
catch (PDOException $e) {
print "Erro!: " . $e->getMessage() . "<br>";
}
Resultado:
1 - Érico Veríssimo
2 - John Lennon
3 - Mahatma Gandhi
4 - Ayrton Senna
5 - Charlie Chaplin
6 - Anita Garibaldi
7 - Mário Quintana
Reescreveremos, então, o programa de inserção de dados que anteriormente
foi escrito para PostgreSQL e agora está em MySQL. Veja que a única
diferença entre um programa e outro é a linha em que instanciamos o objeto
PDO (new PDO), de modo que os parâmetros são diferentes. O restante do
programa é simplesmente igual em razão da interface clara e uni cada da
biblioteca PDO. Veja a seguir como ca o programa que insere dados na
tabela de famosos adaptado para o uso com MySQL.
dados/pdo_insere_my.php
<?php
try {
// instancia objeto PDO, conectando no MySQL
$conn = new PDO('mysql:host=127.0.0.1;port=3306;dbname=livro', 'root',
'mysql');
// executa uma série de instruções SQL
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (1, 'Érico
Veríssimo')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (2, 'John
Lennon')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (3, 'Mahatma
Gandhi')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (4, 'Ayrton
Senna')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (5, 'Charlie
Chaplin')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (6, 'Anita
Garibaldi')");
$conn->exec("INSERT INTO famosos (codigo, nome) VALUES (7, 'Mário
Quintana')");
// fecha a conexão
$conn = null;
}
catch (PDOException $e) {
// caso ocorra uma exceção, exibe na tela
print "Erro!: " . $e->getMessage() . "\n";
}
Tabelas
CREATE TABLE estado (
id integer PRIMARY KEY NOT NULL,
sigla char(2),
nome text
);
CREATE TABLE cidade (
id integer PRIMARY KEY NOT NULL,
nome text,
id_estado INTEGER REFERENCES estado (id)
);
CREATE TABLE pessoa (
id integer PRIMARY KEY NOT NULL,
nome text,
endereco text,
bairro text,
telefone text,
email text,
id_cidade integer references cidade(id)
);
INSERT INTO estado VALUES(1,'AC','Acre');
INSERT INTO cidade VALUES(1,'Rio Branco',1);
nivel1/pessoa_form_insert.php
<html>
<head>
<meta charset="utf-8">
<title> Cadastro de pessoa </title>
<link href="css/form.css" rel="stylesheet" type="text/css" media="screen"
/>
</head>
<body>
<form enctype="multipart/form-data" method="post"
action="pessoa_save_insert.php">
<label>Código</label>
<input name="id" readonly="1" type="text" style="width: 30%">
<label>Nome</label>
<input name="nome" type="text" style="width: 50%">
<label>Endereço</label>
<input name="endereco" type="text" style="width: 50%">
<label>Bairro</label>
<input name="bairro" type="text" style="width: 25%">
<label>Telefone</label>
<input name="telefone" type="text" style="width: 25%">
<label>Email</label>
<input name="email" type="text" style="width: 25%">
<label>Cidade</label>
<select name="id_cidade" style="width: 25%">
<?php
require_once 'lista_combo_cidades.php';
print lista_combo_cidades();
?>
</select>
<input type="submit">
</form>
<body>
</html>
A lista de cidades é formada por uma função localizada em um arquivo
separado (lista_combo_cidades.php), pois será requerida em mais de um
contexto. Além do formulário de inserção, faremos um formulário de edição, e
ele também precisará dessa função. Basicamente essa função abre uma
conexão com a base de dados (PostgreSQL) e busca os dados de cidades com
uma instrução SELECT. Dentro de um laço de repetição, monta as opções da
combo (<option>), sendo que o value da combo é o ID da cidade; já o texto a
ser exibido é o nome da cidade. As opções são retornadas pela variável $output.
Se passarmos um ID como parâmetro para a função, ele representará a cidade
atual (ativa) em uma edição. Nesse caso, precisaremos marcar a cidade como
selecionada (selected=1), e é isso que faz a variável $check ao comparar o id da
cidade corrente com o id passado como parâmetro.
nivel1/lista_combo_cidades.php
<?php
function lista_combo_cidades($id = null) {
$output = '';
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$query = 'SELECT id, nome FROM cidade';
$result = pg_query($conn, $query);
if ($result) {
while ($row = pg_fetch_assoc($result)) {
$check = ($row['id'] == $id) ? 'selected=1' : '';
$output .= "<option $check value='{$row['id']}'> {$row['nome']}
</option>\n";
}
}
pg_close($conn);
return $output;
}
Os campos do formulário cariam mal-posicionados na tela se não
de níssemos seu estilo. Para isso, o arquivo form.css de ne algumas
características básicas dos elementos na tela. É por meio do CSS que
de nimos posicionamentos e margens dos campos, além do posicionamento
lado a lado entre label e input.
nivel1/css/form.css
<?php
input[readonly="1"] {
background:#e0e0e0;
border:1px solid #b0b0b0;
}
form {
display: table;
border: 1px solid #888;
width: 80%;
padding: 10px;
}
form > label, form > input, form > select {
float:left;
margin-top: 5px;
padding-top:4px;
padding-bottom:4px;
}
form > label {
clear:both;
width:25%;
}
form > input[type="submit"] {
margin-left: 25%;
clear:both;
}
Na gura 3.1, podemos ver o formulário de inserção de registros criado.
nivel1/pessoa_save_insert.php
<?php
$dados = $_POST;
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$result = pg_query($conn, "SELECT max(id) as next FROM pessoa");
$next = (int) pg_fetch_assoc($result)['next'] +1;
$sql = "INSERT INTO pessoa (id,
nome,
endereco,
bairro,
telefone,
email,
id_cidade)
VALUES ( '{$next}',
'{$dados['nome']}',
'{$dados['endereco']}',
'{$dados['bairro']}',
'{$dados['telefone']}',
'{$dados['email']}',
'{$dados['id_cidade']}'
)";
$result = pg_query($conn, $sql);
if ($result) {
print 'Registro inserido com sucesso';
}
else {
print pg_last_error($conn);
}
pg_close($conn);
nivel1/pessoa_list.php
<html>
<head>
<meta charset="utf-8">
<title> Listagem de pessoas </title>
<link href="css/list.css" rel="stylesheet" type="text/css" media="screen"
/>
</head>
<body>
<?php
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$result = pg_query($conn, "SELECT * FROM pessoa ORDER BY id");
print '<table border=1>';
print '<thead>';
print '<tr>';
print "<th> </th>";
print "<th> </th>";
print "<th> Id </th>";
print "<th> Nome </th>";
print "<th> Endereço </th>";
print "<th> Bairro </th>";
print "<th> Telefone </th>";
print '</tr>';
print '</thead>';
print '<tbody>';
while ($row = pg_fetch_assoc($result)) {
$id = $row['id'];
$nome = $row['nome'];
$endereco = $row['endereco'];
$bairro = $row['bairro'];
$telefone = $row['telefone'];
$email = $row['email'];
$id_cidade = $row['id_cidade'];
print '<tr>';
print "<td align='center'>
<a href='pessoa_form_edit.php?id={$id}'>
<img src='images/edit.svg' style='width:17px'>
</a></td>";
print "<td align='center'>
<a href='pessoa_delete.php?id={$id}'>
<img src='images/remove.svg' style='width:17px'>
</a></td>";
print "<td> {$id} </td>";
print "<td> {$nome} </td>";
print "<td> {$endereco} </td>";
print "<td> {$bairro} </td>";
print "<td> {$telefone} </td>";
print '</tr>';
}
print '</tbody>';
print '</table>';
?>
<button onclick="window.location='pessoa_form_insert.php'">
<img src='images/insert.svg' style='width:17px'> Inserir
</button>
</body>
</html>
Na gura a seguir, podemos ver a listagem de registros criada.
nivel1/pessoa_form_edit.php
<html>
<head>
<meta charset="utf-8">
<title> Cadastro de pessoa </title>
<link href="css/form.css" rel="stylesheet" type="text/css" media="screen"
/>
</head>
<?php
if (!empty($_GET['id'])) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$id = (int) $_GET['id'];
$result = pg_query($conn, "SELECT * FROM pessoa WHERE id='{$id}'");
$row = pg_fetch_assoc($result);
$id = $row['id'];
$nome = $row['nome'];
$endereco = $row['endereco'];
$bairro = $row['bairro'];
$telefone = $row['telefone'];
$email = $row['email'];
$id_cidade = $row['id_cidade'];
}
?>
<body>
<form enctype="multipart/form-data" method="post"
action="pessoa_save_update.php">
<label>Código</label>
<input name="id" readonly="1" type="text" style="width: 30%" value="<?
=$id?>">
<label>Nome</label>
<input name="nome" type="text" style="width: 50%" value="<?=$nome?>">
<label>Endereço</label>
<input name="endereco" type="text" style="width: 50%" value="<?
=$endereco?>">
<label>Bairro</label>
<input name="bairro" type="text" style="width: 25%" value="<?=$bairro?>">
<label>Telefone</label>
<input name="telefone" type="text" style="width: 25%" value="<?
=$telefone?>">
<label>Email</label>
<input name="email" type="text" style="width: 25%" value="<?=$email?>">
<label>Cidade</label>
<select name="id_cidade" style="width: 25%">
<?php
require_once 'lista_combo_cidades.php';
print lista_combo_cidades( $id_cidade );
?>
</select>
<input type="submit">
</form>
</body>
</html>
O formulário de edição está vinculado ao script pessoa_save_update, o que é
visível na tag <form> do html, pelo atributo “action”. Este script recebe os
dados do formulário via POST e detecta se há um ID passado pelo formulário.
Somente se houver um ID, o script prosseguirá com a operação. Nesse caso, é
aberta uma conexão com a base e executa-se um comando de UPDATE sobre a
tabela de pessoas, passando os campos recebidos via post ($dados) para os
outros campos. Uma mensagem de sucesso ou de erro é apresentada ao nal.
nivel1/pessoa_save_update.php
<?php
$dados = $_POST;
if ($dados['id']) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$sql = "UPDATE pessoa SET nome = '{$dados['nome']}',
endereco = '{$dados['endereco']}',
bairro = '{$dados['bairro']}',
telefone = '{$dados['telefone']}',
email = '{$dados['email']}',
id_cidade = '{$dados['id_cidade']}'
WHERE id = '{$dados['id']}'";
$result = pg_query($conn, $sql);
if ($result) {
print 'Registro atualizado com sucesso';
}
else {
print pg_last_error($conn);
}
pg_close($conn);
}
nivel1/pessoa_delete.php
<?php
$dados = $_GET;
if (!empty($dados['id'])) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$sql = "DELETE FROM pessoa WHERE id = '{$dados['id']}'";
$result = pg_query($conn, $sql);
if ($result) {
print 'Registro excluído com sucesso';
}
else {
print pg_last_error($conn);
}
pg_close($conn);
}
nivel2/pessoa_list.php
<html>
<head>
<meta charset="utf-8">
<title> Listagem de pessoas </title>
<link href="css/list.css" rel="stylesheet" type="text/css" media="screen"
/>
</head>
<body>
<?php
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
if (!empty($_GET['action']) AND $_GET['action'] == 'delete') {
$id = (int) $_GET['id'];
$result = pg_query($conn, "DELETE FROM pessoa WHERE id='{$id}'");
}
$result = pg_query($conn, "SELECT * FROM pessoa ORDER BY id");
nivel2/pessoa_form.php
<html>
<head>
<meta charset="utf-8">
<title> Cadastro de pessoa </title>
<link href="css/form.css" rel="stylesheet" type="text/css"
media="screen" />
</head>
<?php
$id = $nome = $endereco = $bairro = $telefone = $email = $id_cidade = '';
if (!empty($_REQUEST['action'])) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
if ($_REQUEST['action'] == 'edit') {
$id = (int) $_GET['id'];
$result = pg_query($conn, "SELECT * FROM pessoa WHERE id='{$id}'");
if ($row = pg_fetch_assoc($result)) {
$id = $row['id'];
$nome = $row['nome'];
$endereco = $row['endereco'];
$bairro = $row['bairro'];
$telefone = $row['telefone'];
$email = $row['email'];
$id_cidade = $row['id_cidade'];
}
}
else if ($_REQUEST['action'] == 'save') {
$id = $_POST['id'];
$nome = $_POST['nome'];
$endereco = $_POST['endereco'];
$bairro = $_POST['bairro'];
$telefone = $_POST['telefone'];
$email = $_POST['email'];
$id_cidade = $_POST['id_cidade'];
if (empty($_POST['id'])) {
$result = pg_query($conn, "SELECT max(id) as next FROM pessoa");
$next = (int) pg_fetch_assoc($result)['next'] +1;
$sql = "INSERT INTO pessoa (id, nome, endereco, bairro,
telefone, email, id_cidade)
VALUES ( '{$next}', '{$nome}', '{$endereco}',
'{$bairro}',
'{$telefone}', '{$email}', '{$id_cidade}'
)";
$result = pg_query($conn, $sql);
}
else {
$sql = "UPDATE pessoa SET nome = '{$nome}',
endereco = '{$endereco}',
bairro = '{$bairro}',
telefone = '{$telefone}',
email = '{$email}',
id_cidade = '{$id_cidade}'
WHERE id = '{$id}'";
$result = pg_query($conn, $sql);
}
print ($result) ? 'Registro salvo com sucesso' : pg_last_error($conn);
pg_close($conn);
}
}
?>
<body>
<form enctype="multipart/form-data" method="post" action="pessoa_form.php?
action=save">
<label>Código</label>
<input name="id" readonly="1" type="text" style="width: 30%" value="<?
=$id?>">
<label>Nome</label>
<input name="nome" type="text" style="width: 50%" value="<?=$nome?>">
<label>Endereço</label>
<input name="endereco" type="text" style="width: 50%" value="<?
=$endereco?>">
<label>Bairro</label>
<input name="bairro" type="text" style="width: 25%" value="<?=$bairro?>">
<label>Telefone</label>
<input name="telefone" type="text" style="width: 25%" value="<?
=$telefone?>">
<label>Email</label>
<input name="email" type="text" style="width: 25%" value="<?=$email?>">
<label>Cidade</label>
<select name="id_cidade" style="width: 25%">
<?php
require_once 'lista_combo_cidades.php';
print lista_combo_cidades( $id_cidade );
?>
</select>
<input type="submit">
</form>
</body>
</html>
nivel3/html/form.html
<html>
<head>
<meta charset="utf-8">
<title> Cadastro de pessoa </title>
<link href="css/form.css" rel="stylesheet" type="text/css" media="screen"
/>
</head>
<body>
<form enctype="multipart/form-data" method="post" action="pessoa_form.php?
action=save">
<label>Código</label>
<input name="id" readonly="1" type="text" style="width: 30%" value="
{id}">
<label>Nome</label>
<input name="nome" type="text" style="width: 50%" value="{nome}">
<label>Endereço</label>
<input name="endereco" type="text" style="width: 50%" value="{endereco}">
<label>Bairro</label>
<input name="bairro" type="text" style="width: 25%" value="{bairro}">
<label>Telefone</label>
<input name="telefone" type="text" style="width: 25%" value="{telefone}">
<label>Email</label>
<input name="email" type="text" style="width: 25%" value="{email}">
<label>Cidade</label>
<select name="id_cidade" style="width: 25%">
{cidades}
</select>
<input type="submit">
</form>
</body>
</html>
Agora vamos ao nosso script de cadastro de pessoas. Logo de início, você já
perceberá que a parte inicial que continha trechos de HTML não está mais
presente, pois foi transferida para o form.html (nosso template). A parte em
seguida, que veri ca a ação a ser executada (edit, save), não tem alterações em
relação ao script anterior. A única melhoria que zemos foi trabalhar apenas
com um vetor ($pessoa) para carregar os dados, e não mais variáveis separadas
($id, $nome etc.). Após o IF que veri ca as ações, o script prossegue com o
carregamento (file_get_contents) do conteúdo do template. Esse template não
pode ser apresentado em tela antes que façamos a substituição das marcações
como {id}, {nome} etc. Essas substituições são executadas pela função
str_replace(). A função str_replace substitui cada uma das marcações pelos
seus valores correspondentes que estão armazenados no vetor $pessoa.
É importante notar que o vetor $pessoa sempre conterá os dados da pessoa
sendo inserida/editada. Note que, quando o programa é acessado sem
nenhuma ação, esse vetor é inicializado com conteúdo vazio no ELSE. Quando
o programa entra na ação de edição (action=edit), esse vetor contém os dados
carregados a partir do registro escolhido no banco de dados. Já quando o
programa entra na ação de salvar (action=save), signi ca que o usuário clicou
no botão “enviar” do formulário. Nesse caso, os dados do vetor $pessoa são
alimentados a partir do post do formulário ($_POST).
nivel3/pessoa_form.php
<?php
if (!empty($_REQUEST['action'])) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
if ($_REQUEST['action'] == 'edit') {
$id = (int) $_GET['id'];
$result = pg_query($conn, "SELECT * FROM pessoa WHERE id='{$id}'");
$pessoa = pg_fetch_assoc($result);
}
else if ($_REQUEST['action'] == 'save') {
$pessoa = $_POST;
if (empty($_POST['id'])) {
$result = pg_query($conn, "SELECT max(id) as next FROM pessoa");
$next = (int) pg_fetch_assoc($result)['next'] +1;
$sql = "INSERT INTO pessoa (id, nome, endereco, bairro,
telefone, email, id_cidade)
VALUES ( '{$next}', '{$pessoa['nome']}',
'{$pessoa['endereco']}',
'{$pessoa['bairro']}', '{$pessoa['telefone']}',
'{$pessoa['email']}', '{$pessoa['id_cidade']}'
)";
$result = pg_query($conn, $sql);
}
else {
$sql = "UPDATE pessoa SET nome = '{$pessoa['nome']}',
endereco = '{$pessoa['endereco']}',
bairro = '{$pessoa['bairro']}',
telefone = '{$pessoa['telefone']}',
email = '{$pessoa['email']}',
id_cidade = '{$pessoa['id_cidade']}'
WHERE id = '{$pessoa['id']}'";
$result = pg_query($conn, $sql);
}
print ($result) ? 'Registro salvo com sucesso' : pg_last_error($conn);
pg_close($conn);
}
}
else {
$pessoa = [];
$pessoa['id'] = '';
$pessoa['nome'] = '';
$pessoa['endereco'] = '';
$pessoa['bairro'] = '';
$pessoa['telefone'] = '';
$pessoa['email'] = '';
$pessoa['id_cidade'] = '';
}
require_once 'lista_combo_cidades.php';
$form = file_get_contents('html/form.html');
$form = str_replace('{id}', $pessoa['id'], $form);
$form = str_replace('{nome}', $pessoa['nome'], $form);
$form = str_replace('{endereco}', $pessoa['endereco'], $form);
$form = str_replace('{bairro}', $pessoa['bairro'], $form);
$form = str_replace('{telefone}', $pessoa['telefone'], $form);
$form = str_replace('{email}', $pessoa['email'], $form);
$form = str_replace('{id_cidade}', $pessoa['id_cidade'], $form);
$form = str_replace('{cidades}', lista_combo_cidades( $pessoa['id_cidade'] ),
$form);
print $form;
nivel3/html/list.html
<html>
<head>
<meta charset="utf-8">
<title> Listagem de pessoas </title>
<link href="css/list.css" rel="stylesheet" type="text/css" media="screen"
/>
</head>
<body>
<table border=1>
<thead>
<tr>
<th> </th>
<th> </th>
<th> Id </th>
<th> Nome </th>
<th> Endereço </th>
<th> Bairro </th>
<th> Telefone </th>
</tr>
</thead>
<tbody>
{items}
</tbody>
</table>
<button onclick="window.location='pessoa_form.php'">
<img src='images/insert.svg' style='width:17px'> Inserir
</button>
</body>
</html>
Para cada item da tabela, utilizaremos um outro template HTML chamado
html/item.html. Este arquivo representará a estrutura de uma linha de dados
da tabela. Cada linha de dados da tabela conterá variáveis dinâmicas que
serão substituídas pelo script PHP em tempo de execução, tais como: {id},
{nome}, {endereco} etc. Esta linha começa com duas ações: edição e exclusão de
registro.
nivel3/html/item.html
<tr>
<td align='center'>
<a href='pessoa_form.php?action=edit&id={id}'>
<img src='images/edit.svg' style='width:17px'>
</a></td>
<td align='center'>
<a href='pessoa_list.php?action=delete&id={id}'>
<img src='images/remove.svg' style='width:17px'>
</a></td>
<td> {id} </td>
<td> {nome} </td>
<td> {endereco} </td>
<td> {bairro} </td>
<td> {telefone} </td>
</tr>
Agora que já vimos como tratar o HTML da listagem de maneira
independente, vamos veri car como cará o programa principal da listagem
(pessoa_list.php) para fazer uso desses novos templates recém-criados.
Inicialmente, o programa abre uma conexão com a base de dados. Em
seguida, ele veri ca se a ação que queremos executar é a ação de exclusão. Isso
ocorre porque a listagem pode enviar esta ação para ela mesma realizar a
exclusão do registro. O programa segue com a consulta de pessoas da base de
dados. No while, cada linha de retorno é percorrida. Dentro do while,
carregamos o template de um único item de tabela (item.html) e fazemos as
substituições pelos dados dinâmicos vindos da base de dados ($row). Essas
linhas já processadas são acumuladas em uma variável $items.
Ao m do programa, carregamos o template HTML principal (html/list.html)
e substituímos a marcação {items} pela variável $items, que foi processada pelo
laço de repetição.
nivel3/pessoa_list.php
<?php
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
if (!empty($_GET['action']) AND $_GET['action'] == 'delete') {
$id = (int) $_GET['id'];
$result = pg_query($conn, "DELETE FROM pessoa WHERE id='{$id}'");
}
$result = pg_query($conn, "SELECT * FROM pessoa ORDER BY id");
$items = '';
while ($row = pg_fetch_assoc($result)) {
$item = file_get_contents('html/item.html');
$item = str_replace('{id}', $row['id'], $item);
$item = str_replace('{nome}', $row['nome'], $item);
$item = str_replace('{endereco}', $row['endereco'], $item);
$item = str_replace('{bairro}', $row['bairro'], $item);
$item = str_replace('{telefone}', $row['telefone'], $item);
$items.= $item;
}
$list = file_get_contents('html/list.html');
$list = str_replace('{items}', $items, $list);
print $list;
nivel4/db/pessoa_db.php
<?php
function lista_pessoas() {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$result = pg_query($conn, "SELECT * FROM pessoa ORDER BY id");
$list = pg_fetch_all($result);
pg_close($conn);
return $list;
}
function exclui_pessoa($id) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$result = pg_query($conn, "DELETE FROM pessoa WHERE id='{$id}'");
pg_close($conn);
return $result;
}
function get_pessoa($id) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$result = pg_query($conn, "SELECT * FROM pessoa WHERE id='{$id}'");
$pessoa = pg_fetch_assoc($result);
pg_close($conn);
return $pessoa;
}
function get_next_pessoa() {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$result = pg_query($conn, "SELECT max(id) as next FROM pessoa");
$next = (int) pg_fetch_assoc($result)['next'] +1;
pg_close($conn);
return $next;
}
function insert_pessoa($pessoa) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$sql = "INSERT INTO pessoa (id, nome, endereco, bairro,
telefone, email, id_cidade)
VALUES ( '{$pessoa['id']}', '{$pessoa['nome']}',
'{$pessoa['endereco']}',
'{$pessoa['bairro']}', '{$pessoa['telefone']}',
'{$pessoa['email']}', '{$pessoa['id_cidade']}'
)";
$result = pg_query($conn, $sql);
pg_close($conn);
return $result;
}
function update_pessoa($pessoa) {
$dsn = "host=localhost port=5432 dbname=livro user=postgres password=";
$conn = pg_connect($dsn);
$sql = "UPDATE pessoa SET nome = '{$pessoa['nome']}',
endereco = '{$pessoa['endereco']}',
bairro = '{$pessoa['bairro']}',
telefone = '{$pessoa['telefone']}',
email = '{$pessoa['email']}',
id_cidade = '{$pessoa['id_cidade']}'
WHERE id = '{$pessoa['id']}'";
$result = pg_query($conn, $sql);
pg_close($conn);
return $result;
}
nivel4/pessoa_list.php
<?php
require 'db/pessoa_db.php';
if (!empty($_GET['action']) AND $_GET['action'] == 'delete') {
$id = (int) $_GET['id'];
exclui_pessoa($id);
}
$pessoas = lista_pessoas();
$items = '';
if ($pessoas) {
foreach ($pessoas as $pessoa) {
$item = file_get_contents('html/item.html');
$item = str_replace('{id}', $pessoa['id'], $item);
$item = str_replace('{nome}', $pessoa['nome'], $item);
$item = str_replace('{endereco}', $pessoa['endereco'], $item);
$item = str_replace('{bairro}', $pessoa['bairro'], $item);
$item = str_replace('{telefone}', $pessoa['telefone'], $item);
$items.= $item;
}
}
$list = file_get_contents('html/list.html');
$list = str_replace('{items}', $items, $list);
print $list;
3.6.3 Formulário
Após realizarmos as modi cações na listagem para que esta se bene cie das
novas funções de manipulação de banco de dados criadas, precisamos
também fazer as modi cações no formulário de cadastro de pessoas. Este
programa também deve iniciar com a requisição do arquivo contendo as
funções recém-criadas. Em seguida, o mesmo prossegue com a veri cação da
ação (action) a ser executada. Caso a ação seja edit, o cadastro está em modo
de edição (ação executada a partir da listagem), e deve identi car o ID do
registro a ser editado a partir da URL. Nesse caso, é executada a função
get_pessoa($id), que carrega os dados da pessoa em formato de vetor. Porém,
se a ação a ser executada for save, que é o resultado do envio do formulário,
então precisaremos veri car se o registro enviado tem um ID. Se esse registro
não tiver ID, será executada a função insert_pessoa(), mas, se o registro tiver
um ID, será executada a função update_pessoa(). O restante do script
permanece inalterado.
nivel4/pessoa_form.php
<?php
require_once 'db/pessoa_db.php';
if (!empty($_REQUEST['action'])) {
if ($_REQUEST['action'] == 'edit') {
$id = (int) $_GET['id'];
$pessoa = get_pessoa($id);
}
else if ($_REQUEST['action'] == 'save') {
$pessoa = $_POST;
if (empty($_POST['id'])) {
$pessoa['id'] = get_next_pessoa();
$result = insert_pessoa($pessoa);
}
else {
$result = update_pessoa($pessoa);
}
print ($result) ? 'Registro salvo com sucesso' : 'Problemas ao salvar';
}
}
else {
$pessoa = [];
$pessoa['id'] = '';
$pessoa['nome'] = '';
$pessoa['endereco'] = '';
$pessoa['bairro'] = '';
$pessoa['telefone'] = '';
$pessoa['email'] = '';
$pessoa['id_cidade'] = '';
}
require_once 'lista_combo_cidades.php';
$form = file_get_contents('html/form.html');
$form = str_replace('{id}', $pessoa['id'], $form);
$form = str_replace('{nome}', $pessoa['nome'], $form);
$form = str_replace('{endereco}', $pessoa['endereco'], $form);
$form = str_replace('{bairro}', $pessoa['bairro'], $form);
$form = str_replace('{telefone}', $pessoa['telefone'], $form);
$form = str_replace('{email}', $pessoa['email'], $form);
$form = str_replace('{id_cidade}', $pessoa['id_cidade'], $form);
$form = str_replace('{cidades}', lista_combo_cidades( $pessoa['id_cidade'] ),
$form);
print $form;
nivel5/classes/Pessoa.php
<?php
class Pessoa {
public static function save($pessoa) {
$conn = new PDO("pgsql:dbname=livro;user=postgres;host=127.0.0.1");
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if (empty($pessoa['id'])) {
$result = $conn->query("SELECT max(id) as next FROM pessoa");
$row = $result->fetch();
$pessoa['id'] = (int) $row['next'] +1;
3.7.2 Listagem
Agora que já construímos a classe Pessoa, precisamos alterar os scripts para
que estes façam uso dela. Inicialmente, vamos modi car a listagem
(pessoa_list.php) para este script usar os métodos recém-criados. O script
inicia com a requisição da classe Pessoa.php por meio do require_once. Dessa
maneira, os métodos criados já cam à disposição para uso. Em seguida,
vamos executar os métodos criados dentro de um bloco try/catch, pois, caso
ocorra qualquer falha (exceção), esta será capturada pelo bloco catch e exibida
em tela.
O primeiro teste do script é para veri car se o parâmetro action da URL
contém delete. Neste caso, uma exclusão deve ser executada pelo método
Pessoa::delete(). De qualquer maneira, em seguida, uma lista de pessoas é
carregada a partir da base de dados por meio do método Pessoa::all(). Este
vetor é posteriormente percorrido pelo foreach para a montagem da tabela.
Observação: Nos próximos capítulos, abordaremos melhor o controle de exceções.
Não se preocupe se você ainda não tiver compreendido totalmente este conceito.
nivel5/pessoa_list.php
<?php
require_once 'classes/Pessoa.php';
try {
if (!empty($_GET['action']) AND $_GET['action'] == 'delete') {
$id = (int) $_GET['id'];
Pessoa::delete($id);
}
$pessoas = Pessoa::all();
}
catch (Exception $e) {
print $e->getMessage();
}
$items = '';
foreach ($pessoas as $pessoa) {
$item = file_get_contents('html/item.html');
$item = str_replace('{id}', $pessoa['id'], $item);
$item = str_replace('{nome}', $pessoa['nome'], $item);
$item = str_replace('{endereco}', $pessoa['endereco'], $item);
$item = str_replace('{bairro}', $pessoa['bairro'], $item);
$item = str_replace('{telefone}', $pessoa['telefone'], $item);
$items.= $item;
}
$list = file_get_contents('html/list.html');
$list = str_replace('{items}', $items, $list);
print $list;
3.7.3 Formulário
Após ajustarmos a listagem, chegou a hora de ajustarmos o cadastro
(pessoa_form.php). Neste caso, requisitamos as classes Pessoa e Cidade, visto que,
neste script, também precisaremos da lista de cidades. No início do script,
testamos a ação (action) que o usuário está tentando executar. Caso seja a
edição (edit), carregamos os dados da pessoa por meio do método
Pessoa::find(). Esse método preencherá o vetor $pessoa, que será usado no
nal do script para efetuar as substituições e exibir o formulário com dados
em tela.
Caso a ação (action) a ser executada seja igual a save, então o usuário está
submetendo os dados do formulário para gravação. Nessa situação, é
executado o método Pessoa::save(), que receberá o vetor com os dados da
pessoa e procederá com a gravação desse vetor na base de dados. É importante
notar que esse vetor pode conter ou não o ID da pessoa, o que será
determinante para de nir se será executado um INSERT ou um UPDATE. O
restante do script não sofre muitas alterações em relação ao nível anterior. No
nal são realizadas as substituições de dados e também são montadas as
opções da combo (cidade).
nivel5/pessoa_form.php
<?php
require_once 'classes/Pessoa.php';
require_once 'classes/Cidade.php';
if (!empty($_REQUEST['action'])) {
try {
if ($_REQUEST['action'] == 'edit') {
$id = (int) $_GET['id'];
$pessoa = Pessoa::find($id);
}
else if ($_REQUEST['action'] == 'save') {
$pessoa = $_POST;
Pessoa::save($pessoa);
print "Pessoa salva com sucesso";
}
}
catch (Exception $e) {
print $e->getMessage();
}
}
else{
$pessoa = [];
$pessoa['id'] = '';
$pessoa['nome'] = '';
$pessoa['endereco'] = '';
$pessoa['bairro'] = '';
$pessoa['telefone'] = '';
$pessoa['email'] = '';
$pessoa['id_cidade'] = '';
}
$form = file_get_contents('html/form.html');
$form = str_replace('{id}', $pessoa['id'], $form);
$form = str_replace('{nome}', $pessoa['nome'], $form);
$form = str_replace('{endereco}', $pessoa['endereco'], $form);
$form = str_replace('{bairro}', $pessoa['bairro'], $form);
$form = str_replace('{telefone}', $pessoa['telefone'], $form);
$form = str_replace('{email}', $pessoa['email'], $form);
$form = str_replace('{id_cidade}', $pessoa['id_cidade'], $form);
$cidades = '';
foreach (Cidade::all() as $cidade) {
$check = ($cidade['id'] == $pessoa['id_cidade']) ? 'selected=1' : '';
$cidades .= "<option $check value='{$cidade['id']}'> {$cidade['nome']}
</option>\n";
}
$form = str_replace('{cidades}', $cidades, $form);
print $form;
nivel6/classes/Pessoa.php
<?php
class Pessoa {
private static $conn;
public static function getConnection() {
if (empty(self::$conn)) {
$conexao = parse_ini_file( 'config/livro.ini');
$host = $conexao['host'];
$name = $conexao['name'];
$user = $conexao['user'];
$pass = $conexao['pass'];
self::$conn = new PDO("pgsql:dbname={$name};user={$user};password=
{$pass};host={$host}");
self::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return self::$conn;
}
public static function save($pessoa) {
$conn = self::getConnection();
if (empty($pessoa['id'])) {
$result = $conn->query("SELECT max(id) as next FROM pessoa");
$row = $result->fetch();
$pessoa['id'] = (int) $row['next'] +1;
$sql = "INSERT INTO pessoa (id, nome, endereco, bairro,
telefone, email, id_cidade)
VALUES ( :id, :nome, :endereco,
:bairro, :telefone,
:email, :id_cidade
)";
}
else {
$sql = "UPDATE pessoa SET nome = :nome,
endereco = :endereco,
bairro = :bairro,
telefone = :telefone,
email = :email,
id_cidade = :id_cidade
WHERE id = :id";
}
$result = $conn->prepare($sql);
$result->execute([':id' => $pessoa['id'],
':nome' => $pessoa['nome'],
':endereco' => $pessoa['endereco'],
':bairro' => $pessoa['bairro'],
':telefone' => $pessoa['telefone'],
':email' => $pessoa['email'],
':id_cidade' => $pessoa['id_cidade'],
]);
}
O método Pessoa::getConnection() faz a leitura do arquivo config/livro.ini.
Este arquivo é representado no formato INI e contém em cada uma de suas
linhas a de nição de uma variável. O arquivo pode ser lido e interpretado
pelo PHP por meio da função parse_ini_file(), que o retorna em formato de
vetor.
nivel6/con g/livro.ini
host = 127.0.0.1
name = livro
user = postgres
pass = postgres
apache2.conf
<Directorymatch "^/.*/config/">
Order deny,allow
Deny from all
</Directorymatch>
nivel7/PessoaForm.php
<?php
require_once 'classes/Pessoa.php';
require_once 'classes/Cidade.php';
class PessoaForm {
private $html;
private $data;
public function __construct() {
$this->html = file_get_contents('html/form.html');
$this->data = ['id' => null,
'nome' => null,
'endereco' => null,
'bairro' => null,
'telefone' => null,
'email' => null,
'id_cidade' => null];
$cidades = '';
foreach (Cidade::all() as $cidade) {
$cidades .= "<option value='{$cidade['id']}'> {$cidade['nome']}
</option>\n";
}
$this->html = str_replace('{cidades}', $cidades, $this->html);
}
public function edit($param) {
try {
$id = (int) $param['id'];
$pessoa = Pessoa::find($id);
$this->data = $pessoa;
}
catch (Exception $e) {
print $e->getMessage();
}
}
public function save($param) {
try {
Pessoa::save($param);
$this->data = $param;
print "Pessoa salva com sucesso";
}
catch (Exception $e) {
print $e->getMessage();
}
}
public function show(){
$this->html = str_replace('{id}', $this->data['id'], $this->html);
$this->html = str_replace('{nome}', $this->data['nome'], $this->html);
$this->html = str_replace('{endereco}', $this->data['endereco'], $this-
>html);
$this->html = str_replace('{bairro}', $this->data['bairro'], $this-
>html);
$this->html = str_replace('{telefone}', $this->data['telefone'], $this-
>html);
$this->html = str_replace('{email}', $this->data['email'], $this->html);
$this->html = str_replace('{id_cidade}', $this->data['id_cidade'], $this-
>html);
$this->html = str_replace("option value='{$this->data['id_cidade']}'",
"option selected=1 value='{$this->data['id_cidade']}'", $this-
>html);
print $this->html;
}
}
3.9.2 Index
Você deve estar se perguntando como este código funciona, pois ele está claro,
bonito, bem organizado, mas ainda não sabemos quem é responsável pela sua
chamada, ou seja, como funciona a mágica de sua execução? Esta resposta
está em outro arquivo, o index.php. Sempre que desejarmos executar o
formulário de pessoas, faremos essa requisição para o index.php, identi cando
por meio da URL a classe e o método que quisermos executar. Assim, o index
será responsável por carregar a classe identi cada e executar o método
correspondente. Veja alguns exemplos a seguir.
• Ação de salvar registro – O botão de “salvar” do formulário agora será
vinculado à seguinte URL: index.php?class=PessoaForm&method=save.
• Ação de editar registro na listagem – O botão de editar registro agora será
vinculado à seguinte URL: index.php?class=PessoaForm&method=edit&id={id}.
Assim, o arquivo index.php funcionará como um gateway, um único arquivo
que recebe qualquer requisição, bastando informar a classe e o método que
desejamos que seja executado.
O script index.php é bastante enxuto. Ele inicia com uma declaração de
autoloader, por meio da função spl_autoload_register(). Essa função serve
para criarmos um método de carga de classes. Ela recebe o nome de uma
classe e vasculha no sistema de arquivos procurando um arquivo de mesmo
nome, mas com su xo .php. Caso encontre, faz o request desse arquivo.
O arquivo index.php busca no $_REQUEST as posições class e method. Se houver
uma classe (class_exists) com o nome da classe requisitada por parâmetro, e
se essa classe tiver um método (method_exists) com o nome do método
informado como parâmetro, então a princípio será instanciado um objeto
dessa classe ($pagina = new $classe). Em seguida será executado o método
informado ($pagina->$method( $_REQUEST )) e, por m, o método show() dessa
classe ($pagina->show()).
Assim, caso o usuário informe na URL index.php?
class=PessoaForm&method=edit, então o index.php inicialmente criará um objeto
da classe PessoaForm (new PessoaForm); em seguida, executará seu método
edit(), passando toda o vetor de requisição como parâmetro, e, por m,
executará o método show(), encerrando o ciclo. Veja que o vetor $_REQUEST,
contendo toda a requisição ($_GET ou $_POST), é sempre passado como
parâmetro, cando à disposição dentro do método.
Observação: o método spl_autoload_register() será abordado com mais detalhes
nos próximos capítulos do livro.
nivel7/index.php
<?php
spl_autoload_register(function($class) {
if (file_exists($class.'.php')) {
require_once $class.'.php';
}
});
$classe = $_REQUEST['class'];
$method = isset($_REQUEST['method']) ? $_REQUEST['method'] : null;
if (class_exists($classe)) {
$pagina = new $classe( $_REQUEST );
if (!empty($method) AND (method_exists($classe, $method))) {
$pagina->$method( $_REQUEST );
}
$pagina->show();
}
3.9.3 A listagem
Continuando nossas transformações, o próximo script a ser transformado em
classe é a listagem de pessoas. Neste caso, vamos criar uma classe chamada
PessoaList, que em seu início fará as requisições das classes de banco de dados
necessárias para sua operação. A classe PessoaList também terá um método
construtor, executado sempre que alguém requisitar esta classe pela URL e o
primeiro a ser executado. Neste caso, esse método apenas carrega o template
(list.html) e o armazena em um atributo do objeto ($this->html). O método
show() sempre será o último a ser executado, e ele fará a carga da listagem, por
meio do método load(), antes de exibir o conteúdo do HTML pelo comando
print. Caso a listagem seja executada pela URL sem parâmetros (index.php?
class=PessoaList), somente o seu método construtor e o show() serão
executados. Se ela for executada com método (index.php?
class=PessoaList&method=delete&id={id}), então serão executados seu método
construtor, delete(), e show(), nesta ordem.
O método delete() é executado a partir da listagem, e sua função é
simplesmente obter o ID do registro a ser excluído ($param['id']) e proceder
com a execução do método Pessoa::delete($id), que excluirá o registro na
base de dados. Toda a execução ocorre em um bloco try/catch. Assim,
qualquer falha ocorrida dentro do bloco try será capturada pelo bloco catch.
nivel7/PessoaList.php
<?php
require_once 'classes/Pessoa.php';
class PessoaList {
private $html;
public function __construct() {
$this->html = file_get_contents('html/list.html');
}
public function delete($param) {
try {
$id = (int) $param['id'];
Pessoa::delete($id);
}
catch (Exception $e) {
print $e->getMessage();
}
}
public function load() {
try {
$pessoas = Pessoa::all();
$items = '';
foreach ($pessoas as $pessoa) {
$item = file_get_contents('html/item.html');
$item = str_replace('{id}', $pessoa['id'], $item);
$item = str_replace('{nome}', $pessoa['nome'], $item);
$item = str_replace('{endereco}', $pessoa['endereco'], $item);
$item = str_replace('{bairro}', $pessoa['bairro'], $item);
$item = str_replace('{telefone}', $pessoa['telefone'], $item);
$items.= $item;
}
$this->html = str_replace('{items}', $items, $this->html);
}
catch (Exception $e) {
print $e->getMessage();
}
}
public function show() {
$this->load();
print $this->html;
}
}
Com as mudanças feitas, será necessário alterar alguns templates HTML já
constituídos para que então apontem para os novos caminhos. A seguir, as
alterações em destaque.
nivel7/html/form.html
<form enctype="multipart/form-data" method="post" action="index.php?
class=PessoaForm&method=save">
nivel7/html/item.html
<a href='index.php?class=PessoaForm&method=edit&id={id}'>
<a href='index.php?class=PessoaList&method=delete&id={id}'>
nivel7/html/list.html
<button onclick="window.location='index.php?class=PessoaForm'">
CAPÍTULO 4
Tópicos especiais em orientação a objetos
clientes.csv
Cidade;Cliente;Telefone;Endereço;Idade;Sexo
Caxias do Sul;ANDREI ZMIEVSKI;10 1234-5678;Rua Palo Alto.;1;M
São Paulo;RUBENS QUEIROZ;11 4644-5678;Rua Campinas, 123;2;M
São Paulo;AUGUSTO CAMPOS;43 1564-5345;Rua BRLinux, 343;3;M
São Paulo;MARCELIO LEAL;83 1124-3478;Rua Belém, 334;4;M
Vamos desenvolver nossa classe de leitura de arquivos CSV. A classe será
chamada CSVParser e terá apenas três métodos. O método construtor
recebe o nome do arquivo, e um separador de colunas armazena esses
valores em atributos e inicia o contador de linhas (counter). Projetado
para ser executado logo após o construtor, o método parse() lê o arquivo
por meio da função file(), que o carrega em memória e o transforma em
um array ($this->data) em que cada linha lida do arquivo será uma
posição do array. Dentro do método parse(), a função str_getcsv() efetua
a leitura da primeira linha do arquivo e a armazena no atributo header. A
função str_getcsv() converte uma string no formato CSV em um array. O
método fetch() é responsável por retornar para o usuário uma linha lida
do arquivo de cada vez. Se a posição em questão existir (isset), então a
linha será lida a partir do vetor de dados e convertida em array pela
função str_getcsv(), que recebe como parâmetro a linha (String)
delimitada, bem como o caractere separador. A função str_getcsv()
retorna um vetor indexado numericamente (0,1,2,3). Para melhorar o
processamento do retorno, criaremos um foreach, que percorrerá o
cabeçalho (header) e criará índices textuais no vetor de retorno (Codigo,
Cidade etc.) em que cada posição corresponderá a uma coluna do header.
classes/CSVParser.php
<?php
class CSVParser {
private $filename, $data, $header, $counter, $separator;
public function __construct($filename, $separator = ',') {
$this->filename = $filename;
$this->separator = $separator;
$this->counter = 1;
}
public function parse() {
$this->data = file($this->filename);
$this->header = str_getcsv($this->data[0], $this->separator);
}
public function fetch() {
if (isset($this->data[$this->counter])) {
$row = str_getcsv($this->data[$this->counter ++], $this-
>separator);
foreach ($row as $key => $value) {
$row[ $this->header[$key] ] = $value;
}
return $row;
}
}
}
Agora que temos a classe criada, vamos usá-la para efetuar a leitura do
arquivo clientes.csv. Para isso, vamos importar a classe criada (require_once)
e instanciar um objeto. No momento da instanciação, identi caremos o
arquivo lido, bem como o caractere separador (';'). O método parse()
efetuará a carga do arquivo em memória. Em seguida, iniciaremos um
loop (while) no qual efetuaremos a leitura de uma linha do arquivo de
cada vez por meio da função fetch(). Veja que a função fetch() retornará
um vetor no qual poderemos utilizar como índice o próprio nome da
coluna do header.
csv.php
<?php
require_once 'classes/CSVParser.php';
$csv = new CSVParser('clientes.csv', ';');
$csv->parse();
while ($row = $csv->fetch()) {
print $row['Cliente'] . " - ";
print $row['Cidade'] . "<br>\n";
}
Resultado:
ANDREI ZMIEVSKI - Caxias do Sul
RUBENS QUEIROZ - São Paulo
AUGUSTO CAMPOS - São Paulo
MARCELIO LEAL - São Paulo
...
Agora que executamos o arquivo você deve estar se perguntando: “Se deu
tudo certo, por que devo me preocupar?”. De fato, você não viu nenhum
erro ocorrer aqui porque os arquivos foram recém-criados e temos total
permissão de acesso. Mas nem sempre é assim. Muitas vezes, nossos
programas executam em ambientes diferentes, utilizando servidores
remotos nos quais não temos total controle sobre os arquivos que iremos
processar. Assim, não é incomum tentarmos efetuar a leitura de um
arquivo que não existe ou sobre o qual não temos as devidas permissões.
No exemplo que acabamos de escrever, se não tivermos permissão de
acesso su ciente sobre o arquivo, a função file() irá falhar. Nesse caso,
além de não obtermos o retorno correto, teremos uma falha como a
apresentada a seguir. Por esse motivo precisamos programar de maneira
defensiva, sempre testando se os recursos externos utilizados realmente
existem e se temos as devidas permissões antes de utilizá-los.
Warning: file(clientes.csv): failed to open stream: Permission denied
in...
classes/CSVParser.php
<?php
class CSVParser {
// ...
public function parse() {
if (!file_exists($this->filename)) {
die("Arquivo {$this->filename} não existe");
}
if (!is_readable($this->filename)) {
die("Arquivo {$this->filename} sem permissão");
}
$this->data = file($this->filename);
$this->header = str_getcsv($this->data[0], $this->separator);
}
Resultado:
Arquivo clientes.csv não existe
Utilizar a função die() para controlar erros é ruim porque ela
simplesmente aborta a execução do programa, o que na maioria dos casos
não é o comportamento desejado, visto que nem todos os tipos de erro
são fatais para a execução da aplicação.
classes/CSVParser.php
<?php
class CSVParser {
// ...
public function parse() {
if (!file_exists($this->filename)) {
return FALSE;
}
if (!is_readable($this->filename)) {
return FALSE;
}
$this->data = file($this->filename);
$this->header = str_getcsv($this->data[0], $this->separator);
return TRUE;
}
Agora que alteramos a classe CSVParser para retornar ags de controle,
vamos alterar o programa principal para testar essas ags. Como o
retorno de ags está no método parse(), vamos testar no programa
principal por meio de um if o retorno desse método. Se o método parse()
retornar TRUE, isso signi ca que o arquivo pôde ser lido; então
prosseguiremos com o processamento do arquivo por meio do método
fetch(). Caso contrário, emitiremos um aviso com um print. O retorno de
ags é um pouco melhor que o die(), pois transfere a tomada de decisão
do que fazer ao escopo principal, ou seja, enquanto a classe CSVParser
cumpre seu papel, é o programa principal que decide como informar o
usuário quando uma falha ocorrer.
csv.php
<?php
require_once 'classes/CSVParser.php';
$csv = new CSVParser('clientes.csv', ';');
if ($csv->parse()) {
while ($row = $csv->fetch()) {
print $row['Cliente'] . " - ";
print $row['Cidade'] . "<br>\n";
}
}
else {
print "Erro ao ler o arquivo<br>\n";
}
Resultado:
Erro ao ler o arquivo
Outra vantagem desse tipo de abordagem em relação ao primeiro é que a
aplicação segue a sua execução sem ser abortada (a não ser que a
abortemos ao testar o retorno da função). A desvantagem é que não
sabemos exatamente em qual ponto do programa a execução falhou, não
tendo como exibir a mensagem de erro correta (arquivo não existente,
problema de permissão), apenas uma genérica.
Observação: poderíamos fazer o método em questão retornar diferentes valores
para ter um controle mais apurado (por exemplo, 1, 2, 3). No entanto isso não será
necessário, visto que o PHP tem o tratamento de exceções.
classes/CSVParser.php
<?php
class CSVParser {
// ...
public function parse() {
if (!file_exists($this->filename)) {
throw new Exception("Arquivo {$this->filename} não encontrado");
}
if (!is_readable($this->filename)) {
throw new Exception("Arquivo {$this->filename} não pode ser
lido");
}
$this->data = file($this->filename);
$this->header = str_getcsv($this->data[0], $this->separator);
return TRUE;
}
O tratamento de exceções ocorre em dois níveis. Enquanto uma parte do
programa pode emitir uma exceção (throw new Exception), outra parte do
programa (a que executa a ação) precisa “perceber” que uma exceção foi
emitida e tratá-la. Para isso, todo e qualquer código que possa emitir uma
exceção deve estar contido em um bloco de programação chamado try.
Quando usamos o bloco try, qualquer exceção emitida aborta a
continuidade dos comandos desse bloco e a execução continua a partir de
outro bloco chamado catch. É no bloco catch que a exceção é tratada e o
programador executa o tratamento.
Dentro do bloco de comandos catch, programamos o que deve ser feito
quando ocorre uma exceção, podendo emitir uma mensagem ao usuário,
interromper a execução da aplicação, escrever um arquivo de log no disco,
entre outras ações.
O interessante dessa abordagem é que a ação resultante do erro ocorrido
ca totalmente isolada, externa ao contexto do código gerador da exceção.
Essa modularidade permite mudarmos o tratamento da exceção sem
alterar o método executado em si. Veja que neste exemplo estamos
somente capturando a mensagem da exceção (getMessage) e exibindo-a na
tela.
exception.php
<?php
require_once 'classes/CSVParser.php';
$csv = new CSVParser('clientes.csv', ';');
try {
$csv->parse(); // método que pode lançar exceção
while ($row = $csv->fetch()) {
print $row['Cliente'] . " - ";
print $row['Cidade'] . "<br>\n";
}
}
catch (Exception $e) {
print $e->getMessage();
}
Resultado:
Arquivo clientes.csv não encontrado
No exemplo anterior, capturamos o erro no bloco catch e diferenciamos o
erro pela mensagem, mas ainda não temos uma boa separação em relação
ao tratamento de cada tipo de exceção ocorrida. Imagine que você queira
fazer um tipo de tratamento quando o arquivo não for encontrado e outro
quando o arquivo não tiver permissão su ciente para leitura. Para fazer
essa separação, podemos lançar tipos especializados de exceção. No
exemplo a seguir, criaremos dois tipos de exceção especializada para ser
lançados: FileNotFoundException e FilePermissionException. Exceções
especializadas permitem que façamos tratamentos diferenciados quando
ocorrer uma exceção.
classes/CSVParser.php
<?php
class CSVParser {
// ...
public function parse() {
if (!file_exists($this->filename)) {
throw new FileNotFoundException("Arquivo {$this->filename} não
encontrado");
}
if (!is_readable($this->filename)) {
throw new FilePermissionException("Arquivo {$this->filename} não
pode ser lido");
}
$this->data = file($this->filename);
$this->header = str_getcsv($this->data[0], $this->separator);
return TRUE;
}
Para utilizar os tipos especializados de exceção, iremos declarar essas
classes por meio do mecanismo de herança a partir da superclasse
Exception. Para “perceber” a ocorrência de tipos especializados de exceção,
basta criarmos diferentes blocos catch, sendo um para tratar cada tipo
especializado. No exemplo a seguir, quando uma exceção do tipo
FileNotFoundException for lançada, todas as informações a respeito da
exceção serão exibidas (print_r) e a aplicação será encerrada. Quando
uma exceção do tipo FilePermissionException for lançada, somente a
mensagem de exceção será exibida.
subexception.php
<?php
require_once 'classes/CSVParser.php';
// definição das subclasses de erro
class FileNotFoundException extends Exception{}
class FilePermissionException extends Exception{}
$csv = new CSVParser('clientes.csv', ';');
try {
$csv->parse(); // método que pode lançar exceção
while ($row = $csv->fetch()) {
print $row['Cliente'] . " - ";
print $row['Cidade'] . "<br>\n";
}
}
catch (FileNotFoundException $excecao) {
print_r($excecao->getTrace());
die("Arquivo não encontrado");
}
catch (FilePermissionException $excecao) {
echo $excecao->getFile() . ' : ' . $excecao->getLine() . ' # ' .
$excecao->getMessage();
}
Resultado:
Array (
[0] => Array (
[file] => /tmp/subexception.php
[line] => 10
[function] => parse
[class] => CSVParser
[type] => ->
[args] => Array ( )
)
)
Arquivo não encontrado
magic_intro.php
<?php
class Titulo {
private $dt_vencimento;
private $valor;
private $juros;
private $multa;
}
$titulo = new Titulo;
print $titulo->valor;
Resultado:
Fatal error: Cannot access private property Titulo::$valor in...
magic_get.php
<?php
class Titulo {
private $dt_vencimento, $valor, $juros, $multa;
public function __get($propriedade) {
if ($propriedade == 'valor') {
print "Tentou acessar '{$propriedade}' inacessível. Use
getValor()<br>\n";
return 0;
}
}
public function getValor() {
return $this->valor;
}
}
$titulo = new Titulo;
print $titulo->valor;
Resultado:
Tentou acessar 'valor' inacessível. Use getValor()
0
magic_set.php
<?php
class Titulo {
private $dt_vencimento, $valor, $juros, $multa;
public function __set($propriedade, $valor) {
print "Tentou gravar $propriedade = $valor. Use setValor()<br>\n";
}
public function setValor($valor) {
if (is_numeric($valor)) {
$this->valor = $valor;
}
}
}
$titulo = new Titulo;
$titulo->valor = 12345;
Resultado:
Tentou gravar valor = 12345. Use setValor()
magic_array.php
<?php
class Titulo {
private $data;
public function __set($propriedade, $valor) {
$this->data[$propriedade] = $valor;
}
public function __get($propriedade) {
return $this->data[$propriedade];
}
}
$titulo = new Titulo;
$titulo->valor = 12345;
print 'O valor é: ' . number_format($titulo->valor,2, ',', '.');
Resultado:
O valor é: 12.345,00
magic_isset_unset.php
<?php
class Titulo {
private $data;
public function __set($propriedade, $valor) {
$this->data[$propriedade] = $valor;
}
public function __get($propriedade) {
return $this->data[$propriedade];
}
public function __isset($propriedade) {
return isset($this->data[$propriedade]);
}
public function __unset($propriedade) {
unset($this->data[$propriedade]);
}
}
$titulo = new Titulo;
$titulo->valor = 12345;
if (isset($titulo->valor)) {
print "Valor definido<br>\n";
} else {
print "Valor não definido<br>\n";
}
unset($titulo->valor);
if (isset($titulo->valor)) {
print "Valor definido<br>\n";
} else {
print "Valor não definido<br>\n";
}
Resultado:
Valor definido
Valor não definido
magic_tostring.php
<?php
class Titulo {
private $data;
public function __set($propriedade, $valor) {
$this->data[$propriedade] = $valor;
}
public function __get($propriedade) {
return $this->data[$propriedade];
}
public function __toString() {
return json_encode($this->data);
}
}
$titulo = new Titulo;
$titulo->dt_vencimento = '2015-05-20';
$titulo->valor = 12345;
$titulo->juros = 0.1;
$titulo->multa = 2;
print 'O conteúdo do título é: ' .$titulo;
Resultado:
O conteúdo do título é: {"dt_vencimento":"2015-05-
20","valor":12345,"juros":0.1,"multa":2}
magic_set_get.php
<?php
class Titulo {
private $data;
public function __get($propriedade) {
if ($propriedade == 'valor') {
return $this->getValor();
}
else {
return $this->data[$propriedade];
}
}
public function getValor() {
$vecto = new DateTime( $this->data['dt_vencimento'] );
$agora = new DateTime('now');
if ($vecto < $agora) {
$interval = $vecto->diff($agora);
$days = $interval->days;
return $this->data['valor'] + $this->data['multa']
+ ($this->data['valor'] * $this->data['juros'] * $days);
}
else {
return $this->data['valor'];
}
}
public function __set($propriedade, $valor) {
if ($propriedade == 'dt_vencimento') {
$this->setVencimento($valor);
} else {
$this->data[$propriedade] = $valor;
}
}
public function setVencimento($vencimento) {
$partes = explode('-', $vencimento);
if (count($partes)==3) {
if (checkdate ( $partes[1] , $partes[2] , $partes[0] )) {
$this->data['dt_vencimento'] = $vencimento;
} else {
throw new Exception("Data {$vencimento} inválida");
}
}
}
}
try {
$titulo = new Titulo;
$titulo->dt_vencimento = '2015-05-20';
$titulo->valor = 12345;
$titulo->multa = 2;
$titulo->juros = 0.01;
print 'O valor é: ' . number_format($titulo->valor,2, ',', '.');
}
catch (Exception $e) {
print $e->getMessage();
}
Resultado:
O valor é: 12.717,35 // o resultado irá variar conforme a data em que o
programa rodar
reference.php
<?php
class Titulo {
public $codigo, $dt_vencimento, $valor, $juros, $multa;
}
$titulo = new Titulo;
$titulo->codigo = 1;
$titulo->dt_vencimento = '2015-05-20';
$titulo->valor = 12345;
$titulo->juros = 0.1;
$titulo->multa = 2;
$titulo2 = $titulo;
$titulo2->valor = 78910;
var_dump($titulo->valor);
var_dump($titulo2->valor);
Resultado:
int(78910)
int(78910)
Entretanto nem sempre esse é o comportamento desejado. Mas como
proceder quando quisermos de fato duplicar um objeto na memória?
Basta utilizarmos o operador clone. Esse operador nos permite criar um
objeto resultante idêntico ao original, porém apontando para uma nova
região da memória, ou seja, nos permite criar de fato instâncias diferentes.
clone.php
<?php
class Titulo {
public $codigo, $dt_vencimento, $valor, $juros, $multa;
}
$titulo = new Titulo;
$titulo->codigo = 1;
$titulo->dt_vencimento = '2015-05-20';
$titulo->valor = 12345;
$titulo->juros = 0.1;
$titulo->multa = 2;
$titulo2 = clone $titulo;
$titulo2->valor = 78910;
var_dump($titulo->valor);
var_dump($titulo2->valor);
Resultado:
int(12345)
int(78910)
Mas nem sempre, quando clonamos um objeto, o objeto resultante deve
ser idêntico ao original. Em muitos casos é preciso limpar atributos que
não podem estar repetidos em memória. Imagine um objeto que
representa uma pessoa. Provavelmente esse objeto terá um código. Ao
duplicarmos o objeto, teríamos de limpar esse código, pois, caso
contrário, teríamos dois objetos com o mesmo código que, ao serem
armazenados no banco de dados, poderiam nos trazer problemas. Para
limpar alguns atributos no momento da clonagem, o PHP nos oferece o
método mágico __clone(). Quando esse método estiver de nido, ele será
executado automaticamente sempre que executarmos o operador
__clone(), podendo alterar o estado do novo objeto por meio de
mudanças em seus atributos. O exemplo a seguir demonstra que, mesmo
sendo o objeto $titulo2 clone de $titulo, ele tem seu codigo limpo logo
após a clonagem.
clone2.php
<?php
class Titulo {
public $codigo, $dt_vencimento, $valor, $juros, $multa;
public function __clone() {
$this->codigo = NULL;
}
}
$titulo = new Titulo;
$titulo->codigo = 1;
$titulo->dt_vencimento = '2015-05-20';
$titulo->valor = 12345;
$titulo->juros = 0.1;
$titulo->multa = 2;
$titulo2 = clone $titulo;
$titulo2->valor = 78910;
var_dump($titulo->valor);
var_dump($titulo2->valor);
var_dump($titulo->codigo);
var_dump($titulo2->codigo);
Resultado:
int(12345)
int(78910)
int(1)
NULL
call.php
<?php
class Titulo {
public $codigo, $dt_vencimento, $valor, $juros, $multa;
public function __call($method, $values) {
print "Você executou o método {$method}, com os parâmetros:
".implode(',', $values) . "<br>\n";
}
}
$titulo = new Titulo;
$titulo->codigo = 1;
$titulo->dt_vencimento = '2015-05-20';
$titulo->valor = 12345;
$titulo->juros = 0.1;
$titulo->multa = 2;
$titulo->teste1(1,2,3);
$titulo->teste2(4,5,6);
Resultado:
Você executou o método teste1 com os parâmetros: 1,2,3
Você executou o método teste2 com os parâmetros: 4,5,6
Para demonstrar mais ainda a exibilidade do método __call(), vamos
alterá-lo para tratar o nome de método recebido como uma função
comum do PHP, executando-a por meio da função call_user_func() sobre
os atributos do objeto, que, por sua vez, são retornados pela função
get_object_vars(). Observe que essa técnica nos permite executar métodos
como print() e count() sobre o objeto. Neste momento o método __call()
entra em ação, convertendo esse nome de método nas funções print() e
count() e passando os próprios atributos do objeto como parâmetro. Veja
a seguir os resultados obtidos.
call2.php
<?php
class Titulo {
public $codigo, $dt_vencimento, $valor, $juros, $multa;
public function __call($method, $values) {
return call_user_func($method, get_object_vars($this));
}
}
$titulo = new Titulo;
$titulo->codigo = 1;
$titulo->dt_vencimento = '2015-05-20';
$titulo->valor = 12345;
$titulo->juros = 0.1;
$titulo->multa = 2;
$titulo->print_r();
print 'A contagem é: ' . $titulo->count();
Resultado:
Array
(
[codigo] => 1
[dt_vencimento] => 2015-05-20
[valor] => 12345
[juros] => 0.1
[multa] => 2
)
A contagem é: 5
paises.xml
<?xml version="1.0" encoding="UTF-8"?>
<pais>
<nome> Brasil </nome>
<idioma> Português </idioma>
<capital> Brasília </capital>
<moeda> Real (R$) </moeda>
<prefixo> +55 </prefixo>
</pais>
A partir do documento XML salvo, criaremos um pequeno programa que
interpretará o documento XML com a função simplexml_load_file() e
analisará o objeto resultante da sua criação com a função var_dump().
Veremos claramente cada tag do XML sendo transformada em
propriedades do objeto resultante.
Observação: a função simplexml_load_file() faz a leitura de um documento
XML criando um objeto do tipo SimpleXmlElement a partir dessa operação. Caso o
documento seja mal formatado ou se não for um documento XML, essa função
retornará FALSE.
xml1.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises.xml');
// exibe as informações do objeto criado
var_dump($xml);
Resultado:
object(SimpleXMLElement)#1 (5) {
["nome"]=> string(8) " Brasil "
["idioma"]=> string(12) " Português "
["capital"]=> string(11) " Brasília "
["moeda"]=> string(11) " Real (R$) "
["prefixo"]=> string(5) " +55 "
}
xml2.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises.xml');
// exibe os atributos do objeto criado
echo 'Nome : ' . $xml->nome . "<br>\n";
echo 'Idioma : ' . $xml->idioma . "<br>\n";
echo 'Capital : ' . $xml->capital . "<br>\n";
echo 'Moeda : ' . $xml->moeda . "<br>\n";
echo 'Prefixo : ' . $xml->prefixo . "<br>\n";
Resultado:
Nome : Brasil
Idioma : Português
Capital : Brasília
Moeda : Real (R$)
Prefixo : +55
xml3.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises.xml');
foreach ($xml->children() as $elemento => $valor) {
echo "$elemento -> $valor<br>\n";
}
Resultado:
nome -> Brasil
idioma -> Português
capital -> Brasília
moeda -> Real (R$)
prefixo -> +55
paises2.xml
<?xml version="1.0" encoding="UTF-8"?>
<pais>
<nome> Brasil </nome>
<idioma> Português </idioma>
<capital> Brasília </capital>
<moeda> Real (R$) </moeda>
<prefixo> +55 </prefixo>
<geografia>
<clima> tropical </clima>
<costa> 7367 km </costa>
<pico> Neblina (3014 m) </pico>
</geografia>
</pais>
Neste caso, o objeto resultante ($xml) que inicia a partir do nodo-raiz
(pais) irá conter a propriedade geografia, que é também um objeto do
tipo SimpleXMLElement com suas respectivas propriedades, sendo passíveis
da utilização dos mesmos métodos listados no início desta seção (asXML(),
children(), attributes() etc.). Neste exemplo, vamos acessar diretamente
as propriedades do nodo lho.
xml4.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises2.xml');
echo 'Nome : ' . $xml->nome . "<br>\n";
echo 'Idioma : ' . $xml->idioma . "<br>\n";
echo "<br>\n";
echo "*** Informações Geográficas ***<br>\n";
echo 'Clima : ' . $xml->geografia->clima . "<br>\n";
echo 'Costa : ' . $xml->geografia->costa . "<br>\n";
echo 'Pico : ' . $xml->geografia->pico . "<br>\n";
Resultado:
Nome : Brasil
Idioma : Português
*** Informações Geográficas ***
Clima : tropical
Costa : 7367 km
Pico : Neblina (3014 m)
xml5.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises2.xml');
// alteração de propriedades
$xml->moeda = 'Novo Real (NR$)';
$xml->geografia->clima = 'temperado';
// adiciona novo nodo
$xml->addChild('presidente', 'Chapolin Colorado');
// exibindo o novo XML
echo $xml->asXML();
// grava no arquivo paises2.xml
file_put_contents('paises2.xml', $xml->asXML());
Resultado:
<?xml version="1.0" encoding="UTF-8"?>
<pais>
<nome> Brasil </nome>
<idioma> Português </idioma>
<capital> Brasília </capital>
<moeda>Novo Real (NR$)</moeda>
<prefixo> +55 </prefixo>
<geografia>
<clima>temperado</clima>
<costa> 7367 km </costa>
<pico> Neblina (3014 m) </pico>
</geografia>
<presidente>Chapolin Colorado</presidente>
</pais>
Observação: caso esteja rodando o PHP no browser, veja o XML pelo código-
fonte da página.
paises3.xml
<?xml version="1.0" encoding="UTF-8"?>
<pais>
<nome> Brasil </nome>
<idioma> Português </idioma>
<capital> Brasília </capital>
<moeda> Real (R$) </moeda>
<prefixo> +55 </prefixo>
<geografia>
<clima> tropical </clima>
<costa> 7367 km </costa>
<pico> Neblina (3014 m) </pico>
</geografia>
<estados>
<nome> Rio Grande do Sul </nome>
<nome> São Paulo </nome>
<nome> Minas Gerais </nome>
<nome> Rio de Janeiro </nome>
<nome> Paraná </nome>
<nome> Mato Grosso </nome>
</estados>
</pais>
Como já temos o documento XML formado, criaremos um programa
para ler seus elementos repetitivos. O código a seguir exibe em tela
algumas informações do XML, como nos exemplos anteriores, e também
demonstra como exibir esses elementos repetitivos pelo laço de repetições
foreach() sobre o elemento estados.
xml6.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises3.xml');
echo 'Nome : ' . $xml->nome . "<br>\n";
echo 'Idioma : ' . $xml->idioma . "<br>\n";
echo "<br>\n";
echo "*** Estados ***<br>\n";
/**
* Você pode acessar um estado diretamente pelo seu índice
* echo $xml->estados->nome[0];
*/
foreach ($xml->estados->nome as $estado) {
echo 'Estado : ' . $estado . "<br>\n";
}
Resultado:
Nome : Brasil
Idioma : Português
*** Estados ***
Estado : Rio Grande do Sul
Estado : São Paulo
Estado : Minas Gerais
Estado : Rio de Janeiro
Estado : Paraná
Estado : Mato Grosso
paises4.xml
<?xml version="1.0" encoding="UTF-8"?>
<pais>
<nome> Brasil </nome>
<idioma> Português </idioma>
<capital> Brasília </capital>
<moeda> Real (R$) </moeda>
<prefixo> +55 </prefixo>
<geografia>
<clima> tropical </clima>
<costa> 7367 km </costa>
<pico nome="Neblina" altitude="3014"/>
</geografia>
<estados>
<estado nome="Rio Grande do Sul" capital="Porto Alegre"/>
<estado nome="São Paulo" capital="São Paulo"/>
<estado nome="Minas Gerais" capital="Belo Horizonte"/>
<estado nome="Rio de Janeiro" capital="Rio de Janeiro"/>
<estado nome="Paraná" capital="Curitiba"/>
<estado nome="Mato Grosso" capital="Cuiabá"/>
</estados>
</pais>
Com o documento XML formado, percorreremos essas informações de
maneira semelhante à do exemplo anterior com um laço de repetições
foreach sobre o elemento estados. Porém, dentro do laço de repetições,
poderemos acessar cada posição percorrida no formato de array,
informando como índice de acesso para esse array o próprio nome do
atributo (nome, capital).
xml7.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises4.xml');
echo "*** Estados ***<br>\n";
// percorre a lista de estados
foreach ($xml->estados->estado as $estado) {
// imprime o estado e a capital
echo str_pad('Estado : ' . $estado['nome'], 30) .
'Capital: ' . $estado['capital'] . "<br>\n";
}
Resultado:
*** Estados ***
Estado : Rio Grande do Sul Capital: Porto Alegre
Estado : São Paulo Capital: São Paulo
Estado : Minas Gerais Capital: Belo Horizonte
Estado : Rio de Janeiro Capital: Rio de Janeiro
Estado : Paraná Capital: Curitiba
Estado : Mato Grosso Capital: Cuiabá
xml8.php
<?php
// interpreta o documento XML
$xml = simplexml_load_file('paises4.xml');
echo "*** Estados ***<br>\n";
// percorre os estados
foreach ($xml->estados->estado as $estado) {
// percorre os atributos de cada estado
foreach ($estado->attributes() as $key => $value) {
echo "$key=>$value<br>\n";
}
}
Resultado:
*** Estados ***
nome=>Rio Grande do Sul
capital=>Porto Alegre
nome=>São Paulo
capital=>São Paulo
nome=>Minas Gerais
capital=>Belo Horizonte
nome=>Rio de Janeiro
capital=>Rio de Janeiro
nome=>Paraná
capital=>Curitiba
nome=>Mato Grosso
capital=>Cuiabá
bases.xml
<bases>
<base id="1">
<name>teste</name>
<host>192.168.0.1</host>
<type>mysql</type>
<user>mary</user>
</base>
<base id="2">
<name>producao</name>
<host>192.168.0.2</host>
<type>pgsql</type>
<user>admin</user>
</base>
</bases>
Vamos percorrer o documento recém-criado instanciando um objeto da
classe DOMDocument. Esse objeto contém o método load(), que efetua a carga
do documento XML. Depois de carregá-lo, poderemos utilizar métodos
de sua interface como getElementById(), que obtém um elemento (classe
DOMElement) pelo seu ID, ou mesmo getElementsByTagName(), que retorna
uma lista de elementos (classe DOMNodeList) encontrados por seu nome de
tag. Usaremos o método getElementsByTagName() para retornar uma lista de
elementos a partir da tag base. Esse método nos retornará um array de
elementos. Sobre cada elemento base, poderemos coletar seu atributo id
por meio do método getAttribute(). Além disso, cada atributo base
conterá uma série de nodos lhos, como name, host, type e user. Para obter
esses elementos, usaremos o método getElementsByTagName(), que retorna
um vetor com os conteúdos desses nodos (names, hosts, types, users),
mesmo que eles contenham apenas um elemento em seu interior. Isso
ocorre porque o método getElementsByTagName() retorna sempre um vetor
do tipo DOMNodeList. Para obter o conteúdo de cada nodo, basta acessar
sua primeira posição (item(0)) e então o atributo nodeValue, que contém o
valor do nodo.
dom1.php
<?php
$doc = new DOMDocument();
$doc->load( 'bases.xml' );
$bases = $doc->getElementsByTagName( "base" );
foreach( $bases as $base ) {
print 'ID: '.$base->getAttribute('id') . '<br>'.PHP_EOL;
$names = $base->getElementsByTagName( 'name' );
$hosts = $base->getElementsByTagName( 'host' );
$types = $base->getElementsByTagName( 'type' );
$users = $base->getElementsByTagName( 'user' );
$name = $names->item(0)->nodeValue;
$host = $hosts->item(0)->nodeValue;
$type = $types->item(0)->nodeValue;
$user = $users->item(0)->nodeValue;
print 'Name : '. $name . '<br>'.PHP_EOL;
print 'Host : '. $host . '<br>'.PHP_EOL;
print 'Type : '. $type . '<br>'.PHP_EOL;
print 'User : '. $user . '<br>'.PHP_EOL;
print '<br>'.PHP_EOL;
}
Resultado:
ID: 1
Name : teste
Host : 192.168.0.1
Type : mysql
User : mary
ID: 2
Name : producao
Host : 192.168.0.2
Type : pgsql
User : admin
dom2.php
<?php
$dom = new DOMDocument('1.0', "UTF-8");
$dom->formatOutput = true;
$bases = $dom->createElement('bases');
$base = $dom->createElement('base');
$bases->appendChild($base);
$attr = $dom->createAttribute('id');
$attr->value = '1';
$base->appendChild($attr);
$base->appendChild($dom->createElement('name', 'teste'));
$base->appendChild($dom->createElement('host', '192.168.0.1'));
$base->appendChild($dom->createElement('type', 'mysql'));
$base->appendChild($dom->createElement('user', 'mary'));
echo $dom->saveXML($bases);
Resultado:
<bases>
<base id="1">
<name>teste</name>
<host>192.168.0.1</host>
<type>mysql</type>
<user>mary</user>
</base>
</bases>
Observação: caso esteja rodando o PHP no browser, veja o XML pelo código-
fonte da página.
4.5 SPL
SPL é um conjunto de classes e interfaces que fornece ao desenvolvedor
uma API padronizada para resolver problemas comuns e também criar
classes com maior potencial de interoperabilidade.
O PHP sempre ofereceu muitas funções para manipulação de arquivos
(file, basename, fopen, file_get_contents, file_put_contents, copy, unlink,
rename, filesize, fwrite, pathinfo), manipulação de vetores (array_diff,
array_merge, array_shift, array_pop, array_unshift, array_push, array_keys.
array_values), entre outras ações. Antes do PHP 5, quando a SPL ainda
não existia, mesmo tendo esse arsenal de funções à disposição, sempre
havia programadores que preferiam fazer tudo de maneira orientada a
objetos. Assim, muitos acabaram criando classes que “agrupavam”
funcionalidades em comum para fornecer um meio orientado a objetos de
resolver problemas. O que a SPL oferece é um conjunto de classes com
funcionalidades comuns, como manipulação de arquivos, vetores, pilhas e
las de maneira padronizada.
le_info_sem_spl.php
<?php
class MeuArquivo {
private $path;
public function __construct($path) {
$this->path = $path;
}
public function getContents() {
return file_get_contents($this->path);
}
public function getExtension() {
return pathinfo($this->path, PATHINFO_EXTENSION);
}
public function getFileName() {
return basename($this->path);
}
public function getSize() {
return filesize($this->path);
}
}
$file = new MeuArquivo('file_info_sem_spl.php');
print 'Nome: ' . $file->getFileName() . '<br>' . PHP_EOL;
print 'Extensão: ' . $file->getExtension() . '<br>' . PHP_EOL;
print 'Tamanho: ' . $file->getSize() . '<br>' . PHP_EOL;
Resultado:
Nome: file_info_sem_spl.php
Extensão: php
Tamanho: 769
Com o surgimento da SPL, o PHP passou a oferecer classes nativas da
linguagem a m de fornecer funcionalidades comuns à maioria dos
programadores para que eles possam resolver problemas comuns do dia a
dia de maneira totalmente orientada a objetos. Neste primeiro exemplo,
vamos mostrar algumas classes da SPL para executar manipulação de
arquivos, como SplFileInfo e SplFileObject.
A classe SplFileInfo oferece funcionalidades para obtenção de
informações a respeito de determinado arquivo. Ela fornece métodos
como getFileName(), que informa o nome do arquivo, getExtension(), que
mostra qual é a extensão, getSize(), que diz qual é o tamanho,
getRealPath(), que retorna o caminho real, getType(), que mostra o tipo,
isWritable(), que informa se ele pode ser escrito, entre outros.
spl_ le_info.php
<?php
$file = new SplFileInfo('spl_file_info.php');
print 'Nome: ' . $file->getFileName() . '<br>' . PHP_EOL;
print 'Extensão: ' . $file->getExtension() . '<br>' . PHP_EOL;
print 'Tamanho: ' . $file->getSize() . '<br>' . PHP_EOL;
print 'Caminho: ' . $file->getRealPath() . '<br>' . PHP_EOL;
print 'Tipo: ' . $file->getType() . '<br>' . PHP_EOL;
print 'Gravação: ' . $file->isWritable() . '<br>' . PHP_EOL;
Resultado:
Nome: spl_file_info.php
Extensão: php
Tamanho: 415
Caminho: /tmp/spl_file_info.php
Tipo: file
Gravação: 1
Já a classe SplFileObject fornece uma maneira orientada a objetos para
manipular arquivos. A classe SplFileObject estende a classe SplFileInfo.
Com isso, além de oferecer recursos de manipulação de arquivos, ela
também oferece recursos de obtenção de informações. No programa a
seguir, instanciaremos a classe SplFileObject, obtendo algumas
informações do primeiro arquivo e escrevendo um novo arquivo novo.txt.
spl_ le_object.php
<?php
$file = new SplFileObject('spl_file_object.php');
print 'Nome: ' . $file->getFileName() . '<br>' . PHP_EOL;
print 'Extensão: ' . $file->getExtension() . '<br>' . PHP_EOL;
$file2 = new SplFileObject("novo.txt", "w");
$bytes = $file2->fwrite('Olá Mundo PHP' . PHP_EOL);
print 'Bytes escritos ' . $bytes . PHP_EOL;
Resultado:
Nome: spl_file_object.php
Extensão: php
Bytes escritos 15
spl_ le_object2.php
<?php
$file = new SplFileObject('spl_file_object2.php');
print 'forma 1: ' . PHP_EOL;
while (!$file->eof()) {
print 'linha: ' . $file->fgets();
}
print PHP_EOL.PHP_EOL;
print 'forma 2: ' . PHP_EOL;
foreach ($file as $linha => $conteudo) {
print "$linha: $conteudo";
}
Resultado:
forma 1:
linha: <?php
linha: $file = new SplFileObject('spl_file_object2.php');
linha:
linha: print 'forma 1: ' . PHP_EOL;
linha: while (!$file->eof()) {
linha: print 'linha: ' . $file->fgets();
linha: }
...
forma 2:
0: <?php
1: $file = new SplFileObject('spl_file_object2.php');
2:
3: print 'forma 1: ' . PHP_EOL;
4: while (!$file->eof()) {
5: print 'linha: ' . $file->fgets();
6: }
...
No arquivo a seguir, escreveremos um arquivo CSV. Em primeiro lugar,
vamos declarar uma matriz contendo os dados que serão gravados no
arquivo. Na prática, esses dados viriam de uma consulta da base de
dados. Após declarar os dados, instanciaremos o objeto SplFileObject e
de niremos o modo de gravação (w). Então de niremos o caractere de
separação CSV com o método setCsvControl(). Por m, percorreremos os
dados por um foreach e usaremos o método fputcsv() para escrever no
arquivo.
spl_ le_csv.php
<?php
$dados = array (
array('codigo', 'nome', 'endereco', 'telefone'),
array('1', 'Maria da Silva', 'Rua da Maria', '(11) 12345678'),
array('2', 'Pedro Cardoso', 'Rua do Pedro', '(11) 12345678'),
array('3', 'Joana Pereira', 'Rua da Joana', '(11) 12345678')
);
$file = new SplFileObject('dados.csv', 'w');
$file->setCsvControl(',');
foreach ($dados as $linha) {
$file->fputcsv($linha);
}
spl_queue.php
<?php
$ingredientes = new SplQueue();
// acrescentando na fila
$ingredientes->enqueue('Peixe');
$ingredientes->enqueue('Sal');
$ingredientes->enqueue('Limão');
foreach ($ingredientes as $item) {
print 'Item: ' . $item . '<br>' . PHP_EOL;
}
print PHP_EOL;
// removendo da fila
print $ingredientes->dequeue() . '<br>' . PHP_EOL;
print 'Count: ' . $ingredientes->count() . '<br>' . PHP_EOL;
print $ingredientes->dequeue() . '<br>' . PHP_EOL;
print 'Count: ' . $ingredientes->count() . '<br>' . PHP_EOL;
print $ingredientes->dequeue() . '<br>' . PHP_EOL;
print 'Count: ' . $ingredientes->count() . '<br>' . PHP_EOL;
Resultado:
Item: Peixe
Item: Sal
Item: Limão
Peixe
Count: 2
Sal
Count: 1
Limão
Count: 0
spl_stack.php
<?php
$ingredientes = new SplStack();
// acrescentando na pilha
$ingredientes->push('Peixe');
$ingredientes->push('Sal');
$ingredientes->push('Limão');
foreach ($ingredientes as $item) {
print 'Item: ' . $item . '<br>' . PHP_EOL;
}
print PHP_EOL;
// removendo da pilha
print $ingredientes->pop() . '<br>' . PHP_EOL;
print 'Count: ' . $ingredientes->count() . '<br>' . PHP_EOL;
print $ingredientes->pop() . '<br>' . PHP_EOL;
print 'Count: ' . $ingredientes->count() . '<br>' . PHP_EOL;
print $ingredientes->pop() . '<br>' . PHP_EOL;
print 'Count: ' . $ingredientes->count() . '<br>' . PHP_EOL;
Resultado:
Item: Limão
Item: Sal
Item: Peixe
Limão
Count: 2
Sal
Count: 1
Peixe
Count: 0
iter_directory_sem_spl.php
<?php
$dir = opendir('/tmp');
while ($arquivo = readdir($dir)) {
print $arquivo . '<br>' . PHP_EOL;
}
closedir($dir);
Resultado:
.
..
file_antes_spl.php
paises3.xml
novo.txt
A partir da SPL, temos o DirectoryIterator, objeto que permite percorrer
um diretório, retornando cada um dos itens. Cada item retornado
também é uma instância de DirectoryIterator, que por sua vez estende a
classe SplFileInfo, fornecendo informações sobre o arquivo por meio de
métodos já vistos, como getFileName() e getExtension().
spl_directory_iterator.php
<?php
foreach (new DirectoryIterator('/tmp') as $file) {
print (string) $file . '<br>' . PHP_EOL;
print 'Nome: ' . $file->getFileName() . '<br>' . PHP_EOL;
print 'Extensão: ' . $file->getExtension() . '<br>' . PHP_EOL;
print 'Tamanho: ' . $file->getSize() . '<br>' . PHP_EOL;
print '<br>' . PHP_EOL;
}
Resultado:
...
file_antes_spl.php
Nome: file_antes_spl.php
Extensão: php
Tamanho: 766
paises3.xml
Nome: paises3.xml
Extensão: xml
Tamanho: 596
...
Também é possível combinar diferentes iterators. No exemplo a seguir,
vamos combinar o RecursiveIteratorIterator com o
RecursiveDirectoryIterator para gerar uma lista de arquivos
recursivamente a partir de determinado diretório.
RecursiveIteratorIterator é um objeto que implementa a recursão. Você só
precisa passar o Recursive<algumacoisa>Iterator (RecursiveArrayIterator,
RecursiveTreeIterator etc.) para o construtor do RecursiveIteratorIterator
e percorrer o resultante com um foreach.
spl_recursive_directory_iterator.php
<?php
$path = '/tmp';
foreach (new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path,
RecursiveDirectoryIterator::SKIP_DOTS)) as $item)
{
print( (string) $item . '<br>' . PHP_EOL);
}
Resultado:
/tmp/file_antes_spl.php
/tmp/paises3.xml
/tmp/novo.txt
...
spl_array.php
<?php
$dados = [ 'salmão', 'tilápia', 'sardinha', 'badejo', 'pescada',
'dourado', 'corvina', 'cavala', 'bagre' ];
$objarray = new ArrayObject($dados);
// acrescenta
$objarray->append('bacalhau');
// obtém posição 2
print 'Posição 2: ' . $objarray->offsetGet(2) . '<br>' . PHP_EOL;
// substitui posição 2
$objarray->offsetSet(2, 'linguado');
print 'Posição 2: ' . $objarray->offsetGet(2) . '<br>' . PHP_EOL;
// elimina posição 4
$objarray->offsetUnset(4);
// testa se posição existe
if ($objarray->offsetExists(10)) {
echo 'Posição 10 encontrada'. '<br>' . PHP_EOL;
} else {
echo 'Posição 10 não encontrada'. '<br>' . PHP_EOL;
}
print 'Total: ' . $objarray->count();
$objarray[] = 'atum'; // acrescenta
$objarray->natsort(); // ordena
// percorre dados
print '<br>' . PHP_EOL;
foreach ($objarray as $item) {
print 'Item: '. $item . '<br>' . PHP_EOL;
}
print $objarray->serialize();
Resultado:
Posição 2: sardinha
Posição 2: linguado
Posição 10 não encontrada
Total: 9
Item: atum
Item: bacalhau
Item: badejo
Item: bagre
Item: cavala
Item: corvina
Item: dourado
Item: linguado
Item: salmão
Item: tilápia
x:i:0;a:10:
{i:10;s:4:"atum";i:9;s:8:"bacalhau";i:3;s:6:"badejo";i:8;s:5:"bagre";
i:7;s:6:"cavala";i:6;s:7:"corvina";i:5;s:7:"dourado";i:2;s:8:"linguado";
i:0;s:7:"salmão";i:1;s:8:"tilápia";};m:a:0:{}
4.6 Re ection
Re ection (Re exão) é uma API formada por um conjunto de classes que
permite que um programa possa obter informações sobre sua própria
estrutura, suas classes, interfaces, seus métodos, suas funções, entre
outras. Entre essas informações podemos descobrir as constantes, os
atributos e os métodos de uma classe, descobrir se um método é abstrato,
privado ou nal, descobrir informações sobre os parâmetros de um
método, entre outros fatores.
4.6.1 Re ectionClass
A API Re ection pode ser aplicada sobre código-fonte construído pelo
desenvolvedor, mas também sobre classes nativas do PHP, como as classes
da SPL. Para demonstrar algumas funcionalidades, vamos declarar uma
pequena classe somente para propósito didático. No código a seguir,
teremos as classes Veiculo e Automovel com alguns atributos e métodos
de nidos. O próximo passo será investigar essa classe.
veiculo.php
<?php
class Veiculo {
protected $ano;
protected $cor;
protected $marca;
protected $parts;
public function getParts() {}
public function setMarca($marca) {}
}
class Automovel extends Veiculo {
private $placa;
const RODAS=4;
public function setPlaca($placa) {}
public function getPlaca() {}
}
Para investigar determinada classe, utilizamos a classe ReflectionClass,
que oferece funcionalidades como getMethods() para retornar seus
métodos, getInterfaceNames() para retornar o nome de suas interfaces,
getConstants() para retornar suas constantes, getParentClass() para
retornar a classe pai, getProperties() para retornar suas propriedades,
entre outras. Alguns métodos retornam vetores de objetos. O método
getMethods(), por exemplo, retorna um vetor de objetos do tipo
ReflectionMethod, que por sua vez pode ser ainda mais investigado. O
método getProperties() retorna um vetor de objetos do tipo
ReflectionProperty, e getParentClass() retorna um objeto da classe
ReflectionClass. Vejamos o resultado do programa a seguir.
re ection_class.php
<?php
require_once 'veiculo.php';
$rc = new ReflectionClass('Automovel');
print_r($rc->getMethods());
print_r($rc->getProperties());
print_r($rc->getParentClass());
Resultado:
Array (
[0] => ReflectionMethod Object (
[name] => setPlaca
[class] => Automovel
)
[1] => ReflectionMethod Object ...
)
Array (
[0] => ReflectionProperty Object (
[name] => placa
[class] => Automovel
)
[1] => ReflectionProperty Object ...
)
ReflectionClass Object (
[name] => Veiculo
)
4.6.2 Re ectionMethod
Da mesma maneira que investigamos determinada classe, podemos
investigar determinado método por meio da classe ReflectionMethod, que
recebe em seu método construtor o nome da classe e do método a ser
investigado. A classe ReflectionMethod oferece métodos como getName()
para retornar o nome do método, isPrivate(), isProtected() e isPublic()
para retornar se o método é privado, protegido ou público, isStatic() para
retornar se é estático e isFinal() para retornar se é método nal, entre
outros métodos como getParameters(), que retorna os parâmetros que o
método recebe na forma de um vetor de objetos da classe
ReflectionParameter.
re ection_method.php
<?php
require_once 'veiculo.php';
$rm = new ReflectionMethod('Automovel', 'setPlaca');
print $rm->getName() . '<br>' . PHP_EOL;
print $rm->isPrivate() ? 'É privado' : 'Não é privado';
print '<br>' . PHP_EOL;
print $rm->isStatic() ? 'É estático' : 'Não é estático';
print '<br>' . PHP_EOL;
print $rm->isFinal() ? 'É final' : 'Não é final';
print '<br>' . PHP_EOL;
print_r( $rm->getParameters() );
Resultado:
setPlaca
Não é privado
Não é estático
Não é final
Array (
[0] => ReflectionParameter Object (
[name] => placa
)
)
4.6.3 Re ectionProperty
Agora vamos investigar o atributo placa da classe Automovel. Para
investigar um atributo, usaremos a classe ReflectionProperty, que recebe o
nome da classe e do atributo a ser investigado. Essa classe oferece
métodos como getName() para retornar o nome da propriedade,
isPrivate(), que indica se o atributo é privado, e isStatic(), que indica se
o atributo é estático, entre outros.
re ection_property.php
<?php
require_once 'veiculo.php';
$rm = new ReflectionProperty('Automovel', 'placa');
print $rm->getName() . '<br>' . PHP_EOL;
print $rm->isPrivate() ? 'É privado' : 'Não é privado';
print '<br>' . PHP_EOL;
print $rm->isStatic() ? 'É estático' : 'Não é estático';
print '<br>' . PHP_EOL;
Resultado:
placa
É privado
Não é estático
re ection_docs.php
<?php
$classes = spl_classes(); // lista classes da SPL
foreach ($classes as $classe) {
$rc = new ReflectionClass( $classe );
print $classe . PHP_EOL;
foreach ($rc->getMethods() as $method) {
print ' ' . $method->getName();
print '(';
$paramNames = array();
$parameters = $method->getParameters();
if (count($parameters)>0) {
foreach ($parameters as $parameter) {
if ($parameter->isOptional()) {
$paramNames[] = '[' . $parameter->getName() . ']';
} else {
$paramNames[] = $parameter->getName();
}
}
}
print implode(', ', $paramNames);
print ')';
print '<br>' . PHP_EOL;
}
}
Resultado:
AppendIterator
__construct()
append(iterator)
rewind()
valid()
key()
current()
next()
getInnerIterator()
getIteratorIndex()
getArrayIterator()
ArrayIterator
__construct(array)
offsetExists(index)
offsetGet(index)
offsetSet(index, newval)
…
4.7 Traits
O PHP implementa o conceito de herança simples, ou seja, cada classe
pode herdar características (atributos e comportamento) de apenas uma
classe, não de um conjunto de classes. A herança simples é o modelo de
herança mais simples de ser implementado, e também é amplamente
difundido e utilizado em várias outras linguagens de programação
orientadas a objetos, como o C#. O modelo de herança simples atende à
maioria das demandas de reúso de código. Entretanto, existem casos em
que alguns comportamentos (métodos) precisam ser repetidos em
diferentes classes que não compartilham a mesma superclasse. Nesses
casos, o modelo de herança simples não permite isolar comportamentos
compartilhados por diferentes classes que não compartilhem a mesma
superclasse. Isso acaba levando, muitas vezes, à duplicações de código.
Algumas linguagens de programação como C++ implementam o conceito
de herança múltipla, que permite que uma classe possa herdar
características (atributos e comportamentos) a partir de várias outras
classes. Dessa maneira, uma classe pode se bene ciar de comportamentos
originados a partir de diferentes classes. Embora a herança múltipla possa
parecer bené ca, designers de diversas linguagens, entre as quais o PHP,
decidiram manter-se longe das complexidades geradas pela
implementação da herança múltipla. De maneira geral é sabido que a
herança múltipla cria di culdades de implementação, além de permitir
problemas conceituais, como a ambiguidade, não somente quanto à
nomenclatura de métodos, mas também quanto à veri cação de tipagem
de objetos.
Para atender à necessidade de compartilhar pequenos comportamentos
entre diferentes classes, independentemente da hierarquia (superclasses),
foi implementado no PHP o conceito de Traits (traços). Um trait é formado
por um conjunto de métodos que representa uma funcionalidade que
pode ser usada por diversas classes. Uma classe pode ser construída por
meio de métodos próprios e também de comportamentos absorvidos a
partir de traits. Além disso, um trait também pode ser composto de
outros traits. Algumas pessoas chamam um trait de “método inteligente
para copy & paste”, já que ele evita a necessidade de copiar e colar um
determinado trecho de código entre diferentes classes, facilitando, dessa
forma, o reaproveitamento de uma funcionalidade.
Para serem usados, os traits devem ser incorporados a uma classe real.
Um método incorporado a partir de um trait terá precedência sobre um
método herdado. Já um método da própria classe terá precedência sobre
um método originado de um trait.
Por outro lado, o uso de traits deve ser bem analisado. Ele não substitui a
herança e nem deve ser utilizado como solução para todas as situações em
que o reaproveitamento de código se faz necessário. Aliás, talvez os traits
sejam uma solução para poucas situações em que a herança simples não
resolve satisfatoriamente. Contudo é preciso ter um bom conhecimento de
orientação a objetos antes de usar traits, para que eles não sejam usados
indiscriminadamente. O uso excessivo de traits pode fazer surgir classes
compostas a partir de muitas funcionalidades, ou seja, criarem-se classes
com responsabilidades diferentes, o que não é desejável, pois uma classe
deve ter somente uma responsabilidade.
Vejamos alguns exemplos de uso de traits. Em primeiro lugar, vamos
imaginar que todas as classes que representam os dados de nosso sistema
são lhas de uma única classe que cuida da manipulação dos registros da
base de dados. Esse é um padrão de projeto conhecido como Active
Record que será abordado mais adiante. Vamos criar várias classes, como
Pessoa, Fornecedor e Produto. Cada uma dessas classes será lha da classe
Record. A classe Record fornecerá os métodos save(), delete() e load(), que
fazem as operações básicas de salvamento, exclusão e carregamento de
registros baseados nos atributos da classe. Neste exemplo, os métodos
exibem somente mensagens com os comandos SQL em tela.
Observação: a classe Record armazenará os atributos na forma de um vetor
$data, estratégia já vista anteriormente. Para isso, será necessário usar os métodos
mágicos __set() e __get() para armazenar e retornar os atributos a partir desse
vetor sempre que forem solicitados.
classes/Record.php
<?php
class Record {
protected $data;
public function __set($prop, $value) {
$this->data[$prop] = $value;
}
public function __get($prop) {
return $this->data[$prop];
}
public function save() {
print 'INSERT INTO ' . $this::TABLENAME .
'('.implode(',', array_keys($this->data)).') VALUES '.
"('".implode("','", array_values($this->data))."')";
}
public function delete($id) {
print "DELETE FROM " . $this::TABLENAME . ' WHERE id='.$id;
}
public function load($id) {
print "SELECT * FROM " . $this::TABLENAME . ' WHERE id='.$id;
}
}
Agora vamos declarar cada uma das classes lhas de Record. Cada classe
manipulará determinada tabela, o que é de nido pela constante TABLENAME
de cada classe. Veja que instanciaremos um objeto e chamaremos o seu
método load(); em seguida, alteraremos um atributo, solicitaremos a sua
gravação e, por m, a sua exclusão. Em cada operação, o SQL
correspondente será apresentado em tela. No próximo capítulo
implementaremos esses métodos executando operações reais em bases de
dados, mas esse não é o foco no momento.
trait1.php
<?php
require_once 'classes/Record.php';
class Pessoa extends Record {
const TABLENAME = 'pessoas';
}
class Fornecedor extends Record {
const TABLENAME = 'fornecedores';
}
class Produto extends Record {
const TABLENAME = 'produtos';
}
$p = new Pessoa;
$p->load(1);
print '<br>'.PHP_EOL;
$p->nome = 'Maria da Silva';
$p->endereco = 'Rua das flores';
$p->numero = '123';
$p->save();
print '<br>'.PHP_EOL;
$p->delete(3);
print '<br>'.PHP_EOL;
Resultado:
SELECT * FROM pessoas WHERE id=1
INSERT INTO pessoas(nome,endereco,numero) VALUES ('Maria da Silva','Rua
das flores','123')
DELETE FROM pessoas WHERE id=3
Imagine que você precisa de mais funcionalidades referentes a conversões
de tipo (json, xml) para algumas classes do modelo. Digamos que você
precise desses recursos em Pessoa e Fornecedor, mas não em Produtos. Nós
podemos simplesmente adicionar tais funcionalidades à superclasse
Record. Entretanto devemos tomar cuidado de não adicionar muitas
responsabilidades a essa classe, pois sua função básica é cuidar da
gravação do objeto na base de dados. Então, nesse caso, podemos criar
um trait, que é um trecho de código que, por sua vez, pode ser
incorporado em cada classe, conforme necessário. O trait criado
(ObjectConversionTrait) fornecerá funcionalidades de conversão de tipo
que atuarão sobre o vetor de propriedades ($this->data). O método
toXML() fará a conversão para XML, enquanto toJSON() fará a conversão
para o formato JSON.
trait2.php
<?php
require_once 'classes/Record.php';
trait ObjectConversionTrait {
public function toXML() {
$data = array_flip($this->data);
$xml = new SimpleXMLElement('<'.get_class($this).'/>');
array_walk_recursive($data, array ($xml, 'addChild'));
return $xml->asXML();
}
public function toJSON() {
return json_encode($this->data);
}
}
Para acrescentar o trait à classe, basta adicionar o operador use seguido
do nome do trait no início da declaração da classe. É possível usar
diversos traits na mesma classe. Isso é como se a classe Pessoa absorvesse o
conteúdo do trait totalmente, da mesma maneira que fazemos ao copiar e
colar um trecho, só que de forma mais elegante.
class Pessoa extends Record {
const TABLENAME = 'pessoas';
use ObjectConversionTrait;
}
Agora podemos continuar executando os métodos da classe Pessoa, que
estarão acrescidos dos métodos toXML() e toJSON(), ambos importados do
trait.
$p = new Pessoa;
$p->nome = 'Maria da Silva';
$p->endereco = 'Rua das flores';
$p->numero = '123';
print $p->toXML();
print $p->toJSON();
Resultado:
<?xml version="1.0"?>
<Pessoa><nome>Maria da Silva</nome><endereco>Rua das flores</endereco>
<numero>123</numero></Pessoa>
{"nome":"Maria da Silva","endereco":"Rua das flores","numero":"123"}
Além disso, podemos “importar” um método de um trait e disponibilizá-
lo com outro nome por meio do operador as no momento da inclusão do
Trait, o que será demonstrado na classe Produto.
class Produto extends Record {
const TABLENAME = 'produtos';
use ObjectConversionTrait {
toJSON as exportToJSON;
}
}
$p2 = new Produto;
$p2->descricao = 'Chocolate';
$p2->preco = 7;
print $p2->exportToJSON();
Resultado:
{"descricao":"Chocolate","preco":7}
injecao1.php
<?php
require_once 'classes/Record.php';
class JSONExporter {
public function export($data) {
return json_encode($data);
}
}
class Pessoa extends Record {
const TABLENAME = 'pessoas';
public function toJSON() {
$je = new JSONExporter;
return $je->export($this->data);
}
}
$p = new Pessoa;
$p->nome = 'Maria da Silva';
$p->endereco = 'Rua das flores';
$p->numero = '123';
print $p->toJSON();
Resultado:
{"nome":"Maria da Silva","endereco":"Rua das flores","numero":"123"}
Para desacoplar, em vez de usar diretamente a classe, vamos criar um
método dentro da classe Pessoa chamado export(), que receberá o
algoritmo de exportação por meio de um parâmetro e irá rodar a
exportação. Mas, antes disso, vamos criar dois algoritmos de exportação:
o primeiro chamado XMLExporter, que irá exportar um vetor em XML, e
outro, JSONExporter, que irá exportar um vetor em JSON. Ambas as classes
implementarão a interface ExporterInterface, que por sua vez conterá
somente a de nição do método export().
injecao2.php
<?php
require_once 'classes/Record.php';
interface ExporterInterface {
public function export($data);
}
class XMLExporter implements ExporterInterface {
public function export($data) {
$data = array_flip($data);
$xml = new SimpleXMLElement('<record/>');
array_walk_recursive($data, array ($xml, 'addChild'));
return $xml->asXML();
}
}
class JSONExporter implements ExporterInterface {
public function export($data) {
return json_encode($data);
}
}
Vamos escrever a classe Pessoa. Essa classe terá o método export(), que
receberá um objeto por injeção de dependência. Esse método receberá um
objeto externo ($e), que obrigatoriamente precisará implementar a
interface ExporterInterface.
O método export() usará um recurso desse objeto externo, que é o
método export(). Temos a certeza de que o método export() existe no
objeto recebido, pois a interface ExporterInterface garante isso. Neste
exemplo, implementaremos a injeção por meio de uma interface. Porém a
injeção pode ser implementada no método construtor e também por meio
do uso de um método setter para armazenar a instância externa em uma
propriedade.
class Pessoa extends Record {
const TABLENAME = 'produtos';
public function export(ExporterInterface $e) {
return $e->export($this->data);
}
}
Agora vamos criar um objeto e exportá-lo para dois formatos: XML e JSON.
Primeiro, de niremos alguns atributos. Para exportar em XML, basta
executarmos o método export() passando a instância de XMLExporter. Em
seguida, rodaremos novamente o método export(), mas dessa vez
passando a instância de JSONExporter para exportar em JSON. Veja que
nesse cenário a de nição do algoritmo, ou seja, de qual classe irá fazer a
exportação dos dados, está totalmente nas mãos do usuário, o que facilita
o controle das ações e permite que ele crie novos algoritmos de
exportação conforme sua necessidade.
$p = new Pessoa;
$p->nome = 'Maria da Silva';
$p->endereco = 'Rua das flores';
$p->numero = '123';
print $p->export( new XMLExporter );
print $p->export( new JSONExporter );
Resultado:
<?xml version="1.0"?>
<record><nome>Maria da Silva</nome><endereco>Rua das flores</endereco>
<numero>123</numero></record>
{"nome":"Maria da Silva","endereco":"Rua das flores","numero":"123"}
4.10 Namespaces
Quando o PHP surgiu, não existiam classes. Depois de algumas versões
foram criadas as classes e os programadores começaram a criar códigos
orientados a objetos em PHP. No PHP3 já era possível declarar classes e
criar objetos a partir delas. Entretanto, naquela época, o suporte à
orientação a objetos no PHP não era robusto, e os objetos eram tratados
internamente como vetores associativos. Apesar de ter suporte à
orientação a objetos, a maioria ainda programava de maneira
estruturada, declarando funções e usando os comandos include/require
para incluir arquivos e reaproveitar código.
O PHP4 proporcionou melhorias, mas foi o PHP5 que causou uma
revolução. No PHP5 foram introduzidas grandes funcionalidades, como
passagem de objetos por referência, métodos mágicos, interfaces, classes e
métodos abstratos, visibilidade, __construct() e __destruct(), autoload,
static, exceções, SimpleXML, SPL, entre outras. Assim, a comunidade
PHP passou a olhar com outros olhos a orientação a objetos, e
começaram a orescer projetos OO.
Muitas bibliotecas surgiram nessa época. Os principais frameworks
também começaram a se desnvolver a partir do PHP5. Muitas classes
foram criadas, e começaram a se produzir repositórios como o PEAR e o
PHPClasses. Porém os projetos não seguiam uma determinada
padronização que permitisse utilizá-los de maneira consistente. Isso
signi ca que não havia padronização para nomenclatura de classes,
estruturação de diretórios, e assim por diante. Cada projeto era feito de
um jeito. Isso começou a mudar com a criação das primeiras PSRs, como
visto anteriormente. No entanto foi o advento do PHP5.3 que solidi cou
de vez a orientação a objetos no PHP com o surgimento dos namespaces.
Mas o que é exatamente um namespace?
Ao criar uma grande aplicação orientada a objetos, nos deparamos com
situações inusitadas. Imagine que você tenha uma biblioteca de
componentes (classes) para montagem de telas com classes como Form
(formulário), Field (campo), Datagrid (listagem), e assim por diante. Em
seguida, imagine que você tenha que desenvolver um software para
gerenciar questionários eletrônicos. Nesse caso, na modelagem, surgem
conceitos como Question (questão), Form (formulário), Answer (resposta),
entre outros. Veja que temos classes com nomes iguais (Form), mas com
funcionalidades diferentes. A primeira Form representa o componente de
montagem de tela (formulário). A segunda Form é uma classe do modelo
de negócios e representa um formulário do mundo real que será
preenchido e armazenado no banco de dados.
Não podíamos ter duas classes com o mesmo nome até o PHP5.2. Para
resolver esse problema na época, alterávamos o nome de uma das duas
classes ou adicionávamos um caractere na frente para identi car um
conjunto de classes com a mesma característica.
Problema similar ocorria quando os programadores evoluíam o código ou
tentavam combinar códigos de diferentes fornecedores. Certa vez, vi em
uma determinada empresa a seguinte situação: a princípio, tinha sido
criada uma classe chamada Form para representar formulários. Depois de
alguns anos, novos programadores criaram uma classe melhorada para
formulários e, para manter a compatibilidade com os códigos já
existentes, resolveram criar a Form2. Não demorou muito até surgir a Form3.
Não é preciso ser um guru para entender que essa não é uma boa prática
de programação. Outra situação ocorria ao se tentar combinar códigos de
diferentes fornecedores de software. Imagine que usamos uma classe
chamada Mail do fornecedor Mega. E depois de alguns anos surgiu outra
classe chamada Mail do fornecedor Master. Não podíamos usar sem
renomear pelo menos uma das duas classes adicionando um pre xo ou
um su xo. Assim, era comum termos no nal classes como Mega_Mail e
Master_Mail para criar a diferenciação.
Com o advento do PHP5.3 surge o conceito de namespaces ou “espaços de
nome”. Um namespace nada mais é do que uma separação lógica, ou seja,
um encapsulamento ao redor de nomes de classes, interfaces, funções e
constantes. Namespaces permitem que se criem classes com o mesmo
nome, porém fazendo parte de espaços separados ou de diferentes
namespaces. Um namespace cria um isolamento de nomes. Dessa forma,
poderíamos ter a primeira classe Form (que representa o formulário a ser
preenchido) no namespace \Application\Model\Form, já que se trata de uma
classe de modelo, e a segunda classe Form (que representa o componente
de interface) no namespace \Lib\Components\Form. Outra analogia que
podemos fazer é a de um sistema de arquivos. Um namespace funciona
como a separação de diretórios. Assim, é possível haver classes com o
mesmo nome em namespaces diferentes, como pode haver arquivos com o
mesmo nome, mas em diretórios diferentes.
No PHP, desde os primórdios do uso da orientação a objetos, já se
costumava gravar uma classe por arquivo físico. No início era muito
comum termos simplesmente um diretório /classes contendo um arquivo
para cada classe do sistema. Mas como fazemos em relação aos
namespaces? É importante destacar que namespaces são separações
lógicas, reconhecidas “em memória”. Porém, por convenção, a maioria dos
projetos e também a PSR utilizam uma diferenciação na estrutura de
diretórios. Então, para facilitar a compreensão, a estrutura de diretórios
será bastante similar à estrutura de um Namespace. O exemplo a seguir
mostra que a classe Field, que faz parte do Namespace
\Livro\Widgets\Form, estará sicamente localizada no diretório
Lib/Livro/Widgets/Form. Nesse caso, Lib/ é o diretório-base e o restante do
namespace corresponde ao caminho relativo do arquivo. Por exemplo,
classe \Livro\Widgets\Form\Field => Lib/Livro/Widgets/Form/Field.php.
Apesar de ser possível, não é recomendado declarar dois namespaces no
mesmo arquivo. No exemplo, vamos utilizar dessa forma somente para
uma breve explicação. Neste arquivo vamos declarar um namespace
Application contendo a declaração da classe Form. Logo a seguir,
declararemos o namespace Components, também contendo a classe Form.
Por m, executaremos alguns var_dump() para testar. No primeiro caso
(exemplo 1), ainda estaremos dentro do namespace Components, que foi o
último a ser declarado, por isso o var_dump() indicará
object(Components\Form) na saída do debug. No segundo e no terceiro caso
(exemplos 2 e 3), referenciaremos o nome completo do namespace com a
classe, iniciando em \. Nesse caso, não dependeremos de estar dentro de
determinado namespace, pois acessaremos a partir do escopo principal \.
Por m, instanciaremos a classe nativa do PHP SplFileInfo. No quarto
caso (exemplo 4), conseguiremos instanciar essa classe porque
utilizaremos o caminho completo. No quinto caso (exemplo 5), teremos
um erro, pois, como é considerado que a execução está dentro do último
namespace (Components), não será possível encontrar uma classe chamada
SplFileInfo dentro do namespace atual.
namespace1.php
<?php
// declaração
namespace Application;
class Form {}
namespace Components;
class Form {}
// utilização
var_dump(new Form ); // Ex 1: object(Components\Form)
var_dump(new \Components\Form ); // Ex 2: object(Components\Form)
var_dump(new \Application\Form ); // Ex 3: object(Application\Form)
var_dump(new \SplFileInfo('/etc/shaddow') ); // Ex 4: object(SplFileInfo)
var_dump(new SplFileInfo('/etc/shaddow') ); // Ex 5: Fatal error: Class
'Components\SplFileInfo'
Resultado:
object(Components\Form)#1 (0) { }
object(Components\Form)#1 (0) { }
object(Application\Form)#1 (0) { }
object(SplFileInfo)#1 (2) {
["pathName":"SplFileInfo":private]=> string(12) "/etc/shaddow"
["fileName":"SplFileInfo":private]=> string(7) "shaddow"
}
Fatal error: Class 'Components\SplFileInfo' not found in
Para dar sequência, vamos organizar melhor, mas não muito, nossas
classes. Isso porque vamos deixar a melhor forma de organização para o
nal. Neste caso, vamos de nir as classes Form e Field, ambas do
namespace Application, no arquivo a.php.
Observação: é importante dizer que não é uma boa prática declarar mais de
uma classe no mesmo arquivo; vamos corrigir esse equívoco mais adiante.
a.php
<?php
namespace Application;
class Form
{
}
class Field
{
}
E também vamos de nir as classes Form e Field, ambas do namespace
Components, no arquivo b.php.
Observação: é importante dizer que não é uma boa prática declarar mais de
uma classe no mesmo arquivo; vamos corrigir esse equívoco mais adiante.
b.php
<?php
namespace Components;
class Form
{
}
class Field
{
}
Como já temos os arquivos de nidos, vamos criar um terceiro arquivo
importando o a.php. Para usar a classe Application\Form, utilizaremos o
operador use. O que esse operador basicamente faz é dizer para o PHP:
“Use aquela classe daquele namespace”. A partir desse ponto compreende-
se simplesmente que Form na verdade deve ser assimilada a partir de
Application\Form. Veja que o segundo var_dump() resultará em erro. Isso
ocorre porque não usaremos o comando use, e o PHP buscará a classe
Field a partir do escopo global \, onde ela não existe.
c.php
<?php
require_once 'a.php';
use Application\Form;
var_dump( new Form ); // object(Application\Form)
var_dump( new Field ); // Fatal error: Class 'Field'
Resultado:
object(Application\Form)#1 (0) { }
Fatal error: Class 'Field' not found in ...
Agora vamos utilizar os dois namespaces criados ao mesmo tempo. Para
isso, vamos requisitar os arquivos a.php e b.php. Em seguida, usaremos o
operador use para referenciar um determinado recurso de um namespace
qualquer. O comando use basicamente “importará” o recurso externo para
dentro do escopo atual. Ele funcionará estabelecendo uma espécie de
link. Quando usarmos use Application\Form as Form, estaremos dizendo
para usar a classe Application\Form de modo local como sendo
simplesmente Form. É importante notar que a segunda parte do operador
as Form será opcional e, por default, usará o próprio nome da classe.
Assim, importaremos as classes Form e Field do namespace Application
para utilizar no escopo do programa d.php, que não terá um namespace
de nido.
Logo em seguida, usaremos a classe Components\Form. Não poderemos usar
simplesmente o comando use Components\Form ou use Components\Form as
Form, pois haverá uma classe com esse nome de nida. Precisaremos de nir
um novo alias (apelido) para a classe, que neste caso será ComponentForm.
Poderemos usar as duas classes Form usando aliases diferentes ou
referenciando o seu nome completo no operador new, o que será
demonstrado nas duas linhas nais.
d.php
<?php
require_once 'a.php';
require_once 'b.php';
use Application\Form as Form;
use Application\Field as Field;
var_dump( new Form ); // object(Application\Form)
var_dump( new Field ); // object(Application\Field)
use Components\Form as ComponentForm;
var_dump( new ComponentForm ); // object(Components\Form)
var_dump( new Application\Form );
var_dump( new Components\Form );
Resultado:
object(Application\Form)#1 (0) { }
object(Application\Field)#1 (0) { }
object(Components\Form)#1 (0) { }
object(Application\Form)#1 (0) { }
object(Components\Form)#1 (0) { }
Como vimos nos exemplos anteriores, declarar mais de uma classe por
arquivo não é uma boa prática. Então, vamos modi car um pouco nossos
exemplos para separar melhor as coisas. Vamos criar o arquivo a1.php, que
irá conter somente a classe Field do namespace Library\Widgets.
Observação: é importante dizer que não é uma boa prática dar um nome para o
arquivo diferente do nome da classe. Vamos corrigir esse equívoco mais adiante.
a1.php
<?php
namespace Library\Widgets;
class Field
{
}
Em outro arquivo b1.php vamos criar a classe Table do namespace
Library\Container.
b1.php
<?php
namespace Library\Container;
class Table
{
}
Em seguida, vamos criar o arquivo c1.php contendo a classe Form do
namespace Library\Widgets. Devemos prestar atenção em alguns detalhes.
A classe Form contém o método fazAlgo(), que recebe um parâmetro da
classe Field. Como a classe Field é exatamente do mesmo namespace que
a classe Form, ela será reconhecida. Mas, caso precisemos utilizar uma
classe de outro namespace, teremos de importá-la por meio do operador
use. Neste exemplo, precisaremos da classe Table, que é parte do
namespace Library\Container. Caso não utilizássemos o use
Library\Container\Table no início, na medida em que tivéssemos de
utilizar essa classe pela primeira vez, teríamos um erro do tipo Fatal error:
Class ‘Library\Widgets\Table’, pois o PHP tentaria buscá-la no namespace atual,
que é Library\Widgets. O mesmo ocorre com as classes nativas do PHP,
como é o caso da SplFileInfo. Se não tivéssemos utilizado o comando use
SplFileInfo para importar essa classe, teríamos o erro “Fatal error: Class
‘Library\Widgets\SplFileInfo’ not found”. Outra maneira de evitar esse problema é
utilizar new \SplFileInfo(...). Ao referenciarmos o namespace absoluto, é
dispensada a utilização do use.
c1.php
<?php
namespace Library\Widgets;
use Library\Container\Table;
use SplFileInfo;
class Form {
public function fazAlgo( Field $x )
{ }
public function show() {
new Table;
new SplFileInfo('/tmp/shadow');
}
}
Agora temos um novo exemplo em que utilizaremos as classes de nidas
anteriormente. A princípio, usaremos o require_once para importar os
arquivos. Em seguida, utilizaremos o operador use para importar as
classes por meio de seu nome quali cado (completo). Então, criaremos
objetos a partir dessas classes para veri car por meio do var_dump() qual
classe internamente o PHP instanciou de fato.
d1.php
<?php
require_once 'a1.php';
require_once 'b1.php';
require_once 'c1.php';
use Library\Widgets\Field;
use Library\Widgets\Form;
use Library\Container\Table;
var_dump(new Field); // object(Library\Widgets\Field)
var_dump(new Form); // object(Library\Widgets\Form)
var_dump(new Table); // object(Library\Container\Table)
$f = new Form;
$f->show();
Resultado:
object(Library\Widgets\Field)#1 (0) { }
object(Library\Widgets\Form)#1 (0) { }
object(Library\Container\Table)#1 (0) { }
Como pode-se ver nos exemplos anteriores, não tomamos muito cuidado
com o arquivo no qual cada classe seria salvo. Neste exemplo vamos
mostrar como escolher o nome e o local mais apropriados para
determinada classe.
Em primeiro lugar o nome do arquivo deve ser idêntico ao nome da classe
acrescido da extensão .php. Além disso, a classe em questão deve estar
dentro de uma estrutura de diretórios que represente o namespace. Neste
exemplo, a classe proposta Field está no namespace Library\Widgets.
Portanto a localização física nal do arquivo será Library/Widgets/Field.php.
Além disso, toda a estrutura poderia estar dentro de um diretório-base
como Fornecedor/Library/Widgets/. Usaremos essa abordagem nos capítulos
seguintes.
Library/Widgets/Field.php
<?php
namespace Library\Widgets;
class Field
{
}
Para carregar uma classe a partir de um caminho de namespace, vamos
registrar um método de autoloading por meio da função
spl_autoload_register(). A função spl_autoload_register() é responsável
por de nir um algoritmo de carga de classes, ou seja, ela indica para o
PHP como as classes serão carregadas a partir de seu nome. É importante
notar que podemos registrar várias funções de autoloading formando uma
“pilha” de funções. Neste exemplo, registraremos uma função anônima
que irá carregar o arquivo a partir do nome da classe. Assim, sempre que
uma classe for requisitada (por exemplo, quando for executado o new), a
função registrada será executada, recebendo o nome da classe como
parâmetro. Então ela vai substituir as barras do nome do namespace pela
barra de diretórios e acrescentar “.php” ao nal.
x.php
<?php
spl_autoload_register(function ($class) {
require_once(str_replace('\\', '/', $class . '.php'));
});
use Library\Widgets\Field;
var_dump( new Field );
Resultado:
object(Library\Widgets\Field)#2 (0) { }
spl_autoload1.php
<?php
spl_autoload_register( function($class) {
if (file_exists("App/{$class}.php")) {
require_once "App/{$class}.php";
return TRUE;
}
} );
var_dump(new Pessoa);
No próximo exemplo, vamos usar uma abordagem um pouco diferente.
Em vez de utilizar uma função anônima, vamos registrar um método de
uma determinada classe. Neste caso, vamos registrar dois métodos de
carga: LibraryLoader::loadClass(), que buscará por classes no diretório
Lib/, e ApplicationLoader::loadClass(), que buscará por classes no diretório
App/. Caso o primeiro método registrado encontre a classe, esta será
requisitada (require_once) e o segundo método de carga na la não será
executado. É comum em alguns frameworks existir o autoloader das
classes internas do framework, tais como componentes, bem como o
autoloader das classes criadas pelo usuário (aplicação). Este exemplo
tenta demonstrar essa característica.
spl_autoload2.php
<?php
spl_autoload_register(array(new LibraryLoader, 'loadClass'));
spl_autoload_register(array(new ApplicationLoader, 'loadClass'));
class LibraryLoader {
public static function loadClass($class) {
if (file_exists("Lib/{$class}.php")) {
require_once "Lib/{$class}.php";
return TRUE;
}
}
}
class ApplicationLoader {
public function loadClass($class) {
if (file_exists("App/{$class}.php")) {
require_once "App/{$class}.php";
return TRUE;
}
}
}
Por m, temos uma variação do exemplo anterior, em que solicitamos que
determinada classe registre ela mesma o seu método de carregamento de
classes. Neste caso, instanciaremos a classe de carga ApplicationLoader e
solicitaremos o método register(). O método register(), por sua vez, irá
executar a função spl_autoload_register() para registrar o método $this-
>loadClass() como autoloader. Essa abordagem é interessante, pois
permite que a classe de carregamento altere não somente o algoritmo de
carga, mas também o próprio nome do método de carga (loadClass) sem
interferir na chamada externa (register).
spl_autoload3.php
<?php
$al = new ApplicationLoader;
$al->register();
class ApplicationLoader {
public function register() {
spl_autoload_register(array($this, 'loadClass'));
}
public function loadClass($class) {
if (file_exists("App/{$class}.php")) {
require_once "App/{$class}.php";
return TRUE;
}
}
}
4.12 Composer
Quando construímos um projeto, quase sempre precisamos integrar
bibliotecas de terceiros, seja para gerar grá cos, códigos de barras, notas
scais, entre várias outras tarefas. Antes do PHP5, havia repositórios de
pacotes como PEAR e PHPClasses, que eram alimentados por
contribuições da comunidade. Entretanto os projetos dentro desses
repositórios nem sempre seguiam um padrão de organização, tanto de
diretórios como de nomes de classes e métodos.
A criação da PSR e a adoção de namespaces em PHP permitiram a
construção de repositórios de pacotes mais organizados com um padrão
único. Essas técnicas permitiram também o surgimento do Composer,
um gerenciador de pacotes e dependências para PHP, lançado em 2012.
O Composer permite integrar bibliotecas de terceiros em nossos projetos
de maneira transparente. O Composer tem um aplicativo em linha de
comando que instala pacotes e suas dependências, disponibilizando-os
para a aplicação. Esses pacotes estão armazenados no Packagist, que é um
repositório. O primeiro passo para utilizar o Composer é instalá-lo
seguindo as instruções do site getcomposer.org.
Após a instalação do Composer, para instalar pacotes por linha de
comando, você pode seguir o exemplo a seguir para instalar a PHPMailer
e o DomPdf.
php composer.phar require phpmailer/phpmailer
php composer.phar require dompdf/dompdf
Estes comandos instalarão os pacotes em um diretório vendor e criarão o
arquivo composer.json, que contém a receita de instalação, com o seguinte
conteúdo:
composer.json
{
"require": {
"phpmailer/phpmailer": "^6.0",
"dompdf/dompdf": "^0.8.2"
}
}
Você verá, dentro do diretório vendor, as bibliotecas instaladas. Outra
maneira de instalá-las é criando o arquivo composer.json manualmente.
Após a criação do arquivo, com a declaração dos pacotes, para instalar as
bibliotecas bastaria rodar:
php composer.phar install
A partir do momento em que temos as bibliotecas instaladas, podemos
fazer uso imediato delas. Para isso, é necessário usar os seguintes
comandos em qualquer script PHP para que estas classes possam ser
encontradas pelo autoloader. Com isso, se pressupõe que o script esteja
um nível acima da pasta vendor.
$loader = require 'vendor/autoload.php';
$loader->register();
Exemplos de outras bibliotecas úteis para o desenvolvimento de
aplicações:
• Geração de NFE
php composer.phar nfephp-org/sped-nfe
• Remessa e retorno CNAB
php composer.phar andersondanilo/cnab_php
• Geração de QRCode
php composer.phar bacon/bacon-qr-code
• Geração de Barcode
php composer.phar picqer/php-barcode-generator
O homem está sempre disposto a negar tudo aquilo que não compreende.
B P
Ao programarmos de maneira orientada a objetos, nossa aplicação deve
ser formada exclusivamente por um conjunto de objetos relacionados.
Quando trabalhamos com um conjunto de objetos em memória, em
algum momento precisamos manter esses objetos na base de dados, ou
seja, armazená-los e permitir que sejam posteriormente carregados a
partir do banco de dados para a memória. Pensando nisso, estudaremos
as técnicas mais utilizadas para persistência de objetos em bases de dados
relacionais, assim como criaremos uma API orientada a objetos que irá
permitir que façamos tudo isso de forma transparente, sem nos
preocuparmos com os detalhes internos de implementação.
5.1 Introdução
De modo geral, persistência signi ca continuar a existir, perseverar, durar
longo tempo ou permanecer. No contexto de uma aplicação de negócios,
em que temos objetos representando as mais diversas entidades a serem
manipuladas (pessoas, mercadorias, livros, clientes, arquivos etc.),
persistência signi ca a possibilidade de esses objetos existirem em um
meio externo à aplicação que os criou, de modo que esse meio deve
permitir que o objeto perdure, ou seja, não deve ser um meio volátil. Os
bancos de dados relacionais são o meio mais utilizado para isso (embora
não seja o único). Com o auxílio de mecanismos so sticados especí cos
de cada fornecedor, esses bancos de dados oferecem vários recursos que
permitem armazenar e manipular, por meio da linguagem SQL, os dados
neles contidos.
Os bancos de dados relacionais surgiram na década de 1970, quando o
padrão no desenvolvimento de aplicações era o estruturado. Nas últimas
décadas, com a crescente adoção da orientação a objetos no
desenvolvimento de aplicações, surgiram os bancos de dados orientados a
objetos. Apesar disso, sua adoção ainda é muito pequena, em parte, por
causa do desempenho inferior, se comparado aos bancos de dados
relacionais que dominam amplamente o mercado e são utilizados há
muito mais tempo, fornecendo ferramentas mais testadas, aceitas e
maduras. Sendo assim, os bancos de dados relacionais são amplamente
utilizados para armazenar objetos.
Entretanto há diversos conceitos na orientação a objetos para os quais o
modelo relacional simplesmente não oferece suporte, então temos uma
diferença de conceito. Os bancos de dados foram construídos para
armazenar dados, ao passo que, no paradigma orientado a objetos, além
de dados (atributos), temos comportamentos (métodos) e outros
relacionamentos mais complexos. Dessa forma, é necessário utilizar
alguma ferramenta de compatibilidade entre os dois modelos que
geralmente implementa alguma técnica de mapeamento objeto-relacional.
Tais técnicas objetivam a persistência, ou seja, o armazenamento de
objetos em bancos de dados relacionais.
Para realizar este trabalho de armazenar um objeto de negócios em um
banco de dados, também conhecido como mapeamento objeto-relacional,
há diversas técnicas (design patterns) e também alguns frameworks que
desempenham tal papel de forma automatizada.
Um dos grandes motivos para o sucesso dos bancos de dados relacionais é
a utilização do SQL, uma linguagem padronizada para acesso e
manipulação de dados. Apesar da grande utilização da linguagem SQL no
meio corporativo, muitos programadores ainda fazem uso errado dessa
linguagem pelo fato de não a entenderem totalmente ou por fazerem uso
incorreto de seus recursos, o que leva a problemas na de nição das
consultas e consequentes problemas de desempenho e manutenibilidade.
Por essas e outras razões é recomendável separar acesso e manipulação
dos dados via linguagem SQL do domínio de negócios da aplicação,
distribuindo essa parte em diferentes classes da aplicação e constituindo
uma camada de acesso aos dados.
A seguir, estudaremos algumas das técnicas (design patterns) utilizadas
para equalizar as diferenças entre os bancos de dados relacionais e o
modelo de objetos durante a etapa de mapeamento.
Para demonstrar os próximos exemplos deste capítulo, criaremos um
banco de dados simples utilizando o SQLite, localizado em
database/estoque.db. A seguir é apresentado o SQL para criação do banco:
CREATE TABLE produto (id integer PRIMARY KEY NOT NULL,
descricao text,
estoque float,
preco_custo float,
preco_venda float,
codigo_barras text,
data_cadastro date,
origem char(1));
5.2 Gateways
Como vimos anteriormente, em algum momento precisaremos armazenar
as informações de nossa aplicação em um meio externo, como um banco
de dados relacional. Para isso, precisaremos construir uma interface que
mantenha o acesso mais transparente possível a esse recurso externo.
Há diversas formas de organizar classes para acesso aos dados. A forma
mais simples é ter uma classe para manipular o acesso a cada tabela do
banco de dados, o que chamamos de gateway. Um gateway é uma
interface que se comunica com um recurso externo escondendo seus
detalhes. Assim a aplicação só precisará conhecer essa interface para
manipular as informações. O acesso aos dados via linguagem SQL, por
exemplo, ca contido nessa camada. Dessa forma evitamos ter a
manipulação de dados espalhada pelo código-fonte da aplicação, podendo
inclusive estruturar a equipe de desenvolvimento para que os mais
talentosos em SQL sejam responsáveis pelas classes de acesso aos dados.
Existem alguns gateways que implementam o acesso às estruturas de
dados.
A seguir estudaremos alguns dos mais utilizados, que são Table Data
Gateway, Active Record e Data Mapper.
classes/tdg/ProdutoGateway.php
<?php
class ProdutoGateway {
private static $conn;
public static function setConnection( PDO $conn ) {
self::$conn = $conn;
}
public function find($id, $class = 'stdClass') {
$sql = "SELECT * FROM produto where id = '$id' ";
print "$sql <br>\n";
$result = self::$conn->query($sql);
return $result->fetchObject($class);
}
public function all($filter, $class = 'stdClass') {
$sql = "SELECT * FROM produto ";
if ($filter) {
$sql .= "where $filter";
}
print "$sql <br>\n";
$result = self::$conn->query($sql);
return $result->fetchAll(PDO::FETCH_CLASS, $class);
}
public function delete($id) {
$sql = "DELETE FROM produto where id = '$id' ";
print "$sql <br>\n";
return self::$conn->query($sql);
}
public function save($data) {
if (empty($data->id)) {
$id = $this->getLastId() +1;
$sql = "INSERT INTO produto (id, descricao, estoque, preco_custo,
".
" preco_venda, codigo_barras, data_cadastro, origem)"
.
" VALUES ('{$id}', " .
"'{$data->descricao}', " .
"'{$data->estoque}', " .
"'{$data->preco_custo}', " .
"'{$data->preco_venda}', " .
"'{$data->codigo_barras}', " .
"'{$data->data_cadastro}', " .
"'{$data->origem}')";
}
else {
$sql = "UPDATE produto SET descricao = '{$data->descricao}', " .
" estoque = '{$data->estoque}', " .
" preco_custo = '{$data->preco_custo}', " .
" preco_venda = '{$data->preco_venda}', ".
" codigo_barras = '{$data->codigo_barras}', ".
" data_cadastro = '{$data->data_cadastro}', ".
" origem = '{$data->origem}' ".
"WHERE id = '{$data->id}'";
}
print "$sql <br>\n";
return self::$conn->exec($sql); // executa instrução SQL
}
private function getLastId() {
$sql = "SELECT max(id) as max FROM produto";
$result = self::$conn->query($sql);
$data = $result->fetch(PDO::FETCH_OBJ);
return $data->max;
}
}
Agora vamos escrever um exemplo de uso da classe ProdutoGateway. Neste
exemplo, primeiro importamos a classe para uso. Em seguida, declaramos
dois objetos planos (stdClass), $data1 e $data2, contendo os dados do
produto a ser gravado. Como esses objetos não têm ID, serão inseridos.
Em seguida, abrimos uma conexão com a base de dados (new PDO) e
passamos essa conexão para o gateway (setConnection). Utilizamos o
método save() para gravar cada um dos objetos. Usamos o método find()
para buscar um objeto, alterar seu estoque e armazená-lo novamente com
o save(), que desta vez procederá com um UPDATE. Por m, adotamos o
método all() com um ltro para localizar todos os objetos com estoque
inferior ou igual a dez unidades.
exemplo_tdg.php
<?php
require_once 'classes/tdg/ProdutoGateway.php';
$data = new stdClass;
$data->descricao = 'Vinho Brasileiro Tinto Merlot';
$data->estoque = 8;
$data->preco_custo = 12;
$data->preco_venda = 18;
$data->codigo_barras = '13523453234234';
$data->data_cadastro = date('Y-m-d');
$data->origem = 'N';
try {
$conn = new PDO('sqlite:database/estoque.db');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
ProdutoGateway::setConnection($conn);
$gw = new ProdutoGateway;
$gw->save($data);
$produto = $gw->find(1);
$produto->estoque += 2;
$gw->save($produto );
foreach ($gw->all("estoque<=10") as $produto){
print $produto->descricao . "<br>\n";
}
}
catch (Exception $e) {
print $e->getMessage();
}
Resultado:
INSERT INTO produto (id, descricao, estoque, preco_custo, preco_venda,
codigo_barras, data_cadastro, origem) VALUES ('1', 'Vinho Brasileiro
Tinto Merlot', '8', '12', '18', '13523453234234', '2018-03-31', 'N')
SELECT * FROM produto where id = '1'
UPDATE produto SET descricao = 'Vinho Brasileiro Tinto Merlot', estoque
= '10', preco_custo = '12.0', preco_venda = '18.0', codigo_barras =
'13523453234234', data_cadastro = '2018-03-31', origem = 'N' WHERE id
= '1'
SELECT * FROM produto where estoque<=10
Vinho Brasileiro Tinto Merlot
Agora que já construímos o nosso Table Data Gateway e já validamos o
seu funcionamento, podemos ir além e criar um objeto de domínio. Um
Table Data Gateway não foi feito para ser utilizado diretamente pela
aplicação, pois seu papel é somente fazer o transporte dos dados. Nossa
aplicação precisa de um objeto de domínio que, além de transportar os
dados, ofereça também método de negócio, que é um método usado pela
aplicação para executar os processos de negócio. Neste exemplo criaremos
a classe Produto. A classe Produto armazenará seus atributos em um vetor
de propriedades $this->data. Já abordamos essa técnica no capítulo 2. A
classe Produto também terá um método setConnection(), um método para
receber a conexão ativa com a base de dados. Esse método também
repassará a conexão para o gateway. Em seguida, a classe Produto terá
métodos como find(), all(), delete() e save() para realizar a persistência
de seus dados. Note que esses métodos apenas repassam os dados do
objeto Produto para a camada de persistência ProdutoGateway. Isso ca claro
no método save(), que somente repassa o vetor $this->data para a camada
de persistência. Além dos métodos de persistência, a classe Produto
também contém métodos de negócio como getMargemLucro(), que retorna
o percentual de margem de lucro, e registraCompra(), que altera alguns
atributos com base em uma compra efetuada. Note que, enquanto a
classe ProdutoGateway contém somente métodos de persistência, a classe
Produto também contém métodos de negócio. A gura 5.2 representa a
classe de domínio Produto utilizando ProdutoGateway.
classes/tdg/Produto.php
<?php
class Produto {
private static $conn;
private $data;
function __get($prop) {
return $this->data[$prop];
}
function __set($prop, $value) {
$this->data[$prop] = $value;
}
public static function setConnection( PDO $conn ) {
self::$conn = $conn;
ProdutoGateway::setConnection($conn);
}
public static function find($id) {
$gw = new ProdutoGateway;
return $gw->find($id, 'Produto');
}
public static function all($filter = '') {
$gw = new ProdutoGateway;
return $gw->all($filter, 'Produto');
}
public function delete() {
$gw = new ProdutoGateway;
return $gw->delete($this->id);
}
public function save() {
$gw = new ProdutoGateway;
return $gw->save( (object) $this->data);
}
public function getMargemLucro() {
return (($this->preco_venda-$this->preco_custo) / $this-
>preco_custo) * 100;
}
public function registraCompra($custo, $quantidade) {
$this->custo = $custo;
$this->estoque += $quantidade;
}
}
Agora que já temos nosso objeto de domínio formado, vamos simular
uma aplicação que o utiliza. O próximo exemplo inicia com a importação
das classes necessárias. Em seguida, abrimos a conexão (new PDO) com a
base de dados. O exemplo inicia com a chamada do método all(), que
retorna todos os objetos já inseridos na tabela. Em seguida, em um
foreach, excluímos todos eles. Posteriormente, instanciamos os objetos $p1
e $p2 para então salvá-los no banco pelo método save(). Por m, usamos o
find() para buscar um objeto, exibimos sua descrição, sua margem de
lucro, e registramos uma compra com o método registraCompra() para
então armazená-lo novamente com o método save().
exemplo_tdg2.php
<?php
require_once 'classes/tdg/Produto.php';
require_once 'classes/tdg/ProdutoGateway.php';
try {
$conn = new PDO('sqlite:database/estoque.db');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Produto::setConnection($conn);
$produtos = Produto::all();
foreach ($produtos as $produto) {
$produto->delete();
}
$produto = new Produto;
$produto->descricao = 'Vinho Brasileiro Tinto Merlot';
$produto->estoque = 10;
$produto->preco_custo = 12;
$produto->preco_venda = 18;
$produto->codigo_barras = '13523453234234';
$produto->data_cadastro = date('Y-m-d');
$produto->origem = 'N';
$produto->save();
$outro = Produto::find(1);
print 'Descrição: ' . $outro->descricao . "<br>\n";
print 'Lucro: ' . $outro->getMargemLucro() . "% <br>\n";
$outro->registraCompra(14, 5);
$outro->save();
}
catch (Exception $e) {
print $e->getMessage();
}
Resultado:
SELECT * FROM produto
DELETE FROM produto where id = '1'
INSERT INTO produto (id, descricao, estoque, preco_custo, preco_venda,
codigo_barras, data_cadastro, origem) VALUES ('1', 'Vinho Brasileiro
Tinto Merlot', '10', '12', '18', '13523453234234', '2018-03-31', 'N')
SELECT * FROM produto where id = '1'
Descrição: Vinho Brasileiro Tinto Merlot
Lucro: 50%
UPDATE produto SET descricao = 'Vinho Brasileiro Tinto Merlot', estoque
= '15', preco_custo = '12.0', preco_venda = '18.0', codigo_barras =
'13523453234234', data_cadastro = '2018-03-31', origem = 'N' WHERE id
= '1'
exemplo_ar.php
<?php
require_once 'classes/ar/Produto.php';
try {
$conn = new PDO('sqlite:database/estoque.db');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Produto::setConnection($conn);
$produtos = Produto::all();
foreach ($produtos as $produto) {
$produto->delete();
}
$produto = new Produto;
$produto->descricao = 'Vinho Brasileiro Tinto Merlot';
$produto->estoque = 10;
$produto->preco_custo = 12;
$produto->preco_venda = 18;
$produto->codigo_barras = '13523453234234';
$produto->data_cadastro = date('Y-m-d');
$produto->origem = 'N';
$produto->save();
$outro = Produto::find(1);
print 'Descrição: ' . $outro->descricao . "<br>\n";
print 'Lucro: ' . $outro->getMargemLucro() . "% <br>\n";
$outro->registraCompra(14, 5);
$outro->save();
}
catch (Exception $e) {
print $e->getMessage();
}
Resultado:
SELECT * FROM produto
DELETE FROM produto where id = '1'
INSERT INTO produto (id, descricao, estoque, preco_custo, preco_venda,
codigo_barras, data_cadastro, origem) VALUES ('1', 'Vinho Brasileiro
Tinto Merlot', '10', '12', '18', '13523453234234', '2018-03-31', 'N')
SELECT * FROM produto where id = '1'
Descrição: Vinho Brasileiro Tinto Merlot
Lucro: 50%
UPDATE produto SET descricao = 'Vinho Brasileiro Tinto Merlot', estoque
= '15', preco_custo = '12.0', preco_venda = '18.0', codigo_barras =
'13523453234234', data_cadastro = '2018-03-31', origem = 'N' WHERE id
= '1'
classes/dm/Produto.php
<?php
class Produto {
private $data;
function __get($prop) {
return $this->data[$prop];
}
function __set($prop, $value) {
$this->data[$prop] = $value;
}
}
Agora vamos criar uma classe para representar uma venda. Essa classe
terá métodos para atribur e retornar um ID, bem como adicionar (addItem)
e retornar (getItens) itens (produtos).
classes/dm/Venda.php
<?php
class Venda {
private $id;
private $itens;
public function setId($id) {
$this->id = $id;
}
public function getId() {
return $this->id;
}
public function addItem($quantidade, Produto $produto) {
$this->itens[] = array($quantidade, $produto);
}
public function getItens() {
return $this->itens;
}
}
Vamos escrever a classe VendaMapper, responsável por receber uma
estrutura com relacionamentos entre os objetos e persisti-la na base de
dados. A classe VendaMapper terá um método setConnection() para receber
uma conexão PDO criada externamente. Terá também um método privado
getLastId() para descobrir o último ID inserido.
E terá o método save(), que receberá como parâmetro uma venda
(instância de Venda), e irá gravar os dados necessários tanto na tabela venda
quanto em item_venda. Para descobrir quais são os itens da venda, a classe
VendaMapper utiliza o método getItens().
classes/dm/VendaMapper.php
<?php
class VendaMapper {
private static $conn;
public static function setConnection( PDO $conn ) {
self::$conn = $conn;
}
public static function save(Venda $venda) {
$date = date("Y-m-d");
$sql = "INSERT INTO venda (data_venda) values ('$date')";
print $sql . "<br>\n";
self::$conn->query($sql);
$id = self::getLastId();
$venda->setId($id);
// percorre os itens vendidos
foreach ($venda->getItens() as $item) {
$quantidade = $item[0];
$produto = $item[1];
$preco = $produto->preco;
$sql = "INSERT INTO item_venda (id_venda, id_produto, quantidade,
preco)".
" values ('$id', '{$produto->id}', '$quantidade',
'$preco')";
print $sql . "<br>\n";
self::$conn->query($sql);
}
}
private static function getLastId() {
$sql = "SELECT max(id) as max FROM venda";
$result = self::$conn->query($sql);
$data = $result->fetch(PDO::FETCH_OBJ);
return $data->max;
}
}
Agora vamos escrever um exemplo de uso de VendaMapper. O exemplo
inicia com a importação das classes de domínio (Produto, Venda) e de
persistência (VendaMapper). Em seguida, criamos dois objetos
representando produtos ($p1, $p2). É importante notar que esses objetos
deveriam ser carregados a partir do banco de dados por outra Data
Mapper (ProdutoMapper), e não deveriam ser criados diretamente. Fizemos
dessa maneira somente para simpli car o exemplo. Criados os objetos,
instanciamos um objeto da classe Venda e adicionamos os dois produtos,
bem como suas quantidades, pelo método addItem(). Por m, criamos
uma conexão (PDO), passamos essa conexão para o Data Mapper pelo
método setConnection() e solicitamos que VendaMapper persista a venda,
bem como seus itens, o que é feito pelo método save().
exemplo_dm.php
<?php
require_once 'classes/dm/Produto.php';
require_once 'classes/dm/Venda.php';
require_once 'classes/dm/VendaMapper.php';
try {
$p1 = new Produto;
$p1->id = 1;
$p1->preco = 12;
$p2 = new Produto;
$p2->id = 2;
$p2->preco = 14;
$venda= new Venda;
// adiciona alguns produtos
$venda->addItem(10, $p1);
$venda->addItem(20, $p2);
$conn = new PDO('sqlite:database/estoque.db');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
VendaMapper::setConnection($conn);
// salva venda
VendaMapper::save($venda);
}
catch (Exception $e) {
print $e->getMessage();
}
Resultado:
INSERT INTO venda (data_venda) values ('2015-05-30')
INSERT INTO item_venda (id_venda, id_produto, quantidade, preco) values
('1', '1', '10', '12')
INSERT INTO item_venda (id_venda, id_produto, quantidade, preco) values
('1', '2', '20', '14')
con g/estoque.ini
host =
name = database/estoque.db
user =
pass =
type = sqlite
Criaremos em seguida a classe Connection. Repare que os objetos são
instanciados pelo método estático open(). Não faz sentido criar instâncias
da classe Connection, já que ela só existe para prover objetos PDO por meio
da chamada de seu método estático open(). Como não existirão objetos
do tipo Connection, preferimos marcar o método construtor da classe
como private, o que signi ca que esse método só poderia ser chamado
dentro do escopo da própria classe, evitando que algum programador
execute por engano new Connection.
A classe Connection terá o método open(), que irá receber o nome de um
arquivo INI de con guração do banco de dados. Caso o arquivo de
con guração não seja encontrado, uma exceção será lançada. Se o arquivo
existir, será utilizada a função parse_ini_file() para ler as informações
nele contidas (usuário, senha, driver etc.) na forma de um array. Em
seguida, um bloco switch/case testa a variável type para instanciar o objeto
PDO correspondente.
Para que os objetos PDO reportem exceções (PDOException) quando
ocorrerem erros de SQL, é necessário chamar o método setAttribute()
com os parâmetros apropriados. Como essa operação é comum a
qualquer objeto do tipo PDO, podemos colocá-la junto do código que irá
instanciar os objetos.
Observação: essa classe foi testada com os bancos de dados PostgreSQL,
MySQL e SQLite. Para os demais sistemas gerenciadores de bancos de dados,
ainda não é garantido o seu funcionamento.
classes/api/Connection.php
<?php
final class Connection {
private function __construct() {}
public static function open($name) {
// verifica se existe arquivo de configuração para este banco de
dados
if (file_exists("config/{$name}.ini")) {
$db = parse_ini_file("config/{$name}.ini");
}
else {
throw new Exception("Arquivo '$name' não encontrado");
}
// lê as informações contidas no arquivo
$user = isset($db['user']) ? $db['user'] : NULL;
$pass = isset($db['pass']) ? $db['pass'] : NULL;
$name = isset($db['name']) ? $db['name'] : NULL;
$host = isset($db['host']) ? $db['host'] : NULL;
$type = isset($db['type']) ? $db['type'] : NULL;
$port = isset($db['port']) ? $db['port'] : NULL;
// descobre qual o tipo (driver) de banco de dados a ser utilizado
switch ($type) {
case 'pgsql':
$port = $port ? $port : '5432';
$conn = new PDO("pgsql:dbname={$name}; user={$user}; password=
{$pass};
host=$host;port={$port}");
break;
case 'mysql':
$port = $port ? $port : '3306';
$conn = new PDO("mysql:host={$host};port={$port};dbname=
{$name}",
$user, $pass);
break;
case 'sqlite':
$conn = new PDO("sqlite:{$name}");
$conn->query('PRAGMA foreign_keys = ON');
break;
case 'ibase':
$conn = new PDO("firebird:dbname={$name}", $user, $pass);
break;
case 'oci8':
$conn = new PDO("oci:dbname={$name}", $user, $pass);
break;
case 'mssql':
$conn = new PDO("dblib:host={$host},1433;dbname={$name}",
$user, $pass);
break;
}
// define para que o PDO lance exceções na ocorrência de erros
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
}
}
A gura 5.5 apresenta um diagrama que explica a interação entre o
programa e as classes Connection e PDO. Repare que o programa executa o
método open() da classe Connection para obter uma conexão. A classe
Connection então efetua a leitura do arquivo INI e instancia o objeto PDO
correspondente, retornando-o para a aplicação. Em seguida, o programa
pode executar métodos dessa classe como query().
Figura 5.5 – Interação entre as classes Connection e PDO.
Escreveremos agora um exemplo que utiliza a classe Connection. No
programa a seguir, importamos a classe anteriormente criada Produto, que
é uma Active Record, bem como a classe Connection. Depois, abrimos uma
conexão com o método Connection::open(), passando o nome do arquivo
INI de con guração como parâmetro. O retorno ($conn) é do tipo PDO. A
conexão criada é passada para o objeto Produto pelo método
setConnection(). Se a abertura da conexão não puder ser realizada, uma
exceção será lançada e a execução continuará no bloco catch. Em seguida,
instanciamos um Produto, de nimos seus atributos e, por m, solicitamos
sua persistência por meio do método save().
Observação: veja que, neste exemplo, em nenhum momento indicamos a
localização do arquivo de banco de dados, seu tipo, o usuário e a senha. Assim,
caso resolvêssemos transferir o banco de dados para outra tecnologia (por
exemplo, MySQL, PostgreSQL), bastaria alterar os parâmetros do arquivo INI de
configuração.
exemplo_conexao.php
<?php
require_once 'classes/ar/Produto.php';
require_once 'classes/api/Connection.php';
try {
$conn = Connection::open('estoque');
Produto::setConnection($conn);
$pro = new Produto;
$pro->descricao = 'Café torrado e moído brasileiro';
$pro->estoque = 100;
$pro->preco_custo = 4;
$pro->preco_venda = 7;
$pro->codigo_barras = '34963045930455';
$pro->data_cadastro = date('Y-m-d');
$pro->origem = 'N';
$pro->save();
}
catch (Exception $e) {
print $e->getMessage();
}
Resultado:
INSERT INTO produto (id, descricao, estoque, preco_custo, preco_venda,
codigo_barras, data_cadastro, origem) VALUES ('3', 'Café torrado e
moído brasileiro', '100', '4', '7', '34963045930455', '2015-05-30',
'N')
classes/api/Transaction.php
<?php
final class Transaction {
private static $conn; // conexão ativa
private function __construct() {}
public static function open($database) {
if (empty(self::$conn))
{
self::$conn = Connection::open($database);
self::$conn->beginTransaction(); // inicia a transação
}
}
public static function get() {
return self::$conn; // retorna a conexão ativa
}
public static function rollback() {
if (self::$conn) {
self::$conn->rollback(); // desfaz as operações realizadas
self::$conn = NULL;
}
}
public static function close() {
if (self::$conn) {
self::$conn->commit(); // aplica as operações realizadas
self::$conn = NULL;
}
}
}
A gura 5.6 representa como é a interação entre as classes participantes de
uma transação. Veja que o programa abre uma transação executando o
método open() da classe Transaction. Esse método, por sua vez, executa o
método open da classe Connection, obtendo um objeto de conexão PDO, e
armazena-o na propriedade estática $conn. Sempre que o programa quiser
essa variável de conexão, ele irá executar o método estático get(), que
retornará a conexão da transação ativa. De posse da variável de conexão
(objeto $conn), o programa poderá enviar consultas ao banco de dados por
meio do método query(). É importante observar que o método
Transaction::get() pode ser executado de qualquer parte do sistema,
obtendo a conexão da transação ativa.
Após criada a classe Transaction, criaremos um exemplo para demonstrar
sua utilização. O exemplo a seguir inicia com a importação das classes
necessárias. Para isso, usaremos a active record Produto criada
anteriormente. Também importaremos as classes Connection e Transaction.
Figura 5.6 – Interação entre as classes durante uma transação.
Em seguida é aberta uma transação com a base estoque, por meio do
método Transaction::open(). A partir desse momento, de qualquer parte
do programa é possível executarmos o método Transaction::get() para
obter a conexão ativa. Fazemos isso e passamos o resultado (objeto PDO)
para o método Produto::setConnection(). Depois, instanciamos um
produto ($p1), de nimos alguns atributos e solicitamos sua persistência
(save). Veja que, antes de iniciarmos a de nição de outro produto ($p2),
uma exceção é lançada. Essa exceção faz com que, além de o produto $p2
não ser instanciado e persistido, o produto $p1 também não seja, visto que
a transação não chegará ao seu nal, representado pelo método
Transaction::close(), que por sua vez executa o “commit”, ou seja, torna
permanente as operações pendentes no banco de dados. O programa
segue para o catch, no qual as operações são desfeitas (rollback).
exemplo_transacao.php
<?php
require_once 'classes/ar/Produto.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Transaction.php';
try {
Transaction::open('estoque');
// obtém a conexão ativa
$conn = Transaction::get();
Produto::setConnection($conn);
$p1 = new Produto;
$p1->descricao = 'Vinho Brasileiro Tinto Merlot';
$p1->estoque = 10;
$p1->preco_custo = 12;
$p1->preco_venda = 18;
$p1->codigo_barras = '13523453234234';
$p1->data_cadastro = date('Y-m-d');
$p1->origem = 'N';
$p1->save();
throw new Exception('Exceção proposital');
$p2 = new Produto;
$p2->descricao = 'Vinho Importado Tinto Carmenere';
$p2->estoque = 10;
$p2->preco_custo = 18;
$p2->preco_venda = 29;
$p2->codigo_barras = '73450345423423';
$p2->data_cadastro = date('Y-m-d');
$p2->origem = 'I';
$p2->save();
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
Agora que concluímos nossa classe de controle de transações e também
provamos sua utilização, podemos fazer outras melhorias em classes
criadas anteriormente. Quando criamos a classe Active Record Produto
pela primeira vez, de nimos um método setConnection() para passar a
conexão criada externamente por meio de um método. O método
setConnection() recebia a conexão e armazenava internamente no objeto
Active Record. Não precisamos mais nos preocupar em car passando a
conexão adiante, pois, com o novo modelo de transações, sempre que
uma conexão é necessária, basta executarmos o método
Transaction::get() de qualquer lugar do programa que esse método
retornará a conexão da transação ativa. Para isso, basta que haja uma
transação aberta (Transaction::open()) anteriormente.
Dessa forma, escrevemos uma nova classe Active Record Produto, agora
sem o método setConnection(). Sempre que a conexão corrente é
necessária, como nos métodos find() e save(), executamos
Transaction::get() para obter a conexão da transação ativa.
classes/ar/ProdutoComTransacao.php
<?php
class Produto {
private $data;
function __get($prop) {
return $this->data[$prop];
}
function __set($prop, $value) {
$this->data[$prop] = $value;
}
public static function find($id) {
$sql = "SELECT * FROM produto where id = '$id' ";
print "$sql <br>\n";
$conn = Transaction::get();
$result = $conn->query($sql);
return $result->fetchObject(__CLASS__);
}
public function save() {
if (empty($this->data['id'])) {
$id = $this->getLastId() +1;
$sql = "INSERT INTO produto (id, descricao, estoque, preco_custo,
".
" preco_venda, codigo_barras, data_cadastro, origem)"
.
" VALUES ('{$id}', " .
"'{$this->descricao}', " .
"'{$this->estoque}', " .
"'{$this->preco_custo}', " .
"'{$this->preco_venda}', " .
"'{$this->codigo_barras}', " .
"'{$this->data_cadastro}', " .
"'{$this->origem}')";
}
else {
$sql = "UPDATE produto SET descricao = '{$this->descricao}', " .
" estoque = '{$this->estoque}', " .
" preco_custo = '{$this->preco_custo}', " .
" preco_venda = '{$this->preco_venda}', ".
" codigo_barras = '{$this->codigo_barras}', ".
" data_cadastro = '{$this->data_cadastro}', ".
" origem = '{$this->origem}' ".
"WHERE id = '{$this->id}'";
}
print "$sql <br>\n";
$conn = Transaction::get();
return $conn->exec($sql); // executa instrução SQL
}
private function getLastId() {
$sql = "SELECT max(id) as max FROM produto";
$conn = Transaction::get();
$result = $conn->query($sql);
$data = $result->fetch(PDO::FETCH_OBJ);
return $data->max;
}
}
Vamos escrever um exemplo de uso da nova classe Active Record Produto,
que está preparada para usar o novo controle de transações Transaction.
Para isso, iniciamos o programa importando as classes necessárias. Em
seguida, abrimos uma transação com a base estoque pelo método
Transaction::open(). Dentro da transação instanciamos um objeto $p1 e
solicitamos a execução do método save() para persisti-lo. Veja que em
nenhum momento passamos para o objeto $p1 qual é a conexão ativa. Isso
porque dentro do método save(), quando a conexão foi necessária, ela foi
obtida pelo método Transaction::get().
exemplo_transacao2.php
<?php
require_once 'classes/ar/ProdutoComTransacao.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Transaction.php';
try {
Transaction::open('estoque');
$p1 = new Produto;
$p1->descricao = 'Chocolate amargo';
$p1->estoque = 80;
$p1->preco_custo = 4;
$p1->preco_venda = 7;
$p1->codigo_barras = '68323453234234';
$p1->data_cadastro = date('Y-m-d');
$p1->origem = 'N';
$p1->save();
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
classes/api/Logger.php
<?php
abstract class Logger {
protected $filename; // local do arquivo de LOG
public function __construct($filename) {
$this->filename = $filename;
file_put_contents($filename, ''); // limpa o conteúdo do arquivo
}
// define o método write como obrigatório
abstract function write($message);
}
A partir daí podemos ter várias subclasses, tais como: LoggerTXT,
LoggerXML, LoggerHTML, e assim por diante. Vamos criar as classes LoggerTXT
e LoggerXML. Quando compreendemos como declarar uma classe de log,
ca muito mais fácil criar novas classes. Cada classe concreta terá de
implementar o método write(), que será responsável por armazenar a
mensagem no arquivo de log. A primeira classe será a LoggerXML. Essa
classe contém o método write(), que receberá a mensagem (instrução
SQL ou texto) e irá armazená-la em um arquivo no formato XML,
observando algumas tags. Também armazenaremos a hora do log <time>.
classes/api/LoggerXML.php
<?php
class LoggerXML extends Logger {
public function write($message) {
date_default_timezone_set('America/Sao_Paulo');
$time = date("Y-m-d H:i:s");
$text = "<log>\n";
$text.= " <time>$time</time>\n";
$text.= " <message>$message</message>\n";
$text.= "</log>\n";
// adiciona ao final do arquivo
$handler = fopen($this->filename, 'a');
fwrite($handler, $text);
fclose($handler);
}
}
Por m, criaremos a classe que irá registrar as mensagens de log em
arquivos TXT. Mais simples que a anterior, ela armazenará a mensagem
recebida em uma linha contendo a data e a hora.
classes/api/LoggerTXT.php
<?php
class LoggerTXT extends Logger {
public function write($message) {
date_default_timezone_set('America/Sao_Paulo');
$time = date("Y-m-d H:i:s");
// monta a string
$text = "$time :: $message\n";
// adiciona ao final do arquivo
$handler = fopen($this->filename, 'a');
fwrite($handler, $text);
fclose($handler);
}
}
Para que a classe Transaction utilize um dos algoritmos de log, é
necessário que ela tenha uma referência para um objeto do tipo Logger.
Para isso, criaremos o método setLogger() na transação, que irá receber
uma instância de alguma classe concreta de Logger e armazenar essa
instância na propriedade $logger para que seja utilizada posteriormente
no momento do log. Assim, a transação terá uma referência para o
algoritmo de log que deve ser usado quando necessário.
Para registrar uma mensagem de log, será necessário criar um método
log(). O papel desse método será acionar o algoritmo de log de nido,
passando uma mensagem qualquer para ser registrada, como uma
instrução SQL, por exemplo. Como o objeto Transaction terá uma
referência para um objeto concreto do tipo Logger, por meio da
propriedade $logger, a responsabilidade pela execução do log será
delegada a ele por meio da execução do método write(). Sempre que uma
transação é iniciada, o log é limpo.
classes/api/Transaction.php (continuação)
<?php
final class Transaction {
private static $conn; // conexão ativa
private static $logger; // objeto de log
public static function open($database) {
if (empty(self::$conn)) {
self::$conn = Connection::open($database);
self::$conn->beginTransaction();
self::$logger = NULL; // desliga o log de SQL
}
}
public static function get() {}
public static function rollback() {}
public static function close() {}
public static function setLogger(Logger $logger) {
self::$logger = $logger;
}
public static function log($message) {
if (self::$logger) {
self::$logger->write($message);
}
}
}
Agora que já concluímos o mecanismo de log, você deve estar se
perguntando como usá-lo. A resposta é: de qualquer ponto da aplicação.
Para isso, basta termos uma transação aberta. Lembre-se da classe Active
Record Produto que de nimos anteriormente. Nela, tínhamos uma
instrução de print para exibir o SQL em tela para nosso controle. Pois a
partir daquele ponto podemos substituir a instrução de print pelo uso do
controle de logs. Como presumiremos que naquele ponto da aplicação há
uma transação aberta, iremos simplesmente executar o método log().
Quando houver um log de nido, a instrução será escrita no arquivo de
log; caso contrário, nada acontecerá. Veja na classe a seguir, onde, no
método save(), substituímos o comentário pelo método de log.
Observação: não repetiremos aqui métodos já definidos.
classes/ar/ProdutoComTransacaoELog.php (continuação)
<?php
class Produto {
function __get($prop) {}
function __set($prop, $value) {}
public static function find($id) {}
private function getLastId() {}
// ...
public function save() {
if (empty($this->data['id'])) {
$id = $this->getLastId() +1;
$sql = "INSERT INTO produto (id, descricao, estoque, preco_custo,
".
" preco_venda, codigo_barras, data_cadastro, origem)"
.
" VALUES ('{$id}', " .
"'{$this->descricao}', " .
"'{$this->estoque}', " .
"'{$this->preco_custo}', " .
"'{$this->preco_venda}', " .
"'{$this->codigo_barras}', " .
"'{$this->data_cadastro}', " .
"'{$this->origem}')";
}
else {
$sql = "UPDATE produto SET descricao = '{$this->descricao}', " .
" estoque = '{$this->estoque}', " .
" preco_custo = '{$this->preco_custo}', " .
" preco_venda = '{$this->preco_venda}', ".
" codigo_barras = '{$this->codigo_barras}', ".
" data_cadastro = '{$this->data_cadastro}', ".
" origem = '{$this->origem}' ".
"WHERE id = '{$this->id}'";
}
$conn = Transaction::get();
//print "$sql <br>\n";
Transaction::log($sql) ;
return $conn->exec($sql); // executa instrucao SQL
}
}
Veja um exemplo de utilização das estratégias de log. No programa a
seguir, abriremos uma transação com a base de dados e logo em seguida
de niremos a estratégia de log utilizada (LoggerTXT), bem como a
localização do arquivo de log. Na sequência, executamos o método log()
para registrar a string “Inserindo novo produto” no arquivo de log.
Depois, de niremos um objeto Active Record Produto e executaremos o
seu método save(). Lembre-se de que no save() existe agora uma
chamada para o método log() para registrar no arquivo de log a instrução
SQL utilizada. Veja ao nal do exemplo, no log registrado, o SQL gerado
pela classe Produto.
exemplo_log.php
<?php
require_once 'classes/ar/ProdutoComTransacaoELog.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
try {
Transaction::open('estoque');
Transaction::setLogger(new LoggerTXT('/tmp/log.txt'));
Transaction::log('Inserindo produto novo');
$p1 = new Produto;
$p1->descricao = 'Chocolate amargo';
$p1->estoque = 80;
$p1->preco_custo = 4;
$p1->preco_venda = 7;
$p1->codigo_barras = '68323453234234';
$p1->data_cadastro = date('Y-m-d');
$p1->origem = 'N';
$p1->save();
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
/tmp/log.txt
2015-05-31 19:43:16 :: Inserindo produto novo
2015-05-31 19:43:16 :: INSERT INTO produto (id, descricao, estoque,
preco_custo, preco_venda, codigo_barras, data_cadastro, origem) VALUES
('6', 'Chocolate amargo', '80', '4', '7', '68323453234234', '2015-05-
31', 'N')
classes/api/Record.php
<?php
abstract class Record {
protected $data; // array contendo os dados do objeto
public function __construct($id = NULL) {
if ($id) { // se o ID for informado
// carrega o objeto correspondente
$object = $this->load($id);
if ($object)
{
$this->fromArray($object->toArray());
}
}
}
O método __clone() será executado sempre que um objeto for clonado.
Nesses casos em que um Active Record é clonado, ele deve manter todas
as suas propriedades, com exceção de seu ID, por isso estamos limpando o
ID do clone. Se mantivéssemos o mesmo ID, teríamos dois objetos Active
Record com o mesmo ID no sistema, o que causaria erros na persistência
do objeto. Limpar o ID fará com que um novo ID seja gerado para o clone
no momento em que ele for persistido na base de dados.
public function __clone() {
unset($this->data['id']);
}
Sempre que um valor for atribuído a uma propriedade do objeto, o
método __set() será executado, interceptando essa atribuição, pois a
propriedade em questão não estará de nida. O valor a ser atribuído será
armazenado no array $data, indexado pelo nome da propriedade. Antes
disso, porém, será veri cada a existência de um método nomeado por
set_<propriedade> de nido pelo usuário (por meio da função
method_exists). Caso esse método exista, ele terá prioridade de execução.
Veremos, na seção 4.4.7 – Encapsulamento –, como o usuário poderá
de nir métodos para proteger a atribuição de valores.
public function __set($prop, $value) {
if (method_exists($this, 'set_'.$prop)) {
// executa o método set_<propriedade>
call_user_func(array($this, 'set_'.$prop), $value);
}
else {
if ($value === NULL) {
unset($this->data[$prop]);
}
else {
$this->data[$prop] = $value; // atribui o valor da propriedade
}
}
}
Sempre que uma propriedade for requisitada, o método __get() será
executado. O valor da propriedade será lido a partir do array $data, mas,
antes disso, será veri cada a ocorrência de um método nomeado por
get_<propriedade> de nido pelo usuário. Caso esse método exista, ele será
executado no lugar. Mais adiante, quando abordarmos o padrão Lazy
Initialization, no capítulo 8, veremos um caso de uso dessa
funcionalidade, que permite que o programador in uencie no retorno de
propriedades do objeto.
public function __get($prop) {
if (method_exists($this, 'get_'.$prop)) {
// executa o método get_<propriedade>
return call_user_func(array($this, 'get_'.$prop));
}
else {
if (isset($this->data[$prop])) {
return $this->data[$prop];
}
}
}
Também precisaremos de nir um método __isset() que será executado
automaticamente sempre que o programador testar a presença de um
valor no objeto, como ao utilizar a função isset().
public function __isset($prop) {
return isset($this->data[$prop]);
}
Seguindo o código de nossa classe Record, temos o método getEntity().
Esse método é responsável por retornar o nome da tabela na qual o Active
Record será persistido. Para isso, ele veri ca a ocorrência de uma
constante de classe chamada TABLENAME na classe Active Record. O método
getEntity() retornará exatamente o conteúdo dessa constante por meio da
função constant(), que cumpre esse papel.
private function getEntity() {
$class = get_class($this); // obtém o nome da classe
return constant("{$class}::TABLENAME"); // retorna a constante de
classe TABLENAME
}
O método fromArray() será usado para preencher os atributos de um
Active Record com os dados de um array, de modo que os índices desse
array são os atributos do objeto. O método toArray() será utilizado para
retornar todos os atributos de um objeto (armazenados na propriedade
$data) em forma de array.
public function fromArray($data) {
$this->data = $data;
}
public function toArray() {
return $this->data;
}
O método store() é responsável por persistir um objeto no banco de
dados. Basicamente esse método armazena os atributos do objeto, que
estão armazenados no array $data, como colunas do registro do banco de
dados. Antes, porém, os atributos em $data passam por um processo de
preparação (prepare), no qual são formatados para ser inseridos na
instrução SQL.
O método store() continua com uma veri cação por meio da função
empty(). Esse método faz um teste para descobrir se o objeto contém o
atributo ID com algum valor e também veri ca, por meio do método
load(), se o objeto existe no banco de dados. Caso o objeto tenha seu ID
vazio ou não esteja armazenado no banco de dados, montamos uma
instrução de INSERT para persisti-lo pela primeira vez. Caso contrário,
devemos montar uma instrução de UPDATE para atualizar seus atributos no
registro do banco de dados.
O fato de o objeto que estamos persistindo não conter seu campo de ID
com valor signi ca que é um objeto que está para ser persistido pela
primeira vez e, dessa forma, precisamos gerar um ID para ele. Neste caso,
há diversas estratégias: podemos usar recursos do próprio banco de dados
como auto-increment ou sequências. Entretanto, nem todos os bancos de
dados oferecem esse recurso, e os que oferecem trabalham de maneiras
diferentes.
A estratégia que usaremos para geração dos IDs de novos objetos será
utilizar a função MAX(). Pelo método getLast(), descobriremos o maior ID
já inserido na tabela e, então, incrementaremos esse valor. Sabemos que
essa é uma solução simplista, mas ela atende aos propósitos didáticos
aqui estabelecidos.
Para executar a instrução na base de dados, o método store() obtém a
transação ativa por meio do método Transaction::get(). Para isso é
necessário haver uma transação aberta; caso contrário, uma exceção será
lançada. Em seguida o método store() registra a instrução SQL no
arquivo de log e a executa por meio do método exec() da conexão.
public function store() {
$prepared = $this->prepare($this->data);
// verifica se tem ID ou se existe na base de dados
if (empty($this->data['id']) or (!$this->load($this->id))) {
// incrementa o ID
if (empty($this->data['id'])) {
$this->id = $this->getLast() +1;
$prepared['id'] = $this->id;
}
// cria uma instrução de insert
$sql = "INSERT INTO {$this->getEntity()} " .
'('. implode(', ', array_keys($prepared)) . ' )'.
' values ' .
'('. implode(', ', array_values($prepared)) . ' )';
}
else {
// monta a string de UPDATE
$sql = "UPDATE {$this->getEntity()}";
// monta os pares: coluna=valor,...
if ($prepared) {
foreach ($prepared as $column => $value) {
if ($column !== 'id') {
$set[] = "{$column} = {$value}";
}
}
}
$sql .= ' SET ' . implode(', ', $set);
$sql .= ' WHERE id=' . (int) $this->data['id'];
}
// obtém transação ativa
if ($conn = Transaction::get()) {
Transaction::log($sql);
$result = $conn->exec($sql);
return $result;
}
else {
throw new Exception('Não há transação ativa!!');
}
}
O método load() será responsável por ler um registro do banco de dados e
retorná-lo na forma de um objeto. Para isso, montará uma instrução
SELECT para fazer a consulta no banco de dados. O método load() sempre
irá selecionar todas as colunas da tabela (*) e o ltro de seleção (WHERE)
será baseado no atributo ID. Depois disso, o método detecta a transação
ativa pelo método Transaction::get(), registra a operação no arquivo de
log por meio do método log() e executa a consulta SQL por meio do
método query(), retornando os dados para a aplicação. Note que os dados
são retornados na forma de objeto (fetchObject), e a classe desse objeto
retornado sempre será a mesma que a classe atual (get_class($this)).
public function load($id) {
// monta instrução de SELECT
$sql = "SELECT * FROM {$this->getEntity()}";
$sql .= ' WHERE id=' . (int) $id;
// obtém transação ativa
if ($conn = Transaction::get()) {
// cria mensagem de log e executa a consulta
Transaction::log($sql);
$result= $conn->query($sql);
// se retornou algum dado
if ($result) {
// retorna os dados em forma de objeto
$object = $result->fetchObject(get_class($this));
}
return $object;
}
else {
throw new Exception('Não há transação ativa!!');
}
}
O método delete() será responsável por excluir o objeto atual da base de
dados e poderá receber opcionalmente um $id como parâmetro. Neste
caso, assumirá esse $id a ser excluído, mas o comportamento-padrão é
excluir o objeto atual ($this->id). O método delete() monta uma
instrução DELETE com base no ID do objeto. Posteriormente, detecta, pelo
método Transaction::get(), se existe uma transação já aberta com o banco
de dados, registra a instrução SQL no arquivo de log e executa a instrução
pelo método exec().
public function delete($id = NULL) {
// o ID é o parâmetro ou a propriedade ID
$id = $id ? $id : $this->id;
// monsta a string de UPDATE
$sql = "DELETE FROM {$this->getEntity()}";
$sql .= ' WHERE id=' . (int) $this->data['id'];
// obtém transação ativa
if ($conn = Transaction::get()) {
// faz o log e executa o SQL
Transaction::log($sql);
$result = $conn->exec($sql);
return $result; // retorna o resultado
}
else
{
throw new Exception('Não há transação ativa!!');
}
}
O método estático find() também será utilizado para buscarmos um
objeto a partir da base de dados. Ele na verdade é só um atalho para o
método load(), com a facilidade de ser executado estaticamente.
public static function find($id) {
$classname = get_called_class();
$ar = new $classname;
return $ar->load($id);
}
O método getLast() é utilizado apenas internamente. O seu objetivo é
retornar o último ID armazenado na tabela para que se calcule
automaticamente o próximo ID a ser inserido nela no caso de objetos que
estão sendo persistidos pela primeira vez pelo método store().
private function getLast() {
if ($conn = Transaction::get()) {
$sql = "SELECT max(id) FROM {$this->getEntity()}";
// cria log e executa instrução SQL
Transaction::log($sql);
$result= $conn->query($sql);
// retorna os dados do banco
$row = $result->fetch();
return $row[0];
}
else {
throw new Exception('Não há transação ativa!!');
}
}
O método prepare() é responsável por preparar os dados antes de serem
inseridos na base de dados. Para isso, ele percorre o array $data, testando
cada um dos valores presentes. O resultado dessa transformação é outro
array $prepared, que é então utilizado para montarmos a instrução SQL.
Cada atributo do array passa pela função escape(), que formata o valor
para este ser inserido na instrução SQL.
public function prepare($data) {
$prepared = array();
foreach ($data as $key => $value) {
if (is_scalar($value)) {
$prepared[$key] = $this->escape($value);
}
}
return $prepared;
}
A função escape() recebe um valor e o formata conforme o seu tipo. Caso
seja uma string, usamos a função addslashes(); se for um booleano,
retornamos TRUE ou FALSE, e assim por diante. É importante salientar que
essa não é a melhor técnica para protegermos a aplicação de ataques
como SQL Injection. Aqui não usaremos técnicas melhores de proteção e
segurança para simpli car. Porém, outros frameworks, como o Adianti
Framework, implementam mecanismos mais so sticados de segurança no
momento de persistir os dados na base de dados.
public function escape($value) {
if (is_string($value) and (!empty($value))) {
// adiciona \ em aspas
$value = addslashes($value);
return "'$value'";
}
else if (is_bool($value)) {
return $value ? 'TRUE': 'FALSE';
}
else if ($value!=='') {
return $value;
}
else {
return "NULL";
}
}
classes/model/Produto.php
<?php
class Produto extends Record {
const TABLENAME = 'produto';
}
record_novo.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/api/Record.php';
require_once 'classes/model/Produto.php';
try {
Transaction::open('estoque');
Transaction::setLogger(new LoggerTXT('/tmp/log_novo.txt'));
Transaction::log('Inserindo produto novo');
$p1 = new Produto;
$p1->descricao = 'Cerveja artesanal IPA';
$p1->estoque = 50;
$p1->preco_custo = 8;
$p1->preco_venda = 12;
$p1->codigo_barras = '75363453234234';
$p1->data_cadastro = date('Y-m-d');
$p1->origem = 'N';
$p1->store();
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
/tmp/log_novo.txt
2015-05-31 20:46:12 :: Inserindo produto novo
2015-05-31 20:46:12 :: SELECT max(id) FROM produto
2015-05-31 20:46:12 :: INSERT INTO produto (descricao, estoque,
preco_custo, preco_venda, codigo_barras, data_cadastro, origem )
values ('Cerveja artesanal IPA', 50, 8, 12, '75363453234234', '2015-
05-31', 'N' )
record_get.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/api/Record.php';
require_once 'classes/model/Produto.php';
try {
Transaction::open('estoque');
Transaction::setLogger(new LoggerTXT('/tmp/log_find.txt'));
Transaction::log('Buscando um produto');
$p1 = Produto::find(2);
print $p1->descricao;
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
Resultado:
Vinho Brasileiro Tinto Merlot
/tmp/log_ nd.txt
2015-05-31 20:55:54 :: Buscando um produto
2015-05-31 20:55:54 :: SELECT * FROM produto WHERE id=2
record_update.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/api/Record.php';
require_once 'classes/model/Produto.php';
try {
Transaction::open('estoque');
Transaction::setLogger(new LoggerTXT('/tmp/log_update.txt'));
Transaction::log('Alterando um produto');
$p1 = Produto::find(2);
print $p1->estoque . "<br>\n";
$p1->estoque += 10;
print $p1->estoque . "<br>\n";
$p1->store();
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
Resultado:
10.0
20
/tmp/log_update.txt
2015-05-31 21:54:55 :: Alterando um produto
2015-05-31 21:54:55 :: SELECT * FROM produto WHERE id=2
2015-05-31 21:54:55 :: UPDATE produto SET descricao = 'Vinho Brasileiro
Tinto Merlot', estoque = 20, preco_custo = '12.0', preco_venda =
'18.0', codigo_barras = '13523453234234', data_cadastro = '2015-05-
30', origem = 'N' WHERE id=2
record_clone.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/api/Record.php';
require_once 'classes/model/Produto.php';
try {
Transaction::open('estoque');
Transaction::setLogger(new LoggerTXT('/tmp/log_clone.txt'));
Transaction::log('Clonando um produto');
$p1 = Produto::find(2);
$p2 = clone $p1;
$p2->descricao .= ' (clonado)';
$p2->store();
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
/tmp/log_clone.txt
2015-05-31 22:07:31 :: Clonando um produto
2015-05-31 22:07:31 :: SELECT * FROM produto WHERE id=2
2015-05-31 22:07:31 :: SELECT max(id) FROM produto
2015-05-31 22:07:31 :: INSERT INTO produto (descricao, estoque,
preco_custo, preco_venda, codigo_barras, data_cadastro, origem, id )
values ('Vinho Brasileiro Tinto Merlot (clonado)', '20.0', '12.0',
'18.0', '13523453234234', '2015-05-30', 'N', 8 )
record_delete.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/api/Record.php';
require_once 'classes/model/Produto.php';
try {
Transaction::open('estoque');
Transaction::setLogger(new LoggerTXT('/tmp/log_delete.txt'));
Transaction::log('Removendo um produto');
$p1 = Produto::find(8);
if ($p1 instanceof Produto) {
$p1->delete();
}
else {
throw new Exception('Produto não localizado');
}
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
/tmp/log_delete.txt
2015-05-31 22:16:43 :: Removendo um produto
2015-05-31 22:16:43 :: SELECT * FROM produto WHERE id=8
2015-05-31 22:16:43 :: DELETE FROM produto WHERE id=8
5.4.7 Encapsulamento
Como vimos no capítulo 2, o encapsulamento trata de proteger o acesso
às propriedades internas de um objeto. Até o momento, não nos
preocupamos com isso, mas essa questão é de fundamental importância,
principalmente em um ambiente em equipe no qual uma pessoa é
responsável por criar uma classe e outras por utilizarem-na. Se a classe
prover encapsulamento, ela protegerá as suas propriedades contra o mau
uso de terceiros. Assim, teremos um sistema mais con ável e íntegro.
No exemplo a seguir, demonstraremos uma forma de prover
encapsulamento quando trabalhamos com um Active Record, como é a
nossa classe Record. Da maneira que implementamos a classe Record,
sempre que tentarmos atribuir um valor a uma propriedade de um objeto
derivado dessa classe, o método __set() será executado, interceptando
essa atribuição. Caso haja algum método na classe nomeado por
set_<propriedade>, ele será executado no lugar da simples atribuição.
Dessa forma, se estivermos atribuindo um valor para a propriedade nome,
o método __set() irá veri car se há algum método nomeado por
set_nome() na classe e irá executá-lo. Caso contrário, o valor será atribuído
à propriedade diretamente.
Com o exemplo a seguir, procuramos demonstrar como podemos
proteger algumas propriedades internas de um objeto. Para isso, alteramos
a classe Active Record Produto e criamos o método set_estoque(), que irá
proteger a atribuição à propriedade estoque. Sempre que for alterado o
conteúdo dessa propriedade, automaticamente esse método entrará em
funcionamento. O método set_estoque() receberá o novo valor que está
sendo atribuído e testará se este é numérico e maior que zero. Caso passe
neste teste, será atribuído internamente ao vetor $data, que armazena os
atributos do objeto. Caso contrário, uma exceção será lançada. Essa
exceção será tratada no escopo principal do programa pelo catch.
classes/model/Produto.php
<?php
class Produto extends Record {
const TABLENAME = 'produto';
public function set_estoque($estoque) {
if (is_numeric($estoque) AND $estoque >0) {
$this->data['estoque'] = $estoque;
}
else {
throw new Exception("Estoque {$estoque} inválido em ".__CLASS__);
}
}
}
Agora vamos escrever um pequeno trecho de código para testar o
encapsulamento. Neste programa, tentaremos atribuir um valor inválido a
um atributo de um objeto Produto. Para isso, o programa inicia com a
importação das classes necessárias. Em seguida, abrimos uma transação
com a base estoque e habilitamos o log LogerTXT para a transação,
gravando no arquivo /tmp/log_protect.txt.
Primeiro, carregamos o objeto com ID 2 da classe Produto. Em seguida,
alteramos a propriedade estoque, tentando armazenar nela um valor
inválido “dois”. Nesse instante, o método set_estoque() automaticamente
entra em cena, validando essa atribuição. Como uma exceção é lançada, a
execução segue para o bloco catch, no qual a mensagem de exceção é
exibida.
record_protect.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/api/Record.php';
require_once 'classes/model/Produto.php';
try {
Transaction::open('estoque');
Transaction::setLogger(new LoggerTXT('/tmp/log_protect.txt'));
Transaction::log('Protegendo o acesso a um produto');
$p1 = Produto::find(2);
$p1->estoque = 'dois';
$p1->store();
Transaction::close();
}
catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
/tmp/log_protect.txt
2015-05-31 22:22:53 :: Protegendo o acesso a um produto
2015-05-31 22:22:53 :: SELECT * FROM produto WHERE id=2
Resultado:
Estoque dois inválido em Produto
classes/api/Criteria.php
<?php
class Criteria {
private $filters; // armazena a lista de filtros
function __construct() {
$this->filters = array();
}
public function add($variable, $compare_operator, $value,
$logic_operator = 'and') {
// na primeira vez, não precisamos concatenar
if (empty($this->filters)) {
$logic_operator = NULL;
}
$this->filters[] = [$variable, $compare_operator, $this-
>transform($value),
$logic_operator];
}
private function transform($value) {
// caso seja um array
if (is_array($value)) {
foreach ($value as $x) {
if (is_integer($x)) {
$foo[]= $x;
}
else if (is_string($x)) {
$foo[]= "'$x'";
}
}
// converte o array em string separada por ","
$result = '(' . implode(',', $foo) . ')';
}
else if (is_string($value)) {
$result = "'$value'";
}
else if (is_null($value)) {
$result = 'NULL';
}
else if (is_bool($value)) {
$result = $value ? 'TRUE' : 'FALSE';
}
else {
$result = $value;
}
return $result; // retorna o valor
}
public function dump() {
// concatena a lista de expressões
if (is_array($this->filters) and count($this->filters) > 0) {
$result = '';
foreach ($this->filters as $filter) {
$result .= $filter[3] . ' ' . $filter[0] . ' ' . $filter[1] .
' '. $filter[2] . ' ';
}
$result = trim($result);
return "({$result})";
}
}
public function setProperty($property, $value) {
if (isset($value)) {
$this->properties[$property] = $value;
}
else {
$this->properties[$property] = NULL;
}
}
public function getProperty($property) {
if (isset($this->properties[$property])) {
return $this->properties[$property];
}
}
}
Depois de criarmos as classes para construção de critérios, criaremos um
pequeno programa para exempli car sua utilização. Neste ponto
utilizaremos os critérios de maneira isolada, o que não faz muito sentido
de início. Depois, utilizaremos os critérios criados em conjunto com
classes para manipulação de coleções de objetos que permitirão utilizar os
critérios para executar operações como carga, contagem e exclusão de
conjuntos de objetos.
Nos exemplos a seguir, temos critérios baseados nos operadores lógicos OR
e AND. Lembre-se de que o operador default é o AND. Junto a esses
operadores podemos escrever operações contendo operadores como >, <,
>=, <=, além de IN, NOT IN, entre outros. Veja que nos exemplos a seguir
construímos ltros utilizando arrays, strings, números e valores
booleanos.
criteria.php
<?php
// carrega as classes necessárias
require_once 'classes/api/Criteria.php';
// Critério simples com OR, e filtros com valores inteiros
$criteria = new Criteria;
$criteria->add('idade', '<', 16);
$criteria->add('idade', '>', 60, 'or');
print $criteria->dump() . "<br>\n";
// Critério simples com AND, e filtros com vetores de inteiros
$criteria = new Criteria;
$criteria->add('idade','IN', array(24,25,26));
$criteria->add('idade','NOT IN', array(10));
print $criteria->dump() . "<br>\n";
// Critério simples com OR, e filtros com Like
$criteria = new Criteria;
$criteria->add('nome', 'like', 'pedro%');
$criteria->add('nome', 'like', 'maria%', 'or');
print $criteria->dump() . "<br>\n";
// Critério simples com AND e filtros usando IS NOT NULL e "="
$criteria = new Criteria;
$criteria->add('telefone', 'IS NOT', NULL);
$criteria->add('sexo', '=', 'F');
print $criteria->dump() . "<br>\n";
// Critério simples com AND, e filtros usando IN/NOT IN sobre vetores de
strings
$criteria = new Criteria;
$criteria->add('UF', 'IN', array('RS', 'SC', 'PR'));
$criteria->add('UF', 'NOT IN', array('AC', 'PI'));
print $criteria->dump() . "<br>\n";
Resultado:
(idade < 16 or idade > 60)
(idade IN (24,25,26) and idade NOT IN (10))
(nome like 'pedro%' or nome like 'maria%')
(telefone IS NOT NULL and sexo = 'F')
(UF IN ('RS','SC','PR') and UF NOT IN ('AC','PI'))
classes/api/Repository.php
<?php
final class Repository {
private $activeRecord; // classe manipulada pelo repositório
function __construct($class) {
$this->activeRecord = $class;
}
O método load() será responsável por carregar na memória uma coleção
de objetos. Ele receberá um critério de seleção e construirá uma instrução
SELECT. Caso algum critério seja informado, ele usará o método dump() da
classe Criteria para a montagem do SQL. Também usará o método
getProperty() para veri car se o critério tem propriedades de nidas como
ORDER BY, LIMIT ou OFFSET. Em seguida, esse método irá detectar se existe
alguma transação aberta pelo método Transaction::get(). Caso haja
alguma transação aberta, ele registrará o SQL no log e enviará a consulta
para o banco de dados por meio do método query(). Se a consulta gerar
resultados, estes serão percorridos pelo método fetchObject(), que irá
instanciar objetos baseados na classe passada como parâmetro. Depois
disso, o resultado (um array de objetos) é retornado.
function load(Criteria $criteria) {
// instancia a instrução de SELECT
$sql = "SELECT * FROM " . constant($this-
>activeRecord.'::TABLENAME');
// obtém a cláusula WHERE do objeto criteria.
if ($criteria) {
$expression = $criteria->dump();
if ($expression) {
$sql .= ' WHERE ' . $expression;
}
// obtém as propriedades do critério
$order = $criteria->getProperty('order');
$limit = $criteria->getProperty('limit');
$offset= $criteria->getProperty('offset');
// obtém a ordenação do SELECT
if ($order) {
$sql .= ' ORDER BY ' . $order;
}
if ($limit) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
}
// obtém transação ativa
if ($conn = Transaction::get()) {
Transaction::log($sql); // registra mensagem de log
// executa a consulta no banco de dados
$result= $conn->Query($sql);
$results = array();
if ($result) {
// percorre os resultados da consulta, retornando um objeto
while ($row = $result->fetchObject($this->activeRecord)) {
// armazena no array $results;
$results[] = $row;
}
}
return $results;
}
else {
throw new Exception('Não há transação ativa!!');
}
}
O método delete(), da mesma forma que o load(), irá receber um critério
como parâmetro. Com base nesse critério será construída uma instrução
de DELETE.
O programador utilizará o critério para especi car quais registros deseja
excluir da base de dados. Novamente a instrução SQL é montada com o
uso do método dump() do critério, caso o critério seja passado como
parâmetro. Novamente é veri cado se há uma transação ativa. Se houver,
a instrução SQL é registrada no log e em seguida é executada.
function delete(Criteria $criteria) {
$expression = $criteria->dump();
$sql = "DELETE FROM " . constant($this-
>activeRecord.'::TABLENAME');
if ($expression) {
$sql .= ' WHERE ' . $expression;
}
// obtém transação ativa
if ($conn = Transaction::get()) {
Transaction::log($sql); // registra mensagem de log
$result = $conn->exec($sql); // executa instrução de DELETE
return $result;
}
else {
throw new Exception('Não há transação ativa!!');
}
}
O método count() irá contar quantos objetos satisfazem a um dado
critério. Para isso, o critério é recebido como parâmetro do método. Para
fazer a contagem, novamente é construído um SELECT, que irá retornar
uma coluna count(*). Em seguida é veri cado se há transação ativa, e a
consulta ao banco de dados é executada, retornando o número de
registros encontrados.
function count(Criteria $criteria) {
$expression = $criteria->dump();
$sql = "SELECT count(*) FROM " . constant($this-
>activeRecord.'::TABLENAME');
if ($expression) {
$sql .= ' WHERE ' . $expression;
}
// obtém transação ativa
if ($conn = Transaction::get()) {
Transaction::log($sql); // registra mensagem de log
$result= $conn->query($sql); // executa instrução de SELECT
if ($result) {
$row = $result->fetch();
}
return $row[0]; // retorna o resultado
}
else {
throw new Exception('Não há transação ativa!!');
}
}
}
Nos próximos exemplos será demonstrada a utilização da classe
Repository.
colecao_produtos.sql
DELETE FROM produto;
INSERT INTO produto (descricao, estoque, preco_custo, preco_venda,
codigo_barras, data_cadastro, origem) VALUES ('Pendrive
512Mb','10.0','20.0','40.0','0000000001','2015-01-01','N');
INSERT INTO produto (descricao, estoque, preco_custo, preco_venda,
codigo_barras, data_cadastro, origem) VALUES ('HD 120
GB','20.0','100.0','180.0','0000000002','2015-01-01','N');
...
collection_get.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Criteria.php';
require_once 'classes/api/Repository.php';
require_once 'classes/api/Record.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/model/Produto.php';
try {
// inicia a transação com a base de dados
Transaction::open('estoque');
// define o arquivo para LOG
Transaction::setLogger(new LoggerTXT('/tmp/log_collection_get.txt'));
// define o critério de seleção
$criteria = new Criteria;
$criteria->add('estoque', '>', 10);
$criteria->add('origem', '=', 'N');
// cria o repositório
$repository = new Repository('Produto');
// carrega os objetos, conforme o critério
$produtos = $repository->load($criteria);
if ($produtos) {
echo "Produtos <br>\n";
// percorre todas objetos
foreach ($produtos as $produto) {
echo ' ID: ' . $produto->id;
echo ' - Descricao: ' . $produto->descricao;
echo ' - Estoque: ' . $produto->estoque;
echo "<br>\n";
}
}
/tmp/log_collection_get.txt
2015-06-06 20:18:12 :: SELECT * FROM produto WHERE (estoque > 10 AND
origem = 'N')
2015-06-06 20:18:12 :: SELECT count(*) FROM produto WHERE (estoque > 10
AND origem = 'N')
Resultado:
Produtos
ID: 2 - Descricao: HD 120 GB - Estoque: 20.0
ID: 19 - Descricao: CART. 8767 NEGRO - Estoque: 20.0
ID: 20 - Descricao: CD-R TUBO DE 100 52X 700MB - Estoque: 20.0
ID: 22 - Descricao: MOUSE PS2 A7 AZUL/PLATA - Estoque: 20.0
ID: 24 - Descricao: TEC. USB ABNT AK-806 - Estoque: 14.0
Quantidade: 5
collection_update.php
<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Criteria.php';
require_once 'classes/api/Repository.php';
require_once 'classes/api/Record.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/model/Produto.php';
try {
// inicia a transação com a base de dados
Transaction::open('estoque');
// define o arquivo para LOG
Transaction::setLogger(new
LoggerTXT('/tmp/log_collection_update.txt'));
// define o critério de seleção
$criteria = new Criteria;
$criteria->add('preco_venda', '<=', 35);
$criteria->add('origem', '=', 'N');
// cria o repositório
$repository = new Repository('Produto');
// carrega os objetos, conforme o critério
$produtos = $repository->load($criteria);
if ($produtos) {
// percorre todas objetos
foreach ($produtos as $produto) {
$produto->preco_venda *= 1.3;
$produto->store();
}
}
Transaction::close(); // fecha a transação
}
catch (Exception $e) {
echo $e->getMessage();
Transaction::rollback();
}
/tmp/log_collection_update.txt
2015-06-07 17:41:21 :: SELECT * FROM produto WHERE (preco_venda <= 35
AND origem = 'N')
2015-06-07 17:41:21 :: SELECT * FROM produto WHERE id=3
2015-06-07 17:41:21 :: UPDATE produto SET descricao = 'SD CARD 512MB',
estoque = '4.0', preco_custo = '20.0', preco_venda = 45.5,
codigo_barras = '0000000003', data_cadastro = '2015-01-01', origem =
'N' WHERE id=3
2015-06-07 17:41:21 :: SELECT * FROM produto WHERE id=22
2015-06-07 17:41:21 :: UPDATE produto SET descricao = 'MOUSE PS2 A7
AZUL/PLATA', estoque = '20.0', preco_custo = '5.0', preco_venda =
19.5, codigo_barras = '0000000022', data_cadastro = '2015-01-01',
origem = 'N' WHERE id=22
collection_delete.php
<?php
// carrega as classes necessárias
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Criteria.php';
require_once 'classes/api/Repository.php';
require_once 'classes/api/Record.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/model/Produto.php';
try {
// inicia a transação com a base de dados
Transaction::open('estoque');
// define o arquivo para LOG
Transaction::setLogger(new
LoggerTXT('/tmp/log_collection_delete.txt'));
// define o critério de seleção
$criteria = new Criteria;
$criteria->add('descricao', 'like', '%WEBC%');
$criteria->add('descricao', 'like', '%FILMAD%', 'or');
// cria o repositório
$repository = new Repository('Produto');
// exclui os objetos, conforme o critério
$repository->delete($criteria);
Transaction::close(); // fecha a transação
}
catch (Exception $e) {
echo $e->getMessage();
Transaction::rollback();
}
/tmp/log_collection_delete.txt
2015-06-07 17:56:20 :: DELETE FROM produto WHERE (descricao like
'%WEBC%' OR descricao like '%FILMAD%')
CAPÍTULO 6
Apresentação e controle
Aquele que conhece os outros é sábio; mas quem conhece a si mesmo é iluminado! Aquele
que vence os outros é forte; mas aquele que vence a si mesmo é poderoso! Seja humilde, e
permanecerás íntegro.
L -T
Nos capítulos anteriores vimos fundamentos de orientação a objetos,
acesso à base de dados e persistência de objetos. Agora que já concluímos
essa camada da aplicação, precisamos nos preocupar com outros aspectos,
como a sua interface com o usuário, a interpretação e a execução de ações
( uxo de controle). Neste capítulo, desenvolveremos uma série de classes
com o objetivo de construir o visual da aplicação e também de interpretar
as ações solicitadas pelo usuário, coordenando o uxo de execução da
aplicação. Como vamos criar uma grande quantidade de classes,
precisaremos antes organizá-las sob uma estrutura de diretórios e
namespaces. Para começar, vamos abordar o padrão MVC.
Lib/Livro/Database/Record.php
<?php
namespace Livro\Database;
use Exception;
abstract class Record implements RecordInterface
...
App/Model/Cidade.php
<?php
use Livro\Database\Record;
class Cidade extends Record
...
Os arquivos de con guração, que compreendem, entre outras coisas, as
de nições de acesso às bases de dados, estarão localizados na pasta
App/Con g. No arquivo a seguir temos como exemplo a de nição de acesso
a uma base de dados chamada livro, que por sua vez representa um
banco de dados SQLite.
App/Con g/livro.ini
host = localhost
name = App/Database/livro.db
user =
pass =
type = sqlite
Lib/Livro/Core/ClassLoader.php
<?php
namespace Livro\Core;
class ClassLoader {
protected $prefixes = array();
public function register() {
spl_autoload_register(array($this, 'loadClass'));
}
public function addNamespace($prefix, $base_dir, $prepend = false) {
}
public function loadClass($class) {
}
}
Sempre que precisarmos utilizar a classe ClassLoader para realizar a carga
das classes de /Lib, deveremos proceder da maneira apresentada a seguir.
Primeiro, precisaremos carregar a classe ClassLoader. Em seguida iremos
instanciá-la e adicionar um namespace para ser carregado. Por meio do
método addNamespace(), estamos dizendo para a classe que o namespace
Livro está no diretório Lib/Livro. É a partir desse diretório que as classes
serão carregadas. O método register() executa a função
spl_autoload_register(), que registra internamente o método loadClass()
para efetuar a carga das classes.
<?php
require_once 'Lib/Livro/Core/ClassLoader.php';
$al= new Livro\Core\ClassLoader;
$al->addNamespace('Livro', 'Lib/Livro');
$al->register();
Lib/Livro/Core/AppLoader.php
<?php
namespace Livro\Core;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use Exception;
class AppLoader {
protected $directories;
public function addDirectory($directory) {
$this->directories[] = $directory;
}
public function register() {
spl_autoload_register(array($this, 'loadClass'));
}
public function loadClass($class) {
}
}
Sempre que precisarmos utilizar a classe AppLoader para realizar a carga
das classes de /App, deveremos proceder da maneira apresentada a seguir.
Primeiro, precisaremos carregar a classe AppLoader. Em seguida iremos
instanciá-la e adicionar os diretórios que serão vasculhados para localizar
as classes da aplicação. Neste caso registramos os diretórios App/Control e
App/Model. O método register() executa a função spl_autoload_register(),
que registra internamente o método loadClass() para efetuar a carga das
classes.
<?php
require_once 'Lib/Livro/Core/AppLoader.php';
$al= new Livro\Core\AppLoader;
$al->addDirectory('App/Control');
$al->addDirectory('App/Model');
$al->register();
Observação: como pode ser visto, as classes da aplicação farão parte do escopo
global.
exemplo_loader.php
<?php
require_once 'Lib/Livro/Core/ClassLoader.php';
$al= new Livro\Core\ClassLoader;
$al->addNamespace('Livro', 'Lib/Livro');
$al->register();
use Livro\Database\Connection;
$obj1 = Connection::open('livro');
var_dump($obj1);
Resultado:
object(PDO)#2 (0) { }
App/Control/PessoaControl.php
<?php
use Livro\Database\Transaction;
use Livro\Database\Repository;
use Livro\Database\Criteria;
class PessoaControl {
public function listar() {
try {
Transaction::open('livro');
$criteria = new Criteria;
$criteria->setProperty('order', 'id');
$repository = new Repository('Pessoa');
$pessoas = $repository->load($criteria);
if ($pessoas) {
foreach ($pessoas as $pessoa) {
print "{$pessoa->id} - {$pessoa->nome}<br>";
}
}
Transaction::close();
}
catch (Exception $e) {
print $e->getMessage();
}
}
public function show( $param ) {
if ($param['method'] == 'listar') {
$this->listar();
}
}
}
Já construímos a classe de controle PessoaControl, agora precisamos criar
um ponto de acesso a ela. Para isso, vamos criar o arquivo pessoas.php, que
centralizará todas as requisições relativas ao cadastro de pessoas. Ao
implementarmos o padrão Page Controller, acabamos tendo vários pontos
de entrada no sistema, tal como o pessoas.php. O arquivo pessoas.php inicia
com o registro dos autoloaders ClassLoader e AppLoader para que as classes
sejam carregadas automaticamente. Em seguida, instancia um objeto da
classe PessoaControl e executa seu método show(). Nesse instante a classe
PessoaControl, por meio do método show(), avalia a URL recebida ($_GET) e
decide qual método deve ser executado por meio do parâmetro method. A
gura 6.2 apresenta de maneira resumida as classes envolvidas nesse
processo.
pessoas.php
<?php
// Lib loader
require_once 'Lib/Livro/Core/ClassLoader.php';
$al= new Livro\Core\ClassLoader;
$al->addNamespace('Livro', 'Lib/Livro');
$al->register();
// App loader
require_once 'Lib/Livro/Core/AppLoader.php';
$al= new Livro\Core\AppLoader;
$al->addDirectory('App/Control');
$al->addDirectory('App/Model');
$al->register();
$pagina = new PessoaControl;
$pagina->show( $_GET );
A gura 6.3 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos pela URL pessoas.php?method=listar (observe
o diretório cap5), onde quer que esteja rodando nosso servidor de páginas.
Veja que então os dados das pessoas são apresentados na tela.
Figura 6.3 – Listagem de pessoas.
index.php
<?php
// Library loader
require_once 'Lib/Livro/Core/ClassLoader.php';
$al= new Livro\Core\ClassLoader;
$al->addNamespace('Livro', 'Lib/Livro');
$al->register();
// Application loader
require_once 'Lib/Livro/Core/AppLoader.php';
$al= new Livro\Core\AppLoader;
$al->addDirectory('App/Control');
$al->addDirectory('App/Model');
$al->register();
// Vendor
$loader = require 'vendor/autoload.php';
$loader->register();
if ($_GET) {
$class = $_GET['class'];
if (class_exists($class)) {
$pagina = new $class;
$pagina->show();
}
}
Como o comportamento do método show() será comum a todas as classes
de controle, podemos generalizá-lo criando uma superclasse. Vamos
chamar essa classe de Page. Assim, toda classe de controle será lha de
Page. Como ela será comum a todo um conjunto de classes do sistema
(controllers), ela também é um exemplo do design pattern Layer
Supertype.
A classe Page basicamente oferecerá para suas lhas o método show(), que
por sua vez investigará a partir da URL qual ação o usuário deseja
executar dentro da página visitada. Assim, se o usuário acessar a URL
index.php?class=ClienteControl&method=listar, cará claro que ele deseja
executar o método listar() da classe ClienteControl.
O método show() veri cará se há uma requisição ($_GET), se há uma classe
identi cada (class) e se o método informado existe (method_exists). Se as
condições necessárias forem atingidas, então o método correspondente
será executado por meio da função call_user_func().
Lib/Livro/Control/Page.php
<?php
namespace Livro\Control;
class Page {
public function show() {
if ($_GET) {
$class = isset($_GET['class']) ? $_GET['class'] : NULL;
$method = isset($_GET['method']) ? $_GET['method'] : NULL;
if ($class) {
$object = $class == get_class($this) ? $this : new $class;
if (method_exists($object, $method)) {
call_user_func(array($object, $method), $_GET);
}
}
}
}
}
A gura 6.4 demonstra o funcionamento do design pattern Front
Controller. A partir de um ponto centralizador (index.php), este recebe uma
requisição HTTP ($_GET) e, a partir dessa requisição, identi ca a classe a
ser executada (class). Então, instancia a classe requisitada executando seu
método show(). Como a classe requisitada será lha de Page, esse método
será herdado. No cenário demonstrado a seguir, a requisição HTTP
(URL) identi ca que a classe a ser executada é CidadeControl e o método a
ser executado é listar(). Também podemos ver que a classe CidadeControl
depende da classe de modelo Cidade para executar sua ação.
Figura 6.4 – Front Controller.
Por m, vamos construir a classe de controle. Ela é muito semelhante à
classe já criada anteriormente PessoaControl. Entretanto, CidadeControl é
lha de Page, que fornece o método show(), que é responsável por decidir
o método a ser executado. Assim, o script index.php, ao receber como
parâmetro class=CidadeControl&method=listar, simplesmente executa o
método show() desta que, por sua vez, irá veri car a URL e descobrir que
o método a ser executado é o listar(), executando-o.
A classe CidadeControl contém o método listar(), que abre uma transação
com a base de dados, listando diretamente os dados das cidades. Para isso
é importante que exista a classe Cidade, lha de Record no diretório
App/Model.
App/Control/CidadeControl.php
<?php
use Livro\Control\Page;
use Livro\Database\Transaction;
use Livro\Database\Repository;
use Livro\Database\Criteria;
class CidadeControl extends Page {
public function listar() {
try {
Transaction::open('livro');
$criteria = new Criteria;
$criteria->setProperty('order', 'id');
$repository = new Repository('Cidade');
$cidades = $repository->load($criteria);
if ($cidades) {
foreach ($cidades as $cidade) {
print "{$cidade->id} - {$cidade->nome}<br>";
}
}
Transaction::close();
}
catch (Exception $e) {
print $e->getMessage();
}
}
}
A gura 6.5 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessar pela URL index.php?class=CidadeControl&method=listar,
onde quer que esteja rodando nosso servidor de páginas. Veja que então
os dados das cidades são apresentados em tela.
rest.php
<?php
header('Content-Type: application/json; charset=utf-8');
// Lib loader
require_once 'Lib/Livro/Core/ClassLoader.php';
$al= new Livro\Core\ClassLoader;
$al->addNamespace('Livro', 'Lib/Livro');
$al->register();
// App loader
require_once 'Lib/Livro/Core/AppLoader.php';
$al= new Livro\Core\AppLoader;
$al->addDirectory('App/Control');
$al->addDirectory('App/Model');
$al->addDirectory('App/Services');
$al->register();
class LivroRestServer {
public static function run($request) {
$class = isset($request['class']) ? $request['class'] : '';
$method = isset($request['method']) ? $request['method'] : '';
$response = NULL;
try {
if (class_exists($class)) {
if (method_exists($class, $method)) {
$response = call_user_func(array($class, $method), $request);
return json_encode( array('status' => 'success', 'data' =>
$response));
}
else {
$error_message = "Método {$class}::{$method} não encontrado";
return json_encode( array('status' => 'error', 'data' =>
$error_message));
}
}
else {
$error_message = "Classe {$class} não encontrada";
return json_encode( array('status' => 'error', 'data' =>
$error_message));
}
}
catch (Exception $e) {
return json_encode( array('status' => 'error', 'data' => $e-
>getMessage()));
}
}
}
print LivroRestServer::run($_REQUEST);
O próximo passo é escrever uma classe de serviço, uma classe que
responderá às requisições dos clientes, ou seja, de quem requisita o
serviço. O arquivo rest.php é somente o ponto de acesso, pois o que ele
faz é redirecionar a execução para uma classe de serviço. Em primeiro
lugar, provavelmente você já tem uma série de classes de modelo
representando suas tabelas na pasta App/Model, como a classe Pessoa.
Agora, queremos criar um serviço para retornar os dados de uma pessoa.
Para isso, precisamos criar uma classe de serviço que age desenvolvendo
uma espécie de camada que interage com uma funcionalidade interna da
aplicação e expõe essa funcionalidade para o mundo externo. Neste caso,
a classe será chamada PessoaServices e será salva em
App/Services/PessoaServices.php. A classe PessoaServices disponibilizará
um método getData(), que por sua vez receberá o código da pessoa dentro
dos parâmetros do request ($request). Com base nesse código, abrirá uma
transação com a base de dados, localizará a pessoa correspondente por
meio do método find() e retornará a pessoa na forma de array, o que é
feito pelo método toArray(). Caso a pessoa não seja encontrada, uma
exceção será lançada.
App/Services/PessoaServices.php
<?php
use Livro\Database\Transaction;
class PessoaServices {
public static function getData($request) {
$id_pessoa = $request['id'];
$pessoa_array = array();
Transaction::open('livro'); // inicia transação
$pessoa = Pessoa::find($id_pessoa);
if ($pessoa) {
$pessoa_array = $pessoa->toArray();
}
else {
throw new Exception("Pessoa {$id_pessoa} não encontrado");
}
Transaction::close(); // fecha transação
return $pessoa_array;
}
}
Agora que já escrevemos a classe de serviço, o próximo passo é escrever o
programa cliente, que requisitará o serviço. O programa cliente pode ser
escrito em qualquer linguagem de programação com capacidade de fazer
uma requisição HTTP. Mas, como nosso livro é sobre PHP, tanto o
servidor quanto o cliente serão escritos na mesma linguagem. Você pode
exercitar a chamada remota gravando o client.php em outro computador.
O client.php basicamente faz uma requisição HTTP para o endereço
identi cado por $location, que deve apontar para o endereço HTTP do
servidor (rest.php). Esta requisição é feita por meio da função
file_get_contents(), que tem capacidade de obter um arquivo
remotamente (HTTP). Antes de fazer a requisição, precisamos de nir os
parâmetros da transmissão. De nimos os parâmetros pelo vetor
$parameters. No entanto, para serem lidos corretamente no outro lado,
precisamos codi cá-los por meio da função http_build_query(), que lê um
vetor e o converte ao formato adequado de uma requisição HTTP.
Usamos o var_dump() para exibir o output.
client.php
<?php
$location = 'http://localhost/phpoo/rest.php';
$parameters = [];
$parameters['class'] = 'PessoaServices';
$parameters['method'] = 'getData';
$parameters['id'] = '1';
$url = $location . '?' . http_build_query($parameters);
var_dump( json_decode( file_get_contents($url) ) );
Resultado:
object(stdClass)#1 (2) {
["status"]=>
string(7) "success"
["data"]=>
object(stdClass)#2 (7) {
["id"]=> string(1) "1"
["nome"]=> string(14) "Penelope Terry"
["endereco"]=> string(24) "Penelope Terry Street, 1"
["bairro"]=> string(6) "Centro"
["telefone"]=> string(14) "(88) 1234-5678"
["email"]=> string(18) "naoenvie@email.com"
["id_cidade"]=> string(2) "18"
}
}
6.5.1 Componentes
Uma das formas usadas para isolar comportamentos relacionados a
aspectos visuais é a criação de componentes. Componentes podem ser
utilizados para várias nalidades. Eles podem representar formulários,
campos, datagrids, janelas, diálogos, caixas de seleção, entre outros itens.
Praticamente qualquer elemento visual de uma interface pode ser
modelado na forma de um componente, com atributos e métodos
próprios.
Quantas vezes você precisou montar um formulário ou uma datagrid?
Você deve ter percebido o quão repetitivo é esse processo. Quantas vezes
você não precisou copiar e colar trechos idênticos para colocar campos de
formulários, colunas de datagrids e botões de ação na tela. Em todos esses
casos em que detectamos um comportamento “padronizado” podemos
construir um componente para representar esse comportamento de
maneira genérica.
Para demonstrar esse conceito, vamos criar um componente que
representará um formulário. No capítulo seguinte, construiremos diversos
componentes de maneira mais apropriada. A nalidade aqui é somente
demonstrar o conceito. Então a forma de implementação não será
necessariamente a ideal.
Para criar um formulário, é preciso de nir seu nome, um título, quais
serão seus campos e qual será sua ação. Para isso, será declarada a classe
SimpleForm, que terá a série de métodos demonstrados a seguir. O método
construtor será usado para de nir o nome do formulário, bem como
inicializar alguns atributos. O método setTitle() será usado para de nir
um título para o formulário. O método addField() será usado para
acrescentar um campo ao formulário, e ele receberá informações como:
rótulo do campo, nome, tipo, valor e opcionalmente uma classe para
estilo. O método setAction() de nirá a ação do formulário.
Todos os métodos vistos até o momento simplesmente receberam as
informações e armazenaram-nas em atributos do objeto ($this->name,
$this->title, $this->fields, $this->action etc.). O método show() será
responsável por “ler” esses atributos e montar visualmente o formulário
resultante, utilizando tags de formatação HTML, como <div>, <form>,
<input>, entre outras. Neste exemplo estamos usando classes especí cas da
biblioteca Bootstrap. Ao utilizarmos essa abordagem, isolamos os
aspectos relacionados à montagem “visual” de formulários. Assim,
podemos reaproveitar a classe SimpleForm em muitos locais do sistema,
reduzindo o tamanho do código-fonte nal. Além disso, caso quisermos
em algum momento alterar a lógica de construção de formulários,
utilizando outros elementos HTML ou outras classes de estilo,
precisaremos apenas alterar o componente, e não os vários locais nos
quais esse componente é utilizado.
Observação: a classe SimpleForm foi criada aqui apenas para demonstrar um
conceito, ela não é totalmente funcional. No capítulo 7 criaremos uma classe para
formulários totalmente funcional.
Lib/Livro/Widgets/Form/SimpleForm.php
<?php
namespace Livro\Widgets\Form;
class SimpleForm {
private $name, $action, $fields, $title;
public function __construct($name) {
$this->name = $name;
$this->fields = array();
$this->title = '';
}
public function setTitle($title) {
$this->title = $title;
}
public function addField($label, $name, $type, $value, $class = '') {
$this->fields[] = array( 'label' => $label, 'name' => $name, 'type'
=> $type,
'value' => $value, 'class' => $class);
}
public function setAction($action) {
$this->action = $action;
}
public function show() {
echo "<div class='panel panel-default' style='margin: 40px;'>\n";
echo "<div class='panel-heading'> {$this->title} </div>\n";
echo "<div class='panel-body'>\n";
echo "<form method='POST' action='{$this->action}' class='form-
horizontal'>\n";
if ($this->fields) {
foreach ($this->fields as $field) {
echo "<div class='form-group'>\n";
echo "<label class='col-sm-2 control-label'> {$field['label']}
</label>\n";
echo "<div class='col-sm-10'>\n";
echo "<input type='{$field['type']}' name='{$field['name']}'
value='{$field['value']}' class='{$field['class']}'>\n";
echo "</div>\n";
echo "</div>\n";
}
echo "<div class='form-group'>\n";
echo "<div class='col-sm-offset-2 col-sm-8'>\n";
echo "<input type='submit' class='btn btn-success'
value='enviar'>\n";
echo "</div>\n";
echo "</div>\n";
}
echo "</form>";
echo "</div>";
echo "</div>";
}
}
Após de nirmos a classe, vamos utilizá-la. Assim, criaremos uma página
chamada SimpleFormControl ( lha de Page), como demonstrado nas seções
em que foram explicados os padrões Page Controller e Front Controller.
No método construtor da página, estamos instanciando o formulário,
de nindo o seu nome. Em seguida, é utilizado o método setTitle() para
de nir o título do formulário. Depois disso, é utilizado o método
addField() para adicionar alguns campos, de nindo seu rótulo, nome,
tipo, conteúdo e classe de estilo. Em seguida, é de nida a ação do
formulário, que por sua vez aponta para o Front Controller index.php,
passando como parâmetros o nome da classe SimpleFormControl e o
método onGravar(), que será executado quando o usuário clicar no botão
de submissão do formulário. Por m, o formulário é exibido por meio do
método show(), que “lê” todas as de nições feitas até então e “constrói” o
HTML resultante. O método onGravar(), quando acionado, simplesmente
exibe o POST na tela.
Observação: neste exemplo precisamos especificar as classes utilizadas no início
do programa (Page, SimpleForm) por meio do operador use, já que estão dentro
de namespaces, como vimos no capítulo 4. Lembre-se de que as classes são
carregadas automaticamente pelo autoloader, o que é definido no index.php.
App/Control/SimpleFormControl.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Form\SimpleForm;
class SimpleFormControl extends Page {
public function __construct() {
parent::__construct();
$form = new SimpleForm('my_form');
$form->setTitle('Título');
$form->addField('Nome', 'name', 'text', 'Maria', 'form-control');
$form->addField('Endereço', 'endereco', 'text', 'Rua das flores',
'form-control');
$form->addField('Telefone', 'fone', 'text', '(51) 1234-5678', 'form-
control');
$form->setAction('index.php?
class=SimpleFormControl&method=onGravar');
$form->show();
}
public function onGravar($params) {
echo '<pre>';
var_dump($_POST);
echo '</pre>';
}
}
A gura 6.7 demonstra o uso do programa recém-criado. Para acioná-lo,
basta acessarmos pela URL index.php?class=SimpleFormControl, onde quer que
esteja rodando nosso servidor de páginas. Veja que então o formulário é
apresentado na tela.
Observação: como nosso formulário utiliza classes de estilo da biblioteca
Bootstrap, o front controller index.php foi alterado para incluir essa biblioteca
(bootstrap.min.css).
App/Resources/form.html
<div class="panel panel-default" style="margin: 40px;">
<div class="panel-heading">{{title}}</div>
<div class="panel-body">
<form action="{{action}}" method="POST" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label"> Nome </label>
<div class="col-sm-10">
<input type="text" name="name" value="{{nome}}" class="form-
control">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"> Endereço </label>
<div class="col-sm-10">
<input type="text" name="endereco" value="{{endereco}}"
class="form-control">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"> Telefone </label>
<div class="col-sm-10">
<input type="text" name="telefone" value="{{telefone}}"
class="form-control">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="submit" class="btn btn-success" value="enviar">
</div>
</div>
</form>
</div>
</div>
</div>
Agora que temos um template de nido, vamos utilizá-lo. Para isso, vamos
criar uma página ( lha de Page) chamada TwigSampleControl. Essa classe
será executada por meio do front controller index.php. Dentro dessa classe,
vamos usar a biblioteca Twig. Essa biblioteca requer que instanciemos o
objeto Twig_Loader_Filesystem para indicar a pasta em que se encontram
os templates e o Twig_Environment, responsável por fazer a manipulação do
template.
Usamos o método loadTemplate() para carregar o arquivo de template. Em
seguida, podemos de nir as substituições a ser realizadas por meio de um
vetor. Declaramos o vetor $replaces, que será usado para “guiar” as
substituições no template. Neste caso, foram de nidos conteúdos xos
como “título” ou “Maria” para as substituições. Porém, em uma aplicação
real, os dados devem vir da base de dados na maioria das situações. Ao
nal, o método render() é usado para “renderizar” o conteúdo do HTML,
ou seja, processar seu conteúdo resultante já com as substituições.
Observação: neste exemplo, precisamos especificar as classes utilizadas no início
do programa (Page) por meio do operador use, já que elas estão dentro de
namespaces, como vimos no capítulo 4. Lembre-se de que as classes são
carregadas automaticamente pelo autoloader, o que é definido no index.php.
App/Control/TwigSampleControl.php
<?php
use Livro\Control\Page;
class TwigSampleControl extends Page {
public function __construct() {
parent::__construct();
$loader = new Twig_Loader_Filesystem('App/Resources');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('form.html');
$replaces = array();
$replaces['title'] = 'Título';
$replaces['action'] = 'index.php?
class=TwigSampleControl&method=onGravar';
$replaces['nome'] = 'Maria';
$replaces['endereco'] = 'Rua das flores';
$replaces['telefone'] = '(51) 1234-5678';
$content = $template->render($replaces);
echo $content;
}
public function onGravar($params) {
echo '<pre>';
var_dump($_POST);
echo '</pre>';
}
}
A gura 6.8 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos pela URL index.php?class=TwigSampleControl, onde
quer que esteja rodando nosso servidor de páginas. Veja que então o
formulário é apresentado em tela.
A utilização de um Template View proporciona mais controle sobre o
visual, permitindo ao desenvolvedor “criar” de maneira livre dentro do
template, utilizando tags de formatação conforme a necessidade, bem
como chamadas JavaScript. No entanto, um template tem menos
potencial de reaproveitamento e frequentemente é usado em situações
pontuais. Além disso, geralmente utilizamos referências a tecnologias
especí cas dentro de um template, como estilos CSS ou bibliotecas
JavaScript, que podem se tornar obsoletas com o tempo, tornando um
pouco mais difíceis futuras atualizações tecnológicas, como substituição
de chamadas a bibliotecas, visto que teremos vários locais para alterar.
Lib/Livro/Widgets/Base/Element.php
<?php
namespace Livro\Widgets\Base;
class Element {
protected $tagname;
protected $properties;
protected $children;
public function __construct($name) {
$this->tagname = $name; // define o nome do elemento
}
public function __set($name, $value) {
// armazena os valores atribuídos ao array properties
$this->properties[$name] = $value;
}
public function __get($name) {
// retorna os valores atribuídos ao array properties
return isset($this->properties[$name])? $this->properties[$name] :
NULL;
}
O método add() nos permitirá adicionar conteúdo(s) à tag. Neste caso, a
variável $child poderá ser uma string qualquer, mas também poderá ser
algum outro objeto. Esses “ lhos” serão armazenados no vetor $children,
que será percorrido no momento da exibição da tag na tela por meio do
método show().
public function add($child)
{
$this->children[] = $child;
}
Para exibir a tag na tela, basta executarmos o método show(). Esse método
exibirá, por meio do método open(), a tag de abertura e em seguida
percorrerá todos os objetos lhos por meio de um laço de repetição
foreach sobre a propriedade $children. Se o conteúdo lho ($child) for um
objeto (is_object), executará seu método show(); caso contrário, somente o
exibirá na tela (echo). Além disso, esse método mostrará a tag de
fechamento por meio do método close().
public function show() {
$this->open(); // abre a tag
echo "\n";
if ($this->children) {
foreach ($this->children as $child) {
if (is_object($child)) { // se for objeto
$child->show();
}
else if ((is_string($child)) or (is_numeric($child))) {
// se for texto
echo $child;
}
}
$this->close(); // fecha a tag
}
}
O método open() exibirá a tag de abertura do elemento HTML. Além
disso, veri cará se ela contém propriedades. Por exemplo: se a tag em
questão for img, terá como atributo src; se for div, poderá ter como
atributo style, e assim por diante.
private function open() {
// exibe a tag de abertura
echo "<{$this->tagname}";
if ($this->properties) {
// percorre as propriedades
foreach ($this->properties as $name=>$value) {
if (is_scalar($value)) {
echo " {$name}=\"{$value}\"";
}
}
}
echo '>';
}
private function close() {
echo "</{$this->tagname}>\n";
}
A classe ainda terá um método __toString() que será executado
automaticamente quando o desenvolvedor tratar o objeto como se fosse
uma string, como ao aplicar o comando print $objeto, por exemplo. Nesse
caso, o conteúdo do objeto é retornado na forma de string.
public function __toString() {
ob_start();
$this->show();
$content = ob_get_clean();
return $content;
}
}
A classe Element foi criada com o principal objetivo de facilitar a
construção de novos componentes. Porém, antes de usá-la para a
construção desses componentes, vamos criar um exemplo bem simples
que demonstra sua utilização. No exemplo a seguir, estamos criando uma
página ExemploElementControl e, dentro dela, criamos alguns elementos
(tags) de maneira orientada a objetos. A princípio, criamos uma div, que
ao nal será adicionada à página. Criamos também um parágrafo (p)
contendo o texto “Sport Club Internacional”, uma imagem (img) contendo
“inter.png” e outro parágrafo (p) contendo “Clube do povo do Rio Grande
do Sul”. Cada um desses elementos é adicionado à div, que, por sua vez, é
adicionada à página.
Você deve estar se questionando sobre o porquê de se criar uma classe
para exibir tags HTML na tela, visto que a utilização simples do HTML
implica em menos linhas de código. Neste momento você terá certa
di culdade em assimilar isso, mas que atento aos próximos exemplos
não somente neste capítulo. Sempre que a classe Element for utilizada,
imagine como caria o código caso tivéssemos que exibir o conteúdo em
HTML diretamente na tela por meio de comandos como o echo. Neste
ponto, lembre-se da palavra “legibilidade”, ou seja, o código-fonte ca
mais legível e mais claro de ser interpretado quando se utiliza puramente
uma única linguagem – estamos falando do PHP, e não mais de pedaços
de código HTML inseridos nos programas. Veja o programa a seguir e
con ra na gura 6.9 o resultado do exemplo.
Observação: neste exemplo precisamos especificar as classes utilizadas no início
do programa (Page, Element) por meio do operador use, já que elas estão dentro
de namespaces, como vimos no capítulo 4. Lembre-se de que as classes são
carregadas automaticamente pelo autoloader, o que é definido no index.php.
App/Control/ExemploElementControl.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Base\Element;
class ExemploElementControl extends Page {
public function __construct() {
parent::__construct();
$div= new Element('div');
$div->style = 'text-align:center;';
$div->style.= 'font-weight: bold;';
$div->style.= 'font-size: 14pt';
$p = new Element('p');
$p->add('Sport Club Internacional');
$div->add($p);
$img= new Element('img');
$img->src = 'App/Images/inter.png';
$div->add($img);
$p = new Element('p');
$p->add('Clube do povo do Rio Grande do Sul');
$div->add($p);
parent::add($div);
}
}
A gura 6.9 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=ExemploElementControl, onde quer que esteja rodando nosso servidor
de páginas. Veja que, então, a página será apresentada na tela.
6.6.2 Painéis
O próximo componente que vamos construir é um painel que permitirá
criar uma área delimitada com bordas e um título no topo. Para construir
o painel, vamos utilizar classes de estilo da Bootstrap. Basicamente o
painel (Panel) é lho da classe Element e utiliza uma tag div para
encapsular seu conteúdo. A classe criada terá um método construtor que
receberá o nome do painel a ser criado. Caso o painel tenha título, será
criado outro elemento div com a classe de estilo panel-heading.
O conteúdo do painel é contido em um elemento div cuja classe de estilo
é panel-body. Caso a biblioteca Bootstrap mude no futuro, precisaremos
somente alterar essa classe, e não todos os pontos em que ela é utilizada.
Lib/Livro/Widgets/Container/Panel.php
<?php
namespace Livro\Widgets\Container;
use Livro\Widgets\Base\Element;
class Panel extends Element {
private $body;
private $footer;
public function __construct($panel_title = NULL) {
parent::__construct('div');
$this->class = 'panel panel-default';
if ($panel_title) {
$head = new Element('div');
$head->class = 'panel-heading';
$label = new Element('h4');
$label->add($panel_title);
$title = new Element('div');
$title->class = 'panel-title';
$title->add( $label );
$head->add($title);
parent::add($head);
}
App/Control/ExemploPanelControl.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Container\Panel;
class ExemploPanelControl extends Page {
public function __construct() {
parent::__construct();
$panel = new Panel('Título do painel');
$panel->style = 'margin: 20px';
$panel->add( str_repeat('sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf
sdf sdf <br>', 5) );
$panel->show();
}
}
A gura 6.10 demonstra a utilização do programa recém-criado.
6.6.3 Caixas
Até o momento, construímos elementos que permitem conter outros em
seu interior, como a tabela e o painel. Mas eles não são su cientes para
organizar os objetos de uma página. Dessa forma, ainda vamos criar mais
componentes que ajudem a organizar o layout de outros objetos. Para
iniciar, vamos criar a caixa horizontal (HBox). Nos arquivos de download
você encontrará também a caixa vertical (VBox). Não serão colocados os
códigos-fonte de ambas aqui, pois são pouco diferentes. Basicamente a
caixa horizontal é lha de Element e terá um método chamado add() que
encapsulará os objetos lhos dentro de uma div inline antes de adicioná-
los efetivamente (parent:add) ao objeto pai (que é uma div).
Lib/Livro/Widgets/Container/HBox.php
<?php
namespace Livro\Widgets\Container;
use Livro\Widgets\Base\Element;
class HBox extends Element {
public function __construct() {
parent::__construct('div');
}
public function add($child) {
$wrapper = new Element('div');
$wrapper->{'style'} = 'display:inline-block;';
$wrapper->add($child);
parent::add($wrapper);
return $wrapper;
}
}
App/Control/ExemploBoxControl.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Container\Panel;
use Livro\Widgets\Container\HBox;
class ExemploBoxControl extends Page {
public function __construct() {
parent::__construct();
$panel1 = new Panel('Painel 1');
$panel1->style = 'margin: 10px';
$panel1->add( str_repeat('sdf sdf sdf sdf sdf sdf <br>', 5) );
$panel2 = new Panel('Painel 2');
$panel2->style = 'margin: 10px';
$panel2->add( str_repeat('sdf sdf sdf sdf sdf sdf <br>', 5) );
$box = new HBox;
$box->add($panel1);
$box->add($panel2);
$box->show();
}
}
A gura 6.11 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=ExemploBoxControl, onde quer que esteja rodando nosso servidor de
páginas. Veja que então a página será apresentada na tela.
Figura 6.11 – Resultado do programa.
Lib/Livro/Widgets/Dialog/Message.php
<?php
namespace Livro\Widgets\Dialog;
use Livro\Widgets\Base\Element;
class Message {
public function __construct($type, $message) {
$div = new Element('div');
if ($type == 'info') {
$div->class = 'alert alert-info';
}
else if ($type == 'error') {
$div->class = 'alert alert-danger';
}
$div->add($message);
$div->show();
}
}
Agora que já temos a classe de nida, vamos escrever um exemplo de sua
utilização. No momento em que desejarmos exibir uma mensagem ao
usuário, bastará instanciarmos a classe Message, identi cando o tipo da
mensagem (info, error), bem como a própria mensagem a ser exibida.
Observação: neste exemplo precisamos especificar as classes utilizadas no início
do programa (Page, Message) por meio do operador use, já que elas estão dentro
de namespaces, como vimos no capítulo 4. Lembre-se de que as classes são
carregadas automaticamente pelo autoloader, o que é definido no index.php.
App/Control/ExemploMessageControl.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Dialog\Message;
class ExemploMessageControl extends Page {
public function __construct() {
parent::__construct();
new Message('info', 'Mensagem informativa');
new Message('error', 'Mensagem de erro');
}
}
A gura 6.12 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=ExemploMessageControl, onde quer que esteja rodando nosso servidor
de páginas. Veja que então a página será apresentada na tela.
Figura 6.12 – Resultado do programa.
Assim como mensagens de erro e mensagens de informação,
constantemente precisamos emitir questionamentos ao usuário, como
“Deseja excluir este registro?”, “Deseja con rmar o processamento?”, entre
outros. Um diálogo de questionamento é caracterizado por ter pergunta e
ação correspondente.
O diálogo de questionamento que criaremos (classe Question) terá um
botão de con rmação para a pergunta efetuada e também um botão de
negação. Os botões de ação carão vinculados às ações. Uma ação será
correspondente à determinada URL
(class=CidadeList&method=delete&key=1200), que será executada caso o usuário
con rme a ação (con rmando ou negando). As ações serão de nidas por
meio de objetos da classe Action, que serão mais bem explicados na
próxima seção.
A classe Question receberá em seu método construtor uma pergunta
($message) a ser feita ao usuário, uma ação de con rmação ($action_yes) e
uma ação de negação ($action_no), que por sua vez é opcional. O diálogo
será montado por meio de uma div, com a classe de estilo alert-warning.
Cada ação (objeto Action) passada como parâmetro contém um método
chamado serialize(), que por sua vez retorna uma URL representando a
ação. Basicamente, cada opção de resposta (sim/não) irá gerar um link (a
href) para que o usuário possa clicar e ser direcionado para o método
(URL) correspondente.
Observação: caso você não tenha compreendido totalmente o funcionamento da
classe Action, não se preocupe. Ela será explicada na próxima seção.
Lib/Livro/Widgets/Dialog/Question.php
<?php
namespace Livro\Widgets\Dialog;
use Livro\Control\Action;
use Livro\Widgets\Base\Element;
class Question {
function __construct($message, Action $action_yes, Action $action_no =
NULL) {
$div = new Element('div');
$div->class = 'alert alert-warning question';
// converte os nomes de métodos em URL's
$url_yes = $action_yes->serialize();
$link_yes = new Element('a');
$link_yes->href = $url_yes;
$link_yes->class = 'btn btn-default';
$link_yes->style = 'float:right';
$link_yes->add('Sim');
$message .= ' ' . $link_yes;
if ($action_no) {
$url_no = $action_no->serialize();
$link_no = new Element('a');
$link_no->href = $url_no;
$link_no->class = 'btn btn-default';
$link_no->style = 'float:right';
$link_no->add('Não');
$message .= $link_no;
}
$div->add($message);
$div->show();
}
}
Agora que criamos a classe Question, vamos utilizá-la. De niremos duas
ações ($action1, $action2). Cada ação é um objeto da classe Action e recebe
em seu método construtor um array de duas posições que contém o
objeto e o método que deverão ser executados. A primeira ação está
vinculada ao método onConfirmacao(), e a segunda, ao método onNegacao().
Em seguida, ainda no método construtor, é instanciado um objeto da
classe Question que recebe a mensagem, a ação de con rmação ($action1) e
a ação de negação ($action2). Os métodos onConfirmacao() e onNegacao()
simplesmente emitem mensagens ao usuário pelo comando print. Dessa
forma, caso o usuário con rme, será exibido na tela “Você escolheu
con rmar a questão”; caso negue, será exibido “Você escolheu negar a
questão”.
Observação: neste exemplo precisamos especificar as classes utilizadas no início
do programa (Page, Question) por meio do operador use, já que elas estão dentro
de namespaces, como vimos no capítulo 4. Lembre-se de que as classes são
carregadas automaticamente pelo autoloader, o que é definido no index.php.
App/Control/ExemploQuestionControl.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Dialog\Question;
class ExemploQuestionControl extends Page {
public function __construct() {
parent::__construct();
$action1 = new Action(array($this, 'onConfirmacao'));
$action2 = new Action(array($this, 'onNegacao'));
new Question('Você deseja confirmar a ação?', $action1, $action2);
}
public function onConfirmacao() {
print "Você escolheu confirmar a questão";
}
public function onNegacao() {
print "Você escolheu negar a questão";
}
}
A gura 6.13 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=ExemploQuestionControl, onde quer que esteja rodando nosso servidor
de páginas. Veja que então a página é apresentada na tela.
Figura 6.13 – Resultado do programa.
6.6.5 Ações
No exemplo da seção anterior em que demonstramos a utilização de
diálogos de questionamento, usamos objetos da classe Action para
representar as ações de con rmação e de negação para a pergunta.
Optamos por criar um objeto que representasse uma ação feita pelo
usuário por ser mais claro do que simplesmente passar como parâmetro
uma URL com uma série de variáveis concatenadas.
Quando o usuário clica no botão para editar um registro ou para salvar os
dados de um formulário, uma determinada ação é executada. Geralmente,
mapeamos a ocorrência desses eventos diretamente à execução de uma
função ou um método de um objeto.
Em alguns ambientes, principalmente no ambiente web, que é stateless,
ou seja, não armazena o estado atual de seus objetos durante a transição
de páginas (a não ser que utilizemos seções), torna-se difícil mapear tais
eventos diretamente aos objetos que irão receber (ou executar) a ação.
Nesses casos é fundamental termos a possibilidade de transportar a ação
de forma independente do objeto emissor e do objeto receptor do evento.
Na gura 6.14 é demonstrado um exemplo de ação.
Para transportar uma ação de uma página para outra, utilizamos a
própria URL. O formato que criamos (index.php?class=classe&method=metodo)
é interpretado pelo front controller index.php, que então executa o método
informado da classe correspondente. Para criar a URL de maneira
orientada a objetos, sem que precisemos concatenar strings, vamos criar a
classe Action. Essa classe receberá em seu método construtor um
parâmetro do tipo callback, ou seja, a representação de algo que pode ser
invocado, que no PHP é representado por um array contendo um
objeto/classe e o nome do método.
Lib/Livro/Control/Action.php
<?php
namespace Livro\Control;
class Action implements ActionInterface {
private $action;
private $param;
public function __construct(Callable $action) {
$this->action = $action;
}
public function setParameter($param, $value) {
$this->param[$param] = $value;
}
O método serialize() tratará da conversão da ação (propriedade $action)
em uma string que possa ser passada via URL. Para isso, esse método
primeiro veri ca se a ação ($action) se trata de um array (objeto e
método) por meio da função is_array(). Neste caso, a URL será formada
pelo nome da classe (action[0]) e o nome do método (action[1]). Caso a
primeira posição do vetor seja um objeto, será utilizada a função
get_class() para descobrir a classe desse objeto. A função
http_build_query() trata de construir a URL de acordo com o vetor $url,
no qual fomos criando os parâmetros do endereço URL.
public function serialize() {
// verifica se a ação é um método
if (is_array($this->action)) {
// obtém o nome da classe
$url['class'] = is_object($this->action[0])
? get_class($this->action[0]) : $this->action[0];
// obtém o nome do método
$url['method'] = $this->action[1];
// verifica se há parâmetros
if ($this->param) {
$url = array_merge($url, $this->param);
}
// monta a URL
return '?' . http_build_query($url);
}
}
}
Agora que temos a classe Action de nida, podemos criar um exemplo de
sua utilização. No exemplo a seguir demonstramos o uso da classe Action.
A princípio, criamos uma ação passando como parâmetro do método
construtor o método executaAcao1() da própria classe (this). Em seguida,
executamos duas vezes o método setParameter() para de nir os valores
dos parâmetros codigo e nome. Por m, para veri car se a ação será gerada
no formato correto, utilizamos o método serialize() para gerar a URL
resultante. Como podemos ver, a URL foi gerada no formato correto,
contendo a classe, o método e os parâmetros.
Observação: a classe Action será utilizada principalmente na formação de links
de listagens e também em botões de ação de formulários.
App/Control/ExemploActionControl.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
class ExemploActionControl extends Page {
public function __construct() {
parent::__construct();
$action1 = new Action(array($this, 'executaAcao1'));
$action1->setParameter('codigo', 4);
$action1->setParameter('nome', 'teste');
print $action1->serialize();
}
public function executaAcao1($params) {
}
}
Para acionar o programa recém-criado, basta o acessarmos por meio da
URL index.php?class=ExemploActionControl. Veja que então o resultado é
apresentado na tela.
Resultado:
?class=ExemploActionControl&method=executaAcao1&codigo=4&nome=teste
Como o exemplo demonstrado anteriormente era só uma prova de
conceito, agora vamos criar um exemplo um pouco mais funcional com
dois botões na tela. Cada botão estará vinculado à uma ação e
consequentemente a um método. Assim, para cada botão clicado, um
diferente método será executado.
O programa inicia com a criação de dois botões ($button1 e $button2).
Cada botão terá uma classe de estilo correspondente e os rótulos “Ação 1”
e “Ação 2”. Logo em seguida, serão criadas duas ações ($action1 e
$action2). Cada ação estará vinculada a um método diferente
(executaAcao1() e executaAcao2()), bem como passará um valor diferente
para o parâmetro codigo, o que é de nido pelo método setParameter(). A
classe Action contém o método serialize(), que converte a ação no
formato de URL. Neste exemplo estamos simplesmente atribuindo o
resultado do método serialize() para o atributo href do link criado.
Observação: neste exemplo precisamos especificar as classes utilizadas no início
do programa (Page, Element) por meio do operador use, já que elas estão dentro
de namespaces, como vimos no capítulo 4. Lembre-se de que as classes são
carregadas automaticamente pelo autoloader, o que é definido no index.php.
App/Control/ExemploActionButtonControl.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Base\Element;
class ExemploActionButtonControl extends Page {
public function __construct() {
parent::__construct();
$button1 = new Element('a');
$button1->add('Ação 1');
$button1->class = 'btn btn-success';
$button2 = new Element('a');
$button2->add('Ação 2');
$button2->class = 'btn btn-primary';
$action1 = new Action(array($this, 'executaAcao1'));
$action1->setParameter('codigo', 4);
$action2 = new Action(array($this, 'executaAcao2'));
$action2->setParameter('codigo', 5);
$button1->href = $action1->serialize();
$button2->href = $action2->serialize();
$button1->show();
$button2->show();
}
public function executaAcao1($params) {
echo '<br>' . json_encode($params);
}
public function executaAcao2($params) {
echo '<br>' . json_encode($params);
}
}
A gura 6.15 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos pela URL index.php?
class=ExemploActionButtonControl, onde quer que esteja rodando nosso
servidor de páginas. Veja que então a página é apresentada em tela. Nesse
exemplo, o primeiro botão foi clicado.
Figura 6.15 – Resultado do programa.
App/Resources/welcome.html
<div class="jumbotron" style="margin: 20px">
<h1>Bem-vindo!</h1>
<p>Aqui meu amigo <b>{{nome}}</b>, que reside na rua <u>{{rua}}</u>,
CEP <u>{{cep}}</u>.
Tentamos entrar em contato pelo fone <u>{{fone}}</u>, mas não obtivemos
resposta.
Assim que possível faça contato.
</p>
<p><a class="btn btn-primary btn-lg"
href="?class=TwigWelcomeControl&method=onSaibaMais"
role="button"> Saiba Mais </a></p>
</div>
A partir do template de nido podemos fazer uso dele mesmo. No
exemplo a seguir, utilizamos a classe Twig_Loader_Filesystem para indicar o
diretório em que os templates são armazenados e a classe Twig_Environment
para carregar o template por meio do método loadTemplate().
A de nição das substituições é bastante simples e é feita por meio da
de nição de um vetor ($replaces). Esse vetor deve ser passado como
parâmetro para o método render(), que por sua vez faz as devidas
substituições dentro do arquivo de template, retornando-o já processado.
Observação: como já visto anteriormente, a biblioteca Twig é instalada pelo
composer e carregada pelo index.php.
App/Control/TwigWelcomeControl.php
<?php
use Livro\Control\Page;
class TwigWelcomeControl extends Page {
public function __construct() {
parent::__construct();
$loader = new Twig_Loader_Filesystem('App/Resources');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('welcome.html');
$replaces = array();
$replaces['nome'] = 'José Augusto';
$replaces['rua'] = 'Rua das Acácias, 123';
$replaces['cep'] = '12.345-678';
$replaces['fone'] = '(00) 1234-5678';
$content = $template->render($replaces);
echo $content;
}
public function onSaibaMais($params) {
echo 'mais informações...';
}
}
A gura 6.16 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=TwigWelcomeControl, onde quer que esteja rodando nosso servidor de
páginas. Veja que então a página será apresentada na tela.
App/Resources/list.html
<div class="panel panel-default" style="margin: 20">
<div class="panel-heading">
<h3 class="panel-title">{{titulo}}</h3>
</div>
<div class="panel-body">
<table class="table table-striped">
<thead>
<tr> <th> Código </td>
<th> Nome </td>
<th> Endereço </td>
</tr>
</thead>
<tbody>
{% for pessoa in pessoas %}
<tr> <td> {{pessoa.codigo}} </td>
<td> {{pessoa.nome}} </td>
<td> {{pessoa.endereco}} </td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
A partir do template construído, vamos desenvolver o programa que irá
alimentar esse template. Dispensando os comentários já feitos no exemplo
anterior sobre o carregamento da biblioteca, bem como do template,
podemos focar no aspecto da repetição dos dados. Assim como no
exemplo anterior, aqui também temos um vetor de substituições
($replaces). Inclusive sua primeira posição (titulo) é substituída
diretamente da mesma forma que no exemplo anterior.
A diferença principal está na posição pessoas, que, em vez de ser uma
variável escalar simples (integer, string), é na verdade um array que
contém outras posições dentro dele. Veja que cada posição de pessoas
contém os dados de uma pessoa diferente com atributos como (codigo,
nome e endereco). A posição pessoas é percorrida pela biblioteca, e cada
registro de pessoa fará com que o bloco contendo o comando ({% for
pessoa in pessoas %} ) dentro do template seja repetido conforme a
quantidade de pessoas no vetor.
Observação: é importante lembrar que a biblioteca Twig é instalada pelo
Composer e carregada pelo index.php.
App/Control/TwigListControl.php
<?php
use Livro\Control\Page;
class TwigListControl extends Page {
public function __construct() {
parent::__construct();
$loader = new Twig_Loader_Filesystem('App/Resources');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('list.html');
$replaces = array();
$replaces['titulo'] = 'Lista de pessoas';
$replaces['pessoas'] = array(
array('codigo' => '1',
'nome' => 'Anita Garibaldi',
'endereco' => 'Rua dos Gaudérios'),
array('codigo' => '2',
'nome' => 'Bento Gonçalves',
'endereco' => 'Rua dos Gaudérios'),
array('codigo' => '3',
'nome' => 'Giuseppe Garibaldi',
'endereco' => 'Rua dos Gaudérios')
);
$content = $template->render($replaces);
echo $content;
}
}
A gura 6.17 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=TwigListControl, onde quer que esteja rodando nosso servidor de
páginas. Veja que então a página será apresentada na tela.
Figura 6.17 – Resultado do programa.
CAPÍTULO 7
Formulários e listagens
Uma sociedade só será democrática quando ninguém for tão rico que possa comprar
alguém e ninguém for tão pobre que tenha de se vender a alguém.
J -J R
Neste capítulo iremos nos concentrar em alguns dos componentes que
estão entre os mais utilizados na maioria das aplicações: formulários e
listagens. Usamos formulários para as mais diversas formas de entrada de
dados na aplicação, como para a inserção de novos registros, de nição de
preferências do sistema ou de parâmetros para ltragem de um relatório,
entre outras. Usamos listagens para exibir os dados da aplicação para
simples conferência, em relatórios, ou ainda para editar e excluir registros.
Neste capítulo criaremos componentes que visam a facilitar a
implementação de formulários e listagens de forma orientada a objetos.
7.1 Formulários
Ao longo do capítulo 3, criamos alguns exemplos simples de utilização de
formulários em PHP. Naqueles exemplos, construímos os formulários
utilizando simplesmente HTML. No capítulo anterior, abordamos os
benefícios que a utilização de componentes e templates nos proporciona
para a criação de interfaces, sendo o principal deles o maior isolamento
entre a apresentação e a lógica da aplicação. Nesta seção, usaremos os
conhecimentos adquiridos no capítulo anterior para desenvolver um
conjunto de classes que permitirão a criação e a manipulação de
formulários de maneira totalmente orientada a objetos.
Aplicações de negócio frequentemente utilizam diversas telas para entrada
de dados por meio de formulários. Ao projetar um sistema, temos de
pensar no reaproveitamento de código. A abordagem tradicional, vista no
capítulo 3, minimiza o reaproveitamento de código. Sempre que
quisermos criar um formulário baseado em um que já exista, teremos de
copiar e colar seu conteúdo.
Para maximizar o reaproveitamento de código na construção de
formulários, todos os elementos que fazem parte de um formulário serão
transformados em objetos. O primeiro objetivo ao fazer isso é eliminar a
necessidade de escrever código HTML diretamente na aplicação, que
trabalharemos em um nível mais alto somente utilizando os componentes.
Cada objeto terá uma interface bem delineada e usaremos somente esses
métodos para construir o formulário.
A primeira classe que criaremos representará um formulário. Um
formulário deve ter alguns comportamentos (métodos) como: de nir um
nome, de nir um título, adicionar campos, atribuir valores aos campos,
retornar os valores dos campos, adicionar botões de ação, entre outros.
A partir do formulário, precisamos pensar em cada um dos elementos que
ele contém. Assim, criaremos uma classe para cada elemento que um
formulário manipula (text, radiobutton, checkbox, password, select etc.).
Cada elemento tem um nome, um valor e um tamanho, entre outros
atributos. Como os elementos têm várias características em comum,
criaremos uma superclasse, da qual todo elemento do formulário irá
derivar (herança). Para representar cada elemento de um formulário, serão
criadas as seguintes classes:
Classe Descrição
Entry Classe que representará campos de entrada de dados <input
type="text">.
Password Classe que representará campos de senha <input type="password">.
File Classe que representará um botão de seleção de arquivo <input
type="file">.
Hidden Classe que representará um campo escondido <input type="hidden">.
Combo Classe que representará uma lista de seleção <select>.
Text Classe que representará uma área de texto <textarea>.
CheckButto Classe que representará campos de checagem <input type="checkbox">.
n
CheckGroup Classe que representará um grupo de CheckButton.
RadioButto Classe que representará botões de rádio <input type="radio">.
n
RadioGroup Classe que representará um grupo de RadioButton.
Acima de todas essas classes listadas anteriormente, teremos a classe
Field. Essa classe proverá a infraestrutura básica e comum a todo campo
de um formulário, ou seja, aquelas operações básicas que todo elemento
de um formulário deverá oferecer. Entre essas operações, podemos citar:
de nir o nome do campo, de nir um rótulo (label) para o campo, de nir
um valor para o campo, de nir se o campo será editável, entre outras.
Para criar um formulário, utilizaremos um relacionamento de agregação
entre a classe Form e a classe Field. Dessa maneira, será possível adicionar
ao formulário quaisquer objetos lhos de Field. Para isso, será criado na
classe Form um método que permitirá adicionar campos (objetos Field) em
sua estrutura. Veja na gura 7.1 o diagrama de classes resumido.
Lib/Livro/Widgets/Form/Form.php
<?php
namespace Livro\Widgets\Form;
use Livro\Control\ActionInterface;
class Form {
protected $title;
protected $fields;
protected $actions;
public function __construct($name = 'my_form') {
$this->setName($name);
}
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function setTitle($title) {
$this->title = $title;
}
public function getTitle() {
return $this->title;
}
public function addField($label, FormElementInterface $object, $size =
'100%') {
$object->setSize($size);
$object->setLabel($label);
$this->fields[$object->getName()] = $object;
}
public function addAction($label, ActionInterface $action) {
$this->actions[$label] = $action;
}
public function getFields() {
return $this->fields;
}
public function getActions() {
return $this->actions;
}
public function setData($object) {
foreach ($this->fields as $name => $field) {
if ($name AND isset($object->$name)) {
$field->setValue($object->$name);
}
}
}
public function getData($class = 'stdClass') {
$object = new $class;
foreach ($this->fields as $key => $fieldObject) {
$val = isset($_POST[$key]) ? $_POST[$key] : '';
$object->$key = $val;
}
// percorre os arquivos de upload
foreach ($_FILES as $key => $content) {
$object->$key = $content['tmp_name'];
}
return $object;
}
}
Os campos adicionados ao formulário devem implementar a interface
FormElementInterface, que, por sua vez, contém os métodos setName(),
getName(), setValue(), getValue() e show(). Assim, qualquer objeto
adicionado ao formulário deverá obrigatoriamente implementar esses
métodos.
Lib/Livro/Widgets/Form/FormElementInterface.php
<?php
namespace Livro\Widgets\Form;
interface FormElementInterface {
public function setName($name);
public function getName();
public function setValue($value);
public function getValue();
public function show();
}
Lib/Livro/Widgets/Wrapper/FormWrapper.php
<?php
namespace Livro\Widgets\Wrapper;
use Livro\Widgets\Container\Panel;
use Livro\Widgets\Form\Form;
use Livro\Widgets\Form\Button;
use Livro\Widgets\Base\Element;
class FormWrapper {
private $decorated;
public function __construct(Form $form) {
$this->decorated = $form;
}
public function __call($method, $parameters) {
return call_user_func_array(array($this->decorated,
$method),$parameters);
}
public function show() {
$element = new Element('form');
$element->class = "form-horizontal";
$element->enctype = "multipart/form-data";
$element->method = 'post'; // método de transferência
$element->name = $this->decorated->getName();
$element->width = '100%';
foreach ($this->decorated->getFields() as $field) {
$group = new Element('div');
$group->class = 'form-group';
$label = new Element('label');
$label->class= 'col-sm-2 control-label';
$label->add($field->getLabel());
$col = new Element('div');
$col->class = 'col-sm-10';
$col->add($field);
$field->class = 'form-control';
$group->add($label);
$group->add($col);
$element->add($group);
}
$group = new Element('div');
$i = 0;
foreach ($this->decorated->getActions() as $label => $action) {
$name = strtolower(str_replace(' ', '_', $label));
$button = new Button($name);
$button->setFormName($this->decorated->getName());
$button->setAction($action, $label);
$button->class = 'btn ' . ( ($i==0) ? 'btn-success' : 'btn-
default');
$group->add($button);
$i ++;
}
$panel = new Panel($this->decorated->getTitle());
$panel->add($element);
$panel->addFooter($group);
$panel->show();
}
}
Nas próximas seções, criaremos as classes dos componentes que serão
usados no formulário. Mas, antes, para você já ter uma ideia de como será
a criação de um formulário usando Decorator, o código a seguir mostra a
criação do objeto Form, que por sua vez é passado como parâmetro para o
objeto FormWrapper. Veja que, depois, é possível executar métodos como
setTitle(), que são transmitidos da classe FormWrapper para Form, por meio
da delegação que ocorre no método __call():
$this->form = new FormWrapper(new Form('form'));
$this->form->setTitle('Formulário');
Lib/Livro/Widgets/Form/Field.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
abstract class Field implements FormElementInterface {
protected $name;
protected $size;
protected $value;
protected $editable;
protected $tag;
protected $formLabel;
protected $properties;
public function __construct($name) {
// define algumas características iniciais
self::setEditable(true);
self::setName($name);
}
Os métodos setProperty() e getProperty() serão usados para de nir e
retornar propriedades do objeto. O método setProperty() pode ser usado
para de nir um atributo novo para um campo do formulário, utilizando a
sintaxe $input->setProperty('onBlur', 'alert(1)'). Já o método
getProperty() irá retornar o valor de uma propriedade.
public function setProperty($name, $value) {
$this->properties[$name] = $value;
}
public function getProperty($name) {
return $this->properties[$name];
}
Os métodos mágicos __set() e __get() serão usados para interceptar
respectivamente atribuições e leituras de propriedades do objeto. Assim,
alguém poderá de nir uma propriedade de um objeto simplesmente
usando a sintaxe ($input->onBlur = 'alert(1)'). Sempre que alguém tentar
atribuir uma propriedade que ainda não exista, esta será repassada para o
método setProperty(). E sempre que alguém tentar fazer a leitura de um
atributo, será executado o método getProperty().
public function __set($name, $value) {
if (is_scalar($value)) {
$this->setProperty($name, $value);
}
}
public function __get($name) {
return $this->getProperty($name);
}
Os métodos setName() e getName() serão utilizados simplesmente para
de nir e retornar o nome do campo, que por sua vez será armazenado na
propriedade $this->name.
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
Os método setLabel() e getLabel()são usados para de nir o rótulo de
texto (label) que representará o campo no formulário. Trata-se
simplesmente de um texto exibido em frente ao campo para instruir o
usuário no preenchimento.
public function setLabel($label) {
$this->formLabel = $label;
}
public function getLabel() {
return $this->formLabel;
}
Os métodos setValue() e getValue() serão utilizados para de
nir e retornar
o valor do campo no formulário, que por sua vez está armazenado na
propriedade $this->value.
public function setValue($value) {
$this->value = $value;
}
public function getValue() {
return $this->value;
}
O método setEditable() será utilizado para de nir se o campo poderá ou
não ser editado. Já o método getEditable() irá simplesmente retornar essa
informação, que será um atributo booleano.
public function setEditable($editable) {
$this->editable= $editable;
}
public function getEditable() {
return $this->editable;
}
Por m, o método setSize() irá de nir o tamanho do campo.
public function setSize($width, $height = NULL) {
$this->size = $width;
}
Lib/Livro/Widgets/Form/Label.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class Label extends Field implements FormElementInterface {
public function __construct($value) {
$this->setValue($value);
$this->tag = new Element('label');
}
public function add($child) {
$this->tag->add($child);
}
public function show() {
$this->tag->add($this->value);
$this->tag->show();
}
}
Lib/Livro/Widgets/Form/Entry.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class Entry extends Field implements FormElementInterface {
protected $properties;
public function show() {
// atribui as propriedades da TAG
$tag = new Element('input');
$tag->class = 'field'; // classe CSS
$tag->name = $this->name; // nome da TAG
$tag->value = $this->value; // valor da TAG
$tag->type = 'text'; // tipo de input
$tag->style = "width:{$this->size}"; // tamanho em pixels
// se o campo não é editável
if (!parent::getEditable()){
$tag->readonly = "1";
}
if ($this->properties) {
foreach ($this->properties as $property => $value) {
$tag->$property = $value;
}
}
$tag->show(); // exibe a tag
}
}
Lib/Livro/Widgets/Form/File.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class File extends Field implements FormElementInterface {
public function show() {
// atribui as propriedades da TAG
$tag = new Element('input');
$tag->class = 'field';
$tag->name = $this->name; // nome da TAG
$tag->value = $this->value; // valor da TAG
$tag->type = 'file'; // tipo de input
$tag->style = "width:{$this->size}"; // tamanho em pixels
// se o campo não é editável
if (!parent::getEditable()) {
$tag->readonly = "1"; // desabilita a TAG input
}
if ($this->properties) {
foreach ($this->properties as $property => $value) {
$tag->$property = $value;
}
}
$tag->show(); // exibe a tag
}
}
Lib/Livro/Widgets/Form/Text.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class Text extends Field implements FormElementInterface {
private $width;
private $height = 100;
public function setSize($width, $height = NULL) {
$this->size = $width;
if (isset($height)) {
$this->height = $height;
}
}
public function show() {
$tag = new Element('textarea');
$tag->class = 'field'; // classe CSS
$tag->name = $this->name; // nome da TAG
$tag->style = "width:{$this->size};height:{$this->height}"; //
tamanho em pixels
// se o campo não é editável
if (!parent::getEditable()){
// desabilita a TAG input
$tag->readonly = "1";
}
$tag->add(htmlspecialchars($this->value)); // adiciona conteúdo ao
textarea
if ($this->properties) {
foreach ($this->properties as $property => $value) {
$tag->$property = $value;
}
}
$tag->show(); // exibe a tag
}
}
Lib/Livro/Widgets/Form/Combo.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class Combo extends Field implements FormElementInterface {
private $items; // array contendo os itens da combo
protected $properties;
public function addItems($items) {
$this->items = $items;
}
public function show() {
$tag = new Element('select');
$tag->class = 'combo';
$tag->name = $this->name;
$tag->style = "width:{$this->size}"; // tamanho em pixels
// cria uma TAG <option> com um valor padrão
$option = new Element('option');
$option->add('');
$option->value = '0'; // valor da TAG
// adiciona a opção à combo
$tag->add($option);
if ($this->items) {
// percorre os itens adicionados
foreach ($this->items as $chave => $item) {
// cria uma TAG <option> para o item
$option = new Element('option');
$option->value = $chave; // define o índice da opção
$option->add($item); // adiciona o texto da opção
// caso seja a opção selecionada
if ($chave == $this->value) {
// seleciona o item da combo
$option->selected = 1;
}
// adiciona a opção à combo
$tag->add($option);
}
}
// verifica se o campo é editável
if (!parent::getEditable()){
// desabilita a TAG input
$tag->readonly = "1";
}
if ($this->properties) {
foreach ($this->properties as $property => $value) {
$tag->$property = $value;
}
}
$tag->show(); // exibe a combo
}
}
Lib/Livro/Widgets/Form/CheckButton.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class CheckButton extends Field implements FormElementInterface {
public function show() {
// atribui as propriedades da TAG
$tag = new Element('input');
$tag->class = 'field'; // classe CSS
$tag->name = $this->name; // nome da TAG
$tag->value = $this->value; // value
$tag->type = 'checkbox'; // tipo do input
// se o campo não é editável
if (!parent::getEditable()) {
// desabilita a TAG input
$tag->readonly = "1";
}
if ($this->properties) {
foreach ($this->properties as $property => $value) {
$tag->$property = $value;
}
}
$tag->show(); // exibe a tag
}
}
Um check button raramente é utilizado de forma isolada. Geralmente,
utilizamos check buttons em grupos, permitindo ao usuário selecionar
entre várias opções. Como a classe que criamos CheckButton permite a
exibição somente de um check button, criaremos uma classe responsável
pela exibição de um conjunto de check buttons na tela. Chamaremos essa
classe de CheckGroup, a qual terá três métodos: setLayout(), addItems() e
show(). A classe CheckGroup permitirá agruparmos vários CheckButtons por
meio de uma relação de composição, como pode ser demonstrado na
gura 7.15.
Lib/Livro/Widgets/Form/CheckGroup.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class CheckGroup extends Field implements FormElementInterface {
private $layout = 'vertical';
private $items;
public function setLayout($dir) {
$this->layout = $dir;
}
public function addItems($items) {
$this->items = $items;
}
O método show() será responsável por exibir o conjunto de check buttons
na tela. Observe que os itens são percorridos por meio de um foreach, e
para cada item é instanciado um novo check button (objeto da classe
CheckButton). Para demonstrar que se trata de um array de campos, ao
nal do check button adicionamos colchetes []. O índice de cada opção
($index) do vetor será também o valor de cada check button ($button-
>setValue($index)). Caso o índice ($index) de um dos check buttons
coincida com o valor atual ($this->value) do check group, então ele será
marcado como selecionado. O rótulo de texto de cada check button é
criado por meio de um objeto da classe Label que irá conter o check
button.
public function show() {
if ($this->items) {
// percorre cada uma das opções do radio
foreach ($this->items as $index => $label) {
$button = new CheckButton("{$this->name}[]");
$button->setValue($index);
// verifica se deve ser marcado
if (in_array($index, (array) $this->value)) {
$button->setProperty('checked', '1');
}
$obj = new Label($label);
$obj->add($button);
$obj->show();
if ($this->layout == 'vertical') {
// exibe uma tag de quebra de linha
$br = new Element('br');
$br->show();
echo "\n";
}
}
}
}
}
Lib/Livro/Widgets/Form/RadioButton.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class RadioButton extends Field implements FormElementInterface {
public function show() {
$tag = new Element('input');
$tag->class = 'field'; // classe CSS
$tag->name = $this->name;
$tag->value = $this->value;
$tag->type = 'radio';
// se o campo não é editável
if (!parent::getEditable()) {
// desabilita a TAG input
$tag->readonly = "1";
}
if ($this->properties) {
foreach ($this->properties as $property => $value) {
$tag->$property = $value;
}
}
$tag->show(); // exibe a tag
}
}
Um radio button não é usado de forma isolada. Utilizamos radio buttons
em grupos, permitindo ao usuário selecionar apenas uma entre várias
opções. Como a classe RadioButton criada permite a exibição somente de
um radio button, criaremos uma classe responsável pela exibição de um
conjunto de radio buttons na tela. Chamaremos essa classe de RadioGroup,
a qual terá três métodos: setLayout(), addItems() e show(). A classe
RadioGroup permitirá agruparmos vários RadioButtons por meio de uma
relação de composição, como pode ser demonstrado na gura 7.18.
Lib/Livro/Widgets/Form/RadioGroup.php
<?php
namespace Livro\Widgets\Form;
use Livro\Widgets\Base\Element;
class RadioGroup extends Field implements FormElementInterface {
private $layout = 'vertical';
private $items;
public function setLayout($dir) {
$this->layout = $dir;
}
public function addItems($items) {
$this->items = $items;
}
O método show() será responsável por exibir o conjunto de radio buttons
na tela. Note que os itens são percorridos por meio de um foreach, e para
cada item é instanciado um novo radio button (objeto da classe
RadioButton). Os objetos RadioButton pertencentes ao mesmo RadioGroup
terão todos o mesmo nome. Isso fará com que a seleção seja exclusiva. O
índice de cada opção ($index) do vetor será também o valor de cada radio
button ($button->setValue($index)). Caso o índice ($index) de um dos
radio buttons coincida com o valor atual ($this->value) do radio group,
então ele será marcado como selecionado. O rótulo de texto de cada radio
button é criado por meio de um objeto da classe Label que irá conter o
radio button.
public function show() {
if ($this->items) {
// percorre cada uma das opções do radio
foreach ($this->items as $index => $label) {
$button = new RadioButton($this->name);
$button->setValue($index);
// se o índice coincide
if ($this->value == $index) {
// marca o radio button
$button->setProperty('checked', '1');
}
$obj = new Label($label);
$obj->add($button);
$obj->show();
if ($this->layout == 'vertical') {
// exibe uma tag de quebra de linha
$br = new Element('br');
$br->show();
}
echo "\n";
}
}
}
}
7.1.3.8 Classe para botões de ação
Um formulário não é nada sem um botão de ação. Quando construímos a
classe Form, vimos que ela permite adicionar várias ações a um mesmo
formulário por meio do método addAction(). Estas ações depois eram
exibidas pela classe FormWrapper na forma de um botão, pela classe Button.
Um objeto da classe Button é usado para representar um botão de ação em
um formulário. Sempre que um botão é acrescentado a um formulário
pelo método addAction(), ele recebe algumas informações, como o nome
do formulário pelo método setFormName() e a ação a ser executada pelo
método setAction().
A classe Button representará um botão de ação em um formulário. Um
botão deve submeter o formulário para processamento no servidor. Para
isso é necessário que o botão saiba o nome do formulário do qual faz
parte. Isso é possível por meio do método setFormName(), que passa o
nome do formulário como parâmetro para o Button.
A ação do botão é de nida pelo método setAction(), no qual passamos
como parâmetro um objeto que implemente a interface ActionInterface e
um rótulo de texto (label) para ser usado como rótulo do botão.
A principal função da classe Button ocorre em seu método show(), no qual
é exibida uma tag de tipo <input> com o type button e com o evento
onclick disparando o submit do formulário. Mas, antes disso, a ação do
formulário (action) é de nida pela variável $url, que é a própria ação que
passamos ao botão por meio do método setAction() traduzida na forma
de uma URL por meio do método serialize().
Lib/Livro/Widgets/Form/Button.php
<?php
namespace Livro\Widgets\Form;
use Livro\Control\Action;
use Livro\Control\ActionInterface;
use Livro\Widgets\Base\Element;
class Button extends Field implements FormElementInterface {
private $action;
private $label;
private $formName;
public function setAction(ActionInterface $action, $label) {
$this->action = $action;
$this->label = $label;
}
public function setFormName($name) {
$this->formName = $name;
}
public function show() {
$url = $this->action->serialize();
// define as propriedades do botão
$tag = new Element('button');
$tag->name = $this->name; // nome da TAG
$tag->type = 'button'; // tipo de input
$tag->add($this->label);
// define a ação do botão
$tag->onclick = "document.{$this->formName}.action='{$url}'; ".
"document.{$this->formName}.submit()";
if ($this->properties) {
foreach ($this->properties as $property => $value) {
$tag->$property = $value;
}
}
$tag->show(); // exibe o botão
}
}
7.1.4 Exemplos
Nesta seção veremos exemplos de uso de formulários.
App/Control/ContatoForm.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Form\Form;
use Livro\Widgets\Dialog\Message;
use Livro\Widgets\Form\Label;
use Livro\Widgets\Form\Entry;
use Livro\Widgets\Form\Combo;
use Livro\Widgets\Form\Text;
use Livro\Widgets\Wrapper\FormWrapper;
class ContatoForm extends Page {
private $form;
function __construct() {
parent::__construct();
// instancia um formulário
$this->form = new FormWrapper(new Form('form_contato'));
$this->form->setTitle('Formulário de contato');
// cria os campos do formulário
$nome = new Entry('nome');
$email = new Entry('email');
$assunto = new Combo('assunto');
$mensagem = new Text('mensagem');
$this->form->addField('Nome', $nome, 300);
$this->form->addField('E-mail', $email, 300);
$this->form->addField('Assunto', $assunto, 300);
$this->form->addField('Mensagem', $mensagem, 300);
// define alguns atributos
$assunto->addItems( array('1' => 'Sugestão',
'2' => 'Reclamação',
'3' => 'Suporte técnico',
'4' => 'Cobrança',
'5' => 'Outro') );
$mensagem->setSize(300, 80);
$this->form->addAction('Enviar', new Action(array($this,
'onSend')));
// adiciona o formulário à página
parent::add($this->form);
}
Quando o botão de ação “enviar” for clicado, automaticamente uma nova
requisição será gerada ao servidor. Essa requisição será feita para a URL
index.php?class=ContatoForm&method=onSend, já que foi essa a ação con gurada
pelo método addAction(). Nessa nova requisição ao servidor, o método
construtor é executado novamente, criando os objetos em memória, e
logo em seguida é executado o método onSend(), que irá “receber” os
dados via POST.
Como passamos pelo método construtor novamente após a postagem, os
objetos criados nele, como o $this->form, estão disponíveis. Assim, dentro
do método onSend(), podemos executar o método getData() da classe Form,
que basicamente efetua uma “leitura” sobre o POST, devolvendo um
objeto contendo os dados do formulário. Depois disso, chamamos o
método setData() da mesma classe para “manter” o formulário
preenchido após a postagem. Em seguida, efetuamos algumas validações
para veri car se os campos estão preenchidos. Caso não estejam,
“lançamos” uma exceção, fazendo com que a rotina seja automaticamente
direcionada para o bloco catch, emitindo uma mensagem de erro ao
usuário. Caso passe pelas validações, uma string ($mensagem) é montada
para exibir o conteúdo dos campos ao usuário por meio da classe Message.
function onSend() {
try {
// obtém os dados
$dados = $this->form->getData();
// mantém o formulário preenchido
$this->form->setData($dados);
// valida
if (empty($dados->email)) {
throw new Exception('Email vazio');
}
if (empty($dados->assunto)) {
throw new Exception('Assunto vazio');
}
// monta mensagem
$mensagem = "Nome: {$dados->nome} <br>";
$mensagem .= "Email: {$dados->email} <br>";
$mensagem .= "Assunto: {$dados->assunto} <br>";
$mensagem .= "Mensagem: {$dados->mensagem} <br>";
new Message('info', $mensagem);
}
catch (Exception $e) {
new Message('error', $e->getMessage());
}
}
A gura 7.19 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos por meio da URL index.php?class=ContatoForm,
onde quer que esteja rodando nosso servidor de páginas.
Na gura é demonstrada a postagem do formulário após o
preenchimento de alguns campos, o que resulta na execução da URL
index.php?class=ContatoForm&method=onSend. Caso o usuário não tenha
preenchido os campos “e-mail” ou “assunto”, ocorrerá uma exceção,
provocando uma mensagem de erro.
App/Con g/livro.ini
host = localhost
name = App/Database/livro.db
user =
pass =
type = sqlite
Dentro dessa base de dados (livro.db) pré-con gurada no arquivo livro.ini,
temos uma tabela chamada funcionario com a seguinte estrutura:
CREATE TABLE funcionario ( id integer PRIMARY KEY NOT NULL,
nome TEXT,
endereco TEXT,
email TEXT,
departamento INTEGER,
idiomas TEXT,
contratacao INTEGER );
Agora que já temos o banco de dados, bem como a tabela criada, para
manipular a tabela pela aplicação, lembre-se de que precisamos criar uma
classe Active Record. Para isso, vamos criar uma classe chamada
Funcionario. Essa classe cará localizada em App/Model e estenderá a classe
Record.
App/Model/Funcionario.php
<?php
use Livro\Database\Record;
class Funcionario extends Record {
const TABLENAME = 'funcionario';
}
Agora podemos iniciar a implementação de nosso formulário de cadastro
de funcionários. O objetivo é criar um formulário que permita o cadastro
e a edição de funcionários. Para isso, vamos criar a classe FuncionarioForm.
No método construtor da classe, vamos criar o formulário utilizando a
classe Form e os campos do formulário utilizando componentes como
Entry, Combo, CheckGroup e RadioGroup. A classe terá ainda o método
onSave(), que irá armazenar os dados do formulário na tabela do banco de
dados, e o método onEdit(), que, quando acionado pela URL, trará o
formulário preenchido com algum registro existente na tabela do banco
de dados.
Observação: lembre-se de que, para acessar o programa a seguir, é preciso
acessar o index.php, que é o front controller, por meio da URL index.php?
class=FuncionarioForm.
A classe inicia pela de nição de quais classes serão utilizadas por esse
programa, tais como Page, Action, Transaction, Form e outras devidamente
declaradas com seus namespaces. No seu método construtor, criamos o
formulário por meio da classe Form com o nome form_funcionario. Em
seguida, declaramos cada um dos campos do formulário: id, nome,
endereco, email, departamento, idiomas e contratacao. O nome do campo é
de nido pelo primeiro parâmetro do método construtor de cada
componente. Usamos como nome dos campos os próprios nomes das
colunas da tabela correspondente no banco de dados para facilitar o
transporte dos dados entre o formulário e a tabela, e vice-versa.
Após adicionar os campos ao formulário, de nimos o campo id como não
editável e executamos o método setLayout() para que as opções de radio e
check sejam exibidas lado a lado. Além disso, preenchemos os campos
para Departamento (Combo), Idiomas (CheckGroup) e Contratação
(RadioGroup) por meio do método addItems(), que por sua vez recebe como
parâmetro um array com as opções. Lembre-se de que o índice do array é
o que será passado adiante na submissão do formulário.
Observação: o campo id será não editável, pois um novo id será gerado em
inserções. Já em edições de registros, ele não poderá ser alterado. Aqui,
poderíamos usar o componente Hidden também.
Por m, duas ações são adicionadas ao formulário pelo método
addAction().
A primeira delas – Salvar – provocará a execução do método onSave(); já a
segunda provocará a execução do método onClear().
App/Control/FuncionarioForm.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Database\Transaction;
use Livro\Widgets\Form\Form;
use Livro\Widgets\Dialog\Message;
use Livro\Widgets\Form\Entry;
use Livro\Widgets\Form\Combo;
use Livro\Widgets\Form\CheckGroup;
use Livro\Widgets\Form\RadioGroup;
use Livro\Widgets\Wrapper\FormWrapper;
class FuncionarioForm extends Page {
private $form;
function __construct() {
parent::__construct();
// instancia um formulário
$this->form = new FormWrapper(new Form('form_funcionario'));
$this->form->setTitle('Cadastro de funcionário');
// cria os campos do formulário
$id = new Entry('id');
$nome = new Entry('nome');
$endereco = new Entry('endereco');
$email = new Entry('email');
$departamento = new Combo('departamento');
$idiomas = new CheckGroup('idiomas');
$contratacao = new RadioGroup('contratacao');
$this->form->addField('Código', $id, 300);
$this->form->addField('Nome', $nome, 300);
$this->form->addField('Endereço', $endereco, 300);
$this->form->addField('E-mail', $email, 300);
$this->form->addField('Departamento', $departamento, 300);
$this->form->addField('Idiomas', $idiomas, 300);
$this->form->addField('Contratação', $contratacao, 300);
$id->setEditable(FALSE);
$idiomas->setLayout('horizontal');
$contratacao->setLayout('horizontal');
// define alguns atributos
$departamento->addItems( array('1' => 'RH',
'2' => 'Atendimento',
'3' => 'Engenharia',
'4' => 'Produção' ));
$idiomas->addItems( array('1' => 'Inglês',
'2' => 'Espanhol',
'3' => 'Alemão',
'4' => 'Italiano' ));
$contratacao->addItems( array('1' => 'Estagiário',
'2' => 'Pessoa Jurídica',
'3' => 'CLT',
'4' => 'Sócio' ));
// adiciona as ações
$this->form->addAction('Salvar', new Action(array($this,
'onSave')));
$this->form->addAction('Limpar', new Action(array($this,
'onClear')));
// adiciona o formulário na página
parent::add($this->form);
}
Quando o botão de ação “Salvar” for clicado, automaticamente uma nova
requisição será gerada ao servidor. Essa requisição será feita para a URL
index.php?class=FuncionarioForm&method=onSave, já que foi essa a ação
con gurada pelo método addAction(). Nessa nova requisição ao servidor,
o método construtor é executado novamente, criando os objetos em
memória, e logo em seguida é executado o método onSave(), que irá
“receber” os dados via POST.
Como passamos pelo método construtor novamente após a postagem, os
objetos criados nele, como o $this->form, estão disponíveis. Assim, dentro
do método onSave(), podemos executar o método getData() da classe Form,
que basicamente efetua uma “leitura” sobre o POST, devolvendo um
objeto contendo os dados do formulário. Depois disso, validamos o
campo nome para veri car se ele está preenchido.
Em seguida, instanciamos um objeto a partir da classe Funcionario e o
alimentamos por meio de seu método fromArray() a partir dos dados
vindos do formulário ($dados), devidamente convertido para array. Ao
utilizarmos o método fromArray() estaremos preenchendo cada um dos
atributos do objeto Funcionario com os campos vindos do formulário,
desde que os nomes sejam iguais. Depois convertemos o campo idiomas,
que é um CheckGroup e vem na postagem na forma de array, para uma
string separada por vírgulas. Essa não é a melhor opção de modelagem,
mas isso não é o foco no momento. Logo em seguida, executamos o
método store() para armazenar os dados do objeto $funcionario no banco
de dados. Nesse instante, caso tenha vindo um atributo com conteúdo no
campo id, será executado um UPDATE; caso contrário, um INSERT.
Após a execução do método store(), é garantido que o objeto $funcionario
tenha um id, visto que ao executarmos um INSERT um novo id será gerado
e, ao executarmos um UPDATE, o id já existente será mantido. Então
devolvemos esse id ao objeto $dados e o utilizamos para executar o
método setData(), mantendo o formulário preenchido após a postagem.
Se um id novo tiver sido gerado, ele também aparecerá no formulário.
Os dados somente serão persistidos no banco de dados realmente ao
executarmos o método close() da classe Transaction, que fecha a
transação com o banco de dados enviando um commit. Caso venhamos a
esquecer dessa chamada, os dados não serão efetivamente gravados na
base de dados.
public function onSave() {
try {
Transaction::open('livro');
// obtém os dados
$dados = $this->form->getData();
// valida
if (empty($dados->nome)) {
throw new Exception('Nome vazio');
}
$funcionario = new Funcionario;
$funcionario->fromArray( (array) $dados);
$funcionario->idiomas = implode(',', (array) $dados->idiomas);
$funcionario->store();
$dados->id = $funcionario->id;
Transaction::close();
// mantém o formulário preenchido (agora com ID)
$this->form->setData($dados);
new Message('info', 'Dados salvos com sucesso');
}
catch (Exception $e) {
new Message('error', $e->getMessage());
}
}
Ao clicar no botão “Limpar”, uma nova requisição será enviada para o
servidor e a URL index.php?class=FuncionarioForm&method=onClear será
requerida, provocando a execução do método onClear(). Dentro desse
método, podemos usar a chamada $this->form->setData() se quisermos
enviar algum dado ao formulário ou deixá-lo vazio se quisermos provocar
uma execução somente para “limpar” o formulário.
public function onClear() {
}
A gura 7.20 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos pela URL index.php?class=FuncionarioForm, onde
quer que esteja rodando nosso servidor de páginas.
Na gura, é demonstrada a postagem do formulário após o
preenchimento de alguns campos, o que resulta na execução da URL
index.php?class=FuncionarioForm&method=onSave.
Figura 7.20 – Cadastro de funcionários.
7.2 Listagens
Até o momento, vimos como implementar formulários de uma maneira
orientada a objetos. Usamos formulários para cadastrar novos registros,
bem como editar registros preexistentes. Porém, antes de chegarmos a
uma tela que contenha um formulário, geralmente precisamos de uma
tela de listagem para localizar um registro e então editá-lo ou criar um
registro quando não encontramos os dados.
Para implementar listagens de uma maneira orientada a objetos, vamos
criar um conjunto de classes com essa nalidade, assim como zemos
para formulários.
A classe principal se chamará Datagrid. Essa classe terá métodos que
possibilitarão ao programador adicionar novas colunas, ações e também
itens (linhas) na listagem. As colunas conterão os dados da listagem,
enquanto as ações serão exibidas na frente dos dados e permitirão
manipulá-los (deletar, editar, visualizar).
Na gura 7.21 é apresentado o resultado nal de uma listagem construída
com a classe Datagrid. No lado esquerdo da listagem temos os ícones
representando as ações e, em seguida, as colunas contendo os dados. As
ações serão representadas por objetos do tipo Action e as colunas serão
representadas por objetos do tipo DatagridColumn. O objeto do tipo
Datagrid conterá objetos do tipo DatagridColumn em uma estrutura de
agregação, como veremos mais adiante. Poderemos agregar diversas
colunas (DatagridColumn) e ações (Action) na listagem (Datagrid).
Lib/Livro/Widgets/Datagrid/Datagrid.php
<?php
namespace Livro\Widgets\Datagrid;
use Livro\Control\ActionInterface;
class Datagrid {
private $columns;
private $items;
private $actions;
public function addColumn(DatagridColumn $object) {
$this->columns[] = $object;
}
public function addAction($label, ActionInterface $action, $field,
$image = null) {
$this->actions[] = ['label' => $label, 'action'=> $action, 'field'
=> $field,
'image' => $image];
}
public function addItem($object) {
$this->items[] = $object;
foreach ($this->columns as $column) {
$name = $column->getName();
if (!isset($object->$name)) {
// chama o método de acesso
$object->$name;
}
}
}
public function getColumns() {
return $this->columns;
}
public function getItems() {
return $this->items;
}
public function getActions() {
return $this->actions;
}
function clear() {
$this->items = [];
}
}
Lib/Livro/Widgets/Datagrid/DatagridColumn.php
<?php
namespace Livro\Widgets\Datagrid;
use Livro\Control\Action;
class DatagridColumn {
private $name, $label, $align, $width, $action, $transformer;
public function __construct($name, $label, $align, $width) {
// atribui os parâmetros às propriedades do objeto
$this->name = $name;
$this->label = $label;
$this->align = $align;
$this->width = $width;
}
public function getName() {
return $this->name;
}
public function getLabel() {
return $this->label;
}
public function getAlign() {
return $this->align;
}
public function getWidth() {
return $this->width;
}
public function setAction(Action $action) {
$this->action = $action;
}
public function getAction() {
// verifica se a coluna possui ação
if ($this->action) {
return $this->action->serialize();
}
}
public function setTransformer($callback) {
$this->transformer = $callback;
}
public function getTransformer() {
return $this->transformer;
}
}
Lib/Livro/Widgets/Wrapper/DatagridWrapper.php
<?php
namespace Livro\Widgets\Wrapper;
use Livro\Widgets\Container\Panel;
use Livro\Widgets\Datagrid\Datagrid;
use Livro\Widgets\Base\Element;
class DatagridWrapper {
private $decorated;
public function __construct(Datagrid $datagrid) {
$this->decorated = $datagrid;
}
public function __call($method, $parameters) {
return call_user_func_array(array($this->decorated, $method),
$parameters);
}
public function __set($attribute, $value) {
$this->decorated->$attribute = $value;
}
A exibição da datagrid ocorre pelo método show(), que por sua vez cria
um elemento table, bem como as partes da tabela (thead, tbody). A tabela é
inserida dentro de um painel (Panel), e este é exibido pelo método show().
O método createHeaders() cria os cabeçalhos da datagrid, enquanto o
createItem() adiciona uma linha de item na datagrid. O método
getItems() é executado sobre o objeto decorado ($this->decorated), que é
da classe Datagrid, e ele retorna os itens adicionados previamente pelo
addItem().
public function show() {
$element = new Element('table');
$element->class = 'table table-striped table-hover';
// cria o header
$thead = new Element('thead');
$element->add($thead);
$this->createHeaders($thead);
// cria o body
$tbody = new Element('tbody');
$element->add($tbody);
$items = $this->decorated->getItems();
foreach ($items as $item) {
$this->createItem($tbody, $item);
}
$panel = new Panel;
$panel->type = 'datagrid';
$panel->add($element);
$panel->show();
}
O método createHeaders() cria uma coluna vazia (th) para cada ação da
datagrid. Depois, percorre as colunas ($columns) da datagrid e, para cada
coluna, adiciona outro elemento (th), dessa vez contendo o rótulo ($label)
da coluna. Caso a coluna tenha uma ação (getAction), essa ação é
vinculada ao onclick da célula.
public function createHeaders($thead) {
// adiciona uma linha à tabela
$row = new Element('tr');
$thead->add($row);
$actions = $this->decorated->getActions();
$columns = $this->decorated->getColumns();
// adiciona células para as ações
if ($actions) {
foreach ($actions as $action) {
$celula = new Element('th');
$celula->width = '40px';
$row->add($celula);
}
}
// adiciona as células para os títulos das colunas
if ($columns) {
// percorre as colunas da listagem
foreach ($columns as $column) {
// obtém as propriedades da coluna
$label = $column->getLabel();
$align = $column->getAlign();
$width = $column->getWidth();
$celula = new Element('th');
$celula->add($label);
$celula->style = "text-align:$align";
$celula->width = $width;
$row->add($celula);
// verifica se a coluna tem uma ação
if ($column->getAction()) {
$url = $column->getAction();
$celula->onclick = "document.location='$url'";
}
}
}
}
O método createItem() é executado sobre cada item a ser adicionado na
datagrid. Este método cria uma linha (tr) e inicialmente pecorre as ações
(getActions) da datagrid. Para cada ação, cria um elemento (td) contendo
um link ($link); este link é correspondente a ação serializada, passando o
campo ($field) como parâmetro. Estas ações são apresentadas à esquerda
dos dados. Caso a ação tenha uma imagem ($image), esta é apresentada na
forma de ícone (por exemplo, ícones Bootstrap ou Font Awesome). Após
percorrer as ações, este método percorre as colunas (getColumns) da
datagrid. Para cada coluna, adiciona um elemento (td), que conterá os
dados do item ($item->$name). Caso a coluna tenha uma ação de
transformação vinculada (getTransformer), essa ação será aplicada. Um
exemplo de função de transformação é converter para maiúsculo, por
exemplo.
public function createItem($tbody, $item) {
$row = new Element('tr');
$tbody->add($row);
$actions = $this->decorated->getActions();
$columns = $this->decorated->getColumns();
// verifica se a listagem possui ações
if ($actions) {
// percorre as ações
foreach ($actions as $action) {
// obtém as propriedades da ação
$url = $action['action']->serialize();
$label = $action['label'];
$image = $action['image'];
$field = $action['field'];
// obtém o campo do objeto que será passado adiante
$key = $item->$field;
// cria um link
$link = new Element('a');
$link->href = "{$url}&key={$key}&{$field}={$key}";
// verifica se o link será com imagem ou com texto
if ($image) {
// adiciona a imagem ao link
$i = new Element('i');
$i->class = $image;
$i->title = $label;
$i->add('');
$link->add($i);
}
else {
// adiciona o rótulo de texto ao link
$link->add($label);
}
$element = new Element('td');
$element->add($link);
$element->align = 'center';
// adiciona a célula à linha
$row->add($element);
}
}
if ($columns) {
// percorre as colunas da Datagrid
foreach ($columns as $column) {
// obtém as propriedades da coluna
$name = $column->getName();
$align = $column->getAlign();
$width = $column->getWidth();
$function = $column->getTransformer();
$data = $item->$name;
// verifica se há função para transformar os dados
if ($function) {
// aplica a função sobre os dados
$data = call_user_func($function, $data);
}
$element = new Element('td');
$element->add($data);
$element->align = $align;
$element->width = $width;
// adiciona a célula na linha
$row->add($element);
}
}
}
}
7.2.4 Exemplos
Nesta seção serão apresentados exemplos de uso de datagrids.
App/Control/ContatoList.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Datagrid\Datagrid;
use Livro\Widgets\Datagrid\DatagridColumn;
use Livro\Widgets\Wrapper\DatagridWrapper;
class ContatoList extends Page {
private $datagrid; // listagem
public function __construct() {
parent::__construct();
// instancia objeto Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
// instancia as colunas da Datagrid
$codigo = new DatagridColumn('id', 'Código', 'center', '10%');
$nome = new DatagridColumn('nome', 'Nome', 'left', '20%');
$email = new DatagridColumn('email', 'Email', 'left', '30%');
$assunto = new DatagridColumn('assunto', 'Assunto', 'left', '30%');
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($nome);
$this->datagrid->addColumn($email);
$this->datagrid->addColumn($assunto);
// adiciona a datagrid à página
parent::add($this->datagrid);
}
Apenas a de nição do método construtor não é su ciente para formar
uma datagrid completa. É preciso alimentá-la com dados. Já para
criarmos o hábito de escrever métodos separados para nalidades
distintas, criaremos um método somente para alimentar a datagrid,
inserindo as linhas. Vamos chamar esse método de onReload(). A
nalidade desse método é inserir objetos na datagrid. Para isso, ele inicia
com a garantia de que a datagrid esteja limpa (clear). Em seguida, vários
objetos planos (stdClass) são declarados. Cada objeto contém alguns
atributos (id, nome, email, assunto). É importante que os nomes dos
atributos coincidam com o nome das colunas (primeiro atributo de
DatagridColumn), pois somente assim a classe datagrid saberá qual dado
deverá ser “encaixado” em cada coluna.
function onReload() {
$this->datagrid->clear();
$m1 = new stdClass;
$m1->id = 1;
$m1->nome = 'Maria da Silva';
$m1->email = 'maria@email.com';
$m1->assunto = 'Dúvida sobre Formulários';
$this->datagrid->addItem($m1);
$m2 = new stdClass;
$m2->id = 2;
$m2->nome = 'Pedro Cardoso';
$m2->email = 'pedro@email.com';
$m2->assunto = 'Dúvida sobre Listagens';
$this->datagrid->addItem($m2);
// ...
}
Somente a de nição do método onReload() não garante que ele seja
executado. Para garantir que ele seja executado, teríamos de passar pela
URL index.php?class=ContatoList&method=onReload. Para forçar sua execução
sempre que a página for carregada, podemos sobrescrever o método
show(), garantindo a chamada do método onReload() antes do show()
padrão da classe Page.
function show() {
$this->onReload();
parent::show();
}
}
A gura 7.25 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos pela URL index.php?class=ContatoList, onde quer
que esteja rodando nosso servidor de páginas.
Figura 7.25 – Listagem de contatos.
Como você pode perceber, a listagem funcionou adequadamente. Porém o
resultado visual cou novamente ruim, assim como tinham cado nossos
primeiros formulários. Para resolver, poderíamos de nir um conjunto de
classes CSS para melhorar o seu visual por meio de tags como table, entre
outras utilizadas. Mas vamos resolver essas questões visuais por meio de
wrappers, que serão criados na próxima seção, logo após terminarmos os
exemplos. Já utilizados anteriormente em formulários, os wrappers
formarão uma “camada” ao redor da listagem-padrão criada,
embelezando-a.
App/Control/ContatoActionList.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Datagrid\Datagrid;
use Livro\Widgets\Datagrid\DatagridColumn;
use Livro\Widgets\Wrapper\DatagridWrapper;
use Livro\Widgets\Dialog\Message;
class ContatoActionList extends Page {
private $datagrid;
public function __construct() {
parent::__construct();
// instancia objeto Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
// instancia as colunas da Datagrid
$codigo = new DatagridColumn('id', 'Código', 'center', '10%');
$nome = new DatagridColumn('nome', 'Nome', 'left', '20%');
$email = new DatagridColumn('email', 'Email', 'left', '30%');
$assunto = new DatagridColumn('assunto', 'Assunto', 'left', '30%');
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($nome);
$this->datagrid->addColumn($email);
$this->datagrid->addColumn($assunto);
$nome->setTransformer( function($value) {
return strtoupper($value);
});
$this->datagrid->addAction('Visualizar', new Action(array($this,
'onVisualiza')), 'nome')
parent::add($this->datagrid);
}
O método onVisualiza() é executado automaticamente sempre que o
usuário clica sobre a ação no início de cada linha. Neste caso, o método
onVisualiza() recebe como parâmetro a variável ($param), que por sua vez
representa os parâmetros da URL. Nesses parâmetros, uma das
informações presentes é um atributo do objeto clicado que foi de nido
anteriormente como sendo nome no nomento da chamada do addAction().
public function onVisualiza($param) {
new Message('info', 'Você clicou sobre o registro: ' .
$param['nome']);
}
O método onReload() tem exatamente a mesma formação mostrada no
exemplo anterior. Por isso, iremos abreviar seu código aqui.
function onReload() {
$this->datagrid->clear();
$m1 = new stdClass;
$m1->id = 1;
$m1->nome = 'Maria da Silva';
$m1->email = 'maria@email.com';
$m1->assunto = 'Dúvida sobre Formulários';
$this->datagrid->addItem($m1);
// ...
}
O método show() precisa ser reescrito novamente para “forçar” a execução
do onReload() sempre que a página for carregada, sem que precisemos
indicar pela URL.
function show() {
$this->onReload();
parent::show();
}
}
A gura 7.26 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos pela URL index.php?class=ContatoActionList, onde
quer que esteja rodando nosso servidor de páginas. A gura 7.27
representa a listagem, bem como uma mensagem apresentada em seu
topo, indicando que o usuário acabou de clicar sobre a ação
correspondente à primeira linha da datagrid.
App/Control/FuncionarioList.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Datagrid\Datagrid;
use Livro\Widgets\Datagrid\DatagridColumn;
use Livro\Widgets\Wrapper\DatagridWrapper;
use Livro\Widgets\Dialog\Message;
use Livro\Widgets\Dialog\Question;
use Livro\Database\Transaction;
use Livro\Database\Repository;
use Livro\Database\Criteria;
class FuncionarioList extends Page {
private $datagrid; // listagem
private $loaded;
public function __construct() {
parent::__construct();
// instancia objeto Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
Quando morremos, nada pode ser levado conosco, com exceção das
sementes lançadas por nosso trabalho e do nosso conhecimento.
D L
Ao longo desta obra, criamos uma série de classes para automatizar desde
a conexão com banco de dados, as transações, a persistência de objetos e a
manipulação de coleções de objetos até a criação de componentes para
interface como diálogos, formulários, listagens e outros. Em cada capítulo
procuramos dar exemplos da utilização de cada classe criada, e agora
chegou o momento de usar esse conhecimento para construir algo maior,
uma aplicação completa.
8.1.1 Index
No capítulo 6, abordamos o design pattern Front Controller pela primeira
vez.
É importante lembrar que para Martin Fowler um front controller é “um
controlador que manipula todas as requisições do sistema”. Para
implementar um front controller, utilizamos o próprio index.php. A partir
desse instante, convencionamos que, para executar alguma classe
controladora especí ca, seria necessário acessar index.php?
class=ClienteControl.Porém, para executar uma ação especí ca, seria
necessário acessar index.php?class=ClienteControl&method=listar, por exemplo.
Em um sistema grande, há tarefas comuns a todas as páginas do sistema,
como veri cação de permissão, carregamento das classes (autoloader),
carregamento do template do sistema, entre outras. Essas tarefas comuns
podem ser executadas justamente no index.php. O index.php a seguir, que
será utilizado para a nova aplicação, de ne os autoloaders, responsáveis
pelo carregamento dos componentes construídos no diretório Lib
(Lib/Livro/Core/ClassLoader.php) e também para as classes da aplicação
construídas no diretório App (Lib/Livro/Core/AppLoader.php). O index
também carregará o autoloader dos pacotes de terceiros instalados pelo
Composer (vendor/autoload.php). Dessa forma, de nindo todos os
autoloaders, todas as classes cam acessíveis quando são necessárias, pois
os autoloaders sabem onde carregá-las.
Para desenvolver a nova aplicação proposta neste capítulo, vamos realizar
uma melhoria no index.php construído no capítulo 6 para “encaixar” o
conteúdo gerado por uma página dentro de um “template”, ou seja, um
layout prede nido em HTML, com alguns conteúdos como cabeçalho da
página, menus de opções, entre outros. A gura 8.1 mostra o template
escolhido, que também é baseado na biblioteca Bootstrap. Veja que na
parte central do template será exibido o conteúdo da página (classe de
controle) que, por sua vez, é identi cada na URL em cada requisição.
Figura 8.1 – Template.
É relativamente simples “encaixar” o conteúdo do programa dentro do
template. Veja no programa a seguir que é feita a leitura do conteúdo do
arquivo de template que está localizado em App/Templates/template.html. O
conteúdo desse arquivo é armazenado na variável $template. Logo em
seguida, veri ca-se se há alguma requisição ($_GET) informando uma
classe ($_GET['class']). Caso exista alguma requisição desse tipo, o
conteúdo da página será exibido no momento em que executarmos o seu
método show(). Todo esse processo está “protegido” por um controle de
exceções try/catch. Se há uma requisição e se existe um parâmetro
chamado class, este instanciará a classe correspondente identi cada pela
URL e executará seu método show() sobre essa classe. O método show()
deverá decidir qual método deve ser executado internamente na classe.
Para “capturar” o conteúdo gerado por uma página, usamos o controle de
output do PHP, com as funções ob_start(), ob_get_contents() e
ob_end_clean(), que “capturam” o conteúdo gerado por comandos como
echo ou print. Dessa forma, a execução do método show() está entre os
métodos ob_start() e ob_end_clean(). O conteúdo gerado pelo método
show() é armazenado na variável $content. Ao nal do programa, a
variável $content é inserida no template ($template), no lugar da string
“{content}”, que está presente no arquivo de template somente para
“guardar” lugar. Na próxima seção, analisaremos o arquivo de template, e
cará mais claro como é feita a substituição de conteúdo.
index.php
<?php
// Library loader
require_once 'Lib/Livro/Core/ClassLoader.php';
$al= new Livro\Core\ClassLoader;
$al->addNamespace('Livro', 'Lib/Livro');
$al->register();
// Application loader
require_once 'Lib/Livro/Core/AppLoader.php';
$al= new Livro\Core\AppLoader;
$al->addDirectory('App/Control');
$al->addDirectory('App/Model');
$al->register();
// Vendor
$loader = require 'vendor/autoload.php';
$loader->register();
// lê o conteúdo do template
$template = file_get_contents('App/Templates/template.html');
$content = '';
$class = 'Home';
if ($_GET) {
$class = $_GET['class'];
if (class_exists($class)) {
try {
$pagina = new $class; // instancia a classe
ob_start(); // inicia controle de output
$pagina->show(); // exibe página
$content = ob_get_contents(); // lê conteúdo gerado
ob_end_clean(); // finaliza controle de output
}
catch (Exception $e) {
$content = $e->getMessage() . '<br>' .$e->getTraceAsString();
}
}
else {
$content = "Class <b>{$class}</b> not found";
}
}
// injeta conteúdo gerado dentro do template
$output = str_replace('{content}', $content, $template);
$output = str_replace('{class}', $class, $output);
// exibe saída gerada
echo $output;
8.1.2 Template
O template utilizado para o sistema é um simples arquivo HTML,
localizado no diretório App/Templates. Na listagem de código-fonte a seguir,
tivemos de resumir o template para que ele não casse muito extenso no
livro. Porém você o encontra completo com os demais arquivos para
download no site da Novatec Editora.
No arquivo de template, vamos destacar alguns elementos importantes
em negrito. Em primeiro lugar, na seção <head> do documento, temos a
inclusão de algumas bibliotecas importantes para a aplicação como
jQuery, Bootstrap, ChartJs e Font Awesome. Os estilos da biblioteca
Bootstrap, por exemplo, serão utilizados por alguns componentes e pelo
próprio template da aplicação.
Além da importação de bibliotecas de uso comum, destaca-se também o
menu da aplicação, com opções para cadastro de cidades, fabricantes,
produtos e pessoas, processo de vendas e relatórios de vendas e de contas.
Por m, destaca-se a string {content}, que está no arquivo de template
simplesmente “guardando lugar”, pois ela será substituída pelo conteúdo
gerado pela página atual, que é identi cada pela URL ?class=X. Essa
substituição é feita ao nal do index.php pela função str_replace().
App/Templates/template.html
<!DOCTYPE html>
<head>
<title>PHP - Programando com Orientação a Objetos (template)</title>
<link href="App/Templates/assets/css/bootstrap.min.css"
rel="stylesheet" />
<link href="App/Templates/assets/css/font-awesome.min.css"
rel="stylesheet">
<script src="App/Templates/assets/js/Chart.bundle.min.js"></script>
<script src="App/Templates/assets/js/jquery.3.2.1.min.js"
type="text/javascript"></script>
<script src="App/Templates/assets/js/bootstrap.min.js"
type="text/javascript"></script>
</head>
<body>
<div class="wrapper">
<div class="sidebar" ...>
<div class="sidebar-wrapper">
<ul class="nav">
<li><a href="index.php?class=CidadesFormList">
<i class="fa fa-map-marker"></i> Cidades</a></li>
<li><a href="index.php?class=FabricantesFormList">
<i class="fa fa-industry fa-sm"></i> Fabricantes</a></li>
<li><a href="index.php?class=ProdutosList">
<i class="fa fa-cubes fa-sm"></i> Produtos</a></li>
<li><a href="index.php?class=PessoasList">
<i class="fa fa-users"></i> Pessoas</a></li>
<li><a href="index.php?class=VendasForm">
<i class="fa fa-shopping-cart"></i> Vendas</a></li>
<li><a href="index.php?class=VendasReport">
<i class="fa fa-file-text-o"></i> Rel. Vendas</a></li>
<li><a href="index.php?class=ContasReport">
<i class="fa fa-file-text-o"></i> Rel. Contas</a></li>
<li><a href="index.php?class=ProdutosReport">
<i class="fa fa-file-text-o"></i> Rel. Produtos</a></li>
<li><a href="index.php?class=PessoasReport">
<i class="fa fa-file-text-o"></i> Rel. Pessoas</a></li>
<li><a href="index.php?class=VendasMesChart">
<i class="fa fa-bar-chart"></i> Vendas mês</a></li>
<li><a href="index.php?class=VendasTipoChart">
<i class="fa fa-pie-chart"></i> Vendas tipo</a></li>
<li><a href="index.php?class=DashboardView">
<i class="fa fa-tachometer"></i> Dashboard</a></li>
<li>
/ul>
</div>
</div>
</div>
<div class="main-panel">
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
{content}
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
App/Database/livro-sqlite.sql
CREATE TABLE estado (
id integer PRIMARY KEY NOT NULL,
sigla char(2),
nome text
);
CREATE TABLE cidade (
id integer PRIMARY KEY NOT NULL,
nome text,
id_estado INTEGER REFERENCES estado (id)
);
CREATE TABLE grupo (
id integer PRIMARY KEY NOT NULL,
nome text
);
CREATE TABLE fabricante (
id integer PRIMARY KEY NOT NULL,
nome text,
site text
);
CREATE TABLE unidade (
id integer PRIMARY KEY NOT NULL,
sigla text,
nome text
);
CREATE TABLE tipo (
id integer PRIMARY KEY NOT NULL,
nome text
);
CREATE TABLE produto (
id integer PRIMARY KEY NOT NULL,
descricao text,
estoque float,
preco_custo float,
preco_venda float,
id_fabricante integer references fabricante(id),
id_unidade integer references unidade(id),
id_tipo integer references tipo(id)
);
CREATE TABLE pessoa (
id integer PRIMARY KEY NOT NULL,
nome text,
endereco text,
bairro text,
telefone text,
email text,
id_cidade integer references cidade(id)
);
CREATE TABLE venda (
id integer PRIMARY KEY NOT NULL,
id_cliente integer references pessoa(id),
data_venda date,
valor_venda float,
desconto float,
acrescimos float,
valor_final float,
obs text
);
CREATE TABLE item_venda (
id integer PRIMARY KEY NOT NULL,
id_produto integer references produto(id),
id_venda integer references venda(id),
quantidade float,
preco float
);
CREATE TABLE conta (
id integer PRIMARY KEY NOT NULL,
id_cliente INTEGER REFERENCES pessoa(id),
dt_emissao date,
dt_vencimento date,
valor float,
paga char(1)
);
CREATE TABLE pessoa_grupo (
id integer PRIMARY KEY NOT NULL,
id_pessoa integer references pessoa(id),
id_grupo integer references grupo(id)
);
App/Model/Estado.php
<?php
use Livro\Database\Record;
class Estado extends Record {
const TABLENAME = 'estado';
}
App/Model/Cidade.php
<?php
use Livro\Database\Record;
class Cidade extends Record {
const TABLENAME = 'cidade';
private $estado;
public function get_estado() {
if (empty($this->estado)) {
$this->estado = new Estado($this->id_estado);
}
return $this->estado;
}
public function get_nome_estado() {
if (empty($this->estado)) {
$this->estado = new Estado($this->id_estado);
}
return $this->estado->nome;
}
}
App/Model/Grupo.php
<?php
use Livro\Database\Record;
class Grupo extends Record {
const TABLENAME = 'grupo';
}
8.2.1.4 Classe Fabricante
A classe Fabricante representará a tabela fabricante. Por ser subclasse de
Record, já contém métodos como store(), load(), delete(), all(), find() e
outros. Nenhum método adicional é necessário para essa classe.
App/Model/Fabricante.php
<?php
use Livro\Database\Record;
class Fabricante extends Record {
const TABLENAME = 'fabricante';
}
App/Model/Unidade.php
<?php
use Livro\Database\Record;
class Unidade extends Record {
const TABLENAME = 'unidade';
}
App/Model/Tipo.php
<?php
use Livro\Database\Record;
class Tipo extends Record {
const TABLENAME = 'tipo';
}
App/Model/PessoaGrupo.php
<?php
use Livro\Database\Record;
class PessoaGrupo extends Record {
const TABLENAME = 'pessoa_grupo';
}
App/Model/Conta.php
<?php
use Livro\Database\Record;
use Livro\Database\Criteria;
use Livro\Database\Repository;
class Conta extends Record {
const TABLENAME = 'conta';
private $cliente;
public function get_cliente() {
if (empty($this->cliente)) {
$this->cliente = new Pessoa($this->id_cliente);
}
return $this->cliente; // Retorna o objeto instanciado
}
public static function getByPessoa($id_pessoa) {
$criteria = new Criteria;
$criteria->add('paga', '<>', 'S');
$criteria->add('id_cliente', '=', $id_pessoa);
App/Model/Venda.php
<?php
use Livro\Database\Transaction;
use Livro\Database\Record;
use Livro\Database\Repository;
use Livro\Database\Criteria;
class Venda extends Record {
const TABLENAME = 'venda';
private $itens;
private $cliente;
public function set_cliente(Pessoa $c) {
$this->cliente = $c;
$this->id_cliente = $c->id;
}
public function get_cliente() {
if (empty($this->cliente)) {
$this->cliente = new Pessoa($this->id_cliente);
}
return $this->cliente; // Retorna o objeto instanciado
}
public function addItem(Produto $p, $quantidade) {
$item = new ItemVenda;
$item->produto = $p;
$item->preco = $p->preco_venda;
$item->quantidade = $quantidade;
$this->itens[] = $item;
$this->valor_venda += ($item->preco * $quantidade);
}
public function store() {
parent::store(); // armazena a venda
// percorre os itens da venda
foreach ($this->itens as $item) {
$item->id_venda = $this->id;
$item->store(); // armazena o item
}
}
public function get_itens() {
// instancia um repositório de Item
$repositorio = new Repository('ItemVenda');
// define o critério de filtro
$criterio = new Criteria;
$criterio->add('id_venda', '=', $this->id);
$this->itens = $repositorio->load($criterio); // carrega a coleção
return $this->itens; // retorna os itens
}
}
App/Model/ItemVenda.php
<?php
use Livro\Database\Record;
class ItemVenda extends Record {
const TABLENAME = 'item_venda';
private $produto;
public function set_produto(Produto $p) {
$this->produto = $p;
$this->id_produto = $p->id;
}
public function get_produto() {
if (empty($this->produto)) {
$this->produto = new Produto($this->id_produto);
}
return $this->produto;
}
}
App/Model/Produto.php
<?php
use Livro\Database\Record;
class Produto extends Record {
const TABLENAME = 'produto';
private $fabricante;
public function get_nome_fabricante() {
if (empty($this->fabricante)) {
$this->fabricante = new Fabricante($this->id_fabricante);
}
return $this->fabricante->nome;
}
}
App/Control/Tests/ModelTest1.php
<?php
use Livro\Control\Page;
use Livro\Database\Transaction;
class ModelTest1 extends Page {
public function show() {
try {
Transaction::open('livro');
$c1 = Cidade::find(12);
print($c1->nome) . '<br>';
print($c1->estado->nome) . '<br>'; // o mesmo que $c1-
>get_estado()->nome
print($c1->nome_estado) . '<br>'; // o mesmo que $c1-
>get_nome_estado()
$p1 = Pessoa::find(12);
print($p1->nome) . '<br>';
print($p1->nome_cidade) . '<br>';
print($p1->cidade->nome) . '<br>';
print($p1->cidade->estado->nome) . '<br>';
Transaction::close();
}
catch (Exception $e) {
echo $e->getMessage();
}
}
}
Resultado:
João Pessoa
Paraíba
Paraíba
Isaac Morrison
Recife
Recife
Pernambuco
App/Control/Tests/ModelTest2.php
<?php
use Livro\Control\Page;
use Livro\Database\Transaction;
class ModelTest2 extends Page {
public function show() {
try {
Transaction::open('livro');
// busca pessoa 1
$p1 = Pessoa::find(1);
$p1->delGrupos(); // apaga grupos
$p1->addGrupo( new Grupo(1) ); // adiciona grupo
$p1->addGrupo( new Grupo(3) ); // adiciona grupo
$grupos = $p1->getGrupos(); // carrega grupos
if ($grupos) {
foreach ($grupos as $grupo) {
print $grupo->id . ' - ';
print $grupo->nome . '<br>';
}
}
Transaction::close();
}
catch (Exception $e) {
echo $e->getMessage();
}
}
}
Resultado:
1 - Cliente
3 - Revendedor
App/Control/Tests/ModelTest4.php
<?php
use Livro\Control\Page;
use Livro\Database\Transaction;
class ModelTest4 extends Page {
public function show() {
try {
Transaction::open('livro');
$p1 = Pessoa::find(1);
print 'Valor total: ' . $p1->totalDebitos() . '<br>';
echo '<hr>';
$contas = $p1->getContasEmAberto();
if ($contas) {
foreach ($contas as $conta) {
print $conta->dt_emissao . ' - ';
print $conta->dt_vencimento . ' - ';
print $conta->valor . '<br>';
}
}
Transaction::close();
}
catch (Exception $e) {
echo $e->getMessage();
}
}
}
Resultado:
Valor total: 390
2015-04-18 - 2015-04-20 - 195.0
2015-04-18 - 2015-05-20 - 195.0
8.3 Programa
Nesta seção, serão apresentados vários programas individuais que irão
compor nossa aplicação, como cadastro de pessoas, de produtos, de
fabricantes, de cidades, registro de vendas, relatórios, grá cos e painéis.
App/Control/PessoasForm.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Form\Form;
use ...
class PessoasForm extends Page {
private $form;
public function __construct() {
parent::__construct();
// instancia um formulário
$this->form = new FormWrapper(new Form('form_pessoas'));
$this->form->setTitle('Pessoa');
// cria os campos do formulário
$codigo = new Entry('id');
$nome = new Entry('nome');
$endereco = new Entry('endereco');
$bairro = new Entry('bairro');
$telefone = new Entry('telefone');
$email = new Entry('email');
$cidade = new Combo('id_cidade');
$grupo = new CheckGroup('ids_grupos');
$grupo->setLayout('horizontal');
// carrega as cidades do banco de dados
Transaction::open('livro');
$cidades = Cidade::all();
$items = array();
foreach ($cidades as $obj_cidade) {
$items[$obj_cidade->id] = $obj_cidade->nome;
}
$cidade->addItems($items);
$grupos = Grupo::all();
$items = array();
foreach ($grupos as $obj_grupo) {
$items[$obj_grupo->id] = $obj_grupo->nome;
}
$grupo->addItems($items);
Transaction::close();
$this->form->addField('Código', $codigo, '30%');
$this->form->addField('Nome', $nome, '70%');
$this->form->addField('Endereço', $endereco, '70%');
$this->form->addField('Bairro', $bairro, '70%');
$this->form->addField('Telefone', $telefone, '70%');
$this->form->addField('Email', $email, '70%');
$this->form->addField('Cidade', $cidade, '70%');
$this->form->addField('Grupo', $grupo, '70%');
$codigo->setEditable(FALSE);
$this->form->addAction('Salvar', new Action(array($this,
'onSave')));
// adiciona o formulário na página
parent::add($this->form);
}
Quando o botão de ação “Salvar” for clicado, automaticamente uma nova
requisição será gerada ao servidor. Essa requisição será feita para a URL
index.php?class=PessoasForm&method=onSave, visto que foi essa a ação
con gurada pelo método addAction(). Nessa nova requisição ao servidor,
o método construtor é executado novamente, criando os objetos em
memória, e logo em seguida é executado o método onSave(), que irá
“receber” os dados via POST.
No método onSave(), abrimos uma transação com a base de dados e, em
seguida, efetuamos a leitura dos dados enviados por meio do método
getData(). O método setData() é usado para manter o formulário
preenchido após a postagem. Em seguida, instanciamos um objeto da
classe Pessoa e o alimentamos por meio de seu método fromArray() a partir
dos dados vindos do formulário ($dados) devidamente convertidos para
array. Ao utilizarmos o método fromArray(), estaremos preenchendo cada
um dos atributos do objeto Pessoa com os campos vindos do formulário,
desde que os nomes dos atributos sejam iguais aos nomes dos campos.
Depois, os dados da pessoa são armazenados na tabela por meio do
método store().
Após os dados da pessoa serem salvos, é preciso refazer sua relação com
os grupos (tabela pessoa_grupo). Assim, inicialmente são eliminados os
grupos já vinculados por meio da execução do método delGrupos(). A
partir de então, os grupos preenchidos no formulário são percorridos por
um foreach. Como o campo ids_grupos é do tipo CheckGroup, seu retorno
ocorre na forma de um array. Cada grupo selecionado no CheckGroup é
adicionado à pessoa por meio do método addGrupo().
Os dados somente são persistidos no banco de dados realmente ao
executarmos o método close() da classe Transaction, que fecha a
transação com o banco de dados, enviando um commit.
public function onSave() {
try {
// inicia transação com o BD
Transaction::open('livro');
$dados = $this->form->getData();
$this->form->setData($dados);
$pessoa = new Pessoa; // instancia objeto
$pessoa->fromArray( (array) $dados); // carrega os dados
$pessoa->store(); // armazena o objeto no banco de dados
$pessoa->delGrupos();
if ($dados->ids_grupos) {
foreach ($dados->ids_grupos as $id_grupo) {
$pessoa->addGrupo( new Grupo($id_grupo) );
}
}
Transaction::close(); // finaliza a transação
new Message('info', 'Dados armazenados com sucesso');
}
catch (Exception $e) {
// exibe a mensagem de exceção
new Message('error', $e->getMessage());
// desfaz todas alterações no banco de dados
Transaction::rollback();
}
}
O próximo passo é disponibilizar um método para carregar o formulário
para posterior edição. Neste caso, vamos criar um método chamado
onEdit(), cuja nalidade é “carregar” os dados do formulário com um
registro já cadastrado na base de dados. Para isso, teremos de identi car
esse registro também pela URL. Para executar o método onEdit(),
preenchendo o formulário, devemos acionar a URL index.php?
class=PessoasForm&method=onEdit&id=5 para carregar o formulário com os
dados do registro de ID=5.
Observação: neste momento teremos de acionar a URL manualmente para editar
um registro. Entretanto, já no próximo exemplo construiremos um programa para
editar pessoas, que por sua vez será formado por uma datagrid com uma ação de
edição que executará automaticamente esse método onEdit() criado.
Todo método recebe por padrão a variável $_GET, que contém todos os
parâmetros passados via URL da página. Neste caso, o método onEdit()
recebe o parâmetro $param, que contém um array que apresenta as
variáveis da URL. O método onEdit() inicia abrindo uma transação com a
base de dados livro e lendo a variável id da URL. Em seguida, utiliza o
método Pessoa::find() para localizar esse objeto na base de dados. O
método getIdsGrupos() é usado para carregar os grupos vinculados àquela
pessoa em forma de array contendo os IDs cadastrados, que é o formato
esperado pelo componente CheckGroup. Em seguida, o objeto é usado para
preencher o formulário por meio do método setData().
public function onEdit($param) {
try {
if (isset($param['id'])) {
$id = $param['id']; // obtém a chave
Transaction::open('livro'); // inicia transação com o BD
$pessoa = Pessoa::find($id);
if ($pessoa) {
$pessoa->ids_grupos = $pessoa->getIdsGrupos();
$this->form->setData($pessoa); // lança os dados da pessoa no
formulário
}
Transaction::close(); // finaliza a transação
}
}
catch (Exception $e) {
new Message('error', $e->getMessage()); // exibe a mensagem gerada
pela exceção
Transaction::rollback(); // desfaz todas alterações no banco de
dados
}
}
Na gura 8.4 é apresentado o resultado nal do formulário construído. O
formulário em questão está no modo de edição.
App/Control/PessoasList.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Form\Form;
use ...
class PessoasList extends Page {
private $form; // formulário de buscas
private $datagrid; // listagem
private $loaded;
public function __construct() {
parent::__construct();
// instancia um formulário de buscas
$this->form = new FormWrapper(new Form('form_busca_pessoas'));
$this->form->setTitle('Pessoas');
$nome = new Entry('nome');
$this->form->addField('Nome', $nome, '100%');
$this->form->addAction('Buscar', new Action(array($this,
'onReload')));
$this->form->addAction('Novo', new Action(array(new PessoasForm,
'onEdit')));
// instancia objeto Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
// instancia as colunas da Datagrid
$codigo = new DatagridColumn('id', 'Código', 'center', '10%');
$nome = new DatagridColumn('nome', 'Nome', 'left', '40%');
$endereco = new DatagridColumn('endereco', 'Endereco','left',
'30%');
$cidade = new DatagridColumn('nome_cidade','Cidade', 'left',
'20%');
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($nome);
$this->datagrid->addColumn($endereco);
$this->datagrid->addColumn($cidade);
$this->datagrid->addAction( 'Editar', new Action([new PessoasForm,
'onEdit']),
'id', 'fa fa-edit fa-lg blue');
$this->datagrid->addAction( 'Excluir', new Action([$this,
'onDelete']),
'id', 'fa fa-trash fa-lg red');
// monta a página por meio de uma caixa
$box = new VBox;
$box->style = 'display:block';
$box->add($this->form);
$box->add($this->datagrid);
parent::add($box);
}
Toda a interação com a base de dados está concentrada no método
onReload(). Vamos iniciar uma transação com a base de dados pelo
método Transaction::open() e em seguida utilizar a classe Repository para
carregar os objetos por meio de seu método load(), como já abordado no
capítulo 5. Mas, antes de simplesmente executar o método load() e
carregar os registros, veri camos se o método onReload() está sendo
executado a partir de uma postagem do formulário de buscas. Para isso,
usamos o método getData() do formulário de buscas e testamos a variável
$dados->nome. Caso ela tenha conteúdo, signi ca que o usuário realizou
uma busca. Assim, adicionamos um ltro de buscas ao critério já criado
(Criteria) por meio do método add(). O critério é usado como parâmetro
do método load(). Caso o usuário não tenha feito buscas, o ltro não será
adicionado.
A classe Repository contém o método load(), que por sua vez recebe um
critério de seleção de objetos (Criteria). O método load() retorna um
vetor de objetos $pessoas, e cada posição desse vetor é um objeto da classe
Pessoa. Após a carga dos objetos, a datagrid é limpa (clear), e cada um dos
objetos é adicionado a ela por meio do método addItem(). É importante
notar que os atributos do objeto que está sendo adicionado serão
distribuídos pelas colunas da Datagrid conforme o nome de cada coluna,
que por sua vez é de nido no construtor da classe DatagridColumn
(primeiro parâmetro).
public function onReload() {
Transaction::open('livro'); // inicia transação com o BD
$repository = new Repository('Pessoa');
// cria um critério de seleção de dados
$criteria = new Criteria;
$criteria->setProperty('order', 'id');
// obtém os dados do formulário de buscas
$dados = $this->form->getData();
// verifica se o usuário preencheu o formulário
if ($dados->nome) {
// filtra pelo nome do pessoa
$criteria->add('nome', 'like', "%{$dados->nome}%");
}
// carrega os produtos que satisfazem o critério
$pessoas = $repository->load($criteria);
$this->datagrid->clear();
if ($pessoas) {
foreach ($pessoas as $pessoa) {
// adiciona o objeto à Datagrid
$this->datagrid->addItem($pessoa);
}
}
// finaliza a transação
Transaction::close();
$this->loaded = true;
}
No método construtor, criamos uma ação para exclusão de objetos que foi
vinculada ao método onDelete(). O objetivo dessa ação é, inicialmente,
perguntar ao usuário se ele quer excluir o registro atual. Essa ação recebe
o id como parâmetro. Com base nesse id, o método onDelete() monta
uma pergunta para o usuário por meio da classe Question. Caso o usuário
responda a rmativamente, a ação $action1 é executada, transferindo para
o método Delete() a responsabilidade e passando o parâmetro id adiante.
public function onDelete($param) {
$id = $param['id']; // obtém o parâmetro id
$action1 = new Action(array($this, 'Delete'));
$action1->setParameter('id', $id);
new Question('Deseja realmente excluir o registro?', $action1);
}
O método Delete() é acionado por meio do método onDelete() caso o
usuário responda a rmativamente quanto à exclusão do registro. Nesse
caso, ele recebe, por meio do vetor de parâmetros ($param), o id a ser
excluído. Então, abre-se uma transação com a base de dados e em seguida
carrega-se para a memória o objeto da classe Pessoa por meio do método
find(). Daí, executa-se o seu método delete() e a transação é fechada. Por
m, a datagrid é recarregada e uma mensagem de sucesso é exibida ao
usuário por meio da classe Message. Se alguma exceção ocorrer no bloco
try, a execução será automaticamente desfeita, a transação não será
encerrada (close) e o usuário verá a mensagem de exceção no bloco catch.
public function Delete($param) {
try {
$id = $param['id']; // obtém a chave
Transaction::open('livro'); // inicia transação com o banco 'livro'
$pessoa = Pessoa::find($id);
$pessoa->delete(); // deleta objeto do banco de dados
Transaction::close(); // finaliza a transação
$this->onReload(); // recarrega a datagrid
new Message('info', "Registro excluído com sucesso");
}
catch (Exception $e) {
new Message('error', $e->getMessage());
}
}
Por m, o método show() é sobrescrito para garantir que o método
onReload() sempre seja executado antes de a página ser carregada.
public function show() {
// se a listagem ainda não foi carregada
if (!$this->loaded) {
$this->onReload();
}
parent::show();
}
A gura 8.5 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?class=PessoasList,
onde quer que esteja rodando nosso servidor de páginas.
Observação: normalmente em um sistema precisamos de um mecanismo de
paginação. Caso queira acessá-lo, estará disponibilizado com os exemplos do livro
o arquivo App/Control/PessoasListPageNav.php, que utiliza a classe PageNavigation,
criada para fornecer paginação.
Figura 8.5 – Listagem de pessoas.
Lib/Livro/Traits/SaveTrait.php
<?php
namespace Livro\Traits;
use Livro\Database\Transaction;
use Livro\Widgets\Dialog\Message;
use Exception;
trait SaveTrait {
function onSave() {
try {
Transaction::open( $this->connection ); // abre transação
$class = $this->activeRecord; // classe de Active Record
$dados = $this->form->getData(); // lê dados do form
$object = new $class; // instancia objeto
$object->fromArray( (array) $dados); // carrega os dados
$object->store(); // armazena o objeto
Transaction::close(); // finaliza a transação
new Message('info', 'Dados armazenados com sucesso');
}
catch (Exception $e) {
new Message('error', $e->getMessage());
}
}
}
Lib/Livro/Traits/ReloadTrait.php
<?php
namespace Livro\Traits;
use Livro\Database\Transaction;
use Livro\Database\Repository;
use Livro\Database\Criteria;
use Livro\Widgets\Dialog\Message;
use Exception;
trait ReloadTrait {
function onReload() {
try {
Transaction::open( $this->connection ); // abre transação
$repository = new Repository( $this->activeRecord ); // cria
repositório
// cria um critério de seleção de dados
$criteria = new Criteria;
$criteria->setProperty('order', 'id');
// verifica se há filtro predefinido
if (isset($this->filters)) {
foreach ($this->filters as $filter) {
$criteria->add($filter[0], $filter[1], $filter[2],
$filter[3]);
}
}
// carrega os objetos que satisfazem o critério
$objects = $repository->load($criteria);
$this->datagrid->clear();
if ($objects) {
foreach ($objects as $object) {
// adiciona o objeto na Datagrid
$this->datagrid->addItem($object);
}
}
Transaction::close();
}
catch (Exception $e) {
new Message($e->getMessage());
}
}
}
App/Control/ProdutosForm.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use ...
use Livro\Traits\SaveTrait;
use Livro\Traits\EditTrait;
class ProdutosForm extends Page {
private $form, $connection, $activeRecord;
use SaveTrait;
use EditTrait;
public function __construct() {
parent::__construct();
$this->connection = 'livro'; // nome da conexão
$this->activeRecord = 'Produto'; // nome do Active Record
// instancia um formulário
$this->form = new FormWrapper(new Form('form_produtos'));
$this->form->setTitle('Produto');
// cria os campos do formulário
$codigo = new Entry('id');
$descricao = new Entry('descricao');
$estoque = new Entry('estoque');
$preco_custo = new Entry('preco_custo');
$preco_venda = new Entry('preco_venda');
$fabricante = new Combo('id_fabricante');
$tipo = new RadioGroup('id_tipo');
$unidade = new Combo('id_unidade');
// carrega os fabricantes do banco de dados
Transaction::open('livro');
$fabricantes = Fabricante::all();
$items = array();
foreach ($fabricantes as $obj_fabricante) {
$items[$obj_fabricante->id] = $obj_fabricante->nome;
}
$fabricante->addItems($items);
$tipos = Tipo::all();
$items = array();
foreach ($tipos as $obj_tipo) {
$items[$obj_tipo->id] = $obj_tipo->nome;
}
$tipo->addItems($items);
$unidades = Unidade::all();
$items = array();
foreach ($unidades as $obj_unidade) {
$items[$obj_unidade->id] = $obj_unidade->nome;
}
$unidade->addItems($items);
Transaction::close();
// define alguns atributos para os campos do formulário
$codigo->setEditable(FALSE);
$this->form->addField('Código', $codigo, '30%');
$this->form->addField('Descrição', $descricao, '70%');
$this->form->addField('Estoque', $estoque, '70%');
$this->form->addField('Preço custo', $preco_custo, '70%');
$this->form->addField('Preço venda', $preco_venda, '70%');
$this->form->addField('Fabricante', $fabricante, '70%');
$this->form->addField('Tipo', $tipo, '70%');
$this->form->addField('Unidade', $unidade, '70%');
$this->form->addAction('Salvar', new Action(array($this,
'onSave')));
App/Control/ProdutosList.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use ...
use Livro\Traits\DeleteTrait;
use Livro\Traits\ReloadTrait;
class ProdutosList extends Page {
private $form;
private $datagrid;
private $loaded;
private $connection;
private $activeRecord;
private $filters;
use DeleteTrait;
use ReloadTrait {
onReload as onReloadTrait;
}
public function __construct() {
parent::__construct();
$this->connection = 'livro'; // nome da conexão
$this->activeRecord = 'Produto'; // nome do Active Record
// instancia um formulário
$this->form = new FormWrapper(new Form('form_busca_produtos'));
$this->form->setTitle('Produtos');
// cria os campos do formulário
$descricao = new Entry('descricao');
$this->form->addField('Descrição', $descricao, '100%');
$this->form->addAction('Buscar', new Action(array($this,
'onReload')));
$this->form->addAction('Cadastrar', new Action(array(new
ProdutosForm, 'onEdit')));
// instancia objeto Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
// instancia as colunas da Datagrid
$codigo = new DatagridColumn('id', 'Código', 'center', '10%');
$descricao= new DatagridColumn('descricao', 'Descrição', 'left',
'30%');
$fabrica = new
DatagridColumn('nome_fabricante','Fabricante','left', '30%');
$estoque = new DatagridColumn('estoque', 'Estoq.', 'right', '15%');
$preco = new DatagridColumn('preco_venda', 'Venda', 'right',
'15%');
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($descricao);
$this->datagrid->addColumn($fabrica);
$this->datagrid->addColumn($estoque);
$this->datagrid->addColumn($preco);
$this->datagrid->addAction( 'Editar', new Action([new ProdutosForm,
'onEdit']),
'id', 'fa fa-edit fa-lg blue');
$this->datagrid->addAction( 'Excluir', new Action([$this,
'onDelete']),
'id', 'fa fa-trash fa-lg red');
// monta a página através de uma caixa
$box = new VBox;
$box->style = 'display:block';
$box->add($this->form);
$box->add($this->datagrid);
parent::add($box);
}
O método onReload() é executado sempre que a página é exibida e
também quando o usuário faz buscas pelo formulário, já que está
de nido como ação para o botão “Buscar”. Esse método inicia com a
veri cação sobre os dados do formulário ($this->form->getData()). Se o
usuário tiver feito alguma busca, o atributo ($dados->descricao) terá
conteúdo. Neste caso, de nimos o atributo ($this->filters) com o ltro
necessário. O atributo $this->filters é “observado” pelo trait ReloadTrait.
Sempre que um atributo $this->filters tiver conteúdo, ele será
adicionado como critério de ltro na busca. Veja que, após de nirmos o
ltro, executamos o método onReloadTrait(), responsável por efetuar a
carga dos objetos a partir do banco de dados. O método onReloadTrait()
na verdade não consta dessa classe, mas, quando importamos o
ReloadTrait, rede nimos o método onReload() contido no trait para
onReloadTrait(). Assim, ao executarmos onReloadTrait(), estamos na
verdade executando o método onReload() do trait ReloadTrait.
public function onReload() {
// obtém os dados do formulário de buscas
$dados = $this->form->getData();
// verifica se o usuário preencheu o formulário
if ($dados->descricao) {
// filtra pela descrição do produto
$this->filters[] = ['descricao', 'like', "%{$dados->descricao}%",
'and'];
}
$this->onReloadTrait();
$this->loaded = true;
}
Por m, o método show() é sobrescrito para garantir que o método
onReload() sempre seja executado antes de a página ser carregada.
public function show() {
// se a listagem ainda não foi carregada
if (!$this->loaded) {
$this->onReload();
}
parent::show();
}
}
A gura 8.7 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?class=ProdutosList,
onde quer que esteja rodando nosso servidor de páginas.
Figura 8.7 – Listagem de produtos.
App/Control/CidadesFormList.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use ...
class CidadesFormList extends Page {
private $form, $datagrid, $loaded, $connection, $activeRecord;
use EditTrait;
use DeleteTrait;
use ReloadTrait {
onReload as onReloadTrait;
}
use SaveTrait {
onSave as onSaveTrait;
}
public function __construct() {
parent::__construct();
$this->connection = 'livro';
$this->activeRecord = 'Cidade';
// instancia um formulário
$this->form = new FormWrapper(new Form('form_cidades'));
$this->form->setTitle('Cidades');
// cria os campos do formulário
$codigo = new Entry('id');
$descricao = new Entry('nome');
$estado = new Combo('id_estado');
$codigo->setEditable(FALSE);
Transaction::open('livro');
$estados = Estado::all();
$items = array();
foreach ($estados as $obj_estado) {
$items[$obj_estado->id] = $obj_estado->nome;
}
Transaction::close();
$estado->addItems($items);
$this->form->addField('Código', $codigo, '30%');
$this->form->addField('Descrição', $descricao, '70%');
$this->form->addField('Estado', $estado, '70%');
$this->form->addAction('Salvar', new Action(array($this,
'onSave')));
$this->form->addAction('Limpar', new Action(array($this,
'onEdit')));
// instancia a Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
// instancia as colunas da Datagrid
$codigo = new DatagridColumn('id', 'Código', 'center', '10%');
$nome = new DatagridColumn('nome', 'Nome', 'left', '50%');
$estado = new DatagridColumn('nome_estado', 'Estado', 'left',
'40%');
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($nome);
$this->datagrid->addColumn($estado);
$this->datagrid->addAction( 'Editar', new Action([$this,
'onEdit']), 'id',
'fa fa-edit fa-lg blue');
$this->datagrid->addAction( 'Excluir', new Action([$this,
'onDelete']),
'id', 'fa fa-trash fa-lg red');
// monta a página através de uma tabela
$box = new VBox;
$box->style = 'display:block';
$box->add($this->form);
$box->add($this->datagrid);
parent::add($box);
}
O método onSave() será executado sempre que o usuário clicar no botão
“salvar”. Nesse caso, é executado o método-padrão de gravação de dados
vindo do SaveTrait, que se chamava onSave(), mas que na importação foi
renomeado como onSaveTrait(). Assim, sempre que os dados forem
salvos, logo em seguida o método onReload() será executado, para garantir
que os dados da datagrid sejam atualizados.
public function onSave() {
$this->onSaveTrait();
$this->onReload();
}
O método onReload() é executado sempre que a página é carregada e logo
após a gravação dos dados. Neste caso, ele executa o método-padrão de
carregamento de dados em datagrid, vindo do trait ReloadTrait, chamado
originalmente de onReload(), mas que na importação foi renomeado como
onReloadTrait(). Sempre que os dados são carregados, a ag de controle
loaded é marcada como true.
public function onReload() {
$this->onReloadTrait();
$this->loaded = true;
}
Por m, o método show() é sobrescrito para garantir que o método
onReload() sempre seja executado antes de a página ser carregada.
public function show() {
// se a listagem ainda não foi carregada
if (!$this->loaded) {
$this->onReload();
}
parent::show();
}
A gura 8.8 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=CidadesFormList, onde quer que esteja rodando nosso servidor de
páginas.
Figura 8.8 – Formulário e listagem de cidades.
Lib/Livro/Session/Session.php
<?php
namespace Livro\Session;
class Session {
public function __construct() {
if (!session_id()) {
session_start();
}
}
public static function setValue($var, $value) {
$_SESSION[$var] = $value;
}
public static function getValue($var) {
if (isset($_SESSION[$var])) {
return $_SESSION[$var];
}
}
public static function freeSession() {
$_SESSION = array();
session_destroy();
}
}
App/Control/VendasForm.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use ...
class VendasForm extends Page {
private $form;
private $datagrid;
private $loaded;
public function __construct() {
parent::__construct();
new Session; // instancia nova seção
// instancia um formulário
$this->form = new FormWrapper(new Form('form_vendas'));
$this->form->setTitle('Venda');
// cria os campos do formulário
$codigo = new Entry('id_produto');
$quantidade = new Entry('quantidade');
$this->form->addField('Código', $codigo, '50%');
$this->form->addField('Quantidade', $quantidade, '50%');
$this->form->addAction('Adicionar', new Action(array($this,
'onAdiciona')));
$this->form->addAction('Terminar', new Action(array(new
ConcluiVendaForm, 'onLoad')));
// instancia objeto Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
// instancia as colunas da Datagrid
$codigo = new DatagridColumn('id_produto', 'Código', 'center',
'20%');
$descricao = new DatagridColumn('descricao', 'Descrição','left',
'40%');
$quantidade = new DatagridColumn('quantidade', 'Qtde', 'right',
'20%');
$preco = new DatagridColumn('preco', 'Preço', 'right', '20%');
// define um transformador para a coluna preço
$preco->setTransformer(array($this, 'formata_money'));
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($descricao);
$this->datagrid->addColumn($quantidade);
$this->datagrid->addColumn($preco);
$this->datagrid->addAction( 'Excluir', new Action([$this,
'onDelete']),
'id_produto', 'fa fa-trash fa-lg red');
// monta a página através de uma caixa
$box = new VBox;
$box->style = 'display:block';
$box->add($this->form);
$box->add($this->datagrid);
parent::add($box);
}
Sempre que o usuário clicar no botão “Adicionar”, o método onAdiciona()
será executado. Esse método obtém os dados do produto informado no
formulário e acrescenta esse item à sessão. Para isso, inicia obtendo os
dados do formulário pelo método getData() e em seguida abre uma
transação com a base de dados. Depois, localiza o produto na base de
dados por meio do método find(), usando o código informado pelo
usuário (id_produto). Caso o produto seja localizado (if),
complementamos o objeto $item com a descrição do produto (descricao) e
seu preço de venda (preco_venda), ambos lidos do objeto recém-carregado.
Após carregar as informações necessárias do produto, carregamos a
variável de sessão list, que é um array de itens, por meio do método
Session::getValue(). Em seguida, acrescentamos o item recém-formado
nesse array e o regravamos na sessão por meio do método
Session::setValue(). Assim, a sessão mantém os itens já gravados, além do
item recém-acrescentado.
public function onAdiciona() {
try {
// obtém os dados do formulário
$item = $this->form->getData();
Transaction::open('livro'); // abre transação
$produto = Produto::find($item->id_produto); // carrega o produto
if ($produto) {
// busca mais informações do produto
$item->descricao = $produto->descricao;
$item->preco = $produto->preco_venda;
$list = Session::getValue('list'); // lê variável $list da
seção
$list[$item->id_produto] = $item; // acrescenta produto na
variável
Session::setValue('list', $list); // grava variável de volta à
seção
}
Transaction::close('livro'); // fecha transação
}
catch (Exception $e) {
new Message('error', $e->getMessage());
}
$this->onReload(); // recarrega a listagem
}
O método onDelete() será executado sempre que o usuário clicar na ação
“Deletar” da datagrid. Como os itens estão gravados na sessão e não no
banco de dados, esse método precisa excluir apenas um item da sessão.
Para isso, inicialmente ele lê a variável list, que é o array de itens da
venda, por meio do método Session::getValue(). Como esse método
recebe a coluna id_produto via parâmetro (foi de nido pelo setField),
basta eliminarmos essa posição do vetor $list. Após eliminarmos essa
posição do vetor de itens, basta gravá-lo de volta na sessão por meio do
método Session::setValue(). Por m, a lista é recarregada.
public function onDelete($param) {
// lê variável $list da seção
$list = Session::getValue('list');
// exclui a posição que armazena o produto de código
unset($list[$param['id_produto']]);
// grava variável $list de volta à seção
Session::setValue('list', $list);
// recarrega a listagem
$this->onReload();
}
O método onReload() é executado sempre que a página é exibida. O
objetivo dele aqui é preencher a datagrid com os itens que desta vez estão
armazenados na sessão, não no banco de dados. Para isso, ele lê a variável
list, que é o array de itens da venda, por meio do método
Session::getValue(). Em seguida, a datagrid é limpa por meio do método
clear(). A lista de itens é percorrida por meio de um foreach. Cada um
dos itens encontrados na lista é acrescentado à datagrid por meio do
método addItem().
public function onReload() {
// obtém a variável de seção $list
$list = Session::getValue('list');
// limpa a datagrid
$this->datagrid->clear();
if ($list) {
foreach ($list as $item) {
$this->datagrid->addItem($item); // adiciona cada objeto
}
}
$this->loaded = true;
}
O método formata_money() é de nido como método de transformação para
a coluna preço. Ele será executado em cada uma das linhas da datagrid
recebendo o valor do preço diretamente do banco de dados e retornando
o mesmo devidamente formatado.
public function formata_money($valor) {
return number_format($valor, 2, ',', '.');
}
Por m, o método show() é sobrescrito para garantir que o método
onReload() sempre seja executado antes de a página ser carregada.
public function show() {
if (!$this->loaded) {
$this->onReload();
}
parent::show();
}
App/Control/ConcluiVendaForm.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use ...
class ConcluiVendaForm extends Page {
private $form;
public function __construct() {
parent::__construct();
new Session; // instancia nova seção
$this->form = new FormWrapper(new Form('form_conclui_venda'));
$this->form->setTitle('Conclui venda');
// cria os campos do formulário
$cliente = new Entry('id_cliente');
$valor_venda = new Entry('valor_venda');
$desconto = new Entry('desconto');
$acrescimos = new Entry('acrescimos');
$valor_final = new Entry('valor_final');
$parcelas = new Combo('parcelas');
$obs = new Text('obs');
$parcelas->addItems(array(1=>'Uma', 2=>'Duas', 3=>'Três'));
$parcelas->setValue(1);
// define uma ação de cálculo JavaScript
$desconto->onBlur = "$('[name=valor_final]').val(
Number($('[name=valor_venda]').
val()) + Number($('[name=acrescimos]').val()) -
Number($('[name=desconto]').
val()) );";
$acrescimos->onBlur = $desconto->onBlur;
$valor_venda->setEditable(FALSE);
$valor_final->setEditable(FALSE);
$this->form->addField('Cliente', $cliente, '50%');
$this->form->addField('Valor', $valor_venda, '50%');
$this->form->addField('Desconto', $desconto, '50%');
$this->form->addField('Acréscimos', $acrescimos, '50%');
$this->form->addField('Final', $valor_final, '50%');
$this->form->addField('Parcelas', $parcelas, '50%');
$this->form->addField('Obs', $obs, '50%');
$this->form->addAction('Salvar', new Action(array($this,
'onGravaVenda')));
parent::add($this->form);
}
O método onLoad() será executado a partir da classe anterior (VendasForm)
quando o usuário clicar no botão “terminar”. Nesse caso, a URL
executada será index.php?class=ConcluiVendaForm&method=onLoad. O objetivo
desse método é calcular o total da venda e iniciar o formulário dessa
página com alguns valores (totais da venda) já prede nidos para que o
usuário somente precise informar outros dados (cliente, acréscimos,
descontos, parcelamento, observações). Para isso, ele inicia com a leitura
da variável de sessão list, que contém os produtos registrados na tela
anterior (VendasForm), por meio do método Session::getValue(). Em
seguida, percorre os itens por um foreach, acumulando o $total. Após
calcular o valor total, é utilizado o método setData() para enviar o total
para os campos valor_venda e valor_final.
public function onLoad($param) {
$total = 0;
$itens = Session::getValue('list');
if ($itens) {
// percorre os itens
foreach ($itens as $item) {
$total += $item->preco * $item->quantidade;
}
}
$data = new StdClass;
$data->valor_venda = $total;
$data->valor_final = $total;
$this->form->setData($data);
}
O método onGravaVenda() é executado quando o usuário clica no botão
“salvar” da tela de conclusão da venda. Esse método recebe informações
do formulário como código do cliente, descontos, acréscimos, valor nal,
parcelamento e observação. Ele terá como objetivo armazenar a venda e
gerar o parcelamento nanceiro. Para isso, ele inicia obtendo os dados do
formulário por meio do método getData(). Em seguida, usa o método
Pessoa::find() para localizar o cliente. Caso não tenha localizado o
cliente, gera uma exceção. Em seguida, veri ca se o cliente tem débitos
por meio do método totalDebitos(). Caso haja débitos pendentes,
novamente é gerada uma exceção.
Se o cliente for localizado e não houver débitos pendentes, um objeto
Venda será criado e preenchido por meio dos dados do formulário. Então,
será lida da sessão a variável list, que contém os itens registrados na tela
anterior (VendasForm). Cada item é percorrido, e o método addItem() é
executado sobre a classe Venda, adicionando o produto e sua quantidade.
Finalmente, a venda é armazenada pelo método store(). Após o
armazenamento da venda, o nanceiro é gerado por meio do método
Conta::geraParcelas(), informando o cliente, a carência, o valor nal e a
quantidade de parcelas. Então, a transação é fechada e a variável de sessão
list, que contém os itens vendidos, é reinicializada para a próxima venda.
public function onGravaVenda() {
try {
Transaction::open('livro'); // inicia transação com o banco
$dados = $this->form->getData(); // obtém os dados da venda
$cliente = Pessoa::find($dados->id_cliente);
if (!$cliente) {
throw new Exception('Cliente não encontrado');
}
// verifica débitos
if ($cliente->totalDebitos() > 0) {
throw new Exception('Débitos impedem esta operação');
}
// inicia gravação da venda
$venda = new Venda;
$venda->cliente = $cliente;
$venda->data_venda = date('Y-m-d');
$venda->valor_venda = $dados->valor_venda;
$venda->desconto = $dados->desconto;
$venda->acrescimos = $dados->acrescimos;
$venda->valor_final = $dados->valor_final;
$venda->obs = $dados->obs;
// lê a variável list da seção
$itens = Session::getValue('list');
if ($itens) {
// percorre os itens
foreach ($itens as $item) {
// adiciona o item na venda
$venda->addItem(new Produto($item->id_produto), $item-
>quantidade);
}
}
// armazena venda no banco de dados
$venda->store();
// gera o financeiro
Conta::geraParcelas($dados->id_cliente, 2, $dados->valor_final,
$dados->parcelas);
Transaction::close(); // finaliza a transação
Session::setValue('list', array()); // limpa lista de itens da
seção
// exibe mensagem de sucesso
new Message('info', 'Venda registrada com sucesso');
}
catch (Exception $e) {
new Message('error', $e->getMessage());
}
}
}
App/Control/ContasReport.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use ...
use Dompdf\Dompdf;
use Dompdf\Options;
class ContasReport extends Page {
private $form; // formulário de entrada
public function __construct() {
parent::__construct();
// instancia um formulário
$this->form = new FormWrapper(new Form('form_relat_contas'));
$this->form->setTitle('Relatório de contas');
// cria os campos do formulário
$data_ini = new Date('data_ini');
$data_fim = new Date('data_fim');
$this->form->addField('Vencimento Inicial', $data_ini, '50%');
$this->form->addField('Vencimento Final', $data_fim, '50%');
$this->form->addAction('Gerar', new Action(array($this, 'onGera')));
$this->form->addAction('PDF', new Action(array($this, 'onGeraPDF')));
parent::add($this->form);
}
Assim que o usuário clicar no botão “Gerar”, a página será processada
novamente e o método onGera() será executado. Para construir o relatório
em HTML, usaremos a biblioteca Twig, já abordada no capítulo 6. Como
vimos, a biblioteca Twig baseia-se em templates HTML para exibir
informações em tela.
O método inicia com a instanciação do objeto Twig_Loader_Filesystem para
indicar a pasta em que se encontram os templates, bem como
Twig_Environment, responsável por fazer a manipulação do template.
Utilizamos o método loadTemplate() para carregar o arquivo de template,
que se chama contas_report.html e será detalhado logo em seguida.
O próximo passo é ler os dados (datas) informados no formulário pelo
método getData(). O método setData() é usado para manter o formulário
preenchido após a postagem. Depois, declaramos um vetor de
substituições ($replaces), que será usado para “enviar” as informações
para o template HTML. A princípio, ele já receberá as datas de início
(data_ini) e de m (data_fim) recebidas do formulário.
Uma transação é aberta com a base de dados, e um repositório para
manipular a classe Conta é instanciado. Logo em seguida, criamos um
critério (Criteria) de seleção. Caso o usuário tenha preenchido as datas
inicial e nal, será acrescido ao critério um ltro observando o intervalo
de datas. O método load() é usado para carregar na memória todos os
objetos ($contas) da classe Conta que passam pelo critério construído.
Cada objeto no array $contas é percorrido por um foreach e convertido
para o formato de array por meio do método toArray(). O objeto $conta é
convertido para array, pois esse é o formato utilizado pela biblioteca Twig
para enviar informações para o template. Além disso, tomamos o cuidado
de utilizar, dentro do template, nomes de variáveis que coincidissem com
os nomes dos atributos do objeto, tornando a substituição o mais
transparente possível. Cada conta é “acumulada” na posição “contas” do
array de substituições ($replaces).
Por m, a transação com a base de dados é nalizada, o conteúdo do
HTML recebe o vetor de substituições e retorna o conteúdo “renderizado”,
ou seja, processado com os valores recebidos, o que é feito pelo método
render(). O conteúdo retornado é então acrescentado a um painel, e este,
à página.
public function onGera() {
$loader = new Twig_Loader_Filesystem('App/Resources');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('contas_report.html');
// obtém os dados do formulário
$dados = $this->form->getData();
// joga os dados de volta ao formulário
$this->form->setData($dados);
// lê os campos do formulário, converte para o padrão americano
$data_ini = $dados->data_ini;
$data_fim = $dados->data_fim;
// vetor de parâmetros para o template
$replaces = array();
$replaces['data_ini'] = $dados->data_ini;
$replaces['data_fim'] = $dados->data_fim;
try {
// inicia transação com o banco 'livro'
Transaction::open('livro');
// instancia um repositório da classe Conta
$repositorio = new Repository('Conta');
// cria um critério de seleção por intervalo de datas
$criterio = new Criteria;
$criterio->setProperty('order', 'dt_vencimento');
if ($dados->data_ini)
$criterio->add('dt_vencimento', '>=', $data_ini);
if ($dados->data_fim)
$criterio->add('dt_vencimento', '<=', $data_fim);
// lê todas contas que satisfazem ao critério
$contas = $repositorio->load($criterio);
if ($contas) {
foreach ($contas as $conta) {
$conta_array = $conta->toArray();
$conta_array['nome_cliente'] = $conta->cliente->nome;
$replaces['contas'][] = $conta_array;
}
}
// finaliza a transação
Transaction::close();
}
catch (Exception $e) {
new Message('error', $e->getMessage());
Transaction::rollback();
}
$content = $template->render($replaces);
$title = 'Contas';
$title.= (!empty($dados->data_ini)) ? ' de ' . $dados->data_ini : '';
$title.= (!empty($dados->data_fim)) ? ' até ' . $dados->data_fim :
'';
// cria um painél para conter o formulário
$panel = new Panel($title);
$panel->add($content);
parent::add($panel);
return $content;
}
Para geração no formato PDF, há um botão chamado “PDF” no
formulário, que está vinculado ao método onGeraPDF(). Esse método
recebe os valores dos campos do formulário e chama o método onGera(),
que gera o relatório em HTML. Após o método onGera() criar o relatório
em HTML e retorná-lo na variável $html, a execução segue com a
biblioteca DomPDF, que por sua vez converterá o HTML do relatório
para PDF. O método loadHtml() carrega o HTML e a memória, o método
render() o renderiza na memória, e o método output() retorna o conteúdo
do PDF, que é então escrito em um arquivo pelo file_put_contents() e
aberto na tela.
public function onGeraPDF($param) {
$html = $this->onGera($param); // gera o relatório em HTML primeiro
$options = new Options();
$options->set('dpi', '128');
// DomPDF converte o HTML para PDF
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
// Escreve o arquivo e abre em tela
$filename = 'tmp/contas.pdf';
if (is_writable('tmp')) {
file_put_contents($filename, $dompdf->output());
echo "<script>window.open('{$filename}');</script>";
}
else {
new Message('error', 'Permissão negada em: ' . $filename);
}
}
}
É importante lembrar que, para instalar a biblioteca DomPDF, utilizamos
o composer:
php composer.phar require dompdf/dompdf
Para a montagem do relatório, é utilizado como template o arquivo
contas_report.html, localizado no diretório App/Resources. Neste template,
podemos perceber um laço de repetições ({% for conta in contas %}). Esse
laço percorrerá a posição contas do vetor $replaces, e cada posição desse
array apresenta outro array contendo as informações conta por conta.
Para cada conta, serão exibidas algumas informações especí cas como
data de emissão no formato brasileiro ({conta.dt_emissao|date("d/m/Y")}),
nome do cliente ({conta.nome_cliente}), valor da conta devidamente
formatado ({conta.valor|number_format(2, ',', '.')}) e um ternário para
veri car se a conta foi ou não paga, exibindo “sim” ou “não” ({conta.paga
== 'S' ? 'Sim' : 'Não'}). Ao nal, uma linha com o total geral.
App/Resources/contas_report.html
<table class="table table-bordered" width="100%" style="border-
collapse:collapse;border:1px solid #ddd">
<tr bgcolor="#C0C3E8">
<td>Emissão</td> <td>Vencimento</td> <td>Cliente</td>
<td>Valor</td> <td>Paga</td>
</tr>
{% set total = 0 %}
{% for conta in contas %}
<tr>
<td align="center">{{conta.dt_emissao|date("d/m/Y")}}</td>
<td align="center">{{conta.dt_vencimento|date("d/m/Y")}}</td>
<td>{{conta.nome_cliente}}</td>
<td align="right">{{conta.valor|number_format(2, ',', '.')}}</td>
<td align="center" style="color:{{conta.paga == 'S' ? 'green' :
'red'}};">
<b> {{conta.paga == 'S' ? 'Sim' : 'Não'}} </b>
</td>
</tr>
{% set total = total + conta.valor %}
{% endfor %}
<tr bgcolor="whiteSmoke">
<td align="center" colspan="3">Total</td>
<td align="right">{{total|number_format(2, ',', '.')}}</td>
<td></td>
</tr>
</table>
App/Control/ProdutosReport.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Dialog\Message;
use Livro\Database\Transaction;
use Livro\Widgets\Container\Panel;
class ProdutosReport extends Page {
public function __construct() {
parent::__construct();
$loader = new Twig_Loader_Filesystem('App/Resources');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('produtos_report.html');
// vetor de parâmetros para o template
$replaces = array();
// gerador Barcode em HTML
$generator = new Picqer\Barcode\BarcodeGeneratorHTML();
// gerador QRCode em SVG
$renderer = new \BaconQrCode\Renderer\Image\Svg();
$renderer->setHeight(256);
$renderer->setWidth(256);
$renderer->setMargin(0);
$writer = new \BaconQrCode\Writer($renderer);
try {
// inicia transação com o banco 'livro'
Transaction::open('livro');
$produtos = Produto::all();
foreach ($produtos as $produto) {
$produto->barcode = $generator->getBarcode($produto->id,
$generator::TYPE_CODE_128, 5, 100);
$produto->qrcode = $writer->writeString($produto->id . ' ' .
$produto->descricao);
}
$replaces['produtos'] = $produtos;
Transaction::close(); // finaliza a transação
}
catch (Exception $e) {
new Message('error', $e->getMessage());
Transaction::rollback();
}
$content = $template->render($replaces);
// cria um painél para conter o formulário
$panel = new Panel('Produtos');
$panel->add($content);
parent::add($panel);
}
}
O relatório de produtos utilizará o template produtos_report.html,
apresentado a seguir. Ele percorrerá o vetor de produtos, e para cada
produto exibirá alguns atributos como {{produto.id}} e
{{produto.descricao}}. O código de barras e o QRCode são apresentados
no formato {{produto.barcode | raw}} com o raw, pois apresentam código
HTML bruto que não deve ser tratado pela biblioteca de templates.
App/Resources/produtos_report.html
<table class="table table-bordered" width="100%">
<tr bgcolor="#7A9BCB" style="color:white">
<td colspan="5"><b>Produtos</b></td>
</tr>
{% for produto in produtos %}
<tr>
<td align="center" style="vertical-align:middle">
<span style="font-size:17pt">
{{produto.id}} <br>
{{produto.descricao}} <br>
{{produto.estoque}} unidades <br>
R$ {{produto.preco_venda}}
</span>
</td>
<td align="center" style="vertical-align:middle">
{{produto.barcode | raw}}
</td>
<td align="center" style="vertical-align:middle">
{{produto.qrcode | raw}}
</td>
</tr>
{% endfor %}
</table>
8.3.9 Relatório de pessoas
Neste exemplo, criaremos um relatório baseado em uma query, que por
sua vez apresentará informações de pessoas, bem como de valores
nanceiros a receber. Esta query é transformada em uma view do banco
de dados. Portanto, criaremos um relatório baseado em uma view do
banco de dados. A gura 8.14 demonstra o resultado nal do relatório,
que apresentará o código, o nome, o telefone, o email, o total de valor em
contas da pessoa e o total de contas em aberto.
view_saldo_pessoa
CREATE VIEW view_saldo_pessoa as
SELECT
id, nome, endereco, bairro, telefone, email,
(select sum(valor) from conta where id_cliente=pessoa.id) as total,
(select sum(valor) from conta where id_cliente=pessoa.id and
paga='N') as aberto
FROM pessoa
ORDER by 8 desc;
Depois de criar a view, decidimos criar uma classe para disponibilizar a
view rapidamente ao sistema por meio de uma Active Record.
App/Model/ViewSaldoPessoa.php
<?php
use Livro\Database\Record;
class ViewSaldoPessoa extends Record {
const TABLENAME = 'view_saldo_pessoa';
}
Agora já temos condições de escrever o código do programa principal que
gerará o relatório. Para este exemplo, também usaremos a biblioteca Twig,
como nos anteriores. Desta vez, utilizaremos o template
pessoas_report.html.
Após carregarmos o template, abrimos a transação com a base de dados e
carregamos todos os registros da view por meio do método
ViewSaldoPessoa::all().
O resultado dessa execução é atribuído ao vetor $replaces, que será
utilizado para fazer as substituições no template.
Depois, é utilizado o método render() para injetar o vetor de $replaces no
template. Então é criado um painel (Panel), que conterá o conteúdo
gerado ($content), e este painel é então adicionado à página.
App/Control/PessoasReport.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Dialog\Message;
use Livro\Database\Transaction;
use Livro\Widgets\Container\Panel;
class PessoasReport extends Page {
public function __construct() {
parent::__construct();
$loader = new Twig_Loader_Filesystem('App/Resources');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('pessoas_report.html');
// vetor de parâmetros para o template
$replaces = array();
try {
// inicia transação com o banco 'livro'
Transaction::open('livro');
$replaces['pessoas'] = ViewSaldoPessoa::all();
Transaction::close(); // finaliza a transação
}
catch (Exception $e) {
new Message('error', $e->getMessage());
Transaction::rollback();
}
$content = $template->render($replaces);
// cria um painél para conter o formulário
$panel = new Panel('Pessoas');
$panel->add($content);
parent::add($panel);
}
}
Como vimos, o relatório de pessoas baseia-se em um template. O
template pessoas_report.html é apresentado a seguir. Ele tem uma tabela,
uma primeira linha com o cabeçalho dos dados e em seguida um laço de
repetições sobre a posição pessoas do vetor ({% for pessoa in pessoas %}).
Dentro desse laço de repetições, os atributos da pessoa como
{{pessoa.id}}, e {{pessoa.telefone}} são acessados para apresentação no
HTML.
App/Resources/pessoas_report.html
<table class="table table-bordered" width="100%">
<tr bgcolor="#7A9BCB" style="color:white">
<td colspan="8"><b>Pessoas</b></td>
</tr>
<tr bgcolor="#C0C3E8">
<td>Id</td> <td>Nome</td> <td>Telefone</td> <td>Email</td>
<td>Total</td>
<td>Aberto</td>
</tr>
{% for pessoa in pessoas %}
<tr>
<td align="center"> {{pessoa.id}} </td>
<td> {{pessoa.nome}} </td>
<td align="center"> {{pessoa.telefone}} </td>
<td align="center"> {{pessoa.email}} </td>
<td align="right"> R$ {{pessoa.total|number_format(2, ',', '.')}}
</td>
<td align="right"> R$ {{pessoa.aberto|number_format(2, ',', '.')}}
</td>
</tr>
{% endfor %}
</table>
App/Model/Venda.php
<?php
public static function getVendasMes() {
$meses = array();
$meses[1] = 'Janeiro';
$meses[2] = 'Fevereiro';
$meses[3] = 'Março';
// ...
$conn = Transaction::get();
$result = $conn->query("select strftime('%m', data_venda) as mes,
sum(valor_final) as valor from venda group by 1");
$dataset = [];
foreach ($result as $row) {
$mes = $meses[ (int) $row['mes'] ];
$dataset[ $mes ] = $row['valor'];
}
return $dataset;
}
Agora que já temos o método que retornará as vendas por mês, vamos
escrever o programa principal. Neste programa, também usaremos a
biblioteca Twig para gerar o grá co dentro de um template que conterá
HTML e Javascript. Após carregarmos o template vendas_mes.html,
abrimos uma transação com a base de dados e utilizamos o método
Venda::getVendasMes() para retornar o vetor de vendas por mês. Este dado
será utilizado posteriormente no vetor de substituições ($replaces) do
template.
Após nalizarmos a transação com a base de dados, criamos o vetor de
substituições ($replaces), que de nirá quais variáveis serão injetadas no
template. Neste caso, injetamos a variável title, que terá o título do
grá co, labels, que terá somente os nomes dos meses, que são as chaves
do vetor, e data, que terá os valores das barras do grá co, o que é extraído
pela array_values(). Ambos – labels e data – são injetados no template no
formato JSON (json_encode), pois esse é o formato que a biblioteca para
geração de grá cos ChartJs utiliza.
App/Control/VendasMesChart.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Dialog\Message;
use Livro\Database\Transaction;
use Livro\Widgets\Container\Panel;
class VendasMesChart extends Page {
public function __construct() {
parent::__construct();
$loader = new Twig_Loader_Filesystem('App/Resources');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('vendas_mes.html');
try {
// inicia transação com o banco 'livro'
Transaction::open('livro');
$vendas = Venda::getVendasMes();
Transaction::close(); // finaliza a transação
}
catch (Exception $e) {
new Message('error', $e->getMessage());
Transaction::rollback();
}
// vetor de parâmetros para o template
$replaces = array();
$replaces['title'] = 'Vendas por mês';
$replaces['labels'] = json_encode(array_keys($vendas));
$replaces['data'] = json_encode(array_values($vendas));
$content = $template->render($replaces);
// cria um painél para conter o formulário
$panel = new Panel('Vendas/mês');
$panel->add($content);
parent::add($panel);
}
}
Como vimos, o grá co de vendas por mês baseia-se em um template. O
template vendas_mes.html é apresentado a seguir. Toda a lógica de
apresentação do grá co é montada pelo template. Neste caso, o PHP
somente preparou os dados antes de estes serem apresentados.
Basicamente, este script contém uma chamada para a biblioteca ChartJS
(new Chart). Esta biblioteca recebe em algumas posições os dados
preparados pelo PHP, como em labels: {{labels|raw}}, posição que
recebeu os rótulos das barras formatados pelo PHP no formato JSON, ou
em data: {{data|raw}}, posição que recebeu os dados formatados pelo
PHP no formato JSON. As demais partes representam atributos
necessários pela biblioteca para a geração do grá co, tais como
backgroundColor, borderWidth ou borderColor.
App/Resources/vendas_mes.html
<canvas id="bar-chart" height="500"></canvas>
<script>
new Chart(document.getElementById("bar-chart"), {
type: 'bar',
data: {
labels: {{labels|raw}},
datasets: [ {
label: "Vendas",
backgroundColor: [ 'rgba(220,57,18, 0.5)','rgba(255,153,0, 0.5)',
'rgba(16,150,24, 0.5)',...'],
data: {{data|raw}},
borderWidth: 2,
borderColor: [ 'rgb(220,57,18)','rgb(255,153,0)','rgb(16,150,24)',
'rgb(153,0,153)',,...'],
}]},
options: {
legend: { display: false },
maintainAspectRatio: false,
title: {
display: true,
text: '{{title}}'
}}});
</script>
8.3.11 Dashboard de vendas
Neste próximo exemplo, criaremos um painel (dashboard) de vendas em
que poderemos adicionar vários grá cos. Por enquanto, colocaremos dois
grá cos: um grá co de barras com as vendas por mês e um grá co de
pizza com as vendas por tipo de produto. A gura 8.16 demonstra o painel
resultante.
App/Control/DashboardView.php
<?php
use Livro\Control\Page;
use Livro\Widgets\Container\HBox;
class DashboardView extends Page {
public function __construct() {
parent::__construct();
$hbox = new HBox;
$hbox->add( new VendasMesChart )->style.=';width:48%;';
$hbox->add( new VendasTipoChart )->style.=';width:48%';
parent::add($hbox);
}
}
index.php
<?php
// Lib loader
require_once 'Lib/Livro/Core/ClassLoader.php';
$al= new Livro\Core\ClassLoader;
$al->addNamespace('Livro', 'Lib/Livro');
$al->register();
// App loader
require_once 'Lib/Livro/Core/AppLoader.php';
$al= new Livro\Core\AppLoader;
$al->addDirectory('App/Control');
$al->addDirectory('App/Model');
$al->register();
use Livro\Session\Session;
$content = '';
new Session;
if (Session::getValue('logged')) {
$template = file_get_contents('App/Templates/template.html');
$class = '';
}
else {
$template = file_get_contents('App/Templates/login.html');
$class = 'LoginForm';
}
if (isset($_GET['class']) AND Session::getValue('logged')) {
$class = $_GET['class'];
}
if (class_exists($class)) {
try {
$pagina = new $class;
ob_start();
$pagina->show();
$content = ob_get_contents();
ob_end_clean();
}
catch (Exception $e) {
$content = $e->getMessage() . '<br>' .$e->getTraceAsString();
}
}
$output = str_replace('{content}', $content, $template);
$output = str_replace('{class}', $class, $output);
echo $output;
App/Control/LoginForm.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use ...
class LoginForm extends Page {
private $form; // formulário
public function __construct() {
parent::__construct();
// instancia um formulário
$this->form = new FormWrapper(new Form('form_login'));
$this->form->setTitle('Login');
$login = new Entry('login');
$password = new Password('password');
$login->placeholder = 'admin';
$password->placeholder = 'admin';
$this->form->addField('Login', $login, 200);
$this->form->addField('Senha', $password, 200);
$this->form->addAction('Login', new Action(array($this,
'onLogin')));
parent::add($this->form);
}
Sempre que o usuário clicar no botão “Login”, o método onLogin() será
executado. Ele lerá os dados do formulário por meio do método getData()
e, em seguida, comparará os atributos login e password com as strings
admin/admin. Como dito anteriormente, na prática, você deve substituir
essa comparação por uma veri cação em alguma tabela de usuários do
seu sistema. Caso o usuário e as senhas estejam corretos, a variável de
sessão logged será de nida e um script será gerado para redirecionar o
usuário para o index.php.
public function onLogin($param) {
$data = $this->form->getData();
if ($data->login == 'admin' AND $data->password == 'admin') {
Session::setValue('logged', TRUE);
echo "<script language='JavaScript'> window.location =
'index.php'; </script>";
}
}
O método onLogout() será executado a partir da opção “Logout” do menu
de opções. Esse método grava FALSE na variável “logged” e gera um script
para redirecionar o usuário para o index.php. Após esse redirecionamento,
ele deparará com o formulário de login, pois não estará mais autenticado.
public function onLogout($param) {
Session::setValue('logged', FALSE);
echo "<script language='JavaScript'> window.location = 'index.php';
</script>";
}
}