Você está na página 1de 600

4ª Edição

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

Pablo Dall’Oglio é graduado em Análise de Sistemas e mestre em Engenharia


de Software pela Unisinos. Autor dos livros PHP-GTK – Criando aplicações
grá cas com PHP e Criando relatórios com PHP, tem grande experiência no
desenvolvimento de sistemas para gestão de negócios e está
constantemente envolvido com projeto e implementação de softwares
orientados a objetos. Atualmente dedica-se à sua empresa, a Adianti
Solutions (www.adianti.com.br), onde desenvolve ferramentas de
produtividade como o Adianti Framework (www.adianti.com.br/framework) e o
Adianti Reports (www.adianti.com.br/reports), além de prestar consultoria em
projeto e desenvolvimento de sistemas orientados a objetos.
Agradecimentos

Quero agradecer em primeiro lugar ao Rubens Prates, o editor mais


bacana do mundo, por acreditar nas minhas ideias malucas e publicar
meus livros.
Quero agradecer aos meus avós, Gescy e Erni Petter, por terem acreditado
em mim e patrocinado meus primeiros cursos de informática em 1994.
Era uma época difícil, em que meus pais não tinham condições de bancar
esses cursos. Naquela época, estudar informática era uma escolha de
poucos, e nós éramos vistos por alguns como nerds e por outros
simplesmente como “malucos visionários”. Todos achavam que
informática seria a pro ssão do futuro, mas ninguém tinha certeza de que
algum dia seríamos recompensados pela escolha arriscada dessa
pro ssão. Meus avós acreditaram muito, e se hoje eu posso escrever livros
e desenvolver softwares que ajudam muitos outros, eles têm grande
responsabilidade nisso.
Também agradeço muito aos meus pais, Erasmo e Clarise Dall’Oglio, por
todo o empenho em me proporcionar educação superior – foram anos
difíceis que se iniciaram em 1998, quando passei no vestibular. Eles
trabalharam dia e noite arduamente para pagar as mensalidades da
universidade em uma época em que não havia nanciamento estudantil.
Obrigado por terem lutado para que eu tivesse a oportunidade de
estudar; obrigado pelos valores que me passaram. Ter nascido em uma
família humilde me faz valorizar muito cada pequena conquista da minha
vida. Graças a tudo isso, somos uma família muito unida, e minha
querida irmã Daline é uma das pessoas que me acompanhou em toda
essa trajetória. Ela sempre está disposta a me ajudar no que eu precisar, e
eu sei que ela é uma das pessoas com quem sempre posso contar.
Este livro começou a ser escrito em 2006, quando morávamos, Fernanda e
eu, com meus avós. O livro nasceu durante um inverno muito rigoroso.
Lembro-me da felicidade que senti quando fui presenteado com um
aquecedor pelo meu pai, o que me permitiu trabalhar várias madrugadas
para nalizar a escrita. Em 2009 ele teve uma atualização; no inverno de
2015, passou por uma grande reformulação; e no outono de 2018, uma
nova reforma, com importantes conceitos adicionados.
Em 2002, conheci a Fernanda. Falar dela é fácil – ela é minha
companheira, parceira, cúmplice, namorada e esposa; e está sempre
disposta a tudo. Se consegui me dedicar à minha carreira nestes quase
vinte anos, foi porque em grande parte desse tempo tive ao meu lado
alguém especial, que compreende minha pro ssão maluca e me apoia
incondicionalmente. Ela esteve comigo enquanto eu escrevia todos os
meus livros e durante as minhas grandes conquistas. Em 2016, tivemos a
doce Ana Júlia, que veio ao mundo para nos fazer compreender de uma
vez o conceito de família, que tornou o papai uma pessoa de coração
ainda mais mole, mas que também o transformou em uma pessoa ainda
mais determinada. Obrigado, amo muito vocês!
Nota do 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 Rasmus Lerdorf, programador canadense-dinamarquês, autor da primeira versão da


linguagem PHP.
Organização do livro

Con ra a seguir o que veremos em cada capítulo deste livro.


O capítulo 1 faz uma introdução à linguagem PHP. Serão abordados os
tipos de dados, operadores lógicos e aritméticos, estruturas de controle,
manipulação de funções, manipulação de arquivos, de strings, de arrays,
entre outros.
O capítulo 2 aborda os fundamentos da orientação a objetos. Nele,
detalharemos aspectos como classes, atributos, métodos, associação,
agregação, composição, herança, abstração, polimor smo,
encapsulamento, interfaces, métodos etc.
O capítulo 3 aborda a evolução da programação PHP, desde o paradigma
estruturado, até o orientado a objetos, do acesso nativo ao banco de
dados ao acesso com a PDO. Criaremos sete versões diferentes do mesmo
programa, desde a mais estruturada até a mais orientada a objetos.
O capítulo 4 aborda alguns tópicos especiais da orientação a objetos no
PHP, tais como tratamento de exceções, métodos mágicos, manipulação
de XML, DOM, SPL, Re ection, Traits, injeção de dependência, PSR,
namespaces, entre outros.
O capítulo 5 aborda padrões de persistência e acesso a dados. Serão
implementados padrões como Table Data Gateway, Data Maper, Active
Record, Repository, classes para tratar conexões, transações, logs, entre
outros.
O capítulo 6 aborda apresentação e controle. Serão abordados conceitos
como o padrão MVC, a organização de namespaces e diretórios, a
utilização de autoloaders, padrões como o Front Controller, criação de
componentes e templates.
O capítulo 7 aborda a criação de componentes para construção de
formulários de cadastro e edição de registros, componentes de input de
dados, bem como classes para construção de datagrids, colunas, ações,
formatação de dados, entre outros.
O capítulo 8 apresenta o desenvolvimento de uma aplicação de negócios
voltada à área de vendas, que será implementada totalmente por meio das
classes criadas ao longo do livro, apresentando soluções para cadastros de
pessoas, de produtos, registro de vendas, relatórios, grá cos, login, entre
outros.
 Nos códigos-fontes apresentados aqui, você perceberá chaves de abertura de
classes e de funções na mesma linha, embora o padrão seja na linha a seguir. Este
é apenas um truque editorial para condensação de conteúdo e melhor
aproveitamento das páginas. Nos códigos fornecidos para download, você
encontrará o código no estilo original.
CAPÍTULO 1
Introdução ao PHP

A vida é uma peça de teatro que não permite ensaios...


Por isso, cante, ria, dance, chore e viva intensamente cada momento de sua vida,
antes que a cortina se feche e a peça termine sem aplausos...
C C
Ao longo deste livro utilizaremos diversas funções, comandos e estruturas
de controle básicos da linguagem PHP que serão apresentados neste
capítulo. Conheceremos as estruturas básicas da linguagem, suas variáveis
e seus operadores e também um conjunto de funções para manipulação
de strings, arquivos, arrays, bancos de dados, entre outros.

1.1 O que é o PHP?


A linguagem de programação PHP, que no início signi cava Personal
Home Page Tools, foi criada no outono de 1994 por Rasmus Lerdorf. Essa
linguagem era formada por um conjunto de scripts escritos em linguagem
C, voltados à criação de páginas dinâmicas que Rasmus utilizava para
monitorar o acesso ao seu currículo na internet. Com o tempo, mais
pessoas passaram a usá-la, e Rasmus adicionou vários recursos, como a
interação com bancos de dados. Em 1995, o código-fonte do PHP foi
liberado, e com isso mais desenvolvedores puderam se juntar ao projeto.
Naquela época, por um breve período de tempo, o PHP foi chamado de FI
(Forms Interpreter).
O PHP passou por várias reescritas de código ao longo do tempo e nunca
parou de conquistar novos adeptos. Uma segunda versão foi lançada em
novembro de 1997, sob o nome PHP/FI 2.0. Naquele momento,
aproximadamente 60 mil domínios, ou 1% da internet, já utilizavam PHP,
que era mantido principalmente por Rasmus.
No mesmo ano, Andi Gutmans e Zeev Suraski, dois estudantes que
utilizavam essa linguagem em um projeto acadêmico de comércio
eletrônico, resolveram cooperar com Rasmus para aprimorar o PHP. Para
isso, reescreveram todo o código-fonte, com base no PHP/FI 2, dando
início assim ao PHP 3, disponibilizado o cialmente em junho de 1998.
Entre as principais características do PHP 3 estavam a extensibilidade, a
possibilidade de conexão com vários bancos de dados, novos protocolos,
uma sintaxe mais consistente, suporte à orientação a objetos e uma nova
API, que possibilitava a criação de novos módulos e que acabou por atrair
vários desenvolvedores ao PHP. No m de 1998, o PHP já estava presente
em cerca de 10% dos domínios da internet. Nessa época o signi cado da
sigla PHP mudou para PHP: Hypertext Preprocessor, representando,
assim, a nova realidade de uma linguagem com propósitos mais amplos.
No inverno de 1998, Zeev e Andi começaram a trabalhar em uma reescrita
do núcleo do PHP, tendo em vista melhorar seu desempenho e sua
modularidade em aplicações complexas. O nome foi rebatizado para Zend
Engine (Zeev + Andi). O PHP 4, já baseado nesse mecanismo, foi lançado
em maio de 2000, trazendo melhorias como seções, suporte a diversos
servidores web, além da abstração de sua API, permitindo inclusive ser
utilizado como linguagem para shell script.
Apesar de todos os esforços, o PHP ainda necessitava de maior suporte à
orientação a objetos. Esses recursos foram trazidos pelo PHP 5, após um
longo período de desenvolvimento que culminou com sua
disponibilização o cial em julho de 2004. Em 2015 foi lançado o PHP 7,
com melhorias incríveis quanto ao desempenho, ao tratamento de erros, à
indução a tipos, a novos operadores, entre outros fatores. Ao longo do
tempo, o PHP vem adicionando mais recursos e se consolida como uma
das linguagens orientadas a objetos de maior adoção no mundo. Estima-
se que o PHP seja utilizado em mais de 80% dos servidores web
existentes hoje, o que torna essa linguagem disparadamente a mais
utilizada para desenvolvimento web. Ao longo do livro, veremos esses
recursos empregados em exemplos práticos.
Fonte: PHP Group (http://php.net/history.php)

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.

1.2.1.1 Instalação em Linux


A instalação em Linux normalmente é facilitada pela instalação de
pacotes. Recomendamos a utilização do popular Ubuntu Linux tanto em
desktop quanto em servidor. A instalação normalmente é feita por meio
de comandos de instalação de pacotes. Como os nomes de pacotes podem
mudar de uma versão para outra, vamos deixar o seguinte link atualizado
para você:
http://www.php.com.br/instalacao-php-linux

1.2.1.2 Instalação em Windows


A instalação em Windows normalmente é facilitada por instaladores
como o Wamp, possivelmente o mais conhecido de todos. Portanto, no
endereço a seguir, deixaremos as instruções atualizadas de como proceder
com a instalação do PHP para Windows utilizando o Wamp:
http://www.php.com.br/instalacao-php-windows

1.2.1.3 Instalação em MacOS


A instalação em MacOS também é facilitada por instaladores como o
Mamp ou outros. Portanto, no endereço a seguir, deixaremos as
instruções atualizadas de como proceder com a instalação do PHP para
MacOS utilizando instaladores:
http://www.php.com.br/instalacao-php-macos

1.2.1.4 Recomendações gerais


A partir da instalação, você poderá criar scripts em PHP e executá-los no
navegador por meio do endereço http://localhost/<localizaçao do script>. A pasta
utilizada para salvar os scripts varia conforme o sistema operacional e o
instalador utilizado, mas, no nal do processo de instalação apontado
anteriormente, indicaremos as pastas. Os locais mais comuns são:
• Ubuntu: /var/www/html/
• MacOS: /Library/WebServer/Documents/
Windows: C:\wamp64\www\

1.2.2 Con guração


A con guração correta do PHP é um procedimento de extrema
importância. Con gurações mal feitas podem di cultar muito a vida do
desenvolver ao depurar-se erros de programação. Nesta seção, vamos
apresentar uma con guração indicada para desenvolvimento e em seguida
algumas alterações para produção.
O primeiro passo para con gurar apropriadamente o PHP é descobrir
onde se encontra seu arquivo de con guração (php.ini). Não é incomum
encontrarmos mais de um php.ini conforme o instalador que utilizamos.
Por isso, o primeiro passo é descobrir a sua localização exata. Então, crie
um arquivo PHP com o conteúdo a seguir, salve-o na pasta principal do
servidor de páginas (conforme apontado na seção anterior) e execute-o a
partir do navegador (ex.: http://localhost/phpinfo.php).

 phpinfo.php
<?php
phpinfo();

Figura 1.1 – Página de con guração.


Como resultado, você terá um relatório de todas as con gurações atuais
do PHP, bem como a localização de seu arquivo de con guração. Se você
localizar no relatório a expressão "php.ini", verá seu caminho exato.
Agora, sabendo a localização do arquivo de con guração, você poderá
efetuar ajustes de ambiente, tais como:
display_errors = On
error_reporting = E_ALL
Ligar a exibição de erros em tela, bem como indicar que todo tipo de
erro deva ser reportado.
log_errors = On
error_log = /tmp/php_errors.log
Ligar o registro de logs de erros e indicar um caminho para o registro
desses erros.
memory_limit = 256M
max_execution_time = 120
Aumentar o limite de memória (em Mb), bem como o tempo de
execução máximo de cada script (em segundos).
file_uploads = On
post_max_size = 100M
upload_max_filesize = 100M
Permitir o upload de arquivos e aumentar os limites de upload e envio
de formulários.
session.gc_maxlifetime = 14000
Aumentar o tempo de uma sessão de usuário.
Estas são algumas con gurações que tradicionalmente os programadores
fazem antes de iniciar o desenvolvimento. O perigo está em desligar a
exibição de erros (display_errors) e não visualizar os problemas de
programação.
Quando a aplicação entrar em produção, não é interessante continuarmos
a exibir os erros em tela, mas somente registrá-los em um arquivo de log.
Pois nas mensagens de erro podem ser encontrados dados sensíveis como
a localização de arquivos, ou eventualmente até mesmo dados de acesso à
base de dados. Então, quando a aplicação entrar em produção, desligue a
exibição de erros:
log_errors = Off
Uma outra fonte preciosa de registro de logs são os logs do servidor de
página (Apache, Nginx). Às vezes, ocorrem erros mais severos de
aplicação que só são registrados nos logs do servidor de página.

1.3 Um programa PHP


1.3.1 Estrutura do código-fonte
Normalmente um programa PHP tem a extensão .php. Entretanto não é
incomum encontrarmos extensões como .class.php para armazenar classes
ou .inc.php, em projetos mais antigos, para representar simplesmente
arquivos que devem ser incluídos.
O código de um programa escrito em PHP deve iniciar com <?php. Veja:
<?php
// código;
// código;
// código;

 Observação: a finalização da maioria dos comandos se dá por ponto e vírgula


(;).

1.3.2 Comentários
Para comentar uma única linha:
// echo "a";
# echo "a";
Para comentar muitas linhas:
/* echo "a";
echo "b"; */

1.3.3 Comandos de saída (output)


São os comandos utilizados para gerar uma saída em tela (output). Se o
programa PHP for executado na linha de comando (prompt do sistema), a
saída será no próprio console. No entanto, se o programa for executado
via servidor de páginas web (Apache, Nginx), a saída será exibida na
própria página HTML.
echo
É um comando que imprime uma ou mais variáveis no console.
Exemplo:
echo 'a' . '<br>' . PHP_EOL;
echo 'b' . '<br>' . PHP_EOL;

 Observação: utilizaremos o separador '<br>'.PHP_EOL para formar uma quebra


de linha. PHP_EOL representa uma quebra de linha (end of line). Ex.: “\n”.
 Resultado:
a
b
print
É um comando que imprime uma string no console. Exemplo:
print 'abc';

 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

1.4.1 Tipo booleano


Um booleano expressa um valor lógico que pode ser verdadeiro ou falso.
Para especi car um valor booleano, utilize as palavras-chave TRUE ou FALSE.
No exemplo a seguir declaramos a variável booleana $exibir_nome, cujo
conteúdo é TRUE (verdadeiro). Em seguida, testaremos o conteúdo dessa
variável para veri car se ela é realmente verdadeira. Em caso positivo, será
exibido na tela o nome “José da Silva”.
<?php
$exibir_nome = TRUE; // declara variável com valor TRUE
// testa se $exibir_nome é TRUE
if ($exibir_nome) {
echo 'José da Silva';
}

 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.

1.4.2 Tipo numérico


Números podem ser especi cados em notação decimal (base 10),
hexadecimal (base 16) ou octal (base 8), sendo opcionalmente precedidos
de sinal.
<?php
$a = 1234; // número decimal
$a = -123; // um número negativo
$a = 0123; // número octal (equivalente a 83 em decimal)
$a = 0x1A; // número hexadecimal (equivalente a 26 em decimal)
$a = 1.234; // ponto flutuante
$a = 4e23; // notação científica

1.4.3 Tipo string


Uma string é uma cadeia de caracteres alfanuméricos. Para declará-la,
podemos usar aspas simples (‘ ‘) ou aspas duplas (“ “). Veja com mais
detalhes como manipular strings na seção 1.10, “Manipulação de strings”.
<?php
$variavel = 'Isto é um teste';
$variavel = "Isto é um teste";

1.4.4 Tipo array


Array é uma lista de valores armazenados na memória que podem ser de
tipos diferentes (números, strings, objetos) e podem ser acessados a
qualquer momento, pois cada valor é relacionado a uma chave. Um array
também pode crescer dinamicamente com a adição de novos itens. Veja na
seção 1.11, “Manipulação de arrays”, como manipular arrays.
<?php
$carros = array('Palio', 'Corsa', 'Gol');
echo $carros[1]; // resultado = 'Corsa'

1.4.5 Tipo objeto


Um objeto é uma entidade com um determinado comportamento
de nido por seus métodos (suas ações) e suas propriedades (seus dados).
Para criar um objeto deve-se utilizar o operador new. Neste exemplo
criamos um objeto plano (stdClass) e atribuímos algumas propriedades
para ele. No capítulo 2 veremos como manipular classes e objetos.
<?php
$carro = new stdClass;
$carro->modelo = 'Palio';
$carro->ano = 2002;
$carro->cor = 'Azul';
print_r($carro);
print $carro->modelo . ' ';
print $carro->ano . ' ';
print $carro->cor . ' ';

 Resultado:
stdClass Object (
[modelo] => Palio
[ano] => 2002
[cor] => Azul
)
Palio 2002 Azul

1.4.6 Tipo recurso


Recurso (resource) é uma variável que mantém uma referência de recurso
externo. Recursos são criados e utilizados por funções como aquelas que
criam conexões de banco de dados. Quando as funções mysql_connect() e
pg_connect(), por exemplo, são conectadas ao banco de dados, retornam
uma variável de referência do tipo recurso.
resource mysql_connect ([ string $server [, string $username [, string
$password
[, bool $new_link [, int $client_flags ]]]]] )
resource pg_connect ( string $connection_string )

 Observação: uma variável do tipo recurso não pode ser serializada.


1.4.7 Tipo misto
O tipo misto (mixed) é uma identi cação que representa diversos (não
necessariamente todos) tipos de dados que podem ser usados em um
mesmo parâmetro. Um parâmetro do tipo mixed indica que a função aceita
diversos tipos de dados como parâmetro. Um exemplo é a função
gettype(), que obtém o tipo da variável passada como parâmetro (que
pode ser integer, string, array, objeto, entre outros).
string gettype (mixed var)
Veja alguns resultados possíveis:
"boolean" "integer" "double" "string" "array" "object" "resource" "NULL"

1.4.8 Tipo callback


Algumas funções como call_user_func() aceitam um parâmetro que
signi ca uma função a ser executada. Esse tipo de dado é chamado de
callback. Um parâmetro callback pode ser o nome de uma função
representada por uma string ou o método de um objeto a ser executado
representado por um array. Veja os exemplos na documentação da função
call_user_func().

1.4.9 Tipo NULL


A utilização de NULL (nulo) signi ca que a variável não tem valor.

1.5 Declarações de tipo


Algumas linguagens de programação exigem que o tipo (int, bool, string)
das variáveis seja declarado explicitamente. O PHP não requer que você
de na explicitamente o tipo da variável, pois ele infere o tipo. Inferência é
a capacidade do compilador de saber o tipo do dado sem que este esteja
explicitamente declarado. Quando você declarar uma variável e em
seguida executar um var_dump(), por exemplo, será possível ver o tipo real
da variável, escolhido pelo PHP conforme sua utilização:
<?php
$a = 5;
$b = 'teste';
var_dump($a, $b);

 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

1.5.1 Tipagem estrita


O PHP também permite habilitar o modo estrito, ou tipagem estrita. Esta
é uma con guração executada em âmbito de arquivo e, quando
habilitada, exige que o tipo da variável passada como parâmetro em
tempo de execução seja exatamente o mesmo tipo declarado. Caso o tipo
da variável passada como parâmetro seja diferente do tipo esperado, um
erro do tipo TypeError é lançado. Conforme a documentação, a exceção à
regra é que um valor inteiro pode ser passado a uma função que aguarda
um oat.
Para habilitar a tipagem estrita, precisamos usar o declare() no início do
arquivo, passando strict_types=1. Neste caso, nossa função está declarada
para receber dois parâmetros do tipo float. Na primeira chamada,
passamos um int e um oat, o que é permitido. Na segunda, passamos
uma string ‘75.1’, o que provoca um erro de divergência de tipagem,
conforme apresentado no output.
<?php
declare(strict_types=1);
function calcula_imc(float $peso, float $altura): float {
var_dump($peso, $altura);
return $peso / ($altura*$altura);
}
var_dump(calcula_imc(75, 1.85));
var_dump(calcula_imc('75.1',2));

 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

 A cláusula strict é definida no arquivo em que a chamada da função é realizada,


e não no arquivo em que ela é definida. Assim, se tivéssemos dividido a execução
em dois arquivos, onde um fosse responsável pela definição da função
calcula_imc() e outro pelo require e sua chamada, para que o strict_types
tivesse efeito, ele deveria ser definido no arquivo em que a função foi chamada.
1.6 Superglobais
Superglobais são variáveis disponibilizadas pelo próprio PHP em
qualquer local em que você esteja executando, seja no programa principal,
dentro de uma função, ou por um método especí co. Elas possivelmente
carregam alguns conteúdos, dependendo de como o script foi invocado,
como nos exemplos a seguir:
• $_SERVER – Contém informações sobre o ambiente em que o script está
rodando, como endereço do servidor, da requisição, nome do script
acessado, entre outros. Por exemplo, na posição HTTP_USER_AGENT, há
informações sobre o browser do request; na posição SCRIPT_FILENAME, há
o caminho do script sendo executado no servidor.
• $_GET – Contém um vetor com as variáveis informadas em uma
requisição $_GET. Por exemplo, a requisição http://localhost/sample.php?
name=john&age20 retorna um vetor com as posições john e age, contendo
os respectivos valores.
• $_POST – Funciona da mesma maneira que $_GET, mas contém as
informações enviadas pelo método POST, geralmente usado no envio
de formulários.
• $_FILES – Contém um vetor com informações dos arquivos enviados
para upload. Esta pode ser acessada logo após o upload de um arquivo
por upload via formulário.
• $_COOKIE – Contém um vetor com as informações recebidas pelo script
que atualmente estão armazenadas nos cookies.
• $_SESSION – Contém um vetor com as variáveis da sessão do usuário.
• $_REQUEST – Contém um vetor com as informações de $_GET, $_POST, e
$_COOKIE.
• $_ENV – Contém um vetor com variáveis de ambiente. Possivelmente
útil ao executarmos um script PHP pela linha de comando, pois contém
informações sobre o usuário do sistema operacional (USER), diretório
home do usuário (HOME), entre outros dados.
• $GLOBALS – Contém uma lista com todas as variáveis globais
disponíveis para o script.

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.

 Observação: or e and têm precedência menor que && ou ||.


No programa a seguir, se as variáveis $vai_chover e $esta_frio forem
verdadeiras ao mesmo tempo, será impresso no console “Não vou sair de
casa”.
<?php
$vai_chover = TRUE;
$esta_frio = TRUE;
if ($vai_chover and $esta_frio) {
echo "Não vou sair de casa";
}

 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.

Figura 1.2 – Fluxo do comando IF.


Caso a avaliação da expressão seja verdadeira, o programa parte para a
execução de um bloco de comandos; caso seja falsa, parte para a execução
do bloco de comandos dada pelo ELSE.
if (expressão) {
// comandos se expressão é verdadeira;
}
else {
// comandos se expressão é falsa;
}
Exemplo:
<?php
$a = 1;
if ($a==5) {
echo "é igual";
}
else {
echo "não é igual";
}

 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

 Observação: atualmente, se o PHP estiver com o nível de erro NOTICE ligado, o


teste if ($b) emitirá a mensagem de erro “Notice: Undefined variable: b”. A forma
mais correta para testar se uma variável está definida é utilizar a função if
(isset($b)).
Para realizar testes encadeados, basta colocar um IF dentro do outro ou
utilizar o operador AND da seguinte forma:
<?php
$salario = 1020;
$tempo_servico = 12;
$tem_reclamacoes = false;
if ($salario > 1000) {
if ($tempo_servico >= 12) {
if ($tem_reclamacoes != true) {
echo 'parabéns, você foi promovido<br>' . PHP_EOL;
}
}
}
if (($salario > 1000) and ($tempo_servico >= 12) and ($tem_reclamacoes
!= true)) {
echo 'parabéns, você foi promovido<br>' . PHP_EOL;
}

 Observação: quando os exemplos forem executados no ambiente web, é


importante utilizar o marcador <br> para quebrar as linhas. De outra forma,
quando o PHP for executado pela linha de comando, esse marcador será
desnecessário.

 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;
}

Figura 1.3 – Fluxo do comando WHILE.


No exemplo a seguir, o comando WHILE está avaliando a expressão “se $a é
menor que 5” como ponto de entrada do laço de repetições. Na primeira
vez que é executada essa comparação, retorna-se TRUE, visto que o valor de
$a é 1. Logo o programa entra no laço de repetições executando os
comandos entre { }. Observe que, dentro do bloco de comandos, a
variável $a é incrementada. Assim, essa execução perdurará por mais
algumas iterações. Quando seu valor for igual a 5, a comparação retornará
FALSE e não mais entrará no WHILE, deixando de executar o bloco de
comandos.
<?php
$a = 1;
while ($a < 5) {
print $a;
$a ++;
}

 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.

Figura 1.4 – Fluxo do comando SWITCH.


Sintaxe do comando:
<?php
switch ($variavel) {
case valor1:
// comandos
break;
case valor2:
// comandos
break;
default:
// comandos
}
Os exemplos seguintes representam duas formas diferentes de atingir o
mesmo resultado. Primeiro, por meio de uma série de comandos IF e, logo
em seguida, utilizando a estrutura switch.
 Observação: se você tem um switch dentro de um loop e deseja continuar para
a próxima iteração do laço de repetição, utilize o comando continue 2, que
escapará dois níveis acima.
<?php
$i = 1;
if ($i == 0) {
print "i é igual a 0";
}
elseif ($i == 1) {
print "i é igual a 1";
}
elseif ($i == 2) {
print "i é igual a 2";
}
else {
print "i não é igual a 0, 1 ou 2";
}

 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 Manipulação de funções


Uma função é um pedaço de código com um objetivo especí co,
encapsulado sob uma estrutura única que recebe um conjunto de
parâmetros e retorna um dado. Uma função é declarada uma única vez,
mas pode ser utilizada diversas vezes.
É uma das estruturas mais básicas para prover reusabilidade.

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

1.11.2 Variáveis globais


Todas as variáveis declaradas dentro do escopo de uma função são locais.
Para acessar uma variável externa ao contexto de uma função sem passá-
la como parâmetro, é necessário declará-la como global. Uma variável
global é acessada a partir de qualquer ponto da aplicação. No exemplo a
seguir, a função criada converte quilômetros para milhas, enquanto
acumula a quantidade de quilômetros percorridos em uma variável global
($total).
<?php
$total = 0;
function km2mi($quilometros) {
global $total;
$total += $quilometros;
return $quilometros * 0.6;
}
echo 'percorreu ' . km2mi(100) . " milhas <br>\n";
echo 'percorreu ' . km2mi(200) . " milhas <br>\n";
echo 'percorreu no total ' . $total . " quilometros <br>\n";

 Resultado:
percorreu 60 milhas
percorreu 120 milhas
percorreu no total 300 quilometros

 Observação: a utilização de variáveis globais não é considerada uma boa


prática de programação, pois uma variável global pode ser alterada a partir de
qualquer parte da aplicação. Dessa maneira, valores inconsistentes podem ser
armazenados em uma variável global, podendo resultar em um comportamento
inesperado da aplicação.

1.11.3 Variáveis estáticas


Dentro do escopo de uma função, podemos armazenar variáveis de forma
estática. Assim, elas mantêm o valor que lhes foi atribuído na última
execução. Declaramos uma variável estática com o operador static.
<?php
function percorre($quilometros) {
static $total;
$total += $quilometros;
echo "percorreu mais $quilometros do total de $total<br>\n";
}
percorre(100);
percorre(200);
percorre(50);

 Resultado:
percorreu mais 100 do total de 100
percorreu mais 200 do total de 300
percorreu mais 50 do total de 350

1.11.4 Passagem de parâmetros


Há dois tipos de passagem de parâmetros: por valor (by value) e por
referência (by reference). Por padrão, os valores são passados by value para as
funções. Assim o parâmetro que a função recebe é tratado como variável
local dentro do contexto da função, não alterando o seu valor externo. Os
objetos são uma exceção, pois são tratados por referência na passagem de
parâmetros.
<?php
function incrementa($variavel, $valor) {
$variavel += $valor;
}
$a = 10;
incrementa($a, 20);
echo $a;

 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

1.11.6 Funções anônimas


Funções anônimas, ou lambda functions, são funções que podem ser
de nidas em qualquer instante e, diferentemente das funções tradicionais,
não têm um nome de nido. Funções anônimas podem car atreladas à
uma variável. Nesse caso, a variável é usada para fazer a chamada
imediata da função, bem como passá-la como parâmetro de alguma
função. Caso a variável não esteja visível no escopo, a função ca fora de
alcance. Uma função anônima pode ser vista como uma função
descartável, que se aplica ao contexto no qual é criada.
 Observação: no PHP, funções anônimas são instâncias de uma classe interna
chamada Closure.
Funções anônimas são úteis para várias coisas, como passagem de
parâmetros e uso como callback de funções. Para iniciar a explicação de
funções anônimas, vamos criar uma função anônima que remova o
acento de alguns caracteres e atribuir essa função à variável
$remove_acento. Veja que a função não tem nome. A única referência para
esse código criado é a variável para a qual a função foi atribuída.
A partir da atribuição, a função pode ser atribuída e passada como
parâmetro para funções e métodos de objetos. Sempre que quisermos
utilizar a função anônima, basta usar a variável na qual ela está de nida,
como estamos fazendo neste exemplo ao remover os acentos de “José da
Conceição”. Por m, estamos de nindo um vetor com vários termos
acentuados e chamando a função array_map(), que recebe em seu primeiro
parâmetro uma função a ser aplicada (Callback) e como segundo
parâmetro um vetor a ser percorrido. Neste caso, estamos aplicando a
função $remove_acento ao vetor e exibindo seu resultado.
<?php
$remove_acento = function($str) {
$a = array('À', 'Á', 'Â', 'Ã', 'Ä', 'Ç', 'È', 'É', 'Ê',
'Ì', 'Í', 'Ò', 'Ó', 'Ô', 'Õ', 'Ù', 'Ú', 'à',
'á', 'â', 'ã', 'ç', 'è', 'é', 'ê', 'í', 'î',
'ò', 'ó', 'ô', 'õ', 'ù', 'ú', 'û', 'ü');
$b = array('A', 'A', 'A', 'A', 'A', 'C', 'E', 'E', 'E',
'I', 'I', 'O', 'O', 'O', 'O', 'U', 'U', 'a',
'a', 'a', 'a', 'c', 'e', 'e', 'e', 'i', 'i',
'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u');
return str_replace($a, $b, $str);
};
print $remove_acento('José da Conceição');
print '<br>' . PHP_EOL;
$palavras = array();
$palavras[] = 'José da Conceição';
$palavras[] = 'Jéferson Araújo';
$palavras[] = 'Félix Júnior';
$palavras[] = 'Ênio Müller';
$palavras[] = 'Ângelo Ônix';
// array array_map ( callback $callback , array $arr1 [, array $... ] )
$r = array_map( $remove_acento, $palavras );
print_r($r);

 Resultado:
Jose da Conceicao
Array (
[0] => Jose da Conceicao
[1] => Jeferson Araujo
[2] => Felix Junior
[3] => Enio Muller
[4] => Angelo Onix
)

1.12 Manipulação de arquivos e diretórios


A seguir veremos uma série de funções utilizadas exclusivamente para
manipulação de arquivos, como sua abertura, leitura, escrita e seu
fechamento.
fopen
Abre um arquivo e retorna um identi cador. Aceita arquivos no formato
“http://...”.
resource fopen ( string $filename , string $mode [, ...] )
feof
Testa se determinado identi cador de arquivo (criado pela função
fopen()) está no m de um arquivo (End Of File).
bool feof ( resource $handle )
fclose
Fecha o arquivo aberto apontado pela fopen(). Retorna TRUE em caso de
sucesso ou FALSE em caso de falha.
bool fclose ( resource $handle )
fgets
Lê uma linha de um arquivo aberto pela fopen() no formato de string.
Recebe opcionalmente um tamanho máximo a ser lido. Se ocorrer um
erro, retorna FALSE.
string fgets ( resource $handle [, int $length ] )

 Observação: neste exemplo, estamos abrindo o próprio arquivo de código-fonte


(identificado pela constante mágica __FILE__).
Exemplo:
<?php
$fd = fopen ( __FILE__, "r");
$linha = 1;
while (!feof ($fd)) {
$buffer = fgets($fd, 4096); // lê uma linha do arquivo
if ($linha > 1) {
echo $buffer; // imprime a linha
}
$linha ++;
}
fclose ($fd);

 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
...

1.13 Manipulação de strings


1.13.1 Declaração
Uma string é uma cadeia de caracteres alfanuméricos. Para declarar uma
string, podemos usar aspas simples ' ' ou aspas duplas " ".
$variavel = 'Isto é um teste';
$variavel = "Isto é um teste";
A diferença é que todo conteúdo dentro de aspas duplas é avaliado pelo
PHP. Assim, se a string contém uma variável, essa variável será traduzida
pelo seu valor.
<?php
$fruta = 'maçã';
print "como $fruta<br>" . PHP_EOL; // resultado: como maçã
print 'como $fruta<br>' . PHP_EOL; // resultado: como $fruta
Também podemos declarar uma string literal com muitas linhas
observando a sintaxe a seguir, na qual escolhemos uma palavra-chave
(neste caso, escolhemos CHAVE) para delimitar o início e o m da string.
<?php
$texto = <<<CHAVE
Aqui nesta área
você poderá escrever
textos com múltiplas linhas
CHAVE;
echo $texto;

 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

1.13.3 Caracteres de escape


Dentro de aspas duplas “” podemos usar caracteres de escape (\) –
controles especiais interpretados de modo diferente pelo PHP. Veja a
seguir os mais usados:
Caracter
Descrição
e
\n Nova linha (proporciona uma quebra de
linha).
\r Retorno de carro.
\t Tabulação.
\\ Barra invertida “\\” (o mesmo que ‘\’).
\” Aspas duplas.
\$ Símbolo de $.
Exemplo:
<?php
echo "seu nome é \"Paulo\".<br>" . PHP_EOL; // resultado: seu nome é
"Paulo".
echo 'seu nome é "Paulo".<br>' . PHP_EOL; // resultado: seu nome é
"Paulo".
echo 'seu salário é $650,00<br>' . PHP_EOL; // seu salário é $650,00
echo "seu salário é \$650,00<br>" . PHP_EOL; // seu salário é $650,00

 Observação: use aspas duplas para declarar strings somente quando for
necessário avaliar seu conteúdo, evitando, assim, tempo de processamento
desnecessário.

1.13.4 Funções para manipulação de strings


As funções a seguir formam um grupo cuja característica comum é a
manipulação de cadeias de caracteres (strings), como conversões,
transformações etc.
strtoupper
Retorna a string com todos os caracteres alfabéticos convertidos para
letras maiúsculas
string strtoupper ( string $string )
strtolower
Retorna a string com todos os caracteres alfabéticos convertidos para
letras minúsculas.
string strtolower ( string $string )
Exemplo:
<?php
echo strtoupper('Convertendo para maiusculo') . '<br>' . PHP_EOL;
echo strtolower('CONVERTENDO PARA MINUSCULO') . '<br>' . PHP_EOL;

 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

1.14 Manipulação de arrays


A manipulação de arrays no PHP é, sem dúvida, um dos recursos mais
poderosos dessa linguagem. O programador que assimilar bem esta parte
terá muito mais produtividade no seu dia a dia. Isso porque os arrays no
PHP servem como verdadeiros contêineres, armazenando números,
strings, objetos, entre outros, de forma dinâmica. Além disso, o PHP nos
oferece uma gama de funções para manipulá-los, as quais serão vistas a
seguir.

1.14.1 Criando um array


Arrays são acessados mediante uma posição, como um índice numérico.
Para criar um array, pode-se utilizar a função array([chave =>] valor , ...
) ou a sintaxe resumida entre [].
$cores = array(0=>'vermelho', 1=>'azul', 2=>'verde', 3=>'amarelo');
ou
$cores = array('vermelho', 'azul', 'verde', 'amarelo');
ou
$cores = [ 'vermelho', 'azul', 'verde', 'amarelo' ];
Outra forma de criar um array é simplesmente adicionando-lhe valores
com a seguinte sintaxe:
$nomes[] = 'maria';
$nomes[] = 'joão';
$nomes[] = 'carlos';
$nomes[] = 'josé';
De qualquer forma, para acessar o array indexado, basta indicar o seu
índice entre colchetes:
echo $cores[0]; // resultado = vermelho
echo $cores[1]; // resultado = azul
echo $cores[2]; // resultado = verde
echo $cores[3]; // resultado = amarelo
echo $nomes[0]; // resultado = maria
echo $nomes[1]; // resultado = joão
echo $nomes[2]; // resultado = carlos
echo $nomes[3]; // resultado = josé

1.14.2 Arrays associativos


Os arrays no PHP são associativos, pois contêm uma chave de acesso para
cada posição. Para criar um array, pode-se utilizar a função array([chave
=>] valor , ... ).
$cores = array('vermelho' => 'FF0000', 'azul' => '0000FF', 'verde' =>
'00FF00');
Outra forma de criar um array associativo é simplesmente adicionando-
lhe valores com a seguinte sintaxe:
$pessoa = array();
$pessoa['nome'] = 'Maria da Silva';
$pessoa['rua'] = 'São João';
$pessoa['bairro'] = 'Cidade Alta';
$pessoa['cidade'] = 'Porto Alegre';
De qualquer forma, para acessar o array, basta indicar a sua chave entre
colchetes:
echo $cores['vermelho']; // resultado = FF0000
echo $cores['azul']; // resultado = 0000FF
echo $cores['verde']; // resultado = 00FF00
echo $pessoa['nome']; // resultado = Maria da Silva
echo $pessoa['rua']; // resultado = São João
echo $pessoa['bairro']; // resultado = Cidade Alta
echo $pessoa['cidade']; // resultado = Porto Alegre

 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
)

1.14.5 Arrays multidimensionais


Arrays multidimensionais ou matrizes são arrays nos quais algumas de
suas posições podem conter outros arrays de forma recursiva. Um array
multidimensional pode ser criado pela função array():
<?php
$carros = array('Palio' => array('cor'=>'azul',
'potência'=>'1.0',
'opcionais'=>'Ar Cond.'),
'Corsa' => array('cor'=>'cinza',
'potência'=>'1.3',
'opcionais'=>'MP3'),
'Gol' => array('cor'=>'branco',
'potência' => '1.0',
'opcionais' => 'Metalica')
);
echo $carros['Palio']['opcionais']; // resultado = Ar Cond.
Outra forma de criar um array multidimensional é simplesmente
atribuindo-lhe valores:
<?php
$carros = array();
$carros['Palio']['cor'] = 'azul';
$carros['Palio']['potência'] = '1.0';
$carros['Palio']['opcionais'] = 'Ar Cond.';
$carros['Corsa']['cor'] = 'cinza';
$carros['Corsa']['potência'] = '1.3';
$carros['Corsa']['opcionais'] = 'MP3';
$carros['Gol']['cor'] = 'branco';
$carros['Gol']['potência'] = '1.0';
$carros['Gol']['opcionais'] = 'Metalica';
echo $carros['Palio']['opcionais']; // resultado = Ar Cond.
Para fazer iterações em um array multidimensional, é preciso observar
quantos níveis ele tem. No exemplo a seguir, zemos uma iteração para o
primeiro nível do array (veículos) e, para cada iteração, realizamos uma
nova iteração para imprimir suas características.
foreach ($carros as $modelo => $caracteristicas) {
echo "=> modelo $modelo<br>\n";
foreach ($caracteristicas as $caracteristica => $valor) {
echo " - característica $caracteristica => $valor<br>\n";
}
}

 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

1.14.6 Funções para manipulação de arrays


A seguir, veremos uma série de funções utilizadas exclusivamente para
manipulação de arrays; funções de ordenação, intersecção, acesso, entre
outras.
array_unshift
Adiciona elemento(s) ao início de um array.
int array_unshift ( array &$array , mixed $var [, mixed $... ] )
array_push
Adiciona elemento(s) ao nal de um array. Tem o mesmo efeito que
$array[] = $valor.
int array_push ( array &$array , mixed $var [, mixed $... ] )
Exemplo:
<?php
$a = array("verde", "azul", "vermelho");
array_unshift($a, "ciano");
array_push($a, "amarelo");
print_r($a);

 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.

1.16 Ferramentas importantes


Muitas ferramentas são importantes durante o processo de
desenvolvimento. Aqui, procuramos listar algumas que consideramos que
poderão auxiliar em tarefas como versionamento de arquivos e
preparação de ambientes:
• Git – ferramenta de versionamento mais usada no mundo;
• GitHub/Bitbucket – principais servidores para hospedagem de código-
fonte Git; contêm também ferramenta para gestão de tickets;
• GitLab – é uma ferramenta open source para hospedagem de código-
fonte Git e também oferece ferramenta de gestão de tickets e de
integração contínua;
• Vagrant – ferramenta que permite a criação rápida de ambientes virtuais
de desenvolvimento, testes ou produção (máquinas virtuais), em âmbito
local ou em nuvem, por meio de API,
• Docker – tecnologia que permite o gerenciamento de contêineres, que
são uma alternativa de virtualização mais e ciente do que a oferecida
por máquinas virtuais, em que o kernel da máquina hospedeira é
compartilhado com a máquina virtualizada.
CAPÍTULO 2
Fundamentos de orientação a objetos

“No que diz respeito ao desempenho, ao compromisso, ao esforço, à dedicação,


não existe meio-termo. Ou você faz uma coisa bem-feita ou não faz.”
A S
A orientação a objetos é um paradigma que representa uma loso a para
construção de sistemas. Em vez de construir um sistema formado por um
conjunto de procedimentos e variáveis nem sempre agrupadas de acordo
com o contexto, como se fazia em linguagens estruturadas (Cobol,
Clipper, Pascal), na orientação a objetos utilizamos uma óptica mais
próxima do mundo real. Lidamos com objetos: estruturas que carregam
dados e comportamento próprio, além de trocarem mensagens entre si
com o objetivo de formar algo maior, um sistema.
Orientação a objetos é uma abordagem para concepção de sistemas, um
paradigma de programação. Atualmente é a abordagem mais utilizada
para concepção de sistemas. Entretanto, nem sempre foi assim. Para
entender o que é a orientação a objetos e como ela é utilizada, primeiro é
importante compreender como era o desenvolvimento antes de a
orientação a objetos se tornar o paradigma vigente.

2.1 Programação procedural


A abordagem mais usada durante muito tempo foi a programação
procedural. Esta ocorre quando o programa é construído com base em
um conjunto de procedimentos (procedures). Um procedimento (que no
PHP pode ser construído com uma function) basicamente é uma unidade
de código que pode ser reaproveitada em diversas situações. Como já
vimos a maneira de declarar funções em PHP, você já compreende que
uma função é uma unidade de código que recebe parâmetros de entrada,
executa determinado procedimento e devolve um retorno para quem a
chamou.
A gura 2.1 procura demonstrar como um programa procedural pode
car. Nessa gura, cada caixa representa um procedimento (função). Para
realizar determinado trabalho, um procedimento pode se apoiar em
outros procedimentos, resultando em uma cadeia de chamadas com
vários níveis, com funções chamando outras funções e assim por diante.
Imagine que a primeira caixa representa uma função que registra uma
venda. Para cumprir seu papel, ela terá de utilizar outras funções para
veri car débitos, baixar estoque, consultar valores de produtos, gerar
nanceiro, entre outras. Cada uma dessas funções por sua vez pode
depender de outras funções. Não é tarefa fácil visualizar essa cadeia de
dependências em um programa procedural.

Figura 2.1 – Estrutura de um programa procedural.


O PHP é uma linguagem multiparadigma, ou seja, suporta mais de um
paradigma de programação. Isso signi ca que você pode utilizá-lo para
desenvolver de maneira procedural ou orientada a objetos. Claro que
vamos buscar sempre programar de maneira orientada a objetos. No
entanto, por motivos didáticos, vamos imaginar no próximo exemplo
como seria um programa procedural em PHP formado apenas por
funções e chamadas entre elas.
O exemplo a seguir demonstra como seria o procedimento (função)
conclui_venda() para concluir uma venda. Essa função utiliza-se de outras
funções para cumprir seu trabalho: total_debitos_cliente() para veri car
os débitos do cliente; registra_venda() para gravar a venda na base de
dados; consulta_estoque() para consultar o estoque de um produto;
consulta_valor() para consultar o valor de um produto;
registra_venda_item() para registrar um produto na venda; e
gera_financeiro()para gerar o nanceiro. Este programa está incompleto,
pois é somente um esboço. No mundo real, precisaríamos fazer
transações, realizar consultas na base de dados, entre outros
procedimentos, para torná-lo funcional.

 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.2 Orientação a objetos


Como já foi citado anteriormente, a orientação a objetos é uma
abordagem para concepção de sistemas, um paradigma de programação.
Essa abordagem envolve desde a modelagem do sistema até sua
programação por meio do uso de objetos e relacionamentos entre esses
objetos.
Um objeto é uma estrutura de programação que representa uma entidade.
Mas o que é exatamente uma entidade? Uma entidade pode ser uma
representação de algo que existe no mundo real e que tem algum
signi cado para o software, como uma pessoa, um produto, uma venda,
um fornecedor, que são objetos de negócio, pois têm relação com os
requisitos funcionais da aplicação. Um objeto pode ser também algo
necessário na esfera técnica (não funcional) para garantir a interação com
o usuário por meio da interface, como uma janela, um formulário, uma
datagrid, um botão ou até algo que garanta a comunicação da aplicação
com recursos externos: uma conexão com o banco de dados, um arquivo
XML, um web service, entre outros.
Independentemente do contexto, uma aplicação pode ser caracterizada
como orientada a objetos quando é formada exclusivamente por um
conjunto de objetos que trocam mensagens entre si para a resolução de
um problema complexo. A gura 2.2 demonstra de maneira abstrata
como um programa orientado a objetos se comporta. Nesse contexto,
cada objeto é visto como um hexágono que se comunica com outros
objetos, trocando informações com eles. No interior de cada objeto os
pequenos cubos procuram representar características (atributos)
administradas por este (objeto). Então cada objeto apresenta
características (atributos) e se comunica com outros objetos.

Figura 2.2 – Conjunto de objetos inter-relacionados.


Na orientação a objetos, cada objeto tem uma responsabilidade única.
Isso signi ca que um objeto deve, além de administrar seus atributos, ter
um conjunto de funcionalidades coerentes. Assim, com base no programa
procedural que construímos anteriormente, podemos concluir que:
• em um programa procedural, temos funções espalhadas ao longo do
sistema, como consulta_estoque() e consulta_valor(), que lidam com
informações de produto, e registra_venda() e registra_venda_item(), que
lidam com informações de vendas;
• em um programa orientado a objetos, teríamos um objeto Produto que
agruparia funcionalidades como consulta_estoque() e consulta_valor();
também teríamos um objeto Venda que agruparia funcionalidades como
registra_venda() e registra_venda_item().
Assim, podemos imaginar que a orientação a objetos facilita a
manutenção, pois as funcionalidades que têm características em comum
estarão agrupadas em torno de objetos coesos, em vez de estarem
espalhadas ao longo do sistema. Essa maior facilidade de manutenção
também torna o sistema menos suscetível a mudanças, ou seja, quando
uma mudança ocorrer, será mais fácil veri car as consequências.
Para formar um sistema completo de maneira orientada a objetos, é
preciso construir objetos que tratam de diferentes aspectos de uma
aplicação (modelo de negócios, interface, persistência, entre outros) e
também é preciso fazer com que esses objetos de diferentes
responsabilidades conversem para a realização das atividades necessárias.

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.

Figura 2.3 – Objeto com atributos públicos.


No programa a seguir, declaramos uma classe, que é a estrutura utilizada
para criar objetos. A classe em questão chama-se Produto e terá atributos
como $descricao, $estoque e $preco. Para criar objetos a partir de uma
classe, utilizamos o operador new seguido do nome da classe que
desejamos instanciar. O operador new permite criar um objeto de cada vez.
Cada objeto é representado por uma variável que aponta para uma região
da memória em que os atributos do objeto serão armazenados. Diz-se que
o objeto é uma instância de uma classe porque o objeto existe durante um
determinado instante de tempo – da sua criação até a sua destruição.
Após criarmos cada objeto, como seus atributos são públicos, podemos
de ni-los diretamente por meio de uma atribuição simples. Ao nal,
usamos um var_dump() para exibir a estrutura de um objeto em tela e
veri car se ele transporta os atributos de nidos.

 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.

Figura 2.4 – Um objeto.


Na orientação a objetos, esses pontos de acesso podem ser representados
por métodos. Um método é uma função que faz parte de uma classe e
representa um comportamento que essa (classe) precisa expor para o
ambiente externo. Uma classe Produto, por exemplo, pode expor métodos
(funções) como aumentarEstoque(), diminuirEstoque(), reajustarPreco(),
entre outros. No exemplo a seguir vamos criar cada um desses métodos.
Para um método referenciar um atributo interno da classe, basta
utilizarmos a variável $this, que, por sua vez, é uma variável que
representa o objeto atual que está sendo manipulado.
 Observação: uma classe é uma estrutura genérica que pode derivar inúmeros
objetos por meio do operador new. Nesse contexto, a variável $this existe somente
em tempo de execução e representa o objeto em memória que está sendo
manipulado naquele instante.

 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

2.5 Métodos construtores e destrutores


Já vimos como declarar uma classe, criar objetos por meio do operador
new, de nir atributos e criar métodos. Agora vamos entender que um
objeto já pode “nascer” com alguns atributos de nidos no momento de
sua construção. Isso é possível porque o PHP apresenta o conceito de
método construtor. Um método construtor é um método executado
automaticamente no momento em que construímos um objeto por meio
do operador new. Não devemos retornar nenhum valor por meio do
método construtor porque esse método retorna por de nição o próprio
objeto que está sendo instanciado.
 Observação: caso não seja definido um método construtor, automaticamente
todas as propriedades do objeto criado são inicializadas com o valor NULL.
No PHP, um método construtor deve ter o nome __construct(), pois é
uma convenção da linguagem. No exemplo a seguir estamos criando um
método construtor que já recebe três parâmetros: $descricao, $estoque e
$preco. Ao construirmos o objeto, ele receberá essas três variáveis caso elas
passem nas validações necessárias antes de serem atribuídas internamente
ao objeto ($this->). Neste exemplo, criamos um objeto com atributos
válidos e, depois, executamos os métodos getters para obter os valores dos
atributos. Veja que os parâmetros recebidos pelo método construtor
($descricao, $estoque, $preco) devem ser armazenados como atributos do
objeto ($this->descricao, $this->estoque, $this->preco) para serem lidos
posteriormente.

 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

2.6 Conversões de tipo


O PHP nos oferece diversas facilidades no que se refere à manipulação de
objetos. Uma delas é a possibilidade de criar objetos sem ter uma classe
de nida. Isso é possível porque no PHP existe uma classe prede nida
chamada stdClass (Standard Class), que é uma classe vazia (sem atributos e
métodos). Essa classe é utilizada também quando realizamos conversões
de tipo, como de array para objeto, como será demonstrado no exemplo.
Criaremos um objeto $produto da classe stdClass. Inicialmente serão
de nidos alguns atributos. Ao nal utilizaremos o print_r() para exibir a
estrutura do objeto criado.

 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 Relacionamentos entre objetos


Nesta seção demonstraremos os principais tipos de relacionamentos entre
objetos: associação, composição e agregação. A herança será tratada em
uma seção separada, em virtude de ser mais complexa.
 Observação: a partir deste ponto vamos abandonar uma prática utilizada até
então, que é a de mesclar a declaração da classe com a sua utilização no mesmo
arquivo. Como essa não é uma boa prática, a partir deste ponto as classes serão
armazenadas isoladamente no subdiretório classes.

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.

Figura 2.5 – Relacionamento de associação.


No exemplo a seguir criaremos uma associação entre as classes Produto e
Fabricante. A classe Fabricante terá como atributos nome, endereco e
documento. Em seu método construtor, ela receberá via parâmetro esses
dados para já ser inicializada com conteúdo. Para poupar espaço aqui,
criaremos somente o método getter getNome().

 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.

 Observação: perceba que no método setFabricante() utilizamos a expressão


“fabricante” na frente do nome do parâmetro $f. Esse conceito chama-se indução
ao tipo (type hinting). Ao usarmos esse recurso, indicamos explicitamente qual
deverá ser o tipo da variável a ser recebida para evitar erros. Caso o programador
passe como parâmetro uma variável que não seja da classe Fabricante, um erro
será gerado. A indução ao tipo é importante, pois, ao recebermos um parâmetro
que é um objeto, provavelmente iremos executar métodos sobre ele, e é preciso
garantir que esses métodos existam.

 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.

Figura 2.6 – Relacionamento de composição.


Inicialmente será criada a classe Caracteristica. Uma característica terá
apenas dois atributos: nome e valor. São exemplos de nomes: “cor”, “peso”,
“tamanho” e “potência”. São exemplos de valores: “branco”, “20 kg”, “200
watts” e assim por diante.

 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.

Figura 2.7 – Relacionamento de agregação.


A classe Cesta terá um método construtor utilizado para inicializar
algumas variáveis como time (tempo de criação) e itens (vetor de
produtos). Para agregar objetos do tipo Produto à Cesta, criaremos o
método de agregação addItem(), que receberá uma instância de Produto e
irá agregá-la ao vetor ($this->itens) da classe Cesta. Veja que o método
addItem() terá uma indução ao tipo Produto, ou seja, somente poderá
receber objetos da classe Produto. A classe Cesta contará também com o
método getItens(), que por sua vez retornará os itens da cesta.

 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;
}
}

2.9 Polimor smo


O signi cado da palavra polimor smo nos remete a “muitas formas”.
Polimor smo em orientação a objetos é o princípio que permite que
classes derivadas de uma mesma superclasse tenham métodos iguais (com
a mesma nomenclatura e os mesmos parâmetros), mas comportamentos
diferentes, rede nidos em cada uma das classes lhas.
Veja que as classes ContaPoupanca e ContaCorrente, criadas anteriormente,
contêm os mesmos métodos. Repare que o método retirar() tem o
mesmo nome, mas comportamento diferente em ambas. No caso da conta
poupança, ele veri ca somente se há saldo. Já na conta-corrente, a
retirada veri ca se há saldo e também observa o limite da conta. Veja que
o comportamento dessas operações é muito similar. Para demonstrar as
peculiaridades de cada método, inicializaremos duas contas com o
mesmo saldo inicial e efetuaremos os mesmos procedimentos de depósito
e retirada sobre elas dentro de um laço de repetição. No exemplo a seguir,
em vez de armazenar os objetos do tipo Conta em variáveis, formaremos
um array de objetos: $contas. Veja que os resultados obtidos serão
ligeiramente diferentes, pois a primeira conta é uma conta-corrente e tem
um limite, o que permite que se façam retiradas além do saldo real.

 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.

2.10.1 Classes abstratas


Nesse contexto, encontraremos classes estruturais, ou seja, que estão na
nossa hierarquia de classes para servir de base para outras. São classes
que nunca serão instanciadas na forma de objetos; somente suas lhas
serão. Nesses casos é interessante marcar essas classes como classes
abstratas, de modo que cada classe abstrata seja tratada de maneira
diferente pela linguagem de programação, impedindo automaticamente
que se instanciem objetos a partir dela.
Uma conta bancária pode ser uma ContaCorrente ou uma ContaPoupança,
mas jamais poderá ser uma Conta. Isso porque Conta é uma estrutura
incompleta, que não fornece o método de retiradas, por exemplo.
Portanto, marcaremos a classe Conta como abstract. Uma classe abstrata
não pode ser instanciada diretamente; apenas pode servir de base para
criação de novas classes.

 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...

2.10.2 Classes nais


Uma classe nal é uma classe que não pode ser uma superclasse, ou seja,
não pode ser base para construção de outra classe em uma estrutura de
herança. Uma classe é de nida como nal ao acrescentarmos o operador
FINAL na frente de sua declaração. Assim, ela não poderá mais ser
especializada. Em nosso exemplo, tornaremos a classe ContaPoupanca uma
classe nal e, mesmo assim, tentaremos especializá-la com o que
chamamos de ContaPoupancaUniversitaria. Sempre que tentarmos derivar
uma classe nal, um erro ocorrerá, conforme será visto no próximo
exemplo.

 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)...

2.10.3 Métodos abstratos


Como vimos, uma classe abstrata como Conta é incompleta por natureza.
Percebemos isso pela falta do método retirar(), que existe somente em
suas subclasses. Em casos como este, seria prudente que a superclasse
tivesse algum mecanismo de proteção para garantir que as classes lhas
necessariamente implementassem um método chamado retirar(); caso
contrário, elas também seriam incompletas. Felizmente, o PHP tem um
recurso que permite isso; são os métodos abstratos.
Um método abstrato consiste na de nição de uma assinatura de método,
ou seja, na de nição de seu nome e de seus parâmetros, não de sua
implementação. Um método abstrato deve conter uma implementação na
classe lha, mas não deve ter implementação na classe em que ele é
de nido. Neste exemplo de niremos um método abstrato na classe Conta.
Isso faz com que seja obrigatório que qualquer classe lha da classe Conta
forneça a implementação desse método.
No próximo exemplo, de niremos o método retirar() na classe Conta
como abstrato e tentaremos criar uma nova classe lha de Conta. Como
esperado, obteremos um erro como resultado, indicando que a nova classe
não tem a implementação do método abstrato. Para sanar esse erro, basta
implementá-lo conforme a de nição feita na superclasse.

 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)...

2.10.4 Métodos nais


Há situações em que escrevemos determinados métodos, mas não
queremos que eles sejam sobrescritos em classes lhas. Sempre que
quisermos que um método seja a implementação de nitiva e não seja
mais especializado em classes lhas, devemos marcá-lo como um método
nal. Um método nal não pode ser sobrescrito, ou seja, não pode ser
rede nido na classe lha. Para marcar um método como nal, basta
utilizar o operador FINAL no início da sua declaração. No exemplo a seguir
vamos declarar o método retirar() da classe ContaCorrente como sendo
FINAL e, ainda assim, tentaremos rede ni-lo na classe lha
ContaCorrenteEspecial. O resultado será um erro, já que não poderemos
sobrescrever um método nal.

 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.

Figura 2.9 – Encapsulamento.


Para atingir o encapsulamento, uma das formas é de nir a visibilidade
das propriedades e dos métodos de um objeto. A visibilidade de ne a
forma como essas propriedades devem ser acessadas. Existem três formas
de acesso:
Visibilidade Descrição
Visibilidade Descrição
public Membros declarados como public poderão ser acessados livremente a partir
da própria classe em que foram declarados, a partir de classes descendentes e
a partir do programa que faz uso dessa classe (manipulando o objeto em si).
Na UML, simbolizamos com um caractere “+" na frente da propriedade.
private Membros declarados como private somente podem ser acessados dentro da
própria classe em que foram declarados. Não poderão ser acessados a partir
de classes descendentes nem a partir do programa que faz uso dessa classe
(manipulando o objeto). Na UML, simbolizamos com um caractere “-" na
frente da propriedade.
protecte Membros declarados como protected somente podem ser acessados dentro
d da própria classe em que foram declarados e a partir de classes descendentes,
mas não poderão ser acessados a partir do programa que faz uso dessa classe
(manipulando o objeto em si). Na UML, simbolizamos com um caractere
“#" na frente da propriedade.

 Observação: a visibilidade foi introduzida pelo PHP5 e, para manter


compatibilidade com versões anteriores, quando a visibilidade de uma propriedade
ou de um método não for definida, automaticamente será tratada como public.

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

 Observação: o resultado anterior poderá sofrer pequenas variações.


Dependendo de como a variável de configuração error_reporting estiver definida
no php.ini, a linha de “Notice” poderá não ser exibida.
Como esperado, o programa não exibiu o nome do funcionário. Isso
aconteceu porque a propriedade nome é uma propriedade private, o que
signi ca que ela somente pode ser acessada de dentro da classe em que foi
declarada (Pessoa). Para permitir o acesso também nas classes lhas,
devemos alterar a propriedade nome para protected.

 protected.php (correção)
<?php
class Pessoa {
protected $nome;
// ...
}

2.12 Membros da classe


Como vimos anteriormente, a classe é uma estrutura-padrão para criação
dos objetos. Ela permite que armazenemos valores nela de duas formas:
constantes de classe e propriedades estáticas. Esses atributos são comuns
a todos os objetos da mesma classe.

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
)

2.12.2 Atributos estáticos


Atributos estáticos são atributos que pertencem a uma classe, não a um
objeto especí co; são dinâmicos como os atributos de um objeto, mas
estão relacionados à classe. Como a classe é a estrutura comum a todos os
objetos derivados dela, atributos estáticos são compartilhados entre todos
os objetos de uma mesma classe.
Para demonstrar a utilidade de um atributo estático, vamos criar uma
classe chamada Software. Cada objeto dessa classe terá como atributos id
e nome. Além disso, haverá um atributo estático chamado contador. A cada
instância criada de Software, incrementaremos o atributo estático contador
e veri caremos ao nal da execução se ele conservou seu valor. Um
atributo estático conserva seu valor em âmbito de classe, ou seja, seu valor
não está vinculado a um objeto especí co. Enquanto para acessar
atributos de objeto utilizamos a forma ($this->atributo), para acessar
atributos estáticos usamos a forma (self::$atributo), quando acessado de
dentro da classe, e a forma (Classe::$atributo), quando acessado de fora
da classe. Além disso é importante declarar o modi cador static na frente
de seu nome.

 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

2.12.3 Métodos estáticos


No exemplo anterior vimos como manipular um atributo estático, que é
um atributo armazenado em âmbito de classe. Entretanto, para
demonstrar o seu funcionamento, devemos declará-lo como public, o que
nem sempre é uma boa opção, visto que ele pode ser acidentalmente
modi cado por fora da classe.
Para manipular atributos estáticos, podemos usar métodos estáticos.
Métodos estáticos podem inclusive ser executados diretamente a partir da
classe sem a necessidade de criar um objeto para isso. Eles não devem
referenciar propriedades internas pelo operador $this, pois esse operador
é utilizado para referenciar instâncias da classe (objetos), mas não a
própria classe; são limitados a chamar outros métodos estáticos da classe
ou usar apenas atributos estáticos. Para executar um método estático,
basta utilizar a sintaxe self::metodo() de dentro da classe ou
Classe::metodo() de fora dela.
No exemplo a seguir reescreveremos o exemplo anterior, declarando o
atributo estático contador como private. Como o atributo foi remarcado
como private, não será mais possível acessá-lo do contexto externo à
classe pela sintaxe Software::$contador, o que ocasionaria um erro do
seguinte tipo:
Fatal error: Cannot access private property Software::$contador
Para manipularmos um atributo estático, será preciso criar um método
estático. Para isso, criaremos o método getContador(). Um método estático
será de nido pelo modi cador static na frente de seu nome.

 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

2.13 Funções para manipulação de objetos


Nesta seção veremos uma série de funções nativas do PHP relacionadas à
manipulação de objetos. São funções simples que nos permitem investigar
sobre classes e objetos.
get_class_methods
Retorna um vetor com os nomes dos métodos de uma determinada
classe.
array get_class_methods ( mixed $class_name )
Exemplo:
<?php
class Funcionario {
function setSalario() {}
function getSalario() {}
function setNome() {}
function getNome() {}
}
print_r(get_class_methods('Funcionario'));

 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.

Figura 2.10 – Interface.


Um sistema orientado a objetos é formado pela comunicação entre
diferentes objetos, e as interfaces tornam essa comunicação mais clara,
consequentemente fazendo com que diferentes partes de um mesmo
sistema quem menos acopladas. Para exempli car esse conceito, vamos
começar uma implementação sem o uso de interfaces para depois aplicar
o conceito. Voltemos para a classe Produto, que contém descricao e preco,
entre outros atributos. Para implementar este exemplo, temos que
adicionar um método getPreco() para retornar esse atributo. A classe
Produto será utilizada principalmente para ser posteriormente agregada
dentro de um orçamento, que será a próxima classe a ser criada.

 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.

Figura 2.11 – Interface do orçamento.


Vamos criar a interface OrcavelInterface. Para declarar uma interface,
usaremos o operador interface, seguido do nome a ser criado. Dentro da
interface, vamos declarar somente os métodos, bem como sua visibilidade
(public, private) e seus parâmetros (caso existam).

 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 Design patterns


Praticamente todo livro sobre design patterns explica esse conceito por
meio das palavras de Christopher Alexander. De acordo com ele, “um
pattern descreve um problema que ocorre com frequência em nosso
ambiente, e então explica a essência da solução para esse problema, de
forma que tal solução possa ser utilizada milhões de outras vezes, sem ao
menos repeti-la uma única vez”.
Ao dizer isso, Christopher estava se referindo a padrões para construções,
como prédios, pontes, entre outros. Apesar disso, essas palavras podem ser
perfeitamente utilizadas no contexto da engenharia de software.
O termo “design pattern” tem sido amplamente utilizado no mundo da
orientação a objetos para descrever formas de comunicação e
relacionamento entre objetos e classes de maneira a solucionar
determinados problemas de projeto. O uso de um design pattern não é a
mais simples nem a mais rápida maneira de solucionar um determinado
problema, mas é, sem dúvidas, a forma que apresenta maior exibilidade
e capacidade de reúso da solução, trazendo benefícios a médio e a longo
prazo na manutenibilidade1 do código-fonte.
Provavelmente você irá reconhecer em vários design patterns aspectos que
já tenha vivenciado; eles não são criados ou inventados; são somente uma
revelação de técnicas já desenvolvidas, descobertas, reunidas e
documentadas sob um nome. Certamente você irá encontrar diversos
design patterns em um sistema orientado a objetos. Um bom
programador reconhece quando está enfrentando determinado tipo de
problema pela segunda vez. Nesse caso, ele aplica o mesmo padrão de
solução que adotou anteriormente em vez de repensar e projetar
novamente.
Os design patterns propõem uma espécie de catálogo para tais soluções,
provendo um nome, um contexto e uma solução genérica.
O nome do design pattern é usado para descrever o problema, as
circunstâncias sob as quais ele ocorre, bem como sua solução. Escolher
um bom nome é muito importante, pois nomes de patterns são
constantemente usados em livros e em outras documentações de
referência quando é necessário descrever uma situação similar ou
simplesmente fazer referência.
Um design pattern é independente de linguagem de programação. Em
princípio, uma linguagem de programação com um bom suporte aos
conceitos de orientação a objetos deve oferecer os recursos necessários
para implementar a maioria dos patterns existentes.
Os padrões de projeto ganharam popularidade na ciência da computação
após a publicação do livro Design Patterns: Elements of Reusable Object-Oriented
Software, publicado em 1994 (Gamma et al.). Nessa obra os autores Erich
Gamma, Richard Helm, Ralph Johnson e John Vlissides, conhecidos
como a “Gangue dos Quatro” (Gang of Four), apresentaram padrões de
projeto que podiam ser aplicados na concepção da arquitetura de
aplicações orientadas a objetos. Foram apresentados padrões como:
Factory Method, Singleton, Adapter, Composite, Decorator, Facade,
Iterator, Observer, Strategy, entre vários outros. Muitos desses padrões
serão abordados ao longo deste livro com exemplos práticos em PHP.
Anos depois, em 2002, Martin Fowler publicou outra obra que se tornou
muito in uente na área: Patterns of Enterprise Application Architecture. Nessa
obra, Fowler se concentrou em padrões de projeto que podiam ser
utilizados diretamente nas construções de aplicações de negócios. Ele
apresentou padrões como: Active Record, Class Table Inheritance, Data
Mapper, Foreign Key Mapping, Front Controller, Lazy Load, Model View
Controller, Query Object, Repository, Row Data Gateway, Table Data
Gateway, Template View, entre vários outros. Muitos desses padrões
também serão abordados ao longo deste livro, com exemplos práticos em
PHP.
A utilização de design patterns re ete o cuidado e o esmero que o
desenvolvedor teve na preparação da arquitetura do software. Para
exempli car o uso de design patterns, vamos apresentar exemplos de
utilização de alguns dos patterns citados nesta seção, além de vários
outros ao longo do livro.

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.

Figura 2.12 – Estrutura de um Singleton.


A grande singularidade dos padrões de projeto é que eles não têm uma
aplicação, mas várias aplicações diferentes, conforme o contexto da
aplicação. Para demonstrar o funcionamento de um Singleton, vamos
criar uma classe para armazenar as preferências da aplicação. As
preferências serão armazenadas inicialmente em um arquivo chamado
application.ini, com o formato mostrado a seguir. O arquivo armazenará
preferências globais da aplicação, como a timezone, o idioma e o próprio
nome.

 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.

Figura 2.13 – Estrutura de um Facade.


Para demonstrar a utilização do design pattern Facade, usaremos como
exemplo o serviço de cobranças PagSeguro. O PagSeguro disponibiliza
um conjunto de classes que facilita a integração do serviço de cobrança
dentro de nossa aplicação. Para isso, ele disponibiliza classes para
requisição de pagamentos (PagSeguroPaymentRequest), para representar um
item (PagSeguroItem), um endereço (PagSeguroAddress), entre outras, que
precisam ser usadas em conjunto para operar e registrar uma cobrança.
Esse conjunto de classes pode e deverá sofrer atualizações em algum
momento, para oferecer melhorias e/ou corrigir bugs. Além disso,
também existe a possibilidade de que esse conjunto de classes seja
totalmente reescrito, alterando nomes de classes, métodos e outros.
Portanto não é uma boa estratégia utilizar diretamente as classes de um
serviço externo a partir de vários pontos de nossa aplicação, pois, como
vimos, quando esta (aplicação) sofrer alterações, teremos vários pontos
aos quais deveremos dar manutençã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);
// ...

 Observação: não é esperado que este trecho de código-fonte funcione somente


se o copiarmos e colarmos. Para usar um serviço de cobrança, são necessárias
outras classes e credenciais não expostas aqui.
A aplicação do design pattern permite a utilização de uma biblioteca
externa e, ao mesmo tempo, o seu isolamento para facilitar a futura
manutenção. Neste exemplo, criaremos uma classe chamada
PagSeguroFacade. Mas qual é o real propósito dessa classe? Simplesmente
isolar as chamadas à biblioteca externa. A classe em questão terá métodos
que irão “esconder” as chamadas ao recurso externo, representado pelas
instâncias das classes PagSeguroPaymentRequest, PagSeguroItem,
PagSeguroAddress, entre outras. Assim, uma classe Facade não é nada mais
do que uma classe sob nosso controle, que “isola” um recurso externo,
tornando nossa aplicação dependente da Facade, não do recurso externo.

 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.

Figura 2.14 – Estrutura de um Adapter.


Para resolver esse problema, podemos usar o design pattern Adapter para
converter os métodos da PHPMailer para o formato de chamada de
métodos que você espera em sua aplicação. Vamos construir a classe
PHPMailerAdapter, que se trata de uma camada na ao redor da PHPMailer
para converter o formato de chamada de métodos. Assim, IsSMTP() se
transformará em setUseSmtp(), MsgHTML() se transformará em setHtmlBody()
e AddAttachment() se transformará em addAttach(), entre outros.
A seguir temos a classe PHPMailerAdapter. Trata-se de uma classe funcional
que só precisa da PHPMailer para funcionar.

 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 );

 Observação: há dois tipos de utilização do design pattern Adapter – por


composição (utilizado neste exemplo) e por herança, em que a classe de adaptação
herda o comportamento a partir da classe adaptada.
Por m, você deve estar se perguntando “qual é a diferença entre o design
pattern Adapter e o Facade, já que eles têm uma estrutura similar?”. A
diferença básica é a intenção de uso. O objetivo do design pattern Adapter
é simplesmente converter uma interface (conjunto de métodos) em outra,
ou seja, fazer dois lados conversarem por meio de uma interface adaptada.
Já o design pattern Facade fornece uma interface simpli cada para que
possamos usá-la no lugar de acessar diretamente classes individuais de
um subsistema. Ambos os padrões usam composição e delegam
comportamento (direcionam chamadas) para a classe composta.

1 Manutenibilidade é uma característica inerente a um projeto de sistema ou produto, e


se refere à facilidade, precisão, segurança e economia na execução de ações de
manutenção nesse sistema ou produto.
CAPÍTULO 3
Do estruturado à orientação a objetos

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.

3.2 Acesso à base de dados


3.2.1 Acesso nativo
No atual mundo das aplicações de negócios, o armazenamento de
informações de uma organização é implementado por diferentes mecanismos
de bancos de dados em decorrência, muitas vezes, da utilização de sistemas de
diferentes fornecedores e tecnologias. Alguns dados relativos a recursos
humanos podem estar armazenados em um banco de dados DB2, ao passo
que os dados nanceiros podem estar armazenados em um Oracle, as
estatísticas de acesso ao site da empresa podem estar em um MySQL e o Data
Warehouse, que consiste em várias informações estratégicas, pode estar em
um PostgreSQL. A diversidade reside nos mais diversos fornecedores de
bancos de dados, cada qual com métodos diferentes (interfaces) de acesso aos
seus serviços.
 Observação: aprenderemos, primeiro, a utilizar bancos de dados no PHP de maneira
procedural para, ao longo do livro, construir classes que tornarão o acesso totalmente
orientado a objetos.
O PHP conecta-se a uma enorme variedade de gerenciadores de bancos de
dados disponíveis no mercado. Para cada tipo de banco de dados há uma
gama de funções associadas para realizar operações como conexão, consulta,
retorno, desconexão, entre outras. Nesta seção abordaremos resumidamente as
principais funções nativas para conexão. Se você deseja desenvolver uma
aplicação utilizando qualquer um dos bancos de dados listados a seguir, é
imprescindível visitar o site do PHP para obter a lista completa de funções de
acesso.
Banco Site
Sybase www.php.net/sybas
e
SQL Server www.php.net/mssql
MySQL www.php.net/mysql
i
Frontbase www.php.net/fbsql
Interbase www.php.net/ibase
ODBC www.php.net/odbc
Informix www.php.net/ifx
Oracle www.php.net/oci8
PostgreSQ www.php.net/pgsql
L
SQLite www.php.net/sqlit
e

 Observação: neste primeiro momento, entenderemos como funciona a conexão às


bases de dados de modo procedural. No capítulo 3 será abordada a biblioteca PDO,
que possibilita uma conexão ao banco de dados de maneira totalmente orientada a
objetos.
Para exempli car o acesso ao banco de dados, criaremos dois programas. O
primeiro deles será responsável por inserir dados em um banco de dados
PostgreSQL; o segundo listará os resultados inseridos pelo primeiro
programa. Para criar o banco de dados utilizado nos exemplos, certi que-se
de que você tem o PostgreSQL instalado em sua máquina e utilize os
seguintes comandos:
# createdb livro
# psql livro
livro=# CREATE TABLE famosos (codigo integer, nome varchar(40));
CREATE TABLE
livro=# \q

 Observação: é possível criar a base de dados utilizando o software PgAdmin


(www.pgadmin.org), que apresenta uma interface totalmente gráfica para gerenciar o
banco de dados.
Neste primeiro exemplo, o programa se conectará ao banco de dados local
livro, localizado em localhost, com o usuário postgres. Em seguida, irá inserir
dados de algumas pessoas famosas na base de dados. Por m, ele fechará a
conexão ao banco de dados.

 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

3.2.2 Acesso orientado a objetos com PDO


O PHP é, em sua maioria, um projeto voluntário cujos colaboradores estão
distribuídos geogra camente ao redor de todo o planeta. Como resultado, o
PHP evoluiu baseado em necessidades individuais para resolver problemas
pontuais, movidos por razões diversas. Por um lado, essas colaborações
zeram o PHP crescer rapidamente; por outro, geraram uma fragmentação das
extensões de acesso à base de dados, cada qual com sua implementação
particular, não havendo real consistência entre as interfaces das extensões
(Oracle, MySQL, PostgreSQL, SQL Server etc.). Em muitos casos, as funções
de acesso a banco de dados eram criadas com o mesmo nome que a função de
baixo nível tinha em linguagem C.
Em razão da crescente adoção do PHP, houve necessidade de uni car o acesso
às diferentes extensões de bancos de dados contidos nele. Assim surgiu a PDO
(PHP Data Objects), cujo objetivo é prover uma API limpa e consistente,
uni cando a maioria das características presentes nas extensões de acesso a
banco de dados.
A PDO não é uma biblioteca completa para abstração do acesso à base de
dados, visto que ela não faz a leitura nem a tradução das instruções SQL,
adaptando-as aos mais diversos sistemas de bancos de dados já desenvolvidos.
Ela simplesmente uni ca a chamada de métodos, delegando-os para as suas
extensões correspondentes, e faz uso do que há de mais recente no que diz
respeito à orientação a objetos.
Para conectar a bancos de dados diferentes, a única mudança é na string de
conexão. Veja a seguir alguns exemplos nos mais diversos bancos de dados.
Banco String de conexão
SQLite new PDO('sqlite:teste.db');
Firebird new PDO("firebird:dbname=C:\\base.GDB", "SYSDBA", "masterkey");
MySQL new
PDO('mysql:unix_socket=/tmp/mysql.sock;host=localhost;port=3306;
dbname=livro', 'user', 'senha');
PostgreSQ new
L PDO('pgsql:dbname=example;user=user;password=senha;host=localhost')
;
Para podermos utilizar o PDO, deve-se ter habilitadas no php.ini as bibliotecas
(drivers) de acesso para o banco de dados de nossa aplicação. No Linux,
habilitaremos da seguinte forma:
extension=mysql.so
extension=pgsql.so
extension=sqlite.so
extension=pdo_mysql.so
extension=pdo_pgsql.so
extension=pdo_sqlite.so
No Windows, entretanto, habilitaremos assim:
extension=php_mysql.dll
extension=php_pgsql.dll
extension=php_sqlite.dll
extension=php_pdo_mysql.dll
extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll
Para facilitar a aprendizagem da PDO, veremos uma série de exemplos, cada
qual abordando um aspecto diferente relacionado a bancos de dados. Por ora,
veremos como se faz uma inserção e, posteriormente, uma listagem de dados.
Neste primeiro exemplo, o programa irá se conectar ao banco de dados em
localhost, chamado livro, com o usuário postgres. Em seguida, inserirá dados
relativos a pessoas na base de dados. Por m, fechará a conexão ao banco de
dados. Caso ocorra algum erro durante a execução das instruções SQL, será
gerada uma exceção, tratada adequadamente pelo bloco try/catch.

 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";
}

3.3 Nível 1 – Procedural, um script por ação


Neste nível de aprendizado, construiremos um programa estruturado, em que
cada script (.php) representa uma ação do usuário (incluir, editar, excluir,
listar). Neste nível, os programas criados apresentarão eventualmente uma
combinação de PHP, HTML e SQL às vezes no mesmo arquivo, o que não é
uma boa prática, porque mistura diferentes aspectos de programação.
Para executar os exemplos a partir de agora, precisaremos criar as estruturas
no banco de dados. Então, usaremos a estrutura mínima de tabelas
apresentada a seguir. Estas tabelas são parte da aplicação desenvolvida no
último capítulo do livro. O ideal é sugerirmos, desde já, a criação do banco de
dados representado em cap8/App/Database/livro-sqlite.sql.

 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);

3.3.1 Formulário de inserção


O primeiro script é o formulário de cadastro de novas pessoas. Esse
formulário contém basicamente uma página HTML e uma lista com inúmeros
campos, tais como: id, nome, endereço, bairro, telefone, email, e id_cidade.
Perceba que os primeiros campos são textuais (type=text); já o último
(id_cidade) é uma combo (select) com uma lista de cidades. Para apresentar
esta lista de cidades, introduzimos as tags do PHP <?php ?> para, em seu
interior, inserir dinamicamente a lista de cidades, o que é feito pela função
lista_combo_cidades(), que está de nida no arquivo lista_combo_cidades.php.
Essa forma de misturar PHP com HTML é bastante comum, especialmente
quando começamos a programar, embora não seja vista como uma boa
prática, o que veremos em seguida. A ação do formulário de inserção está
vinculada ao programa pessoa_save_insert.php, que será de nido na
sequência.

 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.

Figura 3.1 – Formulário de inserção de registros.


O formulário de inserção de pessoas (pessoa_form_insert.php) tinha sua ação
vinculada ao script (pessoa_save_insert.php). Esse script, por sua vez, recebe os
dados da postagem do formulário pela variável $_POST. Com esses dados na
variável $dados, começamos o script com a conexão com a base de dados
(pg_connect) e em seguida executamos uma consulta para pegar o próximo id
da base de dados. Então, de nimos a query (INSERT) para inserir os dados na
tabela de pessoas e a executamos por meio da função pg_query. Dependendo
do retorno da função, é exibida uma mensagem de sucesso ou do erro
ocorrido.

 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);

3.3.2 Listagem de registros


Agora que já elaboramos nosso formulário de inserção de registros,
precisamos fazer a listagem. Para construir a listagem, faremos um script em
que temos HTML, PHP e SQL no mesmo arquivo, o que não é uma prática
muito adequada, mas você descobrirá logo por quais motivos. Este script
inicia com a de nição de uma página HTML. Em seguida, abrimos as tags do
PHP <?php para fazer a conexão com a base de dados (pg_connect) e a execução
da consulta para trazer os registros (pg_query). Logo em seguida, montamos
uma tabela (<table>) e seu cabeçalho (<thead>). Depois, percorremos os
resultados da consulta com a função pg_fetch_assoc(). Para cada resultado
retornado pela variável $row, armazenamos em variáveis especí cas ($id, nome,
...), e estas são inseridas em uma linha da tabela (<tr>), que é exibida na tela
pelo comando print.
Cada linha da tabela terá duas colunas (<td>) para duas ações, que são editar e
excluir, antes dos dados em si. A ação de editar será um link (<a href>)
vinculado ao script pessoa_form_edit.php, que receberá via parâmetro o ID do
registro corrente. Já a ação de excluir estará vinculada ao script
pessoa_delete.php, que também receberá via parâmetro o ID do registro
corrente.

 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.

Figura 3.2 – Listagem de registros.

3.3.3 Edição de registros


A listagem de pessoas está conectada à ação de editar, que é representada pelo
script pessoa_form_edit.php. Este script basicamente é muito semelhante ao
formulário de inserção de pessoas. A diferença é que ele recebe o ID do registro
a ser editado via parâmetro e precisa carregar esse registro em tela antes da
edição. Para isso, no início do script ele veri ca se foi recebido via parâmetro o
ID !empty($_GET['id']). Caso entre nesta condição, é realizada uma consulta
na base de dados para obter os dados desse registro. Então é realizada uma
conexão (pg_connect), uma consulta (pg_query), com um SELECT na tabela de
pessoas, ltrando pelo ID do registro. Os dados obtidos são carregados em
variáveis especí cas ($id, $nome, $endereço).
Depois de armazenar os dados em variáveis, as tags do PHP são fechadas – “?
>” – e o programa volta para a linguagem HTML. Os inputs dos campos
continuam ali, como no formulário de inserção, mas agora existe a tag value,
apresentando o valor do campo, como em value="<?=$nome?>". Este é um
atalho do PHP que permite inserir uma variável dentro de código HTML. O
trecho nal do arquivo continua igual com a exibição da lista de cidades. A
única diferença é que agora identi camos o ID da cidade como parâmetro para
a função lista_combo_cidades() para que ela marque a cidade selecionada na
lista.

 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);
}

3.3.4 Exclusão de registros


Por m, temos a ação de exclusão de registros. Caso o usuário clique no botão
de exclusão da listagem, ele será direcionado para o script pessoa_delete.php,
passando o ID como parâmetro. Caso o script realmente receba um ID como
parâmetro, o que é veri cado pela função empty(), então uma conexão será
aberta com a base de dados, e o comando DELETE será rodado sobre a tabela de
pessoas, especi cando o registro a ser excluído.

 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);
}

3.3.5 Problemas encontrados


Muitos são os problemas relativos à abordagem que utilizamos para construir
o programa até então. Segue uma lista de alguns deles.
• Muitos arquivos foram criados em consequência da fragmentação por
ações.
• Mais de um script foi criado para manipular a mesma tela
(inserção/edição).
• O HTML (apresentação) está misturado com o PHP (lógica). Isso oferece
di culdades de manutenção a médio e longo prazo.
• A manipulação de dados (banco) está no meio do programa principal.
• É fortemente acoplada a um único banco de dados (PostgreSQL).
• Conecta ao banco de dados usando funções (pg_connect).
• Há di culdades de manutenção de código (diferentes linguagens
misturadas).
• Há vários pontos de entrada para manter (.php separados).
• A segurança é descentralizada. Ao corrigir um script, pode-se esquecer
outro.
• Há fragilidades quanto à injeção de SQL.
• Há di culdades para implementar controle de permissões com arquivos
separados.
• Há dados de conexão espalhados.
• Há dados de conexão explícitos.
Vamos fazer uma sequência de melhorias neste programa. Ao todo, contando
com esta, serão sete etapas.

3.4 Nível 2 – Agrupando ações comuns em scripts


Um dos problemas do cenário apresentado no nível 1 é a grande quantidade
de scripts para realizar as operações. Considerando que, naquele modelo, cada
ação do usuário era respondida por um script. Foram apresentados dois
scripts para inserção, dois para edição, um para listagem e um para exclusão.
Neste novo cenário apresentado no nível 2, reuniremos scripts com ações em
comum. Assim, conseguiremos reduzir a quantidade total de scripts, que foi
de seis scripts no nível 1 para apenas dois scripts no nível 2.
Uma das modi cações que serão feitas é a reunião de ações em scripts. As
ações de inserção e edição de registros carão reunidas em um script que
centralizará as ações de formulário. Já as ações de listagem e exclusão serão
reunidas em outro script, que centralizará as ações de listagem. Assim, no
caso do formulário, o mesmo script será responsável por apresentar o
formulário e também por salvar os dados recebidos pela postagem. Já no caso
da listagem, o mesmo script apresentará a lista dos resultados e controlará a
ação de exclusão de registros.

3.4.1 Listagem de registros


O primeiro script que modi caremos é a listagem de registros. Em vez de
somente exibir registros em tela, essa listagem também poderá executar a ação
de exclusão. Isso só será possível porque utilizaremos um parâmetro da URL
(action=delete) para identi car quando essa ação deverá ser executada.
O início do script é exatamente igual ao anterior. A diferença está no primeiro
IF, que veri ca se foi recebido o parâmetro action e se ele é igual à delete.
Neste caso, o usuário quer excluir um registro. Para isso, é obtido o ID a ser
excluído e é executada uma instrução de DELETE na base de dados. O restante
do script continua inalterado, com exceção das ações. A ação de edição agora
está vinculada ao script pessoa_form.php?action=edit&id={$id}, e a ação de
exclusão, ao script pessoa_list.php?action=delete&id={$id}. Veja neste segundo
que a ação aponta para o próprio script, passando a ação e o ID como
parâmetros. Ao clicar neste link, o usuário é direcionado para a execução do
próprio script, que procederá com a exclusão do registro selecionado e a
posterior exibição dos demais registros em tela.

 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");

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'];
print '<tr>';
print "<td align='center'>
<a href='pessoa_form.php?action=edit&id={$id}'>
<img src='images/edit.svg' style='width:17px'>
</a></td>";
print "<td align='center'>
<a href='pessoa_list.php?action=delete&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.php'">
<img src='images/insert.svg' style='width:17px'> Inserir
</button>
</body>
</html>

3.4.2 Formulário de cadastro


Reunir as ações de listagem e exclusão foi relativamente simples. O nosso
próximo objetivo é um pouco mais complexo: reunir as ações de inserção e
edição em um mesmo script. Para isso, reuniremos todas essas ações em um
script chamado pessoa_form.php. Ao carregar esse script sem parâmetros, o
usuário poderá preencher um formulário para cadastro de um registro novo.
Mas esse script também poderá ser usado para editar um registro já existente,
o que será identi cado pelo parâmetro action=edit da URL. O mesmo script
também poderá ser usado para salvar os registros por ele mesmo gerados, o
que será identi cado pelo parâmetro action=save da URL.
A princípio, o script é semelhante à versão apresentada no nível 1, em que se
inicia com a abertura de uma página HTML. No entanto, logo em seguida, ele
muda signi cativamente, pois temos um IF onde é veri cado se existe o
parâmetro action. Para entendermos bem esse script, imagine que
inicialmente o parâmetro action não terá conteúdo, o que ocorrerá quando
alguém carregar o formulário para o preenchimento de um registro novo.
Neste caso, vamos para o nal do IF, onde temos o fechamento da tag do PHP
“?>”. Depois do PHP, temos um formulário HTML comum, com a ação de
salvamento apontando para o script pessoa_form.php?action=save.
Agora imagine que o usuário preencheu esses campos e clicou no botão de
submissão. Neste caso, esse mesmo script será recarregado e, desta vez, a
execução entrará no IF, que veri cará se alguma action foi preenchida. Veja
que existem dois blocos de IF, um para veri car se a ação é edit e outro para
veri car se a ação é save. No caso do botão de salvar, a action=save será
processada. Nesse caso, os dados são lidos da variável $_POST e armazenadas
em variáveis próprias. Para sabermos se os dados de um formulário devem ser
salvos (INSERT) ou atualizados (UPDATE), utilizamos sempre o campo ID. Se o ID
já tiver sido criado, procederemos com um UPDATE; caso contrário, se trata de
uma inserção (INSERT). Para essa regra funcionar, o campo ID deve ser
bloqueado ou escondido no formulário, para que o usuário não possa mexer
nele.
Outra ação importante é a ação de editar (action=edit). Essa ação somente é
executada a partir da listagem de registros, quando o usuário clica sobre a
ação para editar o registro. Nesse caso, a ação é direcionada para
pessoa_form.php?action=edit&id={$id}. Analisando o formulário que acabamos
de criar, é possível notar um bloco que compara a ação (action=edit). Quando
o usuário clicar nesta ação, os dados do registro serão carregados ("SELECT *
FROM pessoa WHERE id='{$id}'") e, por meio da função pg_fetch_assoc(), que
retorna um vetor, os dados serão armazenados em variáveis especí cas ($id,
$nome). Essas variáveis são usadas no nal do programa para alimentar o
formulário HTML, mais especi camente, o atributo value, como em: value="
<?=$nome?>". Assim, quando o usuário clicar para editar um registro, todos os
campos virão pré-preenchidos, inclusive o seu ID. Dessa maneira, quando ele
submeter o formulário, consequentemente executando a ação “save”, o script
detectará que o registro é pré-existente e, assim, executará uma instrução de
UPDATE, e não de INSERT.
Tanto na ação de edit quanto de save, alimentamos as variáveis ($id, $nome,
$endereco, ...), pois elas são utilizadas no nal do script para preencher o
atributo value de cada campo do formulário. No caso da ação de edição edit,
esses valores são obtidos a partir do registro da base de dados. No caso da
ação de salvamento save, os dados são obtidos a partir do envio (POST) do
formulário, fazendo com que o formulário permaneça preenchido após a
postagem.

 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>

3.5 Nível 3 – Separando o HTML com micro-templates


No nível anterior (2), reunimos todas as ações que o usuário precisava
executar em dois scripts, um que representava tudo o que estava relacionado
ao formulário (inserção, edição) e tudo o que estava relacionado à listagem
(apresentação, exclusão). Entretanto, vimos que, apesar de evoluirmos, alguns
problemas ainda estavam lá. Um desses problemas é a mistura de PHP e
HTML no mesmo arquivo, prejudicando a manutenção da solução. Mas por
que exatamente essa mistura é vista geralmente como algo ruim? Porque
aspectos diferentes (lógica, apresentação, banco de dados) devem estar
isolados na aplicação. Dessa forma, quando alguém precisar fazer a
manutenção de uma apresentação (HTML), irá direto ao ponto, assim como
na lógica e no acesso à base de dados. O fato de todos os aspectos estarem
reunidos no mesmo ponto leva à confusão, a di culdades de compreensão e à
consequente di culdade de fazer a manutenção de um código que envolve
muitas responsabilidades, não apenas uma.

3.5.1 Formulário de cadastros


Neste novo nível, vamos fazer a separação do PHP e do HTML. Assim, tudo o
que se refere à apresentação (HTML) será armazenado em um arquivo à parte
que normalmente é chamado de template quando tem essa função. Para
iniciar, vamos separar o formulário HTML para cadastro de pessoas em um
arquivo que somente contém os aspectos visuais. O arquivo a seguir
(html/form.html) representará o formulário de cadastro e a edição de pessoas.
Porém é necessário preencher determinadas partes dele com dados dinâmicos
que são processados via PHP. Para fazer este preenchimento, vamos deixar
alguns espaços predeterminados, algumas marcações que serão substituídas
em tempo de execução por valores reais. Note, no script a seguir, a existência
de marcações como {id}, {nome} e {endereco}. Essas são apenas marcações que
determinam um espaço no HTML que será substituído em tempo de
execução pelo valor real do campo. Perceba também a presença da marcação
{cidades}, que será substituída pela lista de cidades.

 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;

3.5.2 Listagem de registros


Agora que já modi camos o formulário, vamos proceder com a separação do
HTML também na listagem de registros. Para isso, vamos criar um arquivo
chamado html/list.html, que conterá a estrutura básica de uma listagem. Veja
que, nesse arquivo, declaramos a tabela (table), as colunas (thead) e o
conteúdo (tbody) da tabela. Como o conteúdo é dinâmico e depende da
quantidade de registros que retornarem da base de dados, deixamos um
espaço chamado {items} para ser preenchido posteriormente.

 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;

3.6 Nível 4 – Separando o acesso a dados com funções


No nível anterior (3), conseguimos separar o PHP do HTML, o que tornou os
scripts mais fáceis de serem lidos e compreendidos. A manutenção também
melhorou, pois o isolamento entre diferentes questões (lógica, apresentação)
faz com que melhorias nesses scripts sejam mais rápidas de ser aplicadas. No
entanto, nossos scripts ainda têm aspectos de acesso à base de dados
espalhados no código-fonte, ou seja, ainda vemos os INSERTs, UPDATEs,
DELETEs, e SELECTs por aí. Mas por que ter essas instruções de manipulação
de dados espalhadas é considerado algo ruim? Em primeiro lugar, ao
espalharmos em nosso sistema, várias instruções que manipulam uma única
tabela prejudica a manutenção, pois, sempre que zermos uma mudança
naquela tabela, precisaremos buscar inúmeros arquivos para localizar as
instruções que a manipulam. Se essas instruções estivessem em um único
local, essa tarefa de manter tudo atualizado seria mais fácil.

3.6.1 Funções de acesso à base


Neste nível, vamos reunir estas várias instruções de manipulação de dados em
um arquivo em separado. Neste arquivo, criaremos diversas funções para
serem posteriormente utilizadas pelo programa principal. O fato de elas
estarem reunidas em um único arquivo facilita bastante. Pois sempre que
realizarmos uma manutenção no banco de dados, alterando a tabela, criando
campos, ou alterando os já existentes, precisaremos realizar as mudanças em
um único local.
As funções presentes no arquivo são: a função lista_pessoas() será
responsável por retornar um vetor com todos os registros da tabela de pessoas;
a função exclui_pessoa($id) recebe o ID do registro e procede com a exclusão
deste; a função get_pessoa($id) recebe o ID do registro e retorna todas as
informações daquela pessoa em forma de vetor; a função get_next_pessoa()
retorna o próximo ID de pessoa disponível; a função insert_pessoa($pessoa)
recebe um vetor com os dados da pessoa e faz a inserção na base de dados; já
a função update_pessoa($pessoa) também recebe um vetor contendo os dados
da pessoa, mas dessa vez faz uma atualização na base de dados.

 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;
}

3.6.2 Listagem de registros


Agora que já vimos como estas funções foram colocadas em um arquivo em
separado, vamos ver como devem car os programas principais ao realizarem
as chamadas para essas funções. O primeiro programa que vamos analisar é a
listagem de pessoas. Para poder utilizar as funções, é necessário realizar um
require no início do arquivo. Com este comando, podemos então fazer uso das
funções de banco disponíveis nele. O programa prossegue com a veri cação
da ação (action). Caso a ação solicitada pelo usuário seja a de exclusão
(delete), então a função exclui_pessoa($id) será executada a partir do arquivo
de funções criado anteriormente. O programa segue com o carregamento das
pessoas, o que é realizado pela função lista_pessoas(), que retorna um vetor
com todas as pessoas. O restante do programa não sofre alterações em
comparação com a versão do nível anterior.

 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;

3.7 Nível 5 – Separando o acesso a dados com classes


No nível anterior, separamos as funções de manipulação de dados do script
principal e as colocamos em um arquivo de funções. Com essa separação, já
pudemos perceber as vantagens de separar diferentes aspectos do programa,
mas ainda temos algo a melhorar, pois as funções de acesso ao banco ainda
não utilizam classes e métodos. Neste novo nível, vamos transformar o arquivo
de funções de acesso à base de dados em uma classe em que cada método é
responsável por uma operação.

3.7.1 Classe de acesso à base de dados


Primeiro, vamos criar uma classe Pessoa. Dentro dessa classe, teremos métodos
como save(), que salvará os dados de uma pessoa. Este método, exposto logo
a seguir, receberá um vetor $pessoa, contendo os dados da pessoa, e abrirá
uma conexão com a base de dados e veri cará se o vetor $pessoa engloba a
posição ID. Se esse vetor não contiver a posição ID, o método save() procederá
com uma inserção (INSERT); caso contrário, com uma atualização (UPDATE).
Optamos por criar os métodos todos como estáticos (public static function)
para facilitar sua execução neste momento. Porém, posteriormente,
abordaremos de uma forma mais completa o motivo de usarmos métodos
estáticos e quando devemos usá-los.
Além do método save(), temos métodos como find(), que recebe o $id da
pessoa e faz uma seleção (SELECT) para buscar todos os seus atributos e
retorná-los na forma de um vetor; delete(), que recebe o $id da pessoa e
procede com sua exclusão; e all(), que retorna uma lista (vetor) com todas as
pessoas ordenadas pelo ID.
 Observação: Um fato importante a ser notado é a execução do método $conn-
>setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION), que por sua vez
indica para a biblioteca PDO que qualquer erro ocorrido deve ser emitido para o PHP
na forma de uma exceção que deve ser tratada por um bloco try/catch, explicado em
detalhes nos capítulos seguintes.

 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;

$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']}'
)";
}
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']}'";
}
return $conn->query($sql);
}
public static function find($id) {
$conn = new PDO("pgsql:dbname=livro;user=postgres;host=127.0.0.1");
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$result = $conn->query("SELECT * FROM pessoa WHERE id='{$id}'");
return $result->fetch();
}
// .. outros métodos
}

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;

3.8 Nível 6 – Melhorando as conexões e a segurança


No nível anterior, transformamos funções de manipulação de dados em
classes com métodos estáticos. O código cou mais organizado, e com melhor
legibilidade. Porém ainda podemos fazer melhor. Neste novo nível, vamos
melhorar dois aspectos do nível anterior em relação ao acesso a dados: criação
da conexão e segurança. Quanto à criação da conexão, é possível perceber que
em cada método que se precisava executar uma operação envolvendo o banco
de dados fazia-se a criação da instância da classe PDO. Já no que se refere à
segurança, criamos nossas queries sem nos preocuparmos com injeções de
SQL (SQL Injections). Neste novo nível, faremos modi cações na classe
Pessoa.php para melhorar esses dois aspectos.
Para começar, no lugar de criarmos a instância da classe PDO sempre que ela
for necessária, vamos criar um método para isso. Este método se chamará
getConnection() e será executado pelos outros métodos sempre que eles
precisarem da instância da conexão (PDO). Além de criarmos um método
novo, vamos remover totalmente os dados de conexão (banco, usuário, senha)
do script e movê-los para um arquivo externo. Perceba que, como teremos
várias classes de manipulação de dados, não faz sentido termos os dados de
conexão espalhados em vários lugares do sistema. Para isso, criaremos um
arquivo (config/livro.ini) que armazenará os dados de conexão.
O método getConnection() criará uma conexão e a armazenará na variável
estática (self::$conn). Esta operação ocorrerá uma vez a cada execução, pois,
nas execuções subsequentes, este método retornará a instância já criada. Na
primeira execução, ele entrará no IF e fará a leitura do arquivo livro.ini por
meio da função parse_ini_file(), que retorna um vetor. A partir desse vetor,
são lidas as variáveis ($host, $name, $user, $pass), e essas variáveis são utilizadas
pela conexão (new PDO).
Os métodos que antes criavam uma conexão sempre que necessário agora
simplesmente executam o método self::getConnection() para obter uma
conexão à base de dados. Um desses métodos é o save(), que recebe um vetor
e armazena os dados da pessoa na base de dados. Esse método veri ca a
presença do atributo ID. Quando esse atributo estiver vazio (inserção), será
executado um INSERT; caso contrário, um UPDATE.
Ainda no método save(), alteramos a sua forma de execução das queries para
aumentar a segurança. No método anterior (nível 5), simplesmente inseríamos
as variáveis digitadas pelo usuário no meio de uma string e executávamos essa
string (instrução SQL), o que permite que o usuário injete expressões
maliciosas. Neste novo exemplo, utilizamos o conceito de Prepared
Statements, uma maneira mais segura de executar instruções SQL isolando
(encapsulando) os valores do restante da expressão.
Para usar Prepared Statements, a execução da Query é alterada. Em vez de
inserir os valores diretamente na string com a instrução, passamos a inserir os
parâmetros com uma marcação :parametro, como em :nome, ou :endereco. Em
seguida, usamos o comando prepare() da biblioteca PDO, que prepara o
comando para execução no banco. Por m, o método execute() executa a
query passando um vetor de mapeamento com os parâmetros. Neste vetor, é
feita a tradução dos parâmetros da query com seus respectivos valores. Esta
forma de execução impede que instruções maliciosas sejam interpretadas pelo
banco de dados.

 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

 Observação: os demais arquivos (pessoa_form.php e pessoa_list.php) não


sofreram modificações neste nível.
Além das precauções citadas, o arquivo livro.ini precisa ser protegido para que
ninguém consiga acessá-lo a partir da URL do sistema. Uma das maneiras de
prevenir o acesso é por meio do Apache, usando a cláusula mostrada a seguir
para bloquear seu acesso.

 apache2.conf
<Directorymatch "^/.*/config/">
Order deny,allow
Deny from all
</Directorymatch>

3.9 Nível 7 – Transformando páginas em classes de controle


No nível anterior, melhoramos a criação de conexões e a segurança de nossa
classe de manipulação de dados. Porém nossos principais programas
(pessoa_form.php e pessoa_list.php) ainda são totalmente estruturados e
dependem de que o usuário acesse cada um deles individualmente, ou seja,
não há um ponto central a partir do qual as requisições possam ser feitas. Ter
vários pontos por meio dos quais certas funcionalidades podem ser acessadas
é perigoso, pois precisamos adicionar segurança à cada um deles. Ao termos
um ponto central de acesso, temos uma área menor para controlar. É como ter
uma casa com várias portas ou uma casa com apenas uma porta.
Neste novo nível, vamos transformar nossos programas principais em classes,
e cada ação do usuário será representada por um método. Além disso,
criaremos um ponto centralizador (index.php) a partir do qual as ações serão
direcionadas para a classe correta. Outra vantagem de transformarmos nossos
scripts em classes é que, a partir disso, podemos executá-las de qualquer
ponto do sistema, bastando sua importação (require) para posterior execução.

3.9.1 Formulário de cadastro


Nossas melhorias iniciarão pelo cadastro de pessoas, que agora será
representado por uma classe chamada PessoaForm. Logo no início do arquivo,
importamos as classes Pessoa e Cidade para podermos fazer uso delas. Neste
novo modelo de desenvolvimento, teremos classes, e estas terão métodos. O
método construtor será sempre executado no início e será responsável por
fazer algumas de nições iniciais. É no método construtor que carregaremos o
template (html/form.html) e o armazenaremos em um atributo do objeto
($this->html). O último método a ser executado (por nossa convenção) será
sempre o método show(), que é responsável por exibir o conteúdo ($this-
>html) em tela por meio do comando print. Antes de exibir o html, algumas
substituições serão feitas nas marcações presentes nele, tais como {id}, {nome}
e {endereco}. Essas substituições utilizarão o vetor ($this->data), que
inicialmente é criado vazio no método construtor, mas que pode ser
preenchido com algum conteúdo caso outro método da classe seja executado
antes do show().
O método edit() receberá em $param o vetor de parâmetros da requisição. Esse
método simplesmente buscará carregar a pessoa, pelo método Pessoa::find(),
e armazenará essas informações no vetor $this->data, que será apresentado ao
nal da execução pelo método show(). Toda a execução está em um bloco
try/catch. Assim, se houver alguma exceção, será apresentada ao usuário no
bloco catch. Já o método save() será executado sempre que o usuário salvar
um registro. Este método recebe o vetor $param, que contém todos os dados do
POST. Para isso, ele executa o método Pessoa::save() e atribui esses dados ao
vetor $this->data, que será ao nal apresentado pelo show().
 Observação: nos próximos capítulos serão apresentados mais detalhes sobre o
funcionamento da manipulação de exceções com try/catch.

 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

A melhor maneira de prever o futuro é criá-lo.


P D
A orientação a objetos é um assunto bastante extenso para ser tratado em
apenas um capítulo. Por isso, no capítulo 2 estudamos como implementar
no PHP os principais conceitos da orientação a objetos: encapsulamento,
herança, associação, agregação, composição, polimor smo, abstração e
interfaces. Neste capítulo vamos abordar uma série de tópicos especiais
que podem ser vistos de maneira isolada: métodos mágicos, exceções,
SPL, Re ection, Traits, Namespaces e PDO. Muitos desses tópicos
diferenciam mais o PHP de outras linguagens orientadas a objetos por
oferecer recursos especí cos não encontrados em outras linguagens da
mesma forma ou com a mesma sintaxe.

4.1 Tratamento de erros


Existem diversas formas de fazer tratamento de erros em PHP. Nesta seção
veremos as formas mais comuns utilizadas, desde a simplória função
die() até o re nado tratamento de exceções.

4.1.1 Cenário proposto


Antes de estudarmos algumas formas de tratamento de erros no PHP,
vamos propor um cenário. Esse cenário consiste em demonstrar uma
operação que pode resultar em falha em algum ponto do processo, e essa
falha precisa ser tratada. Dessa forma, vamos criar uma classe que efetua a
leitura de arquivos no formato CSV e gera um retorno no formato de
vetor. Essa classe pode apresentar falhas no momento da leitura do
arquivo, caso o arquivo não exista ou não tenha as devidas permissões de
leitura. A princípio, vamos implementar a classe sem nos preocuparmos
com falhas e, então, acrescentaremos os controles necessários.
Para demonstrar a classe de leitura de arquivos CSV, criaremos um
arquivo CSV no formato apresentado a seguir. Neste caso, optamos por
utilizar “;” e não “,” para separar as colunas.

 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...

4.1.2 Função die()


A forma de manipulação de erro mais simples no PHP ocorre por meio da
função die(). É um exagero dizer que a função die() realiza algum
tratamento de erros, pois ela simplesmente aborta totalmente a execução
de um programa no ponto em que ela é executada, não permitindo ao
programa seguir a execução a partir da linha em que ela é invocada. A
função die() é mais indicada para efetuar operações de debug em tempo de
desenvolvimento, mas nunca deve ser utilizada em produção.
Para utilizar a função die() em nossa classe recém-criada, é bastante
simples. Basta realizarmos alguns testes no método parse() antes do
carregamento do arquivo. Caso o arquivo em questão não exista
(file_exists) ou não tenha permissão para ser lido (is_readable), a
execução será abortada de nitivamente pela função die().
 Observação: apesar de funcionar, a execução de um programa não deve ser
encerrada de maneira abrupta quando o programa já estiver em produção; apenas
no estágio de desenvolvimento para debug.

 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.

4.1.3 Retorno de ags


Outra forma de manipulação de erros é o retorno de ags TRUE ou FALSE.
Neste caso, podemos retornar TRUE em caso de sucesso, ou seja, quando a
leitura do arquivo puder ser efetuada e o programa puder prosseguir, bem
como podemos retornar FALSE na ocorrência de alguma falha que não
permita o prosseguimento da leitura do arquivo. Para implementar essa
estratégia, basta alterarmos o método parse() para retornar FALSE caso o
arquivo não exista (file_exists) ou não tenha permissão para ser lido
(is_readable) e TRUE caso a leitura possa ser feita.

 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.

4.1.4 Tratamento de exceções


Vimos que a função die() oferece uma abordagem bastante simplória ao
abortar a execução do programa de maneira abrupta. Vimos também que
o retorno de ags permite que o programa prossiga a execução, porém
oferece um controle ainda limitado quanto às mensagens de erro.
Poderíamos melhorar o retorno de ags para retornar códigos de erro.
Porém essa estratégia não é indicada, pois em alguns casos nosso método
terá de retornar valores válidos que podem con itar com os códigos de
erro.
Para oferecer um tratamento de erros mais re nado, o PHP implementa o
conceito de tratamento de exceções de maneira bastante similar a outras
linguagens como C++ ou Java. Tratamento de exceções é um processo
dividido em duas etapas: emitir e tratar uma exceção. Por um lado,
sempre que uma rotina passar por um ponto de falha, deverá emitir uma
exceção. Por outro lado, a rotina que está chamando e aguardando a
resposta deverá tratar essa exceção. Uma exceção é uma ocorrência de
programação que se sobressai à execução normal do programa, parando a
execução no momento em que ela é emitida e continuando a execução
diretamente para o ponto em que ela é tratada.
Para entendermos como as exceções funcionam, primeiro, precisamos
compreender que uma exceção em PHP é um objeto especial, derivado da
classe Exception, que contém alguns métodos para informar ao
programador um relato do que aconteceu. A seguir, você confere esses
métodos.
Método Descrição
getMessage() Retorna a mensagem de erro.
getCode() Retorna o código de erro.
getFile() Retorna o arquivo no qual ocorreu o
erro.
getLine() Retorna a linha na qual ocorreu o erro.
Método Descrição
getTrace() Retorna um array com as ações até o
erro.
getTraceAsString( Retorna as ações em forma de string.
)
Utilizar o tratamento de exceções não é muito complicado. Em um
primeiro momento, reescrevemos nosso método parse(), que é o local em
que uma falha pode ocorrer, e substituímos o tratamento de erros
adotado por lançamentos de exceção (throw new Exception). Para que esse
programa funcione, ainda é necessário reprogramar o programa principal
que executa o método parse() para que ele “perceba” que uma exceção foi
lançada. Essa alteração será feita em seguida. É importante lembrar que o
operador throw interrompe a execução do método em questão, e a
execução continua a partir do tratamento da exceção realizada em
seguida.

 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

4.2 Métodos mágicos


Alguns fatores que tornam o PHP uma linguagem tão atraente são a sua
exibilidade e a sua dinâmica. Já vimos até o momento o quão fácil é
trabalhar com arrays e também objetos. No entanto as facilidades não
param por aí. No que diz respeito à orientação a objetos, o PHP
implementa uma série de métodos que produzem interceptação de
operações, também conhecidos por “métodos mágicos”. Esse termo não
foi denominado à toa. Há diversos métodos mágicos no PHP, e todos eles
iniciam com __, como: __construct(), __destruct(), __call(), __get(),
__set(), __isset(), __unset(), entre outros. Enquanto o método
__construct() é executado automaticamente durante a construção do
objeto e o método __destruct() é executado automaticamente durante a
destruição do objeto, outros métodos são executados automaticamente
em algumas circunstâncias. Veremos a seguir exemplos de uso para cada
um dos principais métodos mágicos.

4.2.1 Introdução aos métodos mágicos


Os métodos __set(), __get(), __isset() e __unset() são usados para de nir
um comportamento para o objeto sempre que houver uma tentativa de
acesso a uma propriedade não acessível (private, não existente etc.). Para
entender melhor os “comportamentos mágicos”, vamos começar com uma
classe Titulo na qual todos os seus atributos são private. Como ela não
oferece nenhum método de acesso, logo esses atributos são inacessíveis. Se
houver uma tentativa de leitura do atributo valor, um Fatal Error
ocorrerá, como pode ser percebido a seguir.

 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...

4.2.2 Método __get()


Sempre que houver uma tentativa de acesso a um atributo inacessível,
automaticamente o PHP veri cará se existe na classe um método __get().
Se existir, automaticamente ele será executado, recebendo por parâmetro
o nome do atributo sobre o qual houve a tentativa. Neste próximo
exemplo o Fatal Error não ocorrerá mais; no lugar dele, uma mensagem
será exibida ao usuário (print) dizendo que a propriedade está inacessível
e que é aconselhável usar o método correto.

 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

4.2.3 Método __set()


Da mesma maneira que um Fatal Error ocorre quando tentamos acessar
uma propriedade inacessível para leitura, ele ocorre também quando
tentamos atribuir um novo valor para uma propriedade inacessível. Neste
caso, o atributo $valor é private e será realizada uma tentativa de
gravação. Automaticamente, o método __set() será executado. O valor não
será armazenado no atributo, sendo aconselhável utilizar o método
setValor().

 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()

4.2.4 Armazenando atributos em vetores


Métodos mágicos permitem-nos implementar modi cações
surpreendentes. Para demonstrar o poder desse recurso, vamos alterar o
comportamento-padrão de uma classe para que seus atributos sejam
armazenados não da maneira convencional, mas dentro de um vetor
interno ($this->data). Assim, cada vez que um usuário tentar fazer a
leitura ou a gravação, na verdade, estará manipulando um vetor indexado
($this->data['atributo']), não um atributo real ($this->atributo).
Para que essa abordagem funcione, vamos declarar um vetor chamado
$data na classe. Sempre que alguém tentar fazer uma gravação de atributo
qualquer, como o atributo não existe, automaticamente o método __set()
será executado e o valor será armazenado no vetor $data. Da mesma
maneira, sempre que alguém tentar efetuar uma leitura de uma
propriedade, como ela não existe, automaticamente o método __get() será
executado e o nome da propriedade será usado para obter-se o valor a
partir do vetor $data.

 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

4.2.5 Métodos __isset() e __unset()


Não basta apenas criar os métodos __set() e __get() para substituir o
comportamento convencional de um objeto pelo uso de um vetor para
armazenar seus atributos. Há outras formas de acesso além das
tradicionais leitura e gravação.
O desenvolvedor pode executar a função isset() para veri car se um
atributo existe ou executar a função unset() para eliminar o valor do
atributo. Veja que a função isset() não está preparada para lidar com
__get() e __set(). Neste caso, ela não sabe que o atributo valor está
armazenado de fato em um vetor. Isso porque a implementação-padrão
do unset() tentará veri car um atributo que não existe. Nesse caso, o teste
a seguir exibirá “Valor não de nido”.
if (isset($titulo->valor)) {
print "Valor definido<br>\n";
} else {
print "Valor não definido<br>\n";
}
Veja também que a função unset(), ao ser executada, não elimina o valor
do atributo, que permanece como valor após a chamada do unset(). Isso
porque a implementação-padrão do unset() tentará eliminar um atributo
que não existe. Assim, o resultado a seguir será “O valor é: 12.345,00”.
unset($titulo->valor);
print 'O valor é: ' . number_format($titulo->valor,2, ',', '.');
Dessa forma, precisamos criar mais métodos mágicos __isset() e
__unset(). Esses são dois métodos mágicos executados automaticamente
sempre que o programador invoca as funções isset() e unset() sobre o
objeto.

 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

4.2.6 Método __toString()


Objetos não foram feitos para ser exibidos diretamente em tela por
comandos como o print, por exemplo. Agora, caso venhamos a utilizar o
comando print sobre um objeto, o PHP exibirá um erro (Catchable fatal
error: Object of class Titulo could not be converted to string). Entretanto o PHP nos
oferece um método mágico chamado __toString(), que, ao ser criado,
permite de nir uma representação do objeto no formato de String para que
essa representação seja usada em concatenações de Strings, por exemplo.
Veja que no exemplo a seguir de nimos que o método mágico
__toString() irá retornar uma representação no formato JSON dos dados
do objeto, que, por sua vez, estarão armazenados no vetor $data.

 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}

4.2.7 Exemplo de uso de __get() e __set()


Agora que já entendemos como utilizar os métodos mágicos, vamos
trabalhar mais um pouco com a classe Titulo. A princípio, vamos
adicionar uma validação automática sempre que o programador tentar
de nir o valor do atributo dt_vencimento. Como esse atributo não existe
(visto que é controlado por $data), automaticamente o método __set()
será executado. Neste caso, consequentemente será executado o método
setVencimento(), que só atribuirá o valor da propriedade caso se trate de
uma data válida.
Também queremos modi car o acesso à propriedade $valor de maneira
que ela retorne o valor atualizado do cálculo (com juros e multas) sempre
que for acessada. Assim, sempre que for lida a propriedade $valor, como
se trata de uma propriedade inexistente (controlada por $data),
automaticamente o método __get() será executado, recebendo como
parâmetro o nome do atributo. Neste caso, se o nome da propriedade
acessada for valor, o método getValor() será executado.
O método getValor() por sua vez calculará a diferença entre a data atual e
a data de vencimento. Caso a data de vencimento já tenha passado, esse
método retornará o valor atualizado (incluindo multa e juros diários).

 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

4.2.8 Método __clone()


O comportamento-padrão do PHP quando atribuímos um objeto a outro
é criar uma referência entre os objetos. Dessa forma, sempre que um
objeto for atribuído a outro, teremos duas variáveis apontando para a
mesma região da memória, como pode ser visto no exemplo a seguir. Veja
que neste caso vamos declarar uma classe com uma série de atributos
públicos. Em seguida, instanciaremos um objeto $titulo dessa classe.
Depois criaremos um objeto ($titulo2) a partir do objeto $titulo. Quando
alterarmos o atributo valor do objeto $titulo2, estaremos alterando
também o atributo valor do objeto $titulo. Isso ocorre porque na verdade
essas são duas variáveis que apontam para o mesmo objeto em memória.

 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

4.2.9 Método __call()


O método __call() é outro método mágico bastante poderoso. Ele
intercepta a chamada a métodos que não estão acessíveis. Sempre que for
executado um método que não existir no objeto, automaticamente a
execução será direcionada para ele, que recebe dois parâmetros, o nome
do método requisitado e o(s) parâmetro(s) recebido(s), podendo decidir o
procedimento a realizar. Neste próximo exemplo, tentaremos executar
dois métodos inexistentes (teste1 e teste2), e o método __call() será
automaticamente executado, exibindo em tela o nome dos métodos, bem
como os parâmetros recebidos, mas não ocorrerá nenhum erro.

 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

4.3 Manipulação de XML com a SimpleXML


Nesta seção, abordaremos um conjunto de funções chamadas
SimpleXML.
Seu objetivo, como diz o nome, é tornar simples a tarefa de carregar,
interpretar e alterar documentos XML. É de suma importância a
manipulação de arquivos XML no desenvolvimento de aplicações, seja
para o armazenamento de arquivos de con guração, de dados reais, seja
para o intercâmbio de informações entre diferentes aplicações. A seguir
veremos alguns exemplos práticos sobre carga e alteração de documentos
XML utilizando a biblioteca SimpleXML.

4.3.1 Classe SimpleXmlElement


A princípio, vamos fazer uma simples leitura de um documento XML e
analisar seu retorno. Antes de construir o primeiro programa, vamos criar
um arquivo XML com algumas informações sobre o país. Para isso, o
documento XML terá uma tag pais e, dentro dela, outras tags com
informações detalhadas.

 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 "
}

4.3.2 Acessando atributos


Um objeto SimpleXMLElement representa um elemento de um documento
XML que, quando interpretado pelas funções simplexml_load_file() ou
simplexml_load_string(), resulta em um objeto do tipo SimpleXMLElement,
contendo seus atributos e valores, bem como outros objetos
SimpleXMLElement internos, representando subelementos (nodos). A seguir
veremos alguns dos métodos providos por um objeto do tipo
SimpleXmlElement:
Método Descrição
asXML() Retorna uma string XML formatada que representa o objeto, bem como
seus subelementos.
attributes( Lista os atributos de nidos dentro da tag XML do objeto.
)
children() Retorna os elementos lhos do objeto (subnodos), bem como seus
valores.
addChild() Adiciona um elemento ao nodo especi cado e retorna um objeto do tipo
SimpleXmlElement.
Neste novo exemplo, demonstraremos como acessar diretamente as
propriedades do objeto SimpleXMLElement resultante da leitura do
documento XML. Para isso, utilizaremos o mesmo arquivo XML do
exemplo anterior, exibindo na tela as propriedades do objeto resultante
com seu respectivo valor. Veja que a leitura resultará em um objeto $xml
representando o nodo-raiz (pais). A partir disso, para acessar as
informações, bastará acessar os demais nodos como atributos desse
objeto.

 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

4.3.3 Percorrendo elementos lhos


Você já viu como acessar diretamente as propriedades do XML sabendo
os seus nomes. Neste terceiro exemplo, percorreremos o mesmo
documento XML e exibiremos na tela suas propriedades (tags), mesmo
sem saber os seus nomes diretamente. Isso é possível por meio da
utilização do método children(), que atua sobre um objeto
SimpleXMLElement e retorna todos os seus elementos lhos na forma de um
array contendo a chave e o valor, que pode ser iterado por um laço
FOREACH.

 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

4.3.4 Acessando elementos lhos


Para dar prosseguimento aos exemplos, aperfeiçoaremos um pouco o
documento XML adicionando uma seção geografia dentro da tag pais e
agrupando informações como clima, costa e pico.

 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)

4.3.5 Alterando o conteúdo do documento


Você já aprendeu a acessar o XML de diversas formas; agora, veremos
como alterar o seu conteúdo. Neste caso, após a carga do documento,
atribuiremos novos valores acessando diretamente cada uma das
propriedades que se queira alterar no objeto. Também adicionaremos um
novo nodo, chamado <presidente>, pelo método addChild(). Após as
atribuições de novos valores, utilizaremos o método asXML() para retornar
o novo documento XML formatado e atualizado. Se quisermos sobrepor o
arquivo original, bastará utilizar a função file_put_contents().

 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.

4.3.6 Acessando elementos repetitivos


Agora que você já aprendeu a alterar o conteúdo do XML, veremos como
ocorre o acesso a elementos repetitivos. Neste exemplo, adicionaremos a
seção <estados>, que por sua vez conterá uma listagem repetitiva com
nomes de estados. Em seguida, criaremos um programa para ler essas
informações.

 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

4.3.7 Acessando atributos de elemento


Vamos complicar um pouco mais nosso arquivo XML. O próximo
exemplo demonstra como trabalhar com elementos que contêm atributos
na de nição da própria tag (nome="Minas Gerais" capital="Belo Horizonte").
Primeiro, vamos criar um novo documento XML com esses atributos.

 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á

4.3.8 Percorrendo atributos de elementos


Veja que, no exemplo anterior, é necessário saber exatamente o nome dos
atributos que desejamos acessar de forma indexada dentro do foreach.
Neste último exemplo da série, iniciaremos percorrendo a lista de estados
por meio de um foreach, como no exemplo anterior. Porém, dentro desse
laço de repetição, poderemos percorrer os atributos de cada estado (que
são na verdade um objeto SimpleXMLElement) pelo método attributes(), que
por sua vez retornará a chave e o valor de cada atributo de um elemento
(nodo).

 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á

4.4 Manipulação de XML com DOM


Ao utilizarmos a SimpleXML, vimos que ela oferece uma API simples
para acessar documentos XML simples e percorrer estruturas. Porém ela
tem algumas limitações quanto à manipulação de documentos. A
SimpleXML contém funcionalidades limitadas para percorrer os
elementos. Por outro lado, o PHP oferece a DOM, que é uma
implementação em conformidade com os padrões do W3C e, portanto,
apresenta consistência entre diferentes linguagens de programação, além
de uma quantidade muito maior de funcionalidades que permitem
diferentes meios de acesso aos nodos e formas de rearranjá-los conforme a
necessidade. Apesar de a DOM ser mais poderosa, a SimpleXML atende
bem à maioria dos casos em que a demanda por leitura e manipulação de
XML é pequena.

4.4.1 Leitura de conteúdo


A princípio, vamos ler um arquivo XML usando a implementação DOM.
Para isso, vamos declarar o arquivo a seguir, que contém uma lista de
bases de dados. Cada base de dados tem um ID, que é um atributo da tag,
e tags lhas, como name, host, type e user. Precisaríamos de mais
informações para conectar em uma base de dados, mas aqui o foco não é
esse.

 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

4.4.2 Manipulação de conteúdo


No exemplo a seguir, vamos demonstrar como criar um documento XML
contendo uma base de dados no mesmo estilo que o documento
anteriormente lido. Primeiro, vamos instanciar um objeto da classe
DOMDocument. Ligaremos o atributo formatOutput para que o output seja
formatado com quebras de linha e indentações. Em seguida, usaremos o
método createElement() para criar um nodo. Nesse caso, criaremos o nodo
bases e adicionaremos a ele o nodo base pelo método appendChild(). Além
disso, criaremos um atributo id contendo o valor 1 e o adicionaremos ao
nodo base. Em seguida, adicionaremos ao nodo base uma série de nodos
lhos por meio do método appendChild(), e cada nodo conterá um
atributo e um valor (por exemplo, <name>teste</name>). Por m, usamos o
método saveXML() para obter o retorno formatado do documento criado
em memória por meio da composição de objetos.

 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.

4.5.1 Manipulação de arquivos


Ao longo do tempo, em diferentes projetos, surgiram classes para
manipulação de arquivos e vetores, entre outros problemas comuns que
todos os programadores precisam resolver. O código a seguir demonstra
uma classe criada pelo programador chamada MeuArquivo. Essa classe não
tem absolutamente nada a ver com a SPL, mas ajuda a explicar por que a
SPL é importante. Essa classe fornece alguns métodos básicos, como
getContents(), para retornar o conteúdo, getExtension(), para retornar a
extensão, getFileName(), para retornar o nome do arquivo, e getSize(),
para retornar o tamanho. Veja que a classe na verdade só oferece uma
forma orientada a objetos para acessar funções já conhecidas do PHP.
Claro que poderíamos aumentar sua funcionalidade criando mais
métodos, mas esse não é o objetivo aqui.

 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

 Observação: garanta que haja permissão de escrita no diretório em que esse


exemplo irá rodar. Caso contrário, um erro do tipo “failed to open stream: Permission
denied” será gerado.
A seguir, abordaremos duas maneiras distintas de exibir o conteúdo do
arquivo.
A primeira é por meio dos métodos eof() e fgets() dentro de um laço de
repetição while. A segunda permite que utilizemos o operador foreach
para percorrer o objeto da classe SplFileObject como se esse objeto fosse
um vetor. Isso é possível porque a classe SplFileObject também se
comporta como array, visto que ela implementa a interface Iterator, que
permite justamente que objetos possam ser iterados como vetores.

 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);
}

4.5.2 Manipulação de las


A SPL também fornece classes padronizadas para manipular estruturas de
dados, como SplStack (pilha), SplQueue ( la), entre outras. Essas classes
funcionam como um tipo abstrato de dados, que por sua vez de ne um
conjunto de métodos (operações) que pode ser executado sobre os dados
armazenados internamente em uma pilha ou uma la. Basicamente, essas
classes criam uma fronteira (interface) entre o que o programador pode
invocar e os dados armazenados internamente na estrutura de dados.
A classe SplQueue, por exemplo, permite criar uma la. Fila é um tipo
abstrato de dados, uma coleção de itens em que as operações disponíveis
são adicionar um elemento no nal da la (enqueue) e remover um
elemento da frente da la (dequeue). Esse comportamento torna a la uma
estrutura First In First Out (FIFO). Assim, o primeiro elemento acrescentado
será o primeiro a ser removido. Além das operações enqueue() e dequeue(),
a SplQueue permite também que se percorra a lista por meio de um
foreach, já que ela implementa a interface Iterator.

 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

4.5.3 Manipulação de pilhas


O PHP também fornece a classe SplStack, que permite manipular pilhas.
Uma pilha é um tipo abstrato de dado que permite armazenar uma
coleção de itens; basicamente, ela trabalha no estilo Last In First Out (LIFO).
Uma pilha fornece duas operações básicas: push, que permite adicionar
elementos à coleção, e pop, que remove o último elemento adicionado.
Assim, o primeiro elemento retirado (pop) da pilha é o último
acrescentado (push). As operações ocorrem sempre no nal da estrutura
(topo da pilha). No exemplo a seguir, criaremos uma pilha ao instanciar o
objeto da classe SplStack; usaremos o método push() para acrescentar um
elemento no nal da pilha e pop() para remover um elemento do nal da
pilha.

 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

4.5.4 Percorrendo diretórios


A SPL também fornece um conjunto de iterators. Um iterator basicamente
é um objeto que nos permite percorrer determinada estrutura. O PHP nos
fornece iterators para percorrer coleções de arrays, objetos, diretórios,
entre outros. Além disso, ele nos fornece a estrutura necessária para criar
novos iterators. Para explicar o funcionamento dos iterators, vamos
analisar uma operação executada inicialmente sem iterators e depois com
eles. No código-fonte a seguir, temos uma forma bem comum de percorrer
diretórios: com a função opendir(), que abre um diretório e retorna um
identi cador; e com a função readdir(), que lê uma entrada de cada vez,
até que retorne false, interrompendo a iteração.

 iter_directory_sem_spl.php
<?php
$dir = opendir('/tmp');
while ($arquivo = readdir($dir)) {
print $arquivo . '<br>' . PHP_EOL;
}
closedir($dir);

 Observação: o resultado da execução desse programa pode sofrer alterações


conforme o conteúdo que o programador tiver no diretório /tmp no momento da
execução dos testes.

 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
...

4.5.5 Manipulando arrays


A facilidade de manipulação de iterators é grande. E grande também é o
conjunto de funções no PHP para manipulação de vetores (array_diff,
array_merge, array_shift, array_pop, array_unshift, array_push, array_keys,
array_values etc.). Para oferecer uma interface uniforme para manipulação
de vetores, foi criada a classe ArrayIterator. Essa classe oferece métodos
como offsetGet() para obter uma posição, offsetSet() para substituir uma
posição, offsetUnset() para eliminar uma posição, offsetExists() para
testar a existência de uma posição, natsort() para ordenar naturalmente,
serialize() para serializar, entre outros. Além disso, é possível percorrer
uma instância de ArrayObject, pois a classe também implementa a
interface Iterator.

 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

4.6.4 Gerando documentação


E se pudéssemos gerar uma espécie de documentação a partir das classes?
No código a seguir, vamos investigar a estrutura das classes SPL que o
PHP disponibiliza. A função spl_classes() retorna uma lista de classes
SPL. Para cada classe retornada (dentro do foreach), vamos usar a classe
ReflectionClass para investigar sobre a classe. Vamos usar o getMethods()
para retornar os métodos da classe (objetos ReflectionMethod). Para cada
método encontrado, vamos imprimir seu nome (getName) e percorrer seus
parâmetros a partir do método getParameters(), que retorna um vetor de
objetos ReflectionParameter. Para cada parâmetro, vamos imprimir seu
nome, com colchetes ao redor se ele for opcional (isOptional). Esse código
deve gerar uma lista de classes com seus respectivos métodos e
parâmetros de entrada.

 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}

4.8 Injeção de dependência


No exemplo anterior, vimos que nem todos os objetos lhos de Record
precisariam das funcionalidades de exportação de dados toXML() e
toJSON(). Então um trait foi criado para isolar esse comportamento,
permitindo que ele fosse importado pelo operador use somente para as
classes que precisariam dessa funcionalidade. Sem a utilização de traits, o
caminho mais simples seria implementar esses métodos na superclasse
Record. No entanto, independentemente da utilização de traits, ou por
meio da implementação dos métodos toXML() e toJSON() diretamente na
classe Record, em ambos os casos, o controle de qual será o algoritmo de
exportação não está diretamente nas mãos do programador, mas sim
declarado de maneira absoluta dentro da classe que é utilizada (Pessoa,
Produto), ou seja, ainda é a classe que decide no nal das contas qual
algoritmo será usado. Para deixar a escolha dos algoritmos nas mãos do
programador, devemos aplicar o conceito de injeção de dependência.
Injeção de dependência é um padrão de projetos que permite
implementar a inversão de controle. Inversão de controle é um padrão de
desenvolvimento em que o controle da execução (chamadas) é invertido.
O termo foi popularizado por Robert C. Martin e Martin Fowler.
Basicamente, em vez de os objetos criarem diretamente dependências em
relação a classes externas, passamos as dependências por meio de
métodos setters para que então eles sejam utilizados (consumidos).
A injeção de dependência trata de fornecer objetos com funcionalidades
(quando necessário), em vez de utilizá-los diretamente. Se, por um lado, a
injeção de dependência deixa o código mais desacoplado, por outro lado,
seu uso em excesso pode levar a um tempo maior na implementação de
novas funcionalidades.
No exemplo a seguir escreveremos nosso algoritmo de exportação para
JSON como uma classe externa. Além disso, escreveremos o método
toJSON() na classe Pessoa. Basicamente o método toJSON() instanciará a
classe JSONExporter e executará seu método export() sobre os dados ($this-
>data). Qual o problema dessa forma de trabalhar? Independentemente de
a classe Pessoa declarar o método toJSON() ou usar um trait para importar
a funcionalidade, é ela que dita o algoritmo de exportação a ser utilizado.
Assim, ela cria uma dependência e está fortemente acoplada à classe
JSONExporter.

 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"}

 Observação: para visualizar melhor o resultado, veja o código-fonte da página.


4.9 PSR
A PSR (PHP Standard Recommendation) é uma recomendação de padrões
de escrita de código em PHP que envolve áreas como: carregamento de
classes, estruturação de namespaces, padrões de nomenclatura e estilo,
entre outros itens que objetivam facilitar a interoperabilidade entre
diferentes projetos PHP.
A PSR é mantida pelo FIG (Framework Interoperability Group), um
grupo inicialmente formado em 2009 por pessoas que representam alguns
dos frameworks PHP mais utilizados. O FIG tenta encontrar pontos em
comum entre os diversos frameworks e criar uma abordagem única para
estruturação e escrita de código, com o objetivo de facilitar a
interoperabilidade, ou seja, a possibilidade de utilizarmos diferentes
projetos de maneira conjunta.
A PSR é dividida em níveis (PSR-0, PSR-1, PSR-2 etc.), e cada nível propõe
um conjunto de práticas. Um nível pode referenciar outro. Ao aplicar a
PSR-2, deve-se seguir a PSR-1, por exemplo. A PSR está sempre se
modi cando, por isso é arriscado expor aqui detalhes que podem sofrer
alterações durante as novas edições deste livro. Portanto, veremos a seguir
alguns dos pontos mais importantes propostos pela PSR em um de seus
níveis.
• Usar namespaces para estruturar e isolar as classes.
• Os namespaces devem iniciar com a identi cação do fornecedor da
classe em sua estrutura (por exemplo, \<Fornecedor>\<Subnível>\
<Classe>).
• Os namespaces podem ter vários subníveis.
• Uma parte inicial do namespace corresponderá ao diretório-base de
onde as classes serão carregadas. Subníveis do namespace
corresponderão a subdiretórios.
• Para que a classe seja carregada apropriadamente, o arquivo em que
ela é salva deve ter exatamente o mesmo nome que a classe, acrescido
do su xo .php. Por exemplo, classe \Livro\Widgets\Form\Field =>
Lib/Livro/Widgets/Form/Field.php.
• Os arquivos devem ser armazenados no formato UTF-8.
• As classes devem ser representadas no formato StudlyCaps (por
exemplo, FormField).
• Os métodos devem ser representados no formato camelCase (por
exemplo, getData()).
• Deve existir pelo menos uma linha em branco antes da declaração do
namespace e também antes da declaração de uso (use).
• Namespaces e classes devem seguir um autoloader-padrão.
Ainda há diversas diretrizes que de nem padrão de abertura de chaves, de
declaração de visibilidade de métodos, tamanho de linhas, indentação,
entre outros. Porém o objetivo aqui não é transcrever toda a PSR.
As classes do livro seguem os padrões da PSR em sua grande maioria. Da
mesma maneira, o carregamento das classes é feito por um mecanismo de
carga (autoloader) que segue as diretrizes da PSR-4, que, quando esta
edição foi escrita, era a PSR mais atual sobre esse assunto.

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) { }

4.11 SPL Autoload


A primeira função introduzida no PHP para registrar uma função para
realizar o carregamento automático de classes foi a função __autoload(),
que já não é mais utilizada. Com o surgimento da SPL, um conjunto
padronizado de classes e interfaces, surge também uma nova forma de
registrar funções ou métodos de carga, a spl_autoload_register(). A
spl_autoload_register() é uma função que permite registrar vários
métodos que o PHP colocará em uma la e executar quando uma classe
for requisitada e ainda não estiver carregada na aplicação. Cada um desses
métodos pode ter um algoritmo diferente para avaliar e localizar a classe a
ser carregada.
A spl_autoload_register() permite registrar várias funções de
carregamento. Além disso, permite registrar um método de uma classe, o
que facilita uma implementação mais orientada a objetos.
A função spl_autoload_register() aceita um parâmetro do tipo Callable,
que pode ser um método de objeto, uma função identi cada ou também
uma função anônima. Neste primeiro exemplo, demonstraremos como
registrar uma função anônima como autoloader. A função irá sempre
receber o nome da classe requisitada. Neste caso, ela veri cará se a classe
existe no diretório App com o su xo ".php". Caso exista, será requisitada
com o require_once. Assim que o programa chegar à linha em que o
objeto da classe Pessoa é instanciado (new Pessoa), automaticamente a
função anônima será executada, recebendo o nome da classe em questão
(Pessoa) como parâmetro.

 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;
}
}
}

 Observação: para simplificar, colocamos a declaração da classe


(ApplicationLoader), bem como sua chamada, no mesmo arquivo, porém essa
não é uma boa prática. Na realidade, a classe deverá estar localizada em um
arquivo próprio, que contenha somente a sua definição, enquanto sua instanciação
e a consequente chamada (register) normalmente estarão no principal arquivo da
aplicação (index.php), já que as requisições para as demais classes do projeto
partem dele.

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

 A aplicação criada no último capítulo utilizará algumas bibliotecas instaladas via


Composer. Nela, você encontrará exemplos práticos de utilização.
CAPÍTULO 5
Persistência

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));

 Observação: para que os exemplos a seguir possam gravar nesse banco de


dados, é necessário que tanto a base de dados (estoque.db) quanto a pasta na
qual ela se encontra tenham permissão de escrita.

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.

5.2.1 Table Data Gateway


O objetivo do design pattern Table Data Gateway é oferecer uma interface
de comunicação com o banco de dados que permita operações de
inserção, alteração, exclusão e busca de registros. Essa interface pode ser
implementada por uma classe responsável por persistir e retornar dados
do banco de dados. Para isso existem métodos especí cos que traduzem
sua função em instruções SQL.
No design pattern Table Data Gateway, há uma classe para manipulação
de cada tabela do banco de dados, e apenas uma instância dessa classe irá
manipular todos os registros da tabela, por isso é necessário sempre
identi car o registro sobre o qual o método estará operando. Uma classe
Table Data Gateway é por natureza stateless, ou seja, não mantém o estado
de suas propriedades; atua simplesmente como ponte entre o objeto de
negócios e o banco de dados. Na gura 5.1 vemos a representação de uma
classe Table Data Gateway.

Figura 5.1 – Table Data Gateway.


No programa a seguir estamos criando a classe ProdutoGateway. Essa
classe, que implementa o design pattern Table Data Gateway, contém
métodos para gravação (save), exclusão (delete) e busca (find, all) de
registros em base de dados. Antes de tudo, é preciso criar um método para
receber a conexão ativa (setConnection). Esse método implementa uma
injeção de dependência, pois os demais métodos da classe utilizarão a
conexão recebida (self::$conn) para executar as operações com banco de
dados.
O método find() recebe o ID do registro como parâmetro, executa uma
query simples para buscá-lo do banco de dados e retorna esse registro na
forma de objeto, por meio do método fetchObject(). O método all()
recebe opcionalmente como parâmetro um ltro e por sua vez monta uma
query para retornar todos os registros do banco de dados na forma de um
array de objetos, por meio do método fetchAll(). O método delete()
recebe o ID do registro como parâmetro e simplesmente executa uma
query para sua exclusão. Veja que vários exemplos contêm instruções de
print para debug. Em um cenário real, esses prints não estariam presentes.
O método save() é responsável pela gravação de dados. Para isso, ele
recebe um parâmetro ($data), que é um objeto de transporte de dados, um
data transfer object, um objeto simples, sem relacionamentos como
associações, agregações ou heranças, utilizado apenas como estrutura
para o transporte de dados entre uma chamada de método e outra.
Geralmente esses objetos são pequenos e permitem transportar vários
dados por meio de suas propriedades. Como geralmente não persistimos
todo objeto do modelo conceitual, mas apenas algumas de suas
propriedades, os data transfer objects são uma boa escolha quando se
implementa o design pattern Table Data Gateway. Você perceberá a
simpli cação que ocorre nas chamadas de métodos. O método save() tem
uma dupla função: inserir um registro novo (caso o objeto recebido não
tenha um ID) ou alterar os dados de um registro (caso o objeto recebido já
tenha um ID).
Note que, a cada método executado, é necessário fazer a identi cação do
registro sobre o qual o método irá operar. Algumas vezes essa
identi cação é feita pelo ID dos registros, como na operação de exclusão
(delete); outras vezes, é necessário informar o registro completo, como na
operação de gravação (save), que por sua vez recebe um objeto cuja
função é simplesmente transportar os dados.

 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.

Figura 5.2 – Table Data Gateway em uso.

 Observação: neste exemplo, as propriedades serão armazenadas em um array


chamado $data. Isso porque estamos utilizando os métodos __get() e __set()
para interceptar os acessos aos atributos do objeto.

 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'

5.2.2 Active Record


A utilização do design pattern Table Data Gateway simpli ca o acesso a
dados e isola esta camada em uma classe à parte da classe de domínio. No
entanto você deve ter percebido que, para cada classe de domínio,
criaremos uma classe gateway para realizar as operações com a base de
dados.
Existe um outro design pattern, chamado Active Record, que elimina esta
segunda classe de persistência, reunindo tudo (operações de negócio e
persistência) em uma única classe. Esse design pattern pode ser utilizado
com sucesso nos casos em que o modelo de negócios se parece bastante
com o modelo de dados. Na gura 5.3 temos uma representação do
padrão Active Record. Os métodos com o contorno tracejado representam
métodos de negócio.
 Observação: nestes exemplos, por motivos didáticos, apresentaremos métodos de
negócio bastante simples. Entretanto, na prática, métodos de negócio
frequentemente envolvem outras classes.

Figura 5.3 – Active Record.


O padrão Table Data Gateway provê uma camada de acesso ao banco de
dados para a camada superior (modelo de domínio). Com o design
pattern Active Record temos uma única camada, na qual temos lógica de
negócios (modelo de domínio) e métodos de persistência do objeto na
base de dados (gateway).
Ter duas responsabilidades (negócio e persistência) é visto como algo
negativo, pois quebra um dos princípios básicos da orientação a objetos,
que é a classe ter uma única responsabilidade. Contudo, após vermos o
padrão Active Record, vamos abordar o padrão Layer Supertype, que
resolverá esta questão generalizando a responsabilidade de persistência.
No programa, podemos ver a criação de uma classe Active Record, com a
presença de métodos relativos ao modelo de negócios, como os métodos
getMargemLucro() e registraCompra(). A presença desses métodos torna essa
classe um Active Record, ou seja, um objeto que se comporta exatamente
como um registro do banco de dados e ainda oferece métodos relativos à
lógica de negócios da aplicação.
 classes/ar/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;
}
public static function find($id) {
$sql = "SELECT * FROM produto where id = '$id' ";
print "$sql <br>\n";
$result = self::$conn->query($sql);
return $result->fetchObject(__CLASS__);
}
public static function all($filter = '') {
$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() {
$sql = "DELETE FROM produto where id = '{$this->id}' ";
print "$sql <br>\n";
return self::$conn->query($sql);
}
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";
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;
}
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 nossa classe Active Record construída, podemos
desenvolver um exemplo que a utilize. Nesse exemplo poderemos utilizar
tanto métodos de persistência quanto de negócio. O exemplo inicia com a
importação da classe Active Record Produto. Uma vantagem desse design
pattern é que uma única classe já resolve todas as questões relativas à
tabela manipulada. Após importar a classe, criamos uma conexão ($conn)
com o banco de dados e passamos a conexão para o Active Record. Em
seguida, buscamos todos os objetos pelo método all() e excluímos um a
um pelo método delete(). Depois, instanciamos os objetos $p1 e $p2,
de nimos vários de seus atributos e salvamos cada um deles pelo método
save(). Em seguida, buscamos o produto 1 pelo método find(), exibimos
sua descrição e acionamos o método getMargemLucro() para obter a
margem de lucro. Registramos então a compra pelo método
registraCompra() e por m solicitamos a atualização do objeto pelo
método save(). Note que executamos métodos de persistência e de
negócio sobre o mesmo objeto.
 Observação: você deve ter percebido que esse exemplo é quase idêntico a outro
construído anteriormente (exemplo_tdg2.php), no qual demonstramos a utilização de
um objeto de domínio. Isso porque um Active Record também é um objeto de
domínio, mas com métodos de persistência próprios.

 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'

5.2.3 Data Mapper


Como já vimos anteriormente, a orientação a objetos nos proporciona
uma gama de relacionamentos entre os objetos que enriquecem a
representação de ideias do modelo conceitual. Tais relacionamentos
passam por associações, agregações e heranças, cuja utilização facilita a
compreensão do problema.
Enquanto esses relacionamentos nos ajudam a representar complexas
estruturas em nossas aplicações, distanciamo-nos cada vez mais do
modelo de dados (tabular) de nossa aplicação, pois nele não conseguimos
representar com tamanha riqueza de detalhes os relacionamentos
existentes, o que di culta a tarefa de mapear os objetos para o banco de
dados.
Até agora, vimos Table Data Gateway e Active Record, que funcionam
muito bem quando a representação do modelo conceitual da aplicação
está próxima do modelo de dados. No entanto, quando precisamos
utilizar vários tipos de relacionamento entre os objetos de domínio, temos
de usar uma forma mais exível de mapear esses objetos para o modelo de
dados.
O design pattern Data Mapper forma uma camada que separa os objetos
de domínio em relação à persistência no banco de dados, permitindo
transferir dados entre uma camada e outra de forma transparente, sem
que os objetos do modelo de domínio precisem implementar métodos de
persistência.
Para que o Data Mapper possa fazer isso, é necessário que o objeto de
domínio (camada de negócio) ofereça métodos que permitam ao Data
Mapper inspecionar o valor de suas propriedades, visto que torná-las
public não é a melhor solução.
A classe que implementará o Data Mapper deverá prover métodos de
persistência e recuperação (obtenção) dos objetos de negócio, além de
métodos para pesquisar objetos com base em diferentes padrões (busca
por nome e idade, no caso de uma pessoa).
Para demonstrar o design pattern Data Mapper, primeiro, vamos criar
mais duas tabelas em nosso banco de dados: venda e item_venda. A tabela
venda armazenará uma venda efetuada, e item_venda, os produtos, as
quantidades e os preços praticados.
 Observação: sabemos que a estrutura de tabelas proposta é insuficiente para ser
usada na prática. Uma venda ainda tem um cliente, entre outras informações. No
capítulo 8 criaremos uma estrutura melhor.
CREATE TABLE venda (
id integer PRIMARY KEY NOT NULL,
data_venda date
);
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
);
A implementação do Data Mapper será demonstrada por meio do
relacionamento entre uma venda e seus itens. Na gura 5.4 podemos ver
as classes Venda e Produto. Há uma agregação entre essas duas classes, bem
como métodos que permitem adicionar (addItem) e retornar (getItens) os
itens de uma venda. O vetor itens é responsável por armazenar cada um
dos produtos que integram a venda e também suas quantidades. A classe
VendaMapper será responsável por receber um objeto da classe Venda e
persisti-lo na base de dados. A classe VendaMapper poderá ter outros
métodos como delete() para excluir e find() para buscar uma venda e
instanciá-la em memória.

Figura 5.4 – Data Mapper.


Para demonstrar esse design pattern, vamos criar algumas classes bastante
simples. A princípio, precisamos de uma classe para representar um
produto. Fica claro aqui que a classe proposta é uma classe incompleta,
pois carece de métodos de negócio.

 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')

5.3 Conexões e transações


Nesta seção, criaremos classes para abstrair o processo de criação de
conexões e transações.

5.3.1 Classe para conexões com Factory Method


Vimos anteriormente que a utilização da biblioteca PDO traz inúmeras
vantagens no desenvolvimento de aplicações, visto que deixamos de usar
comandos especí cos de um determinado sistema gerenciador de bancos
de dados (SGBD) para utilizar uma interface orientada a objetos
uni cada, na qual podemos a qualquer momento alterar a tecnologia de
banco de dados sem acarretar grandes modi cações no código-fonte da
aplicação.
Ainda assim, temos a linha de conexão ao banco de dados, na qual
instanciamos o objeto PDO (new PDO). Essa linha contém as informações
especí cas de cada banco de dados, como o tipo, o seu nome, o usuário e
a senha. Não é recomendável ter espalhados ao longo do código-fonte de
uma aplicação esses detalhes de conexão.
Imagine uma situação em que você tenha desenvolvido toda a aplicação
para MySQL, mas um importante cliente tenha como política utilizar
somente PostgreSQL, ou que você tenha de implantar o sistema em uma
empresa que só disponha de DBAs Oracle para dar suporte ao banco de
dados. O que deve ser feito nesse caso? Imagine que você tenha esses
detalhes de conexão com o banco de dados em cada um dos 240
programas que fazem parte de seu sistema. Sempre que teve de conectar-
se ao banco de dados para inserir, excluir ou listar alguma informação, lá
estava você a copiar e a colar a linha de conexão, passando informações
como usuário e senha novamente. Isso é muito ruim, porque, se o
programa for escrito dessa forma, para se adaptar a outro sistema de
banco de dados, você terá de rever cada um desses arquivos para alterar as
strings de conexão.
Uma das soluções para esse problema é criar um ponto central para
aberturas de conexões com o banco de dados, tornando mais simples a
tarefa de gerenciar esse tipo de informação. Para isso, implementaremos
um design pattern conhecido como Factory. Esse pattern pode ser
representado por uma classe ou um método responsável pela criação de
objetos. Implementaremos um Factory Method, que geralmente tem um
bloco de comandos SWITCH ou IF para tomar a decisão sobre qual classe
instanciar.
O padrão Factory existe para esconder os detalhes da criação de um
grupo de objetos com características semelhantes. Factory centraliza a
geração de objetos, fornecendo uma interface única para criação das
instâncias. É uma técnica importante, pois evita que tenhamos essas
instâncias espalhadas por diversos programas diferentes ao fornecer um
ponto central, facilitando a manutenção do código.
Para implementar o padrão Factory, criaremos a classe Connection, cujo
papel será instanciar um objeto PDO de acordo com as informações de
conexão passadas (driver, usuário, senha). Além disso, faremos algo mais
so sticado, gravando essas informações em arquivos de con guração INI
no disco rígido. Dessa forma, nossa aplicação não tomará conhecimento
de qual tecnologia de banco de dados estará rodando, e poderemos a
qualquer momento alterar o sistema gerenciador de banco de dados
(PostgreSQL, Oracle, MySQL, DB2) simplesmente alterando os arquivos
de con guração INI.
Agora criaremos os arquivos de con guração para acessar o bancos de
dados. Nesse arquivo de con guração, teremos variáveis para indicar a
localização do banco de dados (host), o nome do usuário (name) com
permissões de acesso ao banco junto de sua senha (pass) e o tipo de banco
de dados utilizado (type). A princípio, usaremos o banco de dados SQLite
por ser mais simples sua utilização. Entretanto, para alterar o banco de
dados, basta alterarmos os dados do INI. Ao alterarmos o INI, a classe
PDO será instanciada com outros parâmetros e conectará, portanto, no
banco de dados desejado.

 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')

5.3.2 Classe para transações


No exemplo anterior, construímos uma classe para facilitar a criação de
conexões com um banco de dados. Porém, mais importante que criarmos
uma conexão, é criarmos uma transação. Ao executarmos uma série de
operações com o banco de dados, uma transação permite que as
operações sejam desenvolvidas em bloco, ou seja, que todas as operações
sejam feitas ou que todas as operações sejam desfeitas quando houver
uma falha (exceção) por um motivo qualquer no meio do processo.
Uma transação representa uma interação entre a aplicação e o sistema de
banco de dados tratada de forma única e independente. Pense em uma
transação nanceira de transferência de fundos. Esse tipo de transação
requer grande cuidado, pois, do contrário, uma conta pode ser debitada e
a outra não ser creditada, ou vice-versa. Uma transação é uma sequência
de trabalho que tem um ponto de início e de m bem de nidos. Uma
transação tem algumas propriedades, entre as quais podemos destacar:
• Atomicidade – Propriedade que garante que todas as tarefas da transação
sejam cumpridas ou que a transação seja cancelada como um todo.
• Consistência – Propriedade que garante que o banco de dados esteja em
um estado íntegro depois de a transação ser feita, como estava antes de
ela iniciar, sem quebrar nenhuma integridade referencial, por exemplo.
• Isolamento – Propriedade que garante que o resultado de uma transação
só seja visível para outras transações no momento em que ela é
nalizada com sucesso, ou seja, a transação é isolada de outras
operações.
• Durabilidade – Propriedade que garante que a transação seja persistida
assim que nalizada, ou seja, não será desfeita ou perdida mesmo na
ocorrência de falhas do sistema.
No desenvolvimento de aplicações de negócio, é muito importante a
utilização de transações para garantir as propriedades anteriormente
descritas quando processamos e armazenamos dados interdependentes no
banco de dados após sucessivas interações com ele. Implementaremos o
conceito de transações de forma transparente e que nos permita realizar
inúmeras operações no banco de dados, podendo aproveitar a mesma
conexão durante todo o processo.
Quando aberta, uma transação deve disponibilizar uma conexão com o
banco de dados à aplicação. Tal conexão deve ser visível por diferentes
partes do sistema (métodos, funções etc.), para que as diferentes partes
possam enviar comandos ao banco de dados e os comandos sejam
corretamente encapsulados pela transação atual.
Para que um objeto seja visível ao longo de diferentes partes do sistema,
uma das formas utilizadas é tornar esse objeto global. Usar uma variável
global é uma técnica condenável sob o paradigma da orientação a objetos,
pois ela pode a qualquer momento ser excluída ou sobreposta, visto que
seu acesso não é protegido, colocando por terra o encapsulamento.
Uma forma mais consistente de se disponibilizar um objeto para acesso
global é por meio de uma propriedade estática. Uma propriedade estática
de uma classe também é global, mas podemos utilizar a estrutura da
própria classe para proteger tal objeto, fazendo com que a própria classe
garanta que não irão se construir outras instâncias, impedindo que se
execute seu método construtor e também impedindo que se sobreponha
tal objeto.
Existem alguns tipos de classe para as quais não faz sentido ter mais de
uma instância na aplicação – uma classe para gerenciar as preferências da
aplicação, por exemplo. A existência de várias instâncias dessa classe
poderia gerar uma inconsistência, pois, em determinado momento, cada
objeto poderia conter valores diferentes, sendo que, em se tratando de
con guração do sistema, temos somente um conjunto de valores. Outro
exemplo é a conexão com o banco de dados, visto que podemos ter um
único objeto para fazer a conexão durante todo o ciclo de vida da
aplicação e retornar esse mesmo objeto sempre que uma nova conexão for
solicitada.
Para tornar a transação visível durante toda a aplicação e também para
impedir que se sobreponha o objeto, criaremos uma classe para
manipular transações (Transaction), cujo método construtor é marcado
como privado (private), para que não existam várias instâncias da mesma
transação. O método open() é responsável por abrir uma conexão com o
banco de dados, e para isso ele receberá o nome do arquivo INI de
con guração do banco de dados e usará a classe Connection para criar a
conexão (objeto PDO) correspondente, armazenando-a em uma variável
estática. Em seguida, será iniciada uma transação, o que é feito pelo
método beginTransaction(), sendo que a transação só será iniciada se não
houver outra em andamento.
O método get() é responsável por tornar a conexão visível durante todo o
processo, retornando-a sempre que for necessário interagir com o banco
de dados. O método rollback() irá desfazer todas as operações executadas
desde o início da transação, e o método close() irá fechar a conexão com
o banco de dados, aplicando todas as operações realizadas ao longo da
transação.
Só poderemos abrir uma transação pelo método open() caso não exista
nenhuma transação aberta pendente. Isso é feito ao veri car se a variável
estática (self::$conn) está vazia. Sempre que uma transação é encerrada,
essa variável é reinicializada.

 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();
}

5.3.3 Registro de log e strategy pattern


No exemplo anterior, criamos uma classe para executar transações com o
banco de dados. Além de fazermos um controle de transações, também é
muito importante registrarmos as operações ocorridas durante uma
transação, armazenando-as em um arquivo de log. Isso se torna ainda
mais importante quando começamos a construir um conjunto de classes
que vai automatizar o trabalho de persistência. Quando tivermos nossa
camada de persistência pronta, teremos de escrever pouco ou quase
nenhum código SQL, pois as classes cuidarão dessa parte para nós. Nesse
ponto, torna-se ainda mais importante registrar quais operações SQL
estão sendo executadas dentro de uma transação.
Podemos registrar vários tipos de log: em formato XML, em formato TXT,
em bancos de dados, entre outros. A forma mais simples de implementar
o registro de log seria criar um método na classe Transaction responsável
por registrar os comandos SQL executados em um arquivo qualquer. Se
quiséssemos utilizar tipos diferentes de log, teríamos de usar um bloco de
comandos IF/SWITCH para tomar a decisão sobre qual algoritmo usar com
base nas preferências do programador. Essa forma de implementação
acabaria in ando esse método, que teria de implementar todos os
algoritmos possíveis, e a nossa classe caria grande demais.
A terceira opção é separar totalmente os algoritmos de log da classe
Transaction, criando um outro conjunto de classes que irá exclusivamente
representar essas várias estratégias diferentes de log. Dessa forma, faremos
com que a transação mantenha uma referência para o objeto que
implemente o algoritmo de log. Neste caso, estamos falando da utilização
do design pattern strategy.
Um strategy é representado por uma família de algoritmos encapsulados
em uma hierarquia de objetos, de forma que possamos facilmente trocar
de algoritmo. Para implementar o strategy, declararemos uma classe
abstrata contendo os métodos que esse algoritmo deve prover e um
conjunto de classes concretas lhas desta, que implementa cada versão
diferente desse algoritmo. A decisão sobre qual algoritmo adotar ca por
conta do programador, que só terá de escolher determinada classe a
instanciar. Independentemente da classe escolhida, todas as classes lhas
implementarão a mesma interface.
Cada formato de log precisa de um algoritmo diferente para
implementação. Pensando nisso, usaremos o Strategy para estruturar esses
algoritmos sob a classe abstrata Logger. Esta será uma classe abstrata que
só indicará quais são os métodos necessários a ser implementados nas
classes concretas. Em seu método construtor, ela irá receber o nome do
arquivo de log. Criamos também o método write(), que será marcado
como abstract para que todas as classes lhas sejam obrigadas a
implementá-lo. A função desse método será justamente escrever o registro
de log no arquivo. Veja no diagrama a seguir o relacionamento entre as
classes.

Figura 5.7 – Relacionamento entre a transação e a classe Logger.

 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')

 Observação: os IDs gerados podem variar conforme a ordem da execução dos


arquivos de exemplo.

5.4 Active Record e Layer Supertype


Até o momento estudamos diversos design patterns de persistência de
objetos em bases de dados. Não existe um design pattern que seja melhor
que outro. Há casos em que a utilização de um é mais apropriada que a
de outro. Precisaremos escolher um desses design patterns para utilizá-lo
ao longo do livro nos exemplos subsequentes. Em decorrência da
facilidade de uso e da simplicidade dos exemplos que construiremos, nos
quais não haverá relações complexas entre as tabelas (o que poderia exigir
um data mapper), decidimos optar pelo design pattern Active Record.
Um Active Record contém métodos de persistência do objeto na base de
dados e também métodos relativos ao modelo de negócios. Tais métodos
de persistência, que devem ser comuns a todos os objetos de nossa
aplicação, podem ser automatizados se criarmos algumas convenções.
Essa automatização dos métodos de um Active Record poderia estar
localizada em uma superclasse, herdada por todos os nossos objetos.
Quando criamos uma superclasse que reúne funcionalidades em comum
para toda uma camada de objetos, usamos um design pattern conhecido
como Layer Supertype. Neste caso, movemos aquelas funcionalidades que
são comuns a todos os objetos para essa superclasse.
Em um Active Record temos algumas operações que são necessárias para
todo e qualquer objeto. Entre elas podemos destacar a operação de
armazenar um objeto na base de dados (que vamos chamar de store), a
operação de remover um objeto da base de dados (que vamos chamar de
delete), a operação de carregar um registro da base de dados,
instanciando o objeto correspondente para a aplicação (que vamos
chamar de load). Essas operações, que são comuns a todo Active Record,
podem ser elevadas a um nível superior, uma superclasse (um Layer
Supertype). Em nossa aplicação, chamaremos essa classe de Record pelo
fato de ela implementar o padrão Active Record.
Na gura 5.8 retratamos a classe Record em um possível cenário de
modelo de negócios. Por ser uma Layer Supertype, ela fornece os recursos
necessários para persistência de um objeto em bases de dados (store, load,
delete etc.). Dessa forma, a maioria das classes do modelo de negócios só
precisará estendê-la e, em alguns casos especí cos, poderá inclusive
reescrever algum de seus métodos.
A seguir temos o código da classe Record. Em seu método construtor,
podemos passar opcionalmente o ID de um objeto. Neste caso, o objeto
correspondente será carregado automaticamente por meio do método
load(), que retorna um objeto. Se houver algum objeto retornado, este será
convertido em array (toArray) para depois alimentar o objeto atual
(fromArray). Esses métodos (toArray, fromArray) ainda serão criados.
Figura 5.8 – Classe Record e a camada de domínio.

 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";
}
}

5.4.1 De nição da classe Active Record


Antes de iniciarmos uma série de exemplos de como armazenar, excluir e
buscar registros, precisamos de nir nossa classe Active Record Produto.
Não se surpreenda com o tamanho da classe. Apesar de ser tão enxuta,
ela é totalmente funcional, pois os métodos de persistência estão todos
localizados na superclasse Record. Veja que a classe Produto, embora seja
uma Active Record, não tem presentes de maneira explícita os métodos de
persistência, pois estes estão na classe pai (Layer Supertype). Com isso,
temos bastante espaço para de nir métodos de negócio.

 classes/model/Produto.php
<?php
class Produto extends Record {
const TABLENAME = 'produto';
}

5.4.2 Novo objeto


Neste primeiro programa que utiliza a superclasse Record, iremos
demonstrar como se faz a persistência (gravação) de um novo objeto na
base de dados. Nosso código que irá manipular os objetos e
consequentemente realizará interações com o banco de dados está todo
contido dentro de um bloco try/catch para tratamento de exceções.
Sempre que qualquer exceção ocorrer durante o try{}, o programa será
abortado e a execução seguirá para o bloco catch{}, exibindo a mensagem
e desfazendo a transação.
O programa inicia com a importação das classes necessárias. Em seguida,
abrimos a transação com a base estoque, cujas informações de conexão
estão no arquivo estoque.ini. Também habilitamos o log para que as
instruções SQL geradas automaticamente pela classe Record sejam
armazenadas em log_novo.txt.
Em seguida, é instanciado um objeto da classe Produto e são de nidos
vários atributos. Então o método store() é executado, gerando o SQL de
INSERT (pois é um objeto novo, sem ID) e registrando o SQL gerado no log,
pois o log foi habilitado. Por m, a transação é concretizada pelo método
close().

 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' )

5.4.3 Obter objeto


No programa mostrado a seguir, procuramos demonstrar como obter
(retornar) os objetos a partir da base de dados. O código está novamente
contido em um bloco try/catch para controlar a ocorrência de exceções.
No início do programa, importamos as classes necessárias e abrimos a
transação com a base de dados estoque, além de de nir o arquivo
(log_ nd.txt) no qual serão armazenadas as mensagens de log.
Em seguida utilizamos o método find(), identi cando o ID do objeto,
para obter o objeto Produto correspondente. A partir do momento em que
temos o objeto instanciado, podemos acessar qualquer uma de suas
propriedades, como é o caso da propriedade descricao neste exemplo.

 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

5.4.4 Alterar objeto


Neste programa iremos alterar as propriedades de um objeto existente na
base de dados. 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_update.txt.
Para alterar um objeto, basta primeiro o carregarmos para a memória, o
que é feito pelo método find(), que retorna uma instância de Produto. Em
seguida, exibimos o estoque atual, por um print, e então incrementamos
o estoque em dez unidades. Quando executarmos o store() novamente,
será gerada uma instrução de UPDATE, pois o objeto em questão já contém
um ID, resultado de seu carregamento prévio para a memória. É
importante lembrar que a alteração só será concretizada quando a linha
com o método close() for executada. Qualquer exceção que ocorrer antes
disso provocará um rollback().

 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

5.4.5 Clonar objeto


Neste programa, iremos clonar um objeto contido na base de dados. 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_clone.txt.
A clonagem do objeto inicia com o carregamento do objeto original por
meio do método find(). Após carregarmos o objeto em memória, usamos
o operador clone para gerar um clone do objeto. Nesse instante, o método
__clone() da classe Record entra em atividade, limpando a propriedade ID.
Assim, quando esse objeto clonado for persistido pelo método store(),
um novo ID será gerado para ele.

 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 )

5.4.6 Excluir objeto


Neste programa, iremos excluir um objeto contido na base de dados. 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_delete.txt.
Para excluir um objeto, é bastante simples. Primeiro, utilizamos o método
find() para localizá-lo. Se for localizado, executamos seu método delete().
Caso contrário, podemos lançar uma exceção que fará com que o
programa automaticamente caia no catch, exibindo a mensagem de erro
correspondente.

 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

5.5 De nição de critérios


Os exemplos desenvolvidos até então exploraram a individualidade dos
objetos em operações de gravação, leitura e alteração. Assim, sempre que
foi necessário realizar alguma operação, trabalhamos com um objeto por
vez, geralmente identi cado por sua chave primária. Porém, para
desenvolvermos uma aplicação completa, precisaremos criar um conjunto
de operações que permita manipular vários objetos de uma única vez em
operações de leitura, contagem, exclusão, entre outras.
Para trabalharmos com vários objetos é preciso de nir ltros de seleção,
seja com o objetivo de selecioná-los para contagem, leitura, alteração ou
exclusão. Dessa forma, nesta seção, vamos explorar a possibilidade de
de nirmos ltros de seleção de objetos. Além disso, vamos propor a
criação de um conjunto de classes que permita de nir ltros de maneira
orientada a objetos.
A montagem de ltros para seleção de objetos geralmente precisa ser
dinâmica. Podemos citar como exemplo um objeto Livro em um sistema
de bibliotecas. Em algumas funcionalidades da aplicação será necessário
buscar livros por assunto, em outras, por autor, em outras, por editora. E
em algumas situações será necessário buscar livros por mais de um ltro
ao mesmo tempo, como assunto + autor ou autor + editora, e assim por
diante. Geralmente as aplicações trazem algum tipo de formulário de
buscas multicritério, em que se pode ltrar por vários campos de pesquisa
ao mesmo tempo, como pode ser visto na gura 5.9. Nesses casos a
instrução SQL deve ser construída dinamicamente.
Figura 5.9 – Formulário de consulta multicritério.
O código-fonte a seguir procura demonstrar o trecho de uma rotina
hipotética que responde à busca feita em uma tela de buscas multicritério,
na qual se tem um formulário com três campos (artista, album e gravadora)
para pesquisa em um catálogo.
<?php
$artista = isset($_POST['artista']) ? $_POST['artista'] : NULL;
$album = isset($_POST['album']) ? $_POST['album'] : NULL;
$gravadora = isset($_POST['gravadora']) ? $_POST['gravadora'] : NULL;
$sql = "SELECT codigo, nome FROM discos ";
$filters = array();
if (!empty($artista)) {
$filters[] = "artista ILIKE '%$artista%'";
}
if (!empty($album)) {
$filters[] = "album ILIKE '%$album%'";
}
if (!empty($gravadora)) {
$filters[] = "gravadora ILIKE '%$gravadora%'";
}
if (count($filters)>0) {
$sql .= ' WHERE ' . implode(' AND ', $filters);
}
print $sql . "<br>\n";
Além de buscas simples, dentro das rotinas de um sistema é comum
precisarmos localizar objetos usando buscas multicritério. Na devolução
de um livro, por exemplo, precisaremos localizar todos os empréstimos do
usuário que estejam atrasados. Assim, a rotina precisa consultar
empréstimos utilizando como critérios de busca o usuário e a data de
devolução ao mesmo tempo.
Você deve ter percebido que montar a instrução SQL dessa maneira se
tornou complexo e sujeito a erros de digitação e concatenação. Não é raro
encontrar programas com muitas linhas contendo baterias de IF e
concatenações de string com instruções SQL. Quanto mais campos
tivermos de concatenar na instrução SQL, maior será a probabilidade de
erros, pois vamos adicionar naturalmente mais complexidade ao projeto.
SQL não é uma linguagem apropriada para estar no meio do código-fonte.

5.5.1 Query Object


Para não precisarmos mais montar instruções SQL manualmente por
meio da concatenação de strings, vamos criar um mecanismo que permita
construir ltros de maneira orientada a objetos. Para isso vamos usar o
design pattern Query Object, que por sua vez é um objeto que representa
um critério de consulta à base de dados. Em vez de construirmos um SQL
concatenando strings, vamos criar um ltro usando métodos de um
objeto.
As vantagens no uso de um Query Object não estão somente em facilitar
o uso do SQL dentro de uma aplicação, mas também em tornar a forma
como representamos as expressões mais independente de seu contexto,
permitindo-nos reutilizar uma mesma instrução em tabelas e em bancos
de dados diferentes.
A maioria das instruções SQL (com exceção do INSERT) tem um critério de
seleção de dados que se traduz em uma cláusula WHERE. É assim com o
Update, o Delete e o Select. Tal ltro pode ser uma instrução complexa,
composta de operadores lógicos (AND, OR), operadores de comparação (<, >,
=, <>), entre outros.
Até o momento, vínhamos escrevendo manualmente expressões de seleção
(where coluna='valor'), quando necessário. A partir de agora
implementaremos a classe Criteria, que servirá de suporte para
representar expressões de critério de ltro de dados por meio de um
mecanismo totalmente orientado a objetos.
A classe Criteria terá um método add(), pelo qual adicionaremos regras
de ltro ao objeto. Esse método receberá três informações essenciais
(variável, operador e valor). Um último parâmetro representará o
operador lógico, opcional. Sempre que o método add() for executado, ele
aplicará uma transformação sobre o parâmetro valor, visto que o PHP
suporta diversos tipos de dados (string, integer, array, entre outros), e
precisamos converter esses tipos de dados em uma string plana para que
seja montado o SQL nal. Esse será o papel do método transform(), que
fará vários testes para descobrir o tipo do valor e realizará as conversões
necessárias. Isso é necessário porque alguns tipos de dados como o array
são representados diferentemente no PHP e no banco de dados. Ao tipo
string, por exemplo, devemos adicionar aspas antes de enviar para o
banco.
O método dump() será responsável por retornar estas regras de ltro em
formato de string simples, para que possa então ser utilizado dentro de
um comando SQL (SELECT, UPDATE, DELETE). Já os métodos setProperty() e
getProperty() serão utilizados para informar outras propriedades do
critério, tais como order, limit, offset.

 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'))

5.6 Manipulação de coleções de objetos


Já vimos como manipular objetos individualmente e também como
construir objetos para de nição de critérios de busca de registros. O
próximo passo é construirmos classes que permitam a manipulação de
coleções de objetos. Uma coleção representa um conjunto de objetos.
No PHP, a melhor forma de representarmos um conjunto de objetos é por
meio de arrays, estruturas exíveis capazes de comportar vários tipos de
dados. Arrays são mapas que procuram relacionar determinados valores a
chaves. Como chaves de acesso, os arrays aceitam variáveis do tipo integer
ou string. Como valor de cada posição, os arrays permitem uma grande
variedade de campos como integer, oat, string, boolean e até mesmo um
outro array, construindo uma estrutura de matriz. A grande vantagem da
utilização de arrays está em sua exibilidade, que permite adicionarmos e
removermos elementos de qualquer posição do array de forma dinâmica
em qualquer momento. Como os arrays também podem conter objetos,
iremos usá-los para representar coleções ou conjuntos de objetos.

5.6.1 Repository Pattern


Aplicações de negócio geralmente necessitam da elaboração de consultas
SQL complexas em um dado momento para manipular coleções de
objetos. Na maioria das vezes, o desenvolvedor opta por escrever métodos
para manipulação de objetos baseados em diferentes critérios de seleção
de dados. A maneira mais simples de atingir esse objetivo geralmente é
por meio da escrita de um método para cada critério de seleção, como
demonstrado no esboço de código-fonte a seguir.
<?php
class Pessoa {
function listarPorNome($nome)
{ }
function listarPorCidade($cidade)
{ }
function listarPorIdade($idade)
{ }
}
Quando adotamos essa abordagem, temos de escrever nossas consultas
SQL à mão e, pior do que isso, temos de criar um novo método sempre
que desejamos retornar objetos sob um diferente critério de seleção, o que
acaba di cultando a manutenção do código. Sendo assim, torna-se
importante adicionar uma camada à aplicação que permita manipular
coleções de objetos de forma exível. Esse é justamente o contexto
descrito pelo design pattern conhecido por Repository.
Um Repository, ou repositório, é uma camada na aplicação que trata de
mediar a comunicação entre os objetos de negócio e o banco de dados,
atuando como um gerenciador de coleções de objetos. Uma classe
Repository deve aceitar critérios que permitam selecionar um
determinado grupo de objetos de forma exível.
Os objetos devem ser selecionados, excluídos e retornados a partir de uma
classe Repository por meio da especi cação de critérios. Dessa forma,
passamos um pouco da responsabilidade, que antes era da classe, para o
código que fará uso do Repository.
Um Repository utiliza outros design patterns já vistos anteriormente. O
seu funcionamento inicia pela de nição de algum critério de seleção de
objetos (Criteria). Então o critério é passado para o Repository por meio
de um método que executa uma operação especí ca (carregar objetos,
remover objetos, contar objetos etc.). A gura 5.10 demonstra o
funcionamento de um Repository. Nela, podemos ver o programa, que
cria um objeto Criteria, para de nição de um critério de seleção de
objetos. Em seguida, adiciona a esse critério determinadas expressões
lógicas de ltro. A partir do critério de ltro formado, o programa
instancia um Repository e executa algum método como load (para carregar
objetos), count (para contar objetos) ou delete (para excluir objetos). Na
gura, estamos representando o método load(), que, a partir do critério
recebido, instancia os objetos Active Record correspondentes (por
exemplo, Produto), retornando-os para a aplicação.

Figura 5.10 – Repository Pattern.


O programador não precisa saber quais instruções SQL estão sendo
executadas dentro de cada um desses métodos, ele só precisa conhecer o
método necessário para desempenhar determinada função e passar o
critério de seleção desejado. Para implementar um Repository, criaremos a
classe Repository, que implementará os métodos necessários para a
manipulação de coleções de objetos. O método load() será responsável
por carregar uma coleção de objetos, ao passo que o método delete() irá
excluir uma coleção de objetos, e o método count() irá contar quantos
objetos satisfazem a um determinado critério. Em seu método construtor,
a Repository recebe o nome da classe que irá manipular, ou seja, a classe à
qual pertence a coleção de objetos manipulada.

 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.

5.6.2 Preparação dos dados


Para executar os exemplos subsequentes, precisaremos de uma massa de
dados. Para isso, é importante carregar o arquivo colecao_produtos.sql, que
contém alguns dados de produtos.

 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');
...

5.6.3 Carregar coleção de objetos


Neste primeiro exemplo de programa que manipula coleções de objetos,
demonstraremos como obter um conjunto de objetos do banco de dados.
Para isso, faremos uso do método load() da classe Repository, que por sua
vez retorna todos os objetos que satisfazem a um determinado critério de
seleção de dados. Portanto, precisamos utilizar a classe Criteria.
Usaremos essa classe para expressar os ltros de seleção de dados. Note
que o programa, assim como os anteriores, tem tratamento de exceções e
irá gravar as instruções SQL processadas em um arquivo de log.
O programa inicia com a importação das classes necessárias. Em seguida,
dentro de um controle de exceções try/catch, iniciamos a transação com a
base estoque. De nimos então um critério de seleção para ltrar produtos
com estoque superior a 10 e de origem nacional. Para carregar os objetos,
precisamos instanciar a classe Repository, executando seu método load(),
passando como parâmetro o critério de seleção. Posteriormente, podemos
percorrer os objetos retornados por meio de um foreach, exibindo seus
atributos em tela. Aproveitamos o mesmo exemplo para demonstrar o
método count(), que funciona de maneira semelhante, recebendo um
critério de seleção de objetos. O método count(), porém, retorna um
número que representa a quantidade de objetos que passa pelo critério de
seleção.

 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";
}
}

print "Quantidade: " . $repository->count($criteria);


Transaction::close(); // fecha a transação
}
catch (Exception $e) {
echo $e->getMessage();
Transaction::rollback();
}

 /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

5.6.4 Alterar coleção de objetos


No próximo exemplo, demonstraremos como alterar propriedades de uma
coleção de objetos e como persisti-los no banco de dados. O objetivo do
programa é reajustar em 30% o preço dos produtos de origem nacional
cujo preço seja inferior ou igual a 35. O programa inicia com a
importação das classes necessárias. Em seguida, abrimos transação com a
base de dados e habilitamos o log. Depois, é de nido um critério de
seleção de produtos (preço de venda inferior a 35 e origem nacional).
Usamos o método load() para carregar um array de objetos. Dentro do
foreach percorremos esse array, e para cada objeto retornado alteramos a
sua propriedade preco_venda, multiplicando por 1.3. Depois de alterar a
propriedade, utilizamos o método store() para armazenar o objeto
novamente na base de dados. É importante observar que o método load()
retorna um array de objetos Active Record. Dessa forma, dentro do loop
podem ser executados quaisquer métodos fornecidos pela classe Record,
por exemplo.

 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

5.6.5 Excluir coleção de objetos


Neste exemplo, demonstraremos como excluir uma coleção de objetos. O
objetivo do programa é excluir todos os produtos que contenham WEBC ou
FILMAD em sua descrição. O programa inicia com a importação das classes
necessárias. Em seguida, abrimos transação com a base de dados e
habilitamos o log. Logo depois, é de nido um critério de seleção de
produtos (a descrição contém WEBC ou FILMAD). Por m, é executado o
método delete() da classe Repository, que exclui os objetos da tabela
vinculada à classe Produto, com um único comando. Outra maneira de
atingir o mesmo objetivo seria carregar os objetos pelo método load() e
dentro do foreach executar o método delete() um a um dos objetos.
Entretanto, esse procedimento seria mais oneroso, tendo em vista que
executaríamos várias instruções de DELETE.

 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.

6.1 Padrão MVC


Model View Controller (MVC) é um design pattern que está entre os mais
conhecidos. Seus conceitos remontam à plataforma Smaltalk na década de
1970. Basicamente uma aplicação que segue o design pattern Model View
Controller tem as suas classes separadas em três grandes grupos de
responsabilidades. A intenção principal ao utilizarmos o design pattern
MVC é não misturarmos em uma mesma classe responsabilidades
diferentes. A seguir veremos quais são essas responsabilidades.
• Model (modelo) – Uma classe de modelo representa as informações do
domínio de negócios da aplicação. Essa classe tem como
responsabilidades manter as informações de negócio e implementar
métodos que representem as regras de negócio do domínio.
• View (visualização) – Uma classe de visualização representa a fronteira
(interface) da aplicação com seu usuário. Essa classe tem como
responsabilidade a apresentação e a obtenção de dados ao usuário.
Uma classe de visualização de ne, entre outros fatores, como os
campos serão organizados em tela. Uma classe de visualização não deve
conter lógica de negócios.
• Controller (controle) – Uma classe de controle recebe dados e ações do
usuário, interpreta essas ações e executa tarefas correspondentes. Um
exemplo de utilização ocorre quando uma classe Controller recebe da
View um conjunto de dados e uma ação solicitando a atualização
desses dados na base de dados. A Controller aciona a Model, que por
sua vez faz a atualização. Então a Controller aciona a View para
apresentar os resultados ao usuário.
A separação da aplicação nesses três aspectos traz uma série de vantagens
ao desenvolvedor. A separação entre o modelo de dados (Model) e a
visualização (View), por exemplo, permite ao desenvolvedor reutilizar um
mesmo objeto de modelo em diversas visualizações diferentes. Para car
mais claro, imagine uma listagem de clientes e uma listagem de compras
de um cliente, ou uma listagem de clientes por cidade. Todas são
visualizações diferentes, mas tratam de um objeto do modelo de negócios
em comum: o cliente. A camada de visualização (View) deve se preocupar
com a disposição de objetos, com a organização visual, ao passo que o
modelo deve se preocupar com regras de negócios e interação com o
banco de dados.
As três responsabilidades são distintas, porém interagem umas com as
outras da maneira demonstrada pela gura 6.1.

Figura 6.1 – Modelo MVC.


Geralmente a camada de controle recebe uma ação transmitida pelo
protocolo HTTP, como por exemplo uma URL contendo um comando,
bem como seus parâmetros. Então, a classe de controle precisa de uma
classe de modelo para buscar (get) ou alterar (set) o estado de algum
objeto. A classe de modelo responde para a classe de controle, com os
dados (data) resultantes dessa operação. A classe de controle então, como
forma de manter o usuário informado sobre o resultado do
processamento, renderiza (render) uma classe de visualização, que por sua
vez é apresentada ao usuário.
Ao utilizarmos o padrão MVC, alguns cuidados devem ser tomados. Por
exemplo:
• Uma classe de modelo não deve emitir mensagens ao usuário por meio
de comandos como print, muito menos gerar mensagens contendo
marcações como HTML. Exibir informações ao usuário por meio de
uma linguagem de marcação é uma tarefa de uma classe de
visualização.
• Uma classe de controle não deve executar diretamente comandos de
acesso a dados como SQL. Buscar e atualizar dados relativos ao modelo
de domínio são tarefas de uma classe de modelo.
• Uma classe de visualização não deve conter regras de negócio, nem
decidir o que deve ser executado em determinado momento. Regras de
negócio são de responsabilidade de uma classe de modelo, e decidir o
que deve ser executado é de responsabilidade de uma classe de
controle.
 Observação: um sistema MVC clássico terá uma classe Controller para cada view
existente, mas essa abordagem não é a única. Alguns frameworks frequentemente
mesclam view e controller na mesma camada, deixando-as diretamente vinculadas.

6.2 Organização de namespaces e diretórios


Estamos criando uma grande quantidade de classes. No capítulo anterior,
classes relativas à persistência e log foram criadas. Neste capítulo
criaremos classes para apresentação de informações e para controle de
execução de ações. Assim, é fundamental organizarmos as classes em uma
estrutura de diretórios que re ita a responsabilidade de cada grupo de
classes. Então, vamos separar para organizar melhor.
Antes de prosseguirmos criando novas classes, vamos de nir uma
estrutura que compreenda as classes já criadas e as novas classes.
Primeiro, vamos separar em uma estrutura de diretórios as classes que
podem ser reaproveitadas para construir novas aplicações daquelas que
serão especí cas de determinada aplicação. Todas as classes em comum,
que poderão ser reaproveitadas para construir novas aplicações, vão
constituir uma espécie de framework e estarão localizadas na pasta Lib.
Todas as classes especí cas (modelo, visualização, controle) da aplicação
que está sendo construída estarão localizadas na pasta App.
A princípio, vamos organizar aquelas classes que constituirão nosso
núcleo de funcionalidades que podem ser compartilhadas entre diferentes
aplicações. Seguindo uma convenção adotada pela grande maioria dos
frameworks, essa estrutura iniciará pelo diretório Lib. Dentro desse
diretório é comum encontrarmos outro diretório contendo o nome do
fornecedor do framework. Em nosso caso, vamos chamar simplesmente
de Livro. Dentro do diretório Livro teremos diretórios contendo grupos de
classes com funcionalidades em comum: Control (conterá classes que
interpretarão ações e gerenciarão o uxo de controle); Core (conterá
classes que serão responsáveis pela carga das demais classes da aplicação);
Database (conterá classes responsáveis pela persistência de dados); Log
(conterá classes de log); Session (conterá classes de manipulação de
sessões); Traits (conterá traits que poderão ser reaproveitados em diferentes
contextos); Validation (conterá classes de validação de dados); e Widgets
(conterá componentes utilizados para montagem de interfaces, como
formulários e datagrids).
/Lib
/Livro
/Control
/Core
/Database
/Log
/Session
/Traits
/Validation
/Widgets
Agora que já de nimos a estrutura de diretórios de nossa biblioteca de
classes que poderá ser reaproveitada entre diferentes contextos, vamos
de nir a estrutura de diretórios para a nossa aplicação. É importante
lembrar que essa estrutura será recriada com os diretórios vazios a cada
nova aplicação criada. Caberá ao desenvolvedor criar as classes de
modelo, controle e visualização. Os diretórios da aplicação serão Con g
(conterá arquivos de con guração da aplicação, tais como de acesso às
bases de dados); Control (conterá as classes de controle da aplicação);
Database (eventualmente, conterá bases de dados, como aquelas em
SQLite); Images (conterá imagens especí cas da aplicação); Model (conterá
as classes de modelo da aplicação); Resources (conterá recursos externos,
como fragmentos de arquivos HTML utilizados na montagem de
interfaces); Services (conterá classes que formam serviços, como aqueles
voltados para web services); e Templates (conterá os templates HTML que
formarão o layout da aplicação).
/App
/Config
/Control
/Database
/Images
/Model
/Resources
/Services
/Templates
Organizar as classes em diretórios é apenas um primeiro passo rumo à
organização. A organização em diretórios trata da organização física dos
arquivos. No entanto, depois de organizá-los sicamente, é preciso
organizá-los pela lógica. A organização em torno de classes constitui um
primeiro nível da organização lógica. A organização em torno de
namespaces constitui um segundo nível da organização lógica.
É prática comum em projetos em PHP que a organização lógica em
namespaces seja similar à estrutura de diretórios, porém não idêntica.
Inclusive, na maioria dos projetos, a organização lógica é utilizada para
determinar o caminho físico no momento de carregar as classes.
Em um primeiro momento, vamos colocar as classes criadas nos capítulos
anteriores dentro da nova estrutura de diretórios proposta. A classe
Connection, por exemplo, cará dentro do diretório Lib/Livro/Database e
pertencerá ao Namespace Livro\Database. Veja que o Namespace é
bastante similar à estrutura de diretórios, sendo que somente deixamos
de fora a primeira parte /Lib. Agora a classe Connection fará parte do
Namespace Livro\Database, o que é determinado no início do arquivo.
Classes de outros Namespaces devem ser explicitamente importadas por
meio do operador use.
 Lib/Livro/Database/Connection.php
<?php
namespace Livro\Database;
use PDO;
use Exception;
final class Connection
...
A classe Record também cará dentro do diretório Lib/Livro/Database e
pertencerá ao namespace Livro\Database, o que é determinado no início
do arquivo. Classes de outros namespaces devem ser explicitamente
importadas por meio do operador use.

 Lib/Livro/Database/Record.php
<?php
namespace Livro\Database;
use Exception;
abstract class Record implements RecordInterface
...

 Observação: classes como PDO e Exception não precisam necessariamente ser


importadas via use no início do arquivo. Elas também podem ser utilizadas com o
seu nome qualificado (com o namespace na frente), como \PDO ou \Exception,
visto que são classes do escopo global.
Agora que já de nimos como serão organizadas as classes de nosso
pequeno framework, vamos de nir como serão organizadas as classes da
aplicação. Como exemplo, vamos citar uma classe de modelo chamada
Cidade. Essa classe cará localizada em App/Model e, como estende a classe
Record, precisará indicar esse uso no início do arquivo.

 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

6.3 SPL Autoloaders


Não basta de nirmos um conjunto de classes, é preciso carregá-las
apropriadamente no momento de sua utilização. Antigamente, quando o
recurso de namespaces não existia no PHP, um simples require era
su ciente. Agora, com uma estrutura organizada em torno de
namespaces, é preciso um algoritmo mais robusto para carregamento de
classes. Assim, seguindo as recomendações da PSR, vamos usar um
carregador de classes utilizando a SPL, como demonstrado no capítulo 4.
Para fazer o carregamento das classes, criaremos dois algoritmos distintos.
O primeiro fará o carregamento das classes do framework (a partir da
pasta /Lib) e o outro fará o carregamento das classes da aplicação (a partir
da pasta /App). Esses dois carregadores (loaders) serão muito úteis e serão
posteriormente executados a partir do index da aplicação, pois todas as
requisições passarão pelo index, o que será demonstrado na próxima
seção.

6.3.1 Library Loader


Sempre que precisarmos utilizar uma classe de nosso pequeno framework
(a partir da pasta /Lib), usaremos a classe ClassLoader. Essa classe
executará um algoritmo de carregamento de classes. No capítulo 4
abordamos a SPL e, dentro de suas características, vimos a função
spl_autoload_register(), que registra um método de carregamento de
classes em uma pilha de execução. Podemos executar essa função diversas
vezes, e a cada vez “registraremos” uma função para carregamento.
A classe ClassLoader terá o método register(), que irá registrar como
método de carregamento de classes o método loadClass(), que por sua vez
receberá via parâmetro o nome da classe a ser carregada, inclusive com
seu namespace, sempre que uma classe for solicitada na aplicação (por
exemplo, new NomeDaClasse), procedendo a leitura dela a partir do sistema
de arquivos (require etc.). A classe ClassLoader observará o namespace da
classe a ser carregada e o traduzirá em um caminho de diretórios. A classe
terá também o método addNamespace(), que receberá o nome de um
namespace que deve ser carregado.
 Observação: neste exemplo, propositalmente foram suprimidas várias linhas dos
métodos da classe. Você poderá consultar o código-fonte completo da classe ao
baixar os exemplos do livro.

 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();

6.3.2 Application Loader


Sempre que precisarmos utilizar uma classe de nossa aplicação (a partir
da pasta /App), usaremos a classe AppLoader. Essa classe executará um
algoritmo de carregamento de classes. Como vimos anteriormente, a
função spl_autoload_register() registra um método de carregamento de
classes em uma pilha de execução. Podemos executar essa função diversas
vezes, e a cada vez “registraremos” uma função para carregamento.
A classe AppLoader terá o método register(), que irá registrar como
método de carregamento de classes o método loadClass(), que por sua vez
receberá via parâmetro o nome da classe a ser carregada sempre que uma
classe for solicitada na aplicação (por exemplo, new NomeDaClasse), fazendo
a leitura dela a partir do sistema de arquivos (require etc.). A classe
AppLoader terá também o método addDirectory(), que receberá o nome de
um diretório que deve ser vasculhado à procura da classe.
 Observação: neste exemplo, propositalmente foram suprimidas várias linhas dos
métodos da classe. Você poderá consultar o código-fonte completo da classe ao
baixar os exemplos do livro.

 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.

6.3.3 Exemplo de uso


Para entendermos melhor como funciona o mecanismo de carregamento
de classes, vamos escrever um exemplo isolado. O objetivo desse exemplo
é demonstrar como podemos registrar o ClassLoader e em seguida utilizar
uma classe sem nos preocuparmos com seus detalhes de localização. As
primeiras linhas instanciam o ClassLoader, adicionam o namespace e
registram-no para efetuar o carregamento das classes.
Em um segundo momento vamos importar (use) a classe Connection do
namespace Livro\Database. Veja que pudemos usar essa classe diretamente
mediante importação (use). Mas como a classe foi carregada? No
momento em que ela foi utilizada pela primeira vez (Connection::open),
automaticamente o PHP executou as funções registradas na pilha SPL
(spl_autoload_register) e encontrou o método loadClass() da classe
ClassLoader. A partir do namespace da classe (Livro\Database), o método
de carregamento sabe que deve carregar a classe a partir do diretório
Lib/Livro, o que foi indicado pelo método addNamespace(). Sempre que uma
classe do namespace Livro for requisitada, será carregada a partir desse
diretório (Lib/Livro) e a estrutura do namespace (Livro\Database\Connection)
será utilizada para a localização na estrutura de diretórios
(Lib/Livro/Database/Connection.php). Dessa forma, podemos usar a classe de
maneira transparente, sem a preocupação de saber onde ela está
localizada, pois o namespace nos dirá isso.

 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) { }

6.4 Padrões de controle


Na seção anterior, vimos como fazer o carregamento das classes de nossa
aplicação. Agora que organizamos as classes em estruturas de diretórios e
namespaces, o próximo passo será implementarmos a interação do
usuário com a aplicação, o que é feito pela interpretação de ações que
normalmente são requisitadas a partir de uma URL no protocolo HTTP.
Inicialmente vamos estudar os principais padrões usados para organizar o
uxo de controle de uma página: Page Controller e Front Controller.

6.4.1 Page Controller


Para Martin Fowler, um Page Controller é “um objeto que controla uma
requisição para uma página ou ação especí ca”. Esse objeto pode ser
representado por uma classe que representa uma página e decide qual
ação (método) deve executar para cada requisição HTTP realizada.
Quando começamos a estudar o mundo da web, suas páginas HTML e
posteriormente os programas em PHP, temos tendência a resumir tudo
em termos de scripts, de modo que um script representa um programa ou
mesmo uma pequena funcionalidade de um programa. Para executar
uma página, você passa para o servidor a localização do script, que é
processado, e então obtém o retorno desse processamento. Se
exagerarmos, podemos ter vários scripts para o mesmo programa, como
na listagem a seguir, na qual cada script pode representar uma
funcionalidade de um cadastro, por exemplo.
Programa Objetivo
/listar_pessoas.php Listar pessoas.
Programa Objetivo
/gravar_pessoas.php Inserir pessoas.
/editar_pessoas.php Editar pessoas.
/excluir_pessoas.ph Excluir
p pessoas.
Não é difícil encontrar programas estruturados dessa forma, a nal, esse
tipo de pensamento é natural quando evoluímos a partir de páginas
estáticas em HTML. Entretanto, um servidor web, em conjunto com uma
linguagem de programação dinâmica, como o PHP, pode fazer muito mais
em termos de estrutura do uxo de execução de um sistema. Uma página
em PHP é dinâmica e pode decidir sobre o seu uxo de execução baseada
em determinados parâmetros que podem ser passados via URL, como na
listagem a seguir, em que a ação é determinada pelo parâmetro chamado
method.
Programa Objetivo
pessoas.php?method=listar Listar registros.
pessoas.php? Incluir registros.
method=incluir
pessoas.php? Alterar
method=alterar registros.
pessoas.php? Excluir
method=excluir registros.
Essa decisão que tomamos no programa anterior é uma versão bastante
simpli cada de um controlador de página, que é um design pattern
também conhecido como Page Controller. Trata-se de um objeto que
interpreta uma requisição para determinada ação dentro do sistema e
decide o uxo de execução a tomar e o método a ser executado. A ideia
principal é que cada página tenha seu próprio controlador de uxo de
execução.
Para iniciar a demonstração do design pattern Page Controller, vamos
criar uma classe de controle chamada PessoaControl, cujo objetivo é
centralizar ações relativas a pessoas. Porém, para ns de demonstração,
vamos implementar somente um método chamado listar(), cuja
nalidade é listar os dados de pessoas. Como a classe PessoaControl
precisará de outras classes já desenvolvidas para interagir com o banco de
dados, vamos importar essas classes no início do arquivo. O que o
método listar() faz é basicamente abrir uma transação com a base de
dados e listar os dados de pessoas. Para isso, é importante que exista a
classe Pessoa, lha de Record em App/Model. Essa classe contém também o
método show(), que por sua vez recebe um vetor de parâmetros ($param)
correspondente aos parâmetros vindos da requisição HTTP. Caso o
parâmetro method seja listar, ele executará o método listar().
 Observação: como ainda não abordamos padrões de montagem de interface
(View), vamos exibir os dados das pessoas diretamente na classe de controle por
meio de um print.

 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.

Figura 6.2 – Page Controller.

 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.

6.4.2 Front Controller


Para Martin Fowler, um Front Controller é “um controlador que
manipula todas as requisições do sistema”. Esse controlador pode ser
representado por uma classe que recebe todas as requisições feitas em um
sistema e as distribui (delega) às classes correspondentes.
Em um sistema grande, há tarefas comuns a várias páginas ou, em alguns
casos, a todas as páginas do sistema. Podemos citar como exemplos: a
veri cação de permissão, ou seja, se o usuário tem acesso àquele
conteúdo; o carregamento do template do sistema; as de nições de
idioma (internacionalização), o timezone, entre outras. Uma aplicação
pode ser composta de um conjunto de páginas totalmente independentes
umas das outras, como também pode ter um ponto central de acesso, um
ponto em comum que coordena qual programa será executado ou qual
página será exibida. Chamamos esse ponto de entrada da aplicação de
Front Controller.
O Front Controller é um tipo de script centralizador também conhecido
por “One script serves all” ou “Um script serve a todos”. Para
implementar um script centralizador, geralmente utiliza-se o próprio
index.php. Pode-se passar um parâmetro identi cando qual programa
queremos executar, e o próprio index toma as medidas necessárias para
realizar a inclusão no código-fonte. Neste exemplo, temos um script
centralizador index.php. 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.
O script centralizador de ne os autoloaders ClassLoader (para /Lib) e
AppLoader (para /App). Também carrega eventuais bibliotecas instaladas
pelo composer (vendor). Em seguida, veri ca se há alguma requisição
($_GET). Se houver uma requisição, e se existir um parâmetro chamado
class, então ele 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.

 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.

 Observação: como ainda não abordamos os padrões de montagem de interface


(View), vamos exibir os dados das pessoas diretamente na classe de controle por
meio de um print.

 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.

Figura 6.5 – Lista de cidades.

6.4.3 Remote Facade


Os padrões vistos até o momento são voltados para o controle da
navegação de um usuário em uma página. Mas nem sempre nossa
aplicação terá um usuário convencional (pessoa) do outro lado. Em
muitas situações, nossa aplicação terá como usuário outro sistema para o
qual deverá disponibilizar informações. Sempre que o usuário de nossa
aplicação for outro sistema, precisamos pensar em uma estratégia
diferente para disponibilizar informações e também em um ponto de
acesso diferente do index.php tradicional como o que construímos. A forma
mais usual é a utilização de web services, que disponibilizam informações
sobre o protocolo HTTP, utilizando estratégias como SOAP e REST.
6.4.3.1 Web services
Web services são serviços disponibilizados pela internet por meio de
tecnologias independentes de plataforma que permitem
interoperabilidade entre aplicações por meio da entrega de serviços e a
comunicação entre aplicações por meio de padrões abertos conhecidos
como XML e JSON. Os web services permitem que tecnologias de
diferentes plataformas acionem serviços e se comuniquem umas com as
outras. Nessa comunicação, temos o papel do “client”, que é a aplicação
que solicita algo ao serviço. Um “client” pode rodar em um ambiente
desktop, servidor, dispositivos móveis e outros. Além disso, temos o
servidor de web service que fornece o serviço e se comunica com o
“client” respondendo-o sobre o protocolo HTTP. A gura 6.6 procura
ilustrar essa comunicação.

Figura 6.6 – Visão geral de web services.

6.4.3.2 Remote Facade


Como exposto no início desta seção, precisamos disponibilizar algumas
funcionalidades de nossa aplicação para aplicações externas. Para isso,
precisamos construir uma interface que concentre a responsabilidade no
lado da aplicação servidora e seja responsável internamente por diversas
chamadas a métodos internos da camada de modelo. Essa alternativa
permite que a aplicação cliente faça reduzidas chamadas à aplicação
servidora. Em vez de invocarmos vários métodos para atingir
determinado objetivo, concentramos a lógica na aplicação servidora em
uma camada a qual damos o nome de Fachada (Facade). Como estamos
em um cenário com execuções remotas, esse design pattern é chamado de
Remote Facade.
Remote Facades são ideais para utilizar em um ambiente distribuído, em
que temos uma aplicação cliente e uma aplicação servidora. Em
aplicações de negócio, as aplicações cliente precisam abrir muitas
transações com a base de dados para inserir, alterar, excluir e listar
registros. Colocar o código transacional no lado da aplicação cliente
diminui a e ciência da aplicação, além de aumentar o tráfego de dados.
Ao colocar esse código na Remote Facade, transfere-se a responsabilidade
de abrir a transação, registrar as alterações, nalizar a transação e
controlar exceções no lado da aplicação servidora (Remote Facade).
Para disponibilizarmos um serviço, o primeiro passo é criarmos um
ponto de acesso exclusivo para aplicações externas, diferente de nosso
index.php. Como vamos usar o protocolo REST, nosso ponto de acesso
será chamado de rest.php. Basicamente, ele será um servidor do protocolo
REST. O nosso servidor REST inicia pela de nição dos autoloaders da
mesma forma que foram de nidos no index.php. Em seguida, declaramos
a classe LivroRestServer. Essa classe será disponibilizada por nosso web
service. Sempre que este arquivo for invocado, ele executará o método
run() ao nal, recebendo os dados do request e delegando a execução.
Basicamente a classe LivroRestServer contém o método run(), que recebe
uma requisição ($_REQUEST) e, a partir deste request, investiga qual classe e
método do sistema devem ser executados e retornados. A partir do
request, o método run() extrai a classe (class) e método (method). Caso a
classe exista (class_exists) e o método também (method_exists), então este
método é executado por meio da função call_user_func(). O retorno da
execução deste método é formatado em JSON pela json_encode(), que
retornará as posições status (conterá o estado do retorno) e a posição data
(conterá os dados do retorno). Na ocorrência de qualquer falha, como a
classe ou o método não existirem, o retorno também será no formato
JSON, com status indicando erro, e a posição data indicando a mensagem
exata de erro.

 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"
}
}

 Observação: ao trabalhar com web services, mais do que nunca é muito


importante observar os logs de erros gerados pelo PHP. Portanto, verifique se no
php.ini a cláusula de log está ligada (log_errors = On) e se o arquivo de log está
indicado (error_log = /tmp/php_errors.log). Se o programa não funcionar, ligue e
monitore os logs, pois eles indicarão onde está o problema.

6.5 Padrões de apresentação


Antes de ler este livro, provavelmente você já deve ter escrito em algum
momento um código-fonte que misturava diferentes tecnologias, como
PHP, HTML, SQL, JavaScript, em um mesmo arquivo. Com o tempo, você
vai percebendo que não é muito produtivo trabalhar dessa maneira e que,
apesar de inicialmente funcionar, ao longo do tempo acabam criando-se
códigos de difícil manutenção e interpretação, justamente por misturar
diferentes aspectos de desenvolvimento (apresentação, lógica, regras de
negócio).
Ao criarmos códigos que misturam diferentes aspectos, camos de certa
maneira “presos” a determinadas escolhas. O código a seguir mostra
justamente como não devemos implementar. O código tem a
con guração de acesso ao banco de dados explícita, e ela deveria estar
isolada da implementação. Temos também a consulta SQL, que deveria
estar concentrada em uma classe, não espalhada no código. Temos
também o uso de funções especí cas (pg_*) do banco PostgreSQL, o que
nos deixa presos a essa tecnologia. Por m, usamos diretamente HTML
para exibir os resultados em uma tabela.
O cenário que hoje funciona bem pode se tornar desastroso em questão
de alguns meses. Ao decidirmos realocar o banco de dados, precisaremos
editar muitos arquivos para fazer essa substituição. Se precisarmos trocar
a tecnologia de banco de dados, teremos um trabalho enorme ao editar
muitos arquivos. Por m, se resolvermos trocar as tabelas (<table>) por
outras tags utilizando alguma biblioteca visual como a Bootstrap, por
exemplo, teremos novamente muitos arquivos para editar. Portanto, não é
uma boa solução desenvolver de maneira a car estritamente acoplado
com tecnologias especí cas. A seguir temos um exemplo do que
queremos evitar.
 nao_faca_isso.php
<?php
// configuração específica de SGBD
$conn = pg_connect("host=localhost port=5432 dbname=exemplos
user=postgres");
// query, podendo conter alguma lógica
$query = 'SELECT id, nome, endereco FROM cliente WHERE id_cidade=1 ORDER
BY 1';
// resultados
$result = pg_query($conn, $query);
if ($result) {
// apresentação específica usando tables
echo '<table border="1">';
while ($row = pg_fetch_assoc($result)) {
echo '<tr>';
echo '<td>' . $row['id'] . '</td>';
echo '<td>' . $row['nome'] . '</td>';
echo '<td>' . $row['endereco'] . '</td>';
echo '</tr>';
}
echo '</table>';
}
pg_close($conn);
Mas, se não devemos desenvolver dessa maneira, como poderemos fazer,
então? Bem, existem diferentes abordagens para realizar a separação dos
aspectos de desenvolvimento. No capítulo 5 vimos como isolar a camada
de acesso ao banco de dados por meio de padrões como o Active Record,
Repository e outros. Neste capítulo vamos abordar algumas estratégias
para isolar a camada de apresentação. Em seguida, abordaremos a criação
de componentes e também o uso de templates.

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).

Figura 6.7 – Formulário usando componente.


Ao utilizarmos componentes, temos um melhor reaproveitamento de
alguns comportamentos padronizados, como a montagem de formulários
e datagrids. Por outro lado, em algumas situações precisamos construir
interfaces “fora do padrão”. Nem sempre construir um componente é a
melhor saída. Quando não vislumbramos potencial de reaproveitamento,
por exemplo, a construção de um componente pode não ser o melhor
caminho.

6.5.2 Template View


Partimos para a criação de componentes de software quando a
necessidade se repete, tem características claras e uma interface bem-
de nida. No entanto, às vezes, determinada página tem uma necessidade
especí ca de apresentação que não justi ca a criação de um componente.
Neste caso podemos utilizar um Template View.
Template View é um design pattern que permite exibilidade na
apresentação da aplicação por meio do uso de HTML. A ideia do
Template View é a apresentação de HTML com pequenas marcações em
seu conteúdo, sendo essas marcações substituídas por conteúdo dinâmico
de nido em nível de aplicação. Um Template View normalmente permite
substituição e repetição de trechos de um HTML em bloco. Ao ouvir
sobre Template View pela primeira vez, muitas pessoas tendem a pensar
que ele é simplesmente uma mistura de PHP com HTML, quando na
verdade a ideia é exatamente o oposto. Um Template View permite isolar
totalmente a lógica (PHP) da apresentação (HTML).
Normalmente um Template View é implementado por meio de alguma
biblioteca. Para experimentar este conceito, vamos demonstrar a
biblioteca Twig, uma das mais conhecidas e utilizadas para essa
nalidade. Em primeiro lugar, precisamos instalar a Twig com o
composer. Lembrando que ela cará no diretório vendor:
php composer.phar require "twig/twig:^2.0"

 É importante notar que o carregamento da biblioteca Twig se dá


automaticamente pelo index.php, onde existe a importação do autoloader da
pasta vendor.
Para iniciar, vamos criar o arquivo de template. O arquivo a seguir
basicamente de ne um formulário com alguns dados, como nome,
endereço e telefone, e apresenta um botão de envio. Veja que algumas
partes do arquivo têm uma marcação diferenciada como em {{nome}},
{{endereco}} e {{telefone}}. Elas representam marcações que serão
posteriormente substituídas por conteúdo dinâmico em tempo de
execução. No restante do arquivo, basicamente organizamos o conteúdo
utilizando tags HTML como <div>, <form>, entre outras, além de classes
de estilo da biblioteca Bootstrap.
 Observação: neste exemplo, demonstraremos a substituição de conteúdo
somente por meio da sintaxe das chaves duplas {{}}. Mais adiante,
demonstraremos outros recursos da mesma biblioteca.

 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.

Figura 6.8 – Formulário usando template.

6.6 Criando componentes


Anteriormente, quando abordamos padrões de apresentação, vimos que é
possível criar classes para representar elementos visuais, como formulário,
datagrid, entre outros. Nesta seção, desenvolveremos alguns componentes
comuns a vários tipos de aplicação que serão utilizados ao longo do livro,
tais como tabelas, painéis, caixas e diálogos. Mas antes criaremos algumas
classes para abstração de elementos HTML que serão posteriormente
usadas na de nição das demais.

6.6.1 Elementos HTML


Quando criamos a classe SimpleForm para demonstrar a formação de
componentes, usamos uma série de comandos echo para construir o
formulário por meio de tags HTML. Como desenvolveremos mais classes
que precisam gerar código HTML, achamos preferível criar uma classe
para representar as tags. Assim, poderemos exibir qualquer elemento (tag)
por uma estrutura orientada a objetos, sem a necessidade de usar
comandos como echo e print diretamente no código da aplicação,
tornando o código mais limpo.
Para atingir esse objetivo, iremos criar a classe Element. Essa classe
representa um elemento HTML (<p>, <img>, <table>) e recebe em seu
método construtor o nome do elemento (tag), exibindo esse elemento na
tela do usuário. É importante observar que uma tag contém propriedades;
neste caso, optamos por atribuir tais propriedades diretamente ao objeto.
Lembre-se de que, ao atribuirmos uma propriedade que não existe ao
objeto, automaticamente o método __set() intercepta essa atribuição.
Dessa forma, faremos com que as propriedades da tag sejam armazenadas
no array $properties, o qual será lido no momento da exibição da tag de
abertura pelo método open(). O método __get() retorna o valor de uma
propriedade. A classe contém ainda seu método construtor, que recebe o
nome do elemento (<img>, <div> etc.).

 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.

Figura 6.9 – Resultado do programa.

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);
}

$this->body = new Element('div');


$this->body->class = 'panel-body';
parent::add($this->body);
$this->footer = new Element('div');
$this->footer->{'class'} = 'panel-footer';
}

public function add($content) {


$this->body->add($content);
}

public function addFooter($footer) {


$this->footer->add( $footer );
parent::add($this->footer);
}
}

 Observação: neste exemplo, precisamos especificar as classes utilizadas no início


do programa (Page, Panel) 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/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.

Figura 6.10 – Resultado do programa.


Para acioná-lo, basta o acessarmos por meio da URL index.php?
class=ExemploPanelControl, onde quer que esteja rodando nosso servidor de
páginas. Veja que então a página será apresentada na tela.

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;
}
}

 Observação: neste exemplo precisamos especificar as classes utilizadas no início


do programa (Page, Table) 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.
Agora que temos a classe de nida, vamos escrever um exemplo de sua
utilização. Para isso, escreveremos um programa ExemploBoxControl que irá
construir dois painéis ($panel1 e $panel2), cada um contendo um texto
qualquer, e organizá-los lado a lado por meio do contêiner HBox. Se
desejássemos exibi-los um abaixo do outro, bastaria utilizar o contêiner
VBox (vertical box) no lugar.

 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.

6.6.4 Diálogo de mensagem e questionamento


Constantemente precisamos emitir mensagens ao usuário, seja quando
uma operação tiver sido concluída com sucesso, ou quando algum erro
tiver ocorrido. Pensando nisso, iremos construir a classe Message. O
objetivo dessa classe é exibir a mensagem que desejamos transmitir ao
usuário.
Essa classe receberá em seu método construtor um parâmetro para
determinar o tipo de mensagem (info, error) e outro para determinar a
própria mensagem a ser transmitida. Para construir a mensagem,
usaremos uma div, cuja classe de estilo será alert-info ou alert-danger,
conforme o tipo informado (info, error). Essas classes também fazem
parte da Bootstrap, mas, se no futuro quisermos mudar a aparência das
mensagens, bastará alterar essa classe.

 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 .= '&nbsp;' . $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.

Figura 6.14 – Exemplos de ação.


Essa classe também terá o método setParameter(), que possibilitará
adicionarmos parâmetros a essa chamada de função. Assim, para formar a
URL (index.php?class=MinhaClasse&method=meuMetodo&codigo=4&nome=teste),
teríamos de passar no construtor (array('MinhaClasse', 'meuMetodo')) e
executar duas vezes o método setParameter(), sendo uma para de nir o
parâmetro codigo com o valor 4 e outra para de nir o parâmetro nome com
o valor teste.
 Observação: a classe Action implementará a interface ActionInterface, que
basicamente será composta com os métodos setParameter() e serialize().
Assim, se um desenvolvedor quiser construir outra classe que represente uma ação,
terá necessariamente de implementar esses dois métodos.

 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.

6.7 Usando templates


Como vimos anteriormente, um Template View é um design pattern que
permite uma exibilidade na apresentação da aplicação por meio do uso
de HTML.
A ideia do Template View é a apresentação de HTML com pequenas
marcações em seu conteúdo, que são substituídas em tempo de execução
pela aplicação por conteúdo dinâmico. O conteúdo pode vir de consultas
ao banco de dados, entre outros meios, como web services.
Um Template View normalmente permite a substituição e a repetição de
trechos de um HTML em bloco. Nesta seção construiremos dois
exemplos, sendo que o primeiro tem como objetivo demonstrar a simples
substituição de conteúdo, enquanto o segundo tem como objetivo
demonstrar a repetição de trechos do template. É importante lembrar que
a principal intenção de um template é isolar totalmente a lógica (PHP) da
apresentação (HTML).

6.7.1 Substituições simples


No primeiro exemplo, construiremos uma página com um recado. Nesta
página teremos a frase “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.”. Veja que dentro dessa
frase há diversas marcações com a sintaxe de chaves duplas {{}}, que
permite delimitar um conteúdo a ser substituído.
 Observação: o exemplo a seguir utiliza classes da Bootstrap como a classe de
estilo jumbotron.

 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.

6.7.2 Substituições com repetições


No exemplo anterior, vimos como construir uma página a partir de um
template contendo marcações utilizando a sintaxe de chaves duplas {{}}
para fazer substituições simples. No próximo exemplo vamos demonstrar
como exibir elementos repetitivos. Para isso, faremos uma tabela que irá
exibir diversos registros de pessoas que serão posteriormente alimentados
por meio de um array.

Figura 6.16 – Resultado do programa.


O template é formado por um painel (div com classe panel) e um título
({{titulo}}). Dentro do painel, colocamos uma tabela (table com classe
table-striped). Dentro da tabela, colocamos um cabeçalho (thead) e um
corpo (tbody). Dentro do corpo da tabela, no qual serão inseridas
repetidas linhas contendo dados, utilizamos uma sintaxe diferenciada ({%
for pessoa in pessoas %}), indicando que ali serão percorridos vários
registros a partir da posição pessoas do vetor de substituições. Isso indica
que a posição pessoas será um array. Dentro do foreach encontramos a
sintaxe ({{pessoa.codigo}}), indicando que ali será exibido o atributo
codigo de cada pessoa. Depois, veremos como passar essas informações
para o template.
 Observação: o exemplo a seguir utiliza classes da Bootstrap como as classes
panel e table-striped.

 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.

Figura 7.1 – Estrutura de classes para formulários.


A partir daqui, podemos começar a construir as classes que farão parte
desse ecossistema de objetos inter-relacionados que é o formulário.

7.1.1 Classe lógica para formulários


A primeira classe que criaremos representará um formulário e será
também a mais importante, tendo em vista que centralizará as chamadas
para as demais. Assim, para agruparmos todos os tipos de campo que
vimos anteriormente (input texto, combo, radio, check etc.), será
necessário criar uma estrutura que represente um formulário. Essa
estrutura será uma representação lógica em memória e agrupará vários
elementos (campos) e ações. Para isso, criaremos a classe Form.
 Observação: neste exemplo, precisamos especificar as classes utilizadas no início
do programa (Element, Table) 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.
A nossa classe Form começa em seu método construtor recebendo o nome
que usaremos para o formulário a ser construído. Caso não
especi quemos um nome, automaticamente o nome my_form será adotado.
O método setName() é usado para alterar o nome do formulário, e
getName() é usado para obter esse nome. Os métodos setTitle() e
getTitle() são usados para atribuir um título ao formulário e também
obter um título para ele.
O método addField() adiciona um campo ao formulário que é
armazenado em um vetor interno ($this->fields). Este receberá um rótulo
de texto ($label), um objeto representando o elemento a ser adicionado ao
formulário ($object) e terá um tamanho ($size) opcional. O objeto a ser
adicionado ao formulário deverá implementar a interface
FormElementInterface. O método getFields() retorna os campos
adicionados.
O método addAction() adiciona uma ação ao formulário. Ele receberá
como parâmetros um rótulo de texto ($label) e uma ação ($action). A ação
precisará implementar a interface ActionInterface, normalmente um
objeto TAction.
O método setData() é usado para preencher inicialmente o formulário. Ele
deve sempre ser utilizado antes do método show(). Seu objetivo é passar
como parâmetro um objeto qualquer, cujas propriedades são utilizadas
para preencher cada um dos campos do formulário. Para isso é necessário
que os nomes das propriedades coincidam com os nomes dos campos do
formulário. Por exemplo, se passarmos um objeto $pessoa cuja
propriedade $nome contenha “Maria” e a propriedade $endereco contenha
“Rua Conceição”, esse método preencherá o campo nome do formulário
justamente com o valor “Maria” e o campo endereco com o valor “Rua
Conceição”. Para isso, ele percorrerá os campos ($this->fields) do
formulário e, em cada campo, veri cará se o objeto passado como
parâmetro contém um atributo com o mesmo nome do campo ($object-
>$name). Caso seja verdadeiro, passará esse valor para o campo por meio
do método setValue().
O método getData(), por sua vez, é utilizado para retornar os dados de
um formulário depois que este é processado. Quando um formulário é
processado, seus dados são enviados pelo método POST e disponibilizados
ao programador pelo vetor $_POST, ao passo que os arquivos são
disponibilizados pelo vetor $_FILES.
O que o método getData() faz é percorrer esse vetor e disponibilizar ao
usuário um objeto contendo em cada uma de suas propriedades
exatamente os valores que foram enviados pelo formulário. Dessa
maneira, o nome das propriedades (atributos) deverá coincidir com os
nomes dos campos do formulário. Se o usuário tiver preenchido “Rua
General Neto” em um campo com o nome rua, esse objeto retornado
conterá a propriedade $rua exatamente com esse valor. A princípio, o
objeto retornado será da classe stdClass, mas o programador poderá
indicar outra classe por meio do parâmetro.
O método getData() cria um objeto ($object) da classe informada (o
padrão é stdClass). Em seguida, percorre os campos ($this->fields) do
formulário. Para cada um, veri ca se há algo postado ($_POST), atribuindo
ao objeto ($object->$key). Em seguida, também é percorrido o vetor
$_FILES para veri car se algum arquivo foi enviado. Por m, o objeto
construído é retornado.

 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();
}

7.1.2 Classes para apresentação de formulários


Você deve ter percebido que a classe recém-criada Form não implementa
método de apresentação, como show(). A classe Form representa a
estrutura lógica de um formulário em memória, mas não trata de sua
exibição em tela. Para exibir o formulário, usaremos uma classe de
apresentação, a ser criada logo a seguir. Utilizaremos a biblioteca
Bootstrap para a montagem real do formulário.
Ao utilizar uma biblioteca como a Bootstrap, devemos seguir certos
padrões para estruturar o HTML e convenções de nomenclatura para as
tags do HTML para que os estilos sejam apropriadamente aplicados.
Como exemplo, podemos citar a estruturação de um formulário que, na
versão atual da Bootstrap no momento desta escrita, acompanha o padrão
a seguir. Como você pode ver, cada elemento a ser preenchido em um
formulário está encapsulado dentro de uma div com a classe form-group.
Além disso, cada elemento utiliza a classe form-control, e o botão de
submissão usa a classe btn btn-default.
<form>
<div class="form-group">
<label for="exampleInputEmail1">Email</label>
<input type="email" class="form-control" id="exampleInputEmail1"
placeholder="Email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Senha</label>
<input type="password" class="form-control" id="exampleInputPassword1"
placeholder="Password">
</div>
<button type="submit" class="btn btn-default">Enviar</button>
</form>
Para apresentar o formulário, criaremos uma classe chamada FormWrapper,
que receberá a de nição lógica de um formulário (classe Form) como
parâmetro e tratará de sua exibição seguindo a biblioteca Bootstrap.
Enquanto a classe Form é responsável pela representação lógica de um
formulário em memória (campos, labels, ações), a classe FormWrapper cuida
de sua exibição. Há um padrão de projeto chamado Decorator que pode
nos auxiliar nessa nalidade.
Um Decorator é um padrão de projeto que consiste em escrever uma
classe que “adiciona” funcionalidades, recursos ou comportamento à
outra já existente em tempo de execução. Isso signi ca que essa
“agregação” de funcionalidades ocorre de maneira dinâmica. Um
Decorator diminui a necessidade de alterarmos a classe original e também
a necessidade de estendê-la, criando uma classe derivada. Um Decorator é
mais exível que a herança, pois esta é um relacionamento estático entre
duas classes; já um Decorator pode ser aplicado em tempo de execução
conforme a necessidade. A gura 7.2 procura demonstrar a ideia principal
de um Decorator, que é atuar sobre um componente já existente
agregando-o ao comportamento, como se fosse uma nova camada.

Figura 7.2 – Camadas de decoração.


Vamos utilizar a ideia do design pattern Decorator e escrever uma nova
classe para montagem de formulários chamada FormWrapper, que irá
“transformar” a classe já existente. A nova classe atuará sobre um objeto
da classe Form já instanciado e percorrerá os objetos que fazem parte do
formulário, gerando uma nova saída de HTML; dessa vez, no formato
esperado pela biblioteca Bootstrap.
 Observação: para a demonstração do design pattern Decorator, foi escolhida a
biblioteca Bootstrap, pelo fato de ser muito difundida na época desta edição do
livro, mas, a partir do momento em que você entender o conceito, poderá aplicar o
mesmo design pattern para fazer outros tipos de transformação.
Para iniciar, a classe FormWrapper receberá em seu método construtor uma
instância já desenvolvida da classe Form. Essa instância será armazenada
no atributo $decorated.
Uma característica fundamental de um Decorator é que ele deve se
comportar da mesma maneira que o objeto decorado, mas adicionando
funcionalidades. Isso quer dizer que a classe FormWrapper deve continuar
respondendo a métodos como addField(), addAction() e setData(), por
exemplo. Para que isso seja possível, uma vez que esses métodos não
fazem parte desta classe, entra em cena o método __call(). Sempre que
um método novo é solicitado, o método mágico __call() é executado. Ele
então redireciona a execução para o objeto decorado ($this->decorated).
Assim, métodos como addField(), addAction() e setData() continuarão a
funcionar.
Grande parte do trabalho de “transformação” da nova classe concentra-se
no método show(). Sempre que ele for executado, percorrerá a estrutura já
desenvolvida do objeto decorado, investigando-o sobre campos e ações
presentes, e moldará estes conforme a estrutura desejada da biblioteca
Bootstrap.
Dentro do método show(), um novo elemento <form> é construído com a
classe correspondente. Logo após de nir alguns atributos, é executado
um foreach, no qual os campos do formulário são percorridos. Para cada
campo, é instanciado um elemento <div> de classe form-group que conterá
o campo do formulário e também um label. Após percorrer os campos, as
ações do formulário são percorridas (getActions), e, para cada ação, um
botão (Button) é criado e adicionado a um elemento agrupador ($group).
Ao nal, criamos um painel (Panel), para, dentro desse painel, inserir o
formulário ($element). Já no rodapé (addFooter) desse painel serão
dispostas as ações que estão contidas no elemento $group. A gura a
seguir demonstra a relação entre as classes Form e FormWrapper.
Figura 7.3 – Form e FormWrapper

 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');

7.1.3 Classes para campos de formulários


Existem algumas características que estão presentes em todos os campos
de entrada de dados em um formulário. Para não escrevermos tais
características em cada uma das classes, iremos primeiro construir a
classe base Field, que irá prover essa estrutura, ou seja, prover
comportamento a todos os outros tipos de campo. Podemos dizer que
todo e qualquer campo de um formulário de entrada de dados terá um
nome, um tamanho, um valor, assim como poderá ser editado ou não.
Dessa maneira, a classe-base irá prover métodos como setName() e
getName() para de nir e retornar o nome de um campo; setLabel() e
getLabel() para de nir e retornar um rótulo de texto para o campo;
setValue() e getValue() para de nir e retornar o valor de um campo,
setProperty() e getProperty() para de nir e retornar propriedades do
componente, entre outros métodos comuns que serão vistos a seguir.
O método construtor de nirá inicialmente o campo como editável, por
meio do método setEditable(). Além disso, o parâmetro do método
construtor representa o nome do campo, que é utilizado pelo setName(). A
gura 7.4 demonstra a estrutura da classe Field.

Figura 7.4 – Classe Field.

 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;
}

7.1.3.1 Classe para rótulos de texto


Um formulário não é apenas composto de elementos de entrada de dados.
Em frente a cada campo, precisaremos exibir um rótulo de texto (label),
conforme pode ser visto na gura 7.5. Para isso, criaremos a classe Label.

Figura 7.5 – Um rótulo de texto.


A classe Label será responsável por exibir um texto na tela. Em seu
método construtor, receberá o conteúdo desse label ($value). Internamente
ela criará um atributo tag da classe Element. O método add() permitirá
ainda adicionarmos outros conteúdos ao label, e o método show()
simplesmente exibirá o label. Na gura 7.6, a classe é representada pelo
diagrama de classes.

Figura 7.6 – Classe Label.

 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();
}
}

7.1.3.2 Classe para campos de entrada, hidden e senhas


O elemento de formulário mais utilizado será o input de texto, que
chamaremos de Entry. Um Entry será responsável por exibir em tela um
campo de entrada para digitação de dados, como pode ser visto na gura
7.7.

Figura 7.7 – Input de texto.


A classe Entry conterá todos os métodos herdados de sua superclasse
Field e terá, além deles, um método show() próprio, responsável por por
exibir o campo na tela. Para isso, ela cria um objeto $this->tag da classe
Element para construção da tag <input>. A gura 7.8 demonstra a estrutura
das classes relacionadas.
Em virtude da facilidade da utilização da classe Element, só precisamos
de nir seus atributos, tais como name, value, type e style, antes de utilizar
seu método show().
As propriedades $name e $value, por exemplo, foram de nidas
respectivamente pelos métodos setName() e setValue() da classe pai.
Se o objeto não for editável, o que é retornado pelo método getEditable(),
de nimos ainda o atributo readonly. O objeto pode ter ainda
propriedades ($this->properties) de nidas pelo método setProperty() da
classe pai. Neste caso, essas propriedades são transformadas em atributos
do objeto $tag.

Figura 7.8 – Classe Entry.

 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
}
}

 Além da classe Entry, estão disponíveis as classes Password (para digitação de


senhas) e Hidden (para campos escondidos). A estrutura das três classes é bastante
semelhante. Uma das diferenças é o atributo type, que varia entre text (digitação de
textos), password (digitação de senhas) e hidden (campos escondidos).

7.1.3.3 Classe para campos de arquivo


A classe File será responsável por exibir na tela um componente para
envio de arquivos. Esse componente permitirá ao usuário selecionar um
arquivo por meio de um botão que abre um diálogo de seleção de
arquivos. Quando o formulário for processado, esse arquivo será enviado
ao servidor para processamento e cará disponível ao programador em
alguma pasta temporária, provavelmente em /tmp. Neste momento, o
programador geralmente move o arquivo para alguma pasta de nitiva.
Veja na gura 7.9 a classe File.

Figura 7.9 – Classe File.


Note que uma das poucas diferenças entre a classe File e as anteriores,
Entry e Password, é novamente o tipo de campo (type), que neste caso é
file. Como a classe File também é lha de Field, ela contém todos os
métodos desta (construtor, setName(), getName(), setEditable(),
getEditable(), entre outros).

 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
}
}

7.1.3.4 Classe para textos longos


A classe Text será responsável pela construção de um objeto do tipo
textarea. Um objeto text area disponibiliza uma área de digitação de
texto na qual o usuário pode entrar com várias linhas de texto,
diferentemente do componente Entry, em que o usuário pode entrar com
somente uma linha de dados. Diferentemente das classes anteriores, a
classe Text terá também o método setSize(), que permitirá de nir a
largura e a altura de um campo em pixels. Veja na gura 7.10 a classe Text.
Figura 7.10 – Classe Text.
No método show() a tag <textarea> será criada e exibida na tela. Note que
seu funcionamento continua muito similar ao das classes anteriores.
Antes de exibir a tag na tela, utilizamos o método add() para adicionar o
texto (seu conteúdo). O conteúdo da tag passa pela função
htmlspecialchars() para que caracteres especiais como “<” e “>” sejam
corretamente convertidos para seus respectivos códigos HTML &lt; e
&gt;.

 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
}
}

7.1.3.5 Classe para combos


Uma combo é uma lista de opções que permite a seleção de um único
elemento; ela é construída em HTML pelo elemento <select> em
conjunto com vários elementos lhos <option>. Uma combo normalmente
é utilizada para seleção de elementos em quantidades não muito grandes.
Na gura 7.11 está sendo utilizada para seleção do estado.

Figura 7.11 – Uma combo.


Uma combo tem diversos elementos (itens). Para de nirmos as opções da
combo, construímos o método addItems(), que irá receber um array e
armazená-lo na propriedade $items. Os índices desse array serão
utilizados para representar os valores das opções, e os conteúdos serão
exibidos ao usuário.
No método show(), além de criarmos a tag <select>, precisamos percorrer
os itens adicionados à combo pelo método addItems() por meio de um
foreach e, para cada opção, instanciar um elemento Element do tipo
<option>, adicionando-o ao elemento principal ($tag->add($option)). Mas,
antes disso, é adicionado um elemento vazio para que o usuário possa
deixar a combo sem preenchimento. A combo pode conter um valor
($this->value). Sempre que o valor coincidir com a chave de alguma das
opções ($chave == $this->value), a opção deve ser selecionada ($option-
>selected). Quando a tag select ($tag) for exibida pelo seu método show(),
suas opções também serão exibidas. Veja na gura 7.12 a classe Combo.

Figura 7.12 – Classe Combo.

 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
}
}

7.1.3.6 Classe para check buttons


O próximo passo é construir uma classe para construir check buttons.
Um check button é representado por uma pequena caixa que pode ser
marcada e desmarcada. Um conjunto de check buttons permite ao
usuário fazer uma seleção não exclusiva, ou seja, selecionar vários itens ao
mesmo tempo. A gura 7.13 demonstra um conjunto de check buttons.

Figura 7.13 – Conjunto de check buttons.


Para permitir o uso de check buttons, vamos construir duas classes. A
primeira será CheckButton, responsável pela exibição de uma única caixa.
A segunda será CheckGroup, responsável por compor uma série de objetos
CheckButton, formando um conjunto de caixas de marcação.
Na gura 7.14 temos a estrutura da classe CheckButton. Assim como outras
já criadas, ela irá estender a classe Field e terá basicamente um método
show() que construirá um input em tela do tipo checkbox, de maneira
muito similar à de outras classes já criadas, como Entry, Password, entre
outras.

Figura 7.14 – Classe CheckButton.

 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.

Figura 7.15 – Classe CheckGroup.


A classe CheckGroup também irá estender a classe Field e terá métodos
como o setLayout(), que indicará se os botões estarão um ao lado do
outro (horizontal) ou um abaixo do outro (vertical). A única diferença é
que no layout vertical é dada uma quebra de linha ao nal de cada opção.
O método addItems() recebe um conjunto de opções e o armazena
internamente na propriedade $items. As opções serão representadas por
um array indexado. O índice do array será utilizado como valor-chave
para cada check button, ao passo que o valor de cada opção será exibido
ao usuário como rótulo.

 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";
}
}
}
}
}

7.1.3.7 Classe para radio buttons


O próximo passo é criar uma classe para construir radio buttons. Um
radio button é representado por um pequeno círculo que pode ser
marcado. Um conjunto de radio buttons com o mesmo nome permite ao
usuário fazer uma seleção exclusiva, ou seja, selecionar somente um item
ao mesmo tempo. Ao selecionar um item, os outros serão
automaticamente desmarcados. A gura 7.16 demonstra um conjunto de
radio buttons.

Figura 7.16 – Conjunto de radio buttons.


Para permitir o uso de radio buttons, vamos construir duas classes. A
primeira será RadioButton, responsável pela exibição de um único radio
button. A segunda será RadioGroup, responsável por compor uma série de
objetos RadioButton, formando um conjunto de radio buttons.
Na gura 7.17 temos a estrutura da classe RadioButton. Assim como outras
já criadas, ela irá estender a classe Field e terá basicamente um método
show() que construirá um input em tela do tipo radio, de maneira muito
similar à de outras classes já criadas como Entry, Password, entre outras.

Figura 7.17 – Classe RadioButton.

 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.

Figura 7.18 – Classe RadioGroup.


A classe RadioGroup também irá estender a classe Field e terá métodos
como o setLayout() que indicará se os botões estarão um ao lado do outro
(horizontal) ou um abaixo do outro (vertical). A única diferença é que no
layout vertical é dada uma quebra de linha ao nal de cada opção. O
método addItems() recebe um conjunto de opções e armazena
internamente na propriedade $items. As opções serão representadas por
um array indexado. O índice do array será utilizado como valor-chave
para cada radio button, ao passo que o valor de cada opção será exibido
ao usuário como rótulo.

 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.

7.1.4.1 Testando o envio


Agora que já de nimos um conjunto de classes para construção de
formulários, é necessário escrevermos alguns exemplos de utilização para
que a estrutura e os métodos disponíveis para uso sejam assimilados com
mais facilidade, a nal, foram várias classes criadas neste capítulo. Para
demonstrarmos o uso das classes em conjunto, criaremos uma nova
página (Page) contendo um pequeno formulário para contatos que terá
campos como nome, email, assunto e mensagem. O formulário será
usado somente para apresentar os dados preenchidos pelo usuário em
tela, não enviará emails.
 Observação: neste exemplo, precisamos especificar as classes utilizadas no início
do programa (Page, Action) 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.
Como a classe principal entre todas as criadas é Form, visto que ela
centraliza as chamadas às demais, iniciaremos nossa explicação por
intermédio dela. O exemplo inicia efetivamente em seu método construtor
pela criação do objeto $this->form da classe Form, empacotado pela
FormWrapper. Como parâmetro do método construtor, informamos o nome
do formulário. Como a classe Form é só uma representação lógica do
formulário, passamos esse objeto recém-criado como parâmetro do
construtor da classe FormWrapper. A classe FormWrapper implementa o
método show(), que trata de exibir o formulário conforme os padrões da
biblioteca Bootstrap. Logo em seguida, de nimos o título do formulário
pelo método setTitle().
Após criarmos o formulário, inicia-se a criação dos objetos que farão
parte dele. Assim, criamos objetos para nome, email, assunto e mensagem
utilizando as classes Entry, Combo e Text criadas anteriormente. Como
parâmetro do método construtor, informamos o nome do objeto, que
também identi cará a informação passada no POST. Depois de criar os
objetos, podemos ainda incrementá-los, de nindo mais algumas
características, como zemos ao adicionar os itens da combo (objeto
assunto) por meio do método addItems() e ao ajustar o tamanho do
campo mensagem pelo método setSize().
Criados os objetos e de nidas algumas de suas características,
adicionamos uma ação ao formulário, o que é feito pelo método
addAction() da classe Form. Esse método cria internamente um objeto da
classe Button e o adiciona ao nal do formulário. O método addAction()
recebe como parâmetros o label e a ação do botão, identi cada por um
objeto da classe Action, contendo um array de duas posições, sendo a
primeira com o objeto (ou nome de classe) e a segunda com o nome do
método a ser executado. Por m, o formulário é adicionado à página.
 Observação: lembre-se de que, para acessar o programa a seguir, é preciso
acessar o index.php, que é o Front Controller, pela URL index.php?
class=ContatoForm.

 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.

Figura 7.19 – Formulário de contato.

 Observação: veja que todos os campos textuais foram passados integralmente,


com exceção da Combo Box, na qual o conteúdo passado no POST é o índice do
array de opções, não o seu conteúdo.
Como você pode perceber, o formulário funcionou adequadamente.
Porém o resultado visual está longe de impressionar. Para falar a verdade,
o formulário cou feio. Mas o que fazer para melhorá-lo? Poderíamos
de nir um conjunto de classes CSS para melhorar o visual por meio de
tags como form, table, entre outras. No entanto 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. Os wrappers permitirão
desenvolver uma “camada” ao redor do formulário-padrão criado por nós,
embelezando-o.

7.1.4.2 Formulário com valores prede nidos


Como pudemos ver no exemplo anterior, o formulário abriu inicialmente
vazio para o usuário preencher. Em seguida, o botão de ação foi clicado,
provocando a postagem e a exibição dos dados. Mas e se quiséssemos que
o formulário fosse carregado previamente com alguns dados para o
usuário? Um determinado campo poderia vir com instruções de
preenchimento, por exemplo.
Para que um campo já venha preenchido com um valor-padrão, a
primeira forma seria chamar o método setValue() logo após instanciar os
campos. O método setValue() de ne o valor de um objeto. Como os
objetos somente são “renderizados” em tela pelo método show(), o último
método a ser executado na página, qualquer transformação neles é válida
antes que sejam exibidos na tela. Veja que, no caso de uma Combo, é
necessário informar o índice da opção selecionada a partir do array de
opções.
$assunto = new Combo('assunto');
$mensagem = new Text('mensagem');
$mensagem->setValue(
'Escreva aqui o motivo do contato. Seja o mais claro
possível...');
$assunto->setValue(3);
Essa não é a única maneira de se ter os campos com valores prede nidos
no formulário. A outra maneira é criar um método de preenchimento e
chamá-lo por meio da URL. Nesse caso, estamos criando um método
chamado onLoad(), cuja nalidade é “passar” os dados para o formulário.
Para passar os dados ao formulário, utilizamos o método setData() da
classe Form. Conforme a implementação da classe Form, esse método recebe
como parâmetro um objeto em que cada atributo deve coincidir com o
nome dos campos do formulário. Neste caso, estamos declarando um
objeto ($obj) da classe stdClass e de nindo sua propriedade mensagem. Ao
executarmos o método setData(), esse objeto é passado para a classe Form,
que “transmite” o conteúdo da propriedade mensagem para o campo
correspondente.
Para executar o método onLoad(), preenchendo o formulário, devemos
acionar a URL index.php?class=ContatoForm&method=onLoad. Neste caso é
possível visualizar o formulário com o campo “mensagem” já preenchido.
function onLoad() {
$obj = new stdClass;
$obj->mensagem = 'Escreva aqui o motivo do contato. Seja o mais claro
possível...';
$this->form->setData($obj);
}

7.1.4.3 Gravando no banco de dados


Até o momento, escrevemos um exemplo de como usar as classes criadas
para criar um formulário com dados estáticos, testando o envio de valores.
O próximo passo é criar um formulário que permita o cadastro e a edição
de valores a partir de uma tabela da base de dados. Para isso, vamos criar
a tabela, a classe Active Record para manipulá-la, bem como o formulário.
A princípio, vamos lembrar que 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. Lá temos a base de dados
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
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.1.4.4 Editando dados existentes


Como vimos no exemplo anterior, o formulário abriu inicialmente vazio
para o usuário preencher. Em seguida, o botão de ação foi clicado,
provocando a postagem e a gravação dos dados. Mas e se quiséssemos
que o formulário fosse carregado previamente com dados de um
funcionário já cadastrado no banco de dados para a edição? Neste
exemplo, vamos complementar a classe construída anteriormente para
permitir essa funcionalidade.
Como já vimos anteriormente, uma forma de atingir esse resultado é criar
um método de preenchimento e chamá-lo pela URL. Neste caso, vamos
criar um método chamado onEdit(), cuja nalidade é “carregar” os dados
do formulário com um registro já existente 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=FuncionarioForm&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. Posteriormente, quando já tivermos criadas as classes para datagrids, a
ação de edição será executada a partir de uma datagrid, e não a partir da URL.
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 apresenta um array contendo 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
Funcionario::find() para tentar localizar esse objeto na base de dados.
Caso o objeto tenha sido localizado, ele é utilizado para preencher o
formulário por meio do método setData(). Mas antes o campo idiomas,
que no banco de dados está como uma string separada por vírgulas, é
convertido em array, que é o formato aguardado pelo componente
CheckGroup.
public function onEdit($param) {
try {
Transaction::open('livro');
$id = $param['id'];
$funcionario = Funcionario::find($id);
if ($funcionario) {
if (isset($funcionario->idiomas)) {
$funcionario->idiomas = explode(',', $funcionario-
>idiomas);
}
$this->form->setData( $funcionario );
}
Transaction::close();
}
catch (Exception $e) {
new Message('error', $e->getMessage());
}
}

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).

Figura 7.21 – Listagem construída com a classe Datagrid

7.2.1 Classe lógica para Datagrids


Para iniciarmos, precisamos criar a classe principal: Datagrid. A classe
Datagrid será responsável pela exibição de listagens. A classe Datagrid terá
o método addColumn(), que será utilizado para adicionar uma coluna à
listagem. Cada coluna será representada por um objeto do tipo
DatagridColumn, classe que criaremos em seguida. Um objeto
DatagridColumn conterá informações relativas à coluna, tais como nome,
rótulo, alinhamento e largura. Para adicionar uma coluna na Datagrid,
iremos simplesmente adicionar, por meio de uma operação que constitui
uma agregação, o objeto DatagridColumn ao vetor $columns, propriedade da
classe Datagrid.
Da mesma forma que adicionamos colunas à listagem, adicionamos ações.
Estas serão representadas por objetos geralmente do tipo Action e
conterão informações como o rótulo de texto da ação, o campo passado
como parâmetro para a ação ($field), bem como um ícon ($image). As
ações serão armazenadas no vetor $actions.
O método addItem() será utilizado para adicionar um objeto à Datagrid.
Esse objeto conterá os dados que serão distribuídos em cada uma das
colunas da listagem. O objeto poderá ser tão simples quanto um objeto
stdClass, contendo somente propriedades, como também poderá ser um
objeto Active Record, derivado da classe Record. Cada objeto passado para
o método addItem() será armazenado em um vetor chamado $this->items
para ser usado posteriormente na exibição. A gura 7.22 demonstra a
estrutura da classe e suas agregações.

Figura 7.22 – Classe 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 = [];
}
}

7.2.2 Classe para colunas da datagrid


A classe DatagridColumn será utilizada para representar as características
que fazem parte de uma coluna em uma listagem. Para isso, essa classe
receberá em seu método construtor o nome do campo do banco de dados
que a coluna exibirá ($name), o rótulo de texto que será exibido no título
da coluna ($label), o alinhamento da coluna ($align) e a largura da coluna
($width). Os métodos getName(), getLabel(), getAlign() e getWidth() serão
utilizados para retornar essas propriedades de nidas pelo método
construtor.
O método setAction() será utilizado para de nir uma ação que será
executada sempre que o usuário clicar sobre o título da coluna.
Normalmente utiliza-se esse recurso para de nir uma ação de ordenação.
Essa ação será opcional, mas, caso seja necessária, deverá ser representada
por um objeto da classe Action.
O método getAction(), por sua vez, será utilizado para retornar essa ação
na forma de URL pelo método serialize() da classe Action. Assim, se
indicarmos como ação da coluna o método ordenar da classe PessoasList,
o método getAction() retornará ?class=PessoasList&method=ordenar.
O método setTransformer() será utilizado para de nir o nome de uma
função ou de um método do PHP ou de nido pelo usuário, que será
aplicado sobre cada um dos elementos listados naquela coluna,
modi cando-os de acordo com a necessidade (aplicando uma máscara,
convertendo para maiúsculas etc.). O método getTransformer() retornará
essa função. Veja na gura 7.23 a classe DatagridColumn.

Figura 7.23 – Classe DatagridColumn.

 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;
}
}

7.2.3 Classe para apresentação da datagrid


Você deve ter percebido que a classe recém-criada Datagrid não
implementa método de apresentação, como show(). A classe Datagrid
representa logicamente uma datagrid mas não a exibe verdadeiramente.
Para exibir a datagrid, utilizaremos uma classe de apresentação, a ser
criada logo a seguir. Utilizaremos a biblioteca Bootstrap para a montagem
real da datagrid.
Como vimos anteriormente, ao utilizar uma biblioteca como a Bootstrap,
devemos seguir certos padrões para estruturar o HTML e convenções de
nomenclatura para as tags do HTML, para que os estilos sejam
apropriadamente aplicados. Como exemplo, podemos citar a estruturação
de uma tabela que, na versão atual da Bootstrap no momento desta
escrita, obedece ao padrão a seguir. Veja que a principal característica é a
presença das classes de estilo table-striped table-hover, responsáveis
respectivamente pelo efeito “zebra” das linhas e também pelo destaque da
linha ao se passar o mouse sobre.
<table class="table table-striped table-hover" style="max-width:700px">
<thead>
<tr>
<th align="center" width="80"> Código </th>
<th align="left" width="200"> Nome </th>
<th align="left" width="150"> Email </th>
<th align="left" width="230"> Assunto </th>
</tr>
</thead>
<tbody>
<tr>
<td align="center" width="80"> 1 </td>
<td align="left" width="200"> Maria da Silva </td>
<td align="left" width="150"> maria@email.com </td>
<td align="left" width="230"> Dúvida sobre Formulários </td>
</tr>
</tbody>
</table>
Para apresentar a datagrid, criaremos uma classe chamada
DatagridWrapper, que receberá a de nição lógica de uma datagrid (classe
Datagrid) como parâmetro e tratará de sua exibição seguindo a estrutura
Bootstrap. Quando escrevemos a classe datagrid, não de nimos seu
formato de exibição. Para exibir a datagrid, vamos implementar a classe
DatagridWrapper, para que esta “transforme” a datagrid atual em uma
datagrid com as características da Bootstrap. Como já vimos, o padrão
decorator pode nos auxiliar justamente nessa nalidade.
Um decorator, como já visto, é um padrão de projeto que consiste em
escrever uma classe que “adiciona” funcionalidades, recursos ou
comportamento à outra em tempo de execução. Isso signi ca que essa
“agregação” de funcionalidades ocorre de maneira dinâmica. Um
decorator diminui a necessidade de alterarmos a classe original e também
de estendê-la, criando uma classe derivada. Um decorator é mais exível
que a herança, pois esta é um relacionamento estático entre duas classes;
já um decorator pode ser aplicado em tempo de execução conforme a
necessidade. A gura 7.24 procura demonstrar a ideia principal de um
decorator, que é atuar sobre um componente preexistente agregando
comportamento, como se fosse uma nova camada.

Figura 7.24 – Camadas de decoração.


Vamos utilizar a ideia do design pattern decorator e escrever uma nova
classe para montagem de datagrids chamada DatagridWrapper, que irá
“transformar” a classe já existente. A nova classe atuará sobre um objeto
da classe Datagrid já instanciado e irá modi cá-la para que sua estrutura
que conforme o formato esperado pela biblioteca Bootstrap.
 Observação: para a demonstração do design pattern Decorator foi escolhida a
biblioteca Bootstrap, por ser muito difundida na época da edição deste livro. Mas, a
partir do momento que você entender o conceito, poderá aplicar o mesmo design
pattern para realizar outros tipos de transformação.
Para iniciar, a classe DatagridWrapper receberá em seu método construtor
uma instância preexistente da classe Datagrid, sobre a qual fará as
transformações necessárias para adequar a estrutura de tabela existente e
convertê-la na estrutura que a biblioteca Bootstrap espera. A instância já
existente será armazenada no atributo $decorated.
Como a ideia do design pattern é adicionar comportamento a um objeto
preexistente, não podemos “anular” os comportamentos (métodos) já
existentes. Também não vamos reescrevê-los aqui. Métodos da classe
Datagrid como addColumn(), addAction(), addItem(), entre outros, devem
continuar funcionando a partir da classe nova. Para que eles continuem a
funcionar, sempre que o desenvolvedor executar um método não
encontrado na classe DatagridWrapper, automaticamente a execução será
redirecionada para o objeto “decorado”, representado pelo atributo
$decorated. Esse redirecionamento de chamadas é obtido pelo método
__call(), que é automaticamente executado sempre que um método não
encontrado na classe atual for executado. Nesses casos, a execução é
redirecionada por meio da função call_user_func_array() para o objeto
decorado, repassando também os parâmetros recebidos. Da mesma
maneira que o __call() redireciona chamadas de métodos, o __set()
redireciona de nição de atributos.

 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.

7.2.4.1 Listagem com dados estáticos


Agora que já de nimos um conjunto de classes para construção de
datagrids, é necessário escrevermos alguns exemplos de utilização para
que a estrutura e os métodos disponíveis para uso sejam assimilados com
mais facilidade, a nal, foram várias classes criadas nesta seção. Para
demonstrarmos o uso das classes em conjunto, criaremos uma nova
página (Page) contendo uma pequena listagem de contatos que terá
campos como nome, email e assunto. A listagem será utilizada somente
para apresentar dados estáticos, e posteriormente criaremos um exemplo
que faz a leitura do banco de dados.
 Observação: neste exemplo, precisamos especificar as classes utilizadas no início
do programa (Page, Datagrid) 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.
O primeiro exemplo inicia em seu método construtor pela criação do
objeto da classe Datagrid. Em seguida, de nimos uma propriedade da
Datagrid (border).
A classe Datagrid aceita tal atribuição, pois ela é subclasse de Table, que
por sua vez é subclasse de Element, que fornece tal recurso (atribuição).
Em seguida, de nimos quatro colunas (objetos DatagridColumn). No
método construtor informamos o nome da coluna, seu título,
alinhamento e largura. Depois, utilizamos o método addColumn() para
adicionar as colunas criadas à Datagrid. Por m, a datagrid é adicionada à
página por meio do método add().
 Observação: lembre-se de que para acessar o programa a seguir é preciso
acessar o index.php, que é o front controller, pela URL index.php?class=ContatoList.

 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.

7.2.4.2 Listagem com ações e transformações


No exemplo anterior, vimos uma datagrid com alguns dados. Porém, em
grande parte dos casos isso não é su ciente. Na maioria das vezes,
precisamos de nir “ações” para a datagrid, tais como editar e excluir.
Cada ação poderá nos “transportar” para outro método ou outra página.
Além de precisarmos de nir ações, eventualmente também precisamos
fazer pequenas “transformações” sobre os dados antes de exibi-los por
estarem em um formato no banco de dados diferente do esperado pelo
usuário. Portanto, neste próximo exemplo, vamos escrever uma datagrid
com uma ação, que será um botão de “visualizar” na frente da linha, e
também vamos modi car a coluna “nome”, convertendo-a para
maiúsculas.
 Observação: neste exemplo, precisamos especificar as classes utilizadas no início
do programa (Page, Datagrid) 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.
O método construtor inicia de maneira muito similar à mostrada no
exemplo anterior. Nele, instanciamos a Datagrid e de nimos o seu
atributo border. Em seguida, instanciamos algumas colunas
(DatagridColumn) e as adicionamos à Datagrid pelo método addColumn(), e o
construtor DatagridColumn recebe parâmetros que são: nome da coluna,
título, alinhamento e largura.
Em seguida, de nimos um método de “transformação” para a coluna
nome. Um método de transformação, de nido pelo setTransformer(), é um
método qualquer que será aplicado sobre uma coluna. Ele receberá o
conteúdo da coluna e executará uma transformação. O resultado dessa
transformação será apresentado no lugar do dado original. Neste caso,
de nimos o método de transformação usando uma função anônima.
Após de nirmos o método de transformação, criamos uma ação para a
datagrid por meio do método addAction(). Neste caso, de nimos que a
ação criada representa o método onVisualiza(), que será criado em
seguida. Assim, ao clicar nessa ação, esse método será executado. Esta
ação receberá o atributo nome como parâmetro. Por m, a datagrid é
adicionada à página.
 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=ContatoActionList.

 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.

Figura 7.26 – Listagem com ações e transformações.

7.2.4.3 Listagem com dados do banco


Nos exemplos anteriores, vimos como instanciar uma datagrid e carregá-
la com dados estáticos. Também vimos como acrescentar ações em uma
datagrid e transformar alguns de seus dados antes da apresentação. Mas
em nenhum momento utilizamos um banco de dados.
O objetivo deste próximo exemplo é construir uma datagrid que
apresente dados de uma determinada tabela do banco de dados. Além de
apresentar os registros da tabela, a datagrid também permitirá excluir os
registros (perguntando antes para o usuário), bem como editar os
registros, sendo que nesta operação o usuário será redirecionado para o
programa que contém o formulário de edição, criado anteriormente na
seção sobre formulários (FuncionarioForm).
 Observação: neste exemplo, precisamos especificar as classes utilizadas no início
do programa (Page, Action) 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.
Para demonstrar uma datagrid integrada com o banco de dados, vamos
construir uma nova página (FuncionarioList). O método construtor dessa
classe será pouco diferente em relação aos exemplos anteriores.
Começamos novamente pela instância da classe Datagrid. Depois,
algumas colunas (DatagridColumn) são criadas e adicionadas à datagrid por
meio do método addColumn(). É importante lembrar que os parâmetros
recebidos na instanciação de uma coluna são: nome da coluna, rótulo do
título, alinhamento e largura.
Após adicionarmos as colunas, duas ações são criadas pelo método
addAction(); a primeira representa a ação de edição de registro, e a
segunda, a ação de exclusão de registro. A ação de edição estará vinculada
ao método onEdit() da classe FuncionarioForm. Neste caso, o atributo
passado como parâmetro será o id, de nido pelo terceiro parâmetro.
Então, quando o usuário acionar essa ação, será redirecionado para outra
página (FuncionarioForm) por meio da URL index.php?
class=FuncionarioForm&method=onEdit&id=1. Nesse momento, o formulário de
edição será exibido na tela. A outra ação está vinculada ao método
onDelete() da própria classe. O objetivo desse método é perguntar ao
usuário se ele deseja excluir o registro para só então excluí-lo. O método
onDelete() será detalhado a seguir.

 Observação: lembre-se de que para acessar o programa a seguir, é preciso


acessar o index.php, que é o Front Controller, pela URL index.php?
class=FuncionarioList.

 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);

// instancia as colunas da Datagrid


$codigo = new DatagridColumn('id', 'Código', 'right', '10%');
$nome = new DatagridColumn('nome', 'Nome', 'left', '30%');
$endereco = new DatagridColumn('endereco', 'Endereco','left', '30%');
$email = new DatagridColumn('email', 'Email', 'left', '30%');
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($nome);
$this->datagrid->addColumn($endereco);
$this->datagrid->addColumn($email);
$this->datagrid->addAction('Editar', new
Action(array(new FuncionarioForm, 'onEdit')), 'id');
$this->datagrid->addAction('Deletar', new Action(array($this,
'onDelete')), 'id');
parent::add( $this->datagrid );
}
Toda a interação com a base de dados está concentrada no método
onReload(). Nesse método, em vez de carregar dados estáticos a partir de
objetos planos (stdClass), como feito nos exemplos anteriores, vamos
carregar objetos vindos por meio do banco de dados. Para isso, iniciamos
uma transação com a base de dados pelo método Transaction::open() e,
em seguida, utilizamos a classe Repository para carregar os objetos por
meio de seu método load(), como já abordado no capítulo 5.
A classe Repository contém o método load(), que por sua vez recebe um
critério de seleção de objetos (Criteria). Neste caso, de nimos somente a
ordenação, por meio do método setProperty(), mas não de nimos os
ltros. O método load() retorna um vetor de objetos $funcionarios, sendo
cada posição desse vetor um objeto da classe Funcionario. 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 nida no construtor da classe DatagridColumn (primeiro parâmetro).
function onReload() {
Transaction::open('livro'); // inicia transação com o BD
$repository = new Repository('Funcionario');
// cria um critério de seleção de dados
$criteria = new Criteria;
$criteria->setProperty('order', 'id');
// carrega os produtos que satisfazem o critério
$funcionarios = $repository->load($criteria);
$this->datagrid->clear();
if ($funcionarios) {
foreach ($funcionarios as $funcionario) {
// adiciona o objeto à Datagrid
$this->datagrid->addItem($funcionario);
}
}
Transaction::close(); // finaliza a transação
$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, o que foi de nido pelo método setField() no
método construtor. 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.
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 a partir 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 do registro
a ser excluído. Então, é aberta uma transação com a base de dados e em
seguida é carregado para a memória o objeto correspondente da classe
Funcionario, por meio do método find(). Em seguida, é executado o seu
método delete() e fechada a transação. Por m, a Datagrid é recarregada e
uma mensagem de sucesso é exibida ao usuário por meio da classe
Message. Caso qualquer exceção ocorra no bloco try, a execução é
automaticamente desfeita, a transação não é encerrada (close) e o usuário
verá a mensagem de exceção no bloco catch.
function Delete($param) {
try {
$id = $param['id']; // obtém a chave
Transaction::open('livro'); // inicia transação com o banco
'livro'
$funcionario = Funcionario::find($id); // busca objeto
Funcionario
if ($funcionario) {
$funcionario->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.
function show() {
// se a listagem ainda não foi carregada
if (!$this->loaded) {
$this->onReload();
}
parent::show();
}
}
A gura 7.27 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta acessarmos pela URL index.php?class=FuncionarioList, onde
quer que esteja rodando nosso servidor de páginas.

Figura 7.27 – Listagem de funcionários.

7.2.4.4 Listagem com formulário de busca


No exemplo anterior, construímos uma datagrid que é alimentada por
dados de uma tabela do banco de dados e também de nimos ações que
permitiam excluir os registros, bem como editá-los, por meio do
redirecionamento para a classe de formulário (FuncionarioForm). Apesar de
a datagrid parecer conter todas as funcionalidades necessárias para uma
listagem, frequentemente precisamos localizar registros em grandes
tabelas. Assim, o objetivo deste próximo exemplo é complementar o
exemplo anterior, acrescentando ao topo da datagrid um formulário para
buscas de registros. Assim, o usuário poderá localizar e somente
visualizar os registros que atendam ao critério de buscas. Neste exemplo,
faremos buscas por nome.
 Observação: neste exemplo, não iremos detalhar todas as classes utilizadas por
meio do operador use. O programa completo encontra-se com os demais para
download no site da Novatec Editora.
O programa a seguir inicia em seu método construtor, onde
construiremos um formulário (Form) de busca de funcionários. Esse
formulário terá apenas um campo da classe Entry (nome) e duas ações
acrescentadas pelo método addAction(): “Buscar”, que irá executar o
método onReload(), e “Novo”, que irá executar o método onEdit() da classe
FuncionarioForm, permitindo a criação de um novo registro. Assim, sempre
que o usuário zer alguma busca, será executado o método onReload().
Portanto, o método onReload() terá uma pequena modi cação mostrada a
seguir para “ler” a informação digitada pelo usuário no formulário de
buscas e fazer o ltro correspondente na busca de registros.
Após criarmos o formulário de buscas, também criamos, como das vezes
anteriores, a datagrid (Datagrid), contendo as mesmas colunas (id, nome,
endereco, email) que o exemplo anterior. Depois de criar as colunas da
datagrid, criaremos duas ações de ordenação. Ações de ordenação são
vinculadas às colunas da datagrid e permitem ao usuário ordenar a
datagrid ao clicar sobre o título da coluna. Neste exemplo, criaremos as
ações $codigo_order e $nome_order. Ambas as ações estão vinculadas ao
método onReload(), mas cada uma passa um parâmetro diferente chamado
order. Logo após criar as ações e de nir o parâmetro que elas passarão
adiante, usaremos o método setAction() para “amarrar” a ação à coluna.
Assim, a coluna $codigo executará a ação $codigo_order e a coluna $nome
executará a ação $nome_order. Para simpli car o entendimento, em linhas
gerais, sempre que o usuário clicar sobre a coluna codigo, será direcionado
para a URL index.php?class=FuncionarioBuscaList&method=onReload&order=id, e
sempre que clicar sobre a coluna nome, será direcionado para a URL
index.php?class=FuncionarioBuscaList&method=onReload&order=nome. Dessa forma,
o método onReload() deverá estar preparado para “receber” o parâmetro
order.
Após criarmos as ações de ordenação, que são executadas quando o
usuário clica sobre o título de uma coluna, criaremos as ações “normais”
da datagrid, que são as ações exibidas linha a linha na frente dos
registros. As ações criadas são: “Editar”, vinculada ao método onEdit() da
classe FuncionarioForm, e “Deletar”, vinculada ao método onDelete() da
própria classe.
Ao nal do método construtor, utilizaremos o contêiner VBox (caixa
vertical) para dispor o formulário e a datagrid um sobre o outro antes de
adicionar esse contêiner à página.
 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=FuncionarioBuscaList.
 App/Control/FuncionarioBuscaList.php
<?php
use Livro\Control\Page;
use Livro\Control\Action;
use Livro\Widgets\Form\Form;
use ...
use ...
class FuncionarioBuscaList extends Page {
private $form; // formulário de buscas
private $datagrid; // listagem
private $loaded;
public function __construct() {
parent::__construct();
// instancia um formulário
$this->form = new FormWrapper(new Form('form_busca_funcionarios'));
// cria os campos do formulário
$nome = new Entry('nome');
$this->form->addField('Nome', $nome, 300);
$this->form->addAction('Buscar', new Action(array($this,
'onReload')));
$this->form->addAction('Novo', new Action(array(new
FuncionarioForm, 'onEdit')));
// instancia objeto Datagrid
$this->datagrid = new DatagridWrapper(new Datagrid);
// instancia as colunas da Datagrid
$codigo = new DatagridColumn('id', 'Código', 'right', '10%');
$nome = new DatagridColumn('nome', 'Nome', 'left', '30%');
$endereco = new DatagridColumn('endereco', 'Endereco','left',
'30%');
$email = new DatagridColumn('email', 'Email', 'left', '30%');
$codigo_order = new Action(array($this, 'onReload'));
$codigo_order->setParameter('order', 'id');
$codigo->setAction( $codigo_order );
$nome_order = new Action(array($this, 'onReload'));
$nome_order->setParameter('order', 'nome');
$nome->setAction( $nome_order );
// adiciona as colunas à Datagrid
$this->datagrid->addColumn($codigo);
$this->datagrid->addColumn($nome);
$this->datagrid->addColumn($endereco);
$this->datagrid->addColumn($email);
// adiciona as ações à Datagrid
$this->datagrid->addAction('Editar', new Action(array(new
FuncionarioForm,
'onEdit')), 'id');
$this->datagrid->addAction('Deletar', new Action(array($this,
'onDelete')), 'id');
// monta a página através de uma caixa
$box = new VBox;
$box->style = 'display:block; margin: 20px';
$box->add($this->form);
$box->add($this->datagrid);
parent::add($box);
}
O método onReload() é responsável por carregar os dados do banco na
datagrid da mesma maneira que o exemplo anterior. Porém, no exemplo
anterior, não havia um formulário de buscas de registros nem a ação de
ordenação sobre o título de uma coluna. Então, o método onReload() terá
de ser modi cado para “observar” mais parâmetros vindos da URL que
antes.
A princípio, o método onReload() abre uma transação com o banco de
dados e, em seguida, instancia um objeto da classe Repository, responsável
pelo carregamento dos registros. Depois criamos um objeto Criteria, que
será utilizado para formar um “ ltro” de buscas. Em primeiro lugar,
veri camos se na URL ($param) há um parâmetro chamado order. A
presença desse parâmetro indica que o usuário clicou sobre o título de
uma coluna, solicitando a ordenação por ela. Se estiver presente, esse
parâmetro será usado pelo método setProperty() para de nir a ordenação
da busca.
Em um segundo momento, utilizamos o método getData() para veri car
se existem dados preenchidos no formulário, o que retornará para o
objeto $dados. Caso o usuário tenha feito uma busca pelo campo nome,
então o atributo ($dados->nome) terá conteúdo. Nesse caso, é acrescentado
um ltro ao critério de buscas. Esse ltro ocorrerá pelo campo nome,
utilizando o operador like, comparando com o que o usuário digitou no
formulário ($dados->nome).
Após de nirmos a ordenação da busca, e também o ltro, utilizamos o
método load() da classe Repository para carregar os objetos. Em seguida,
iteramos os objetos por um foreach, adicionando cada um desses à
datagrid por meio do método addItem(). Por m, nalizamos a transação
com a base de dados.
function onReload( $param = null) {
Transaction::open('livro'); // inicia transação com o BD
$repository = new Repository('Funcionario');
// cria um critério de seleção de dados
$criteria = new Criteria;
$criteria->setProperty('order', isset($param['order']) ?
$param['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 da pessoa
$criteria->add('nome', 'like', "%{$dados->nome}%");
}
// carrega os produtos que satisfazem o critério
$funcionarios = $repository->load($criteria);
$this->datagrid->clear();
if ($funcionarios) {
foreach ($funcionarios as $funcionario) {
// adiciona o objeto à Datagrid
$this->datagrid->addItem($funcionario);
}
}
// finaliza a transação
Transaction::close();
$this->loaded = true;
}
function onDelete($param) {
// ...
}
function Delete($param) {
// ...
}
function show() {
// ...
}
}
A gura 7.28 demonstra a utilização do programa recém-criado. Para
acioná-lo, basta o acessarmos por meio da URL index.php?
class=FuncionarioBuscaList, onde quer que esteja rodando nosso servidor de
páginas.

Figura 7.28 – Listagem de funcionários com busca.


CAPÍTULO 8
Criando uma aplicação

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 Visão geral da aplicação


O objetivo deste capítulo é o desenvolvimento de uma aplicação para
controle de vendas. Essa aplicação contará com cadastros básicos como
clientes, cidades, produtos, fabricantes, processo de registro de vendas e
também relatórios de vendas e de contas geradas. Com isso, pretendemos
mostrar como gerar diferentes tipos de interface e interação entre o
usuário e a aplicação. A aplicação criada será de pequeno porte e não terá
como objetivo o uso em um ambiente real, a nal, é somente um protótipo
voltado para o aprendizado. Ao compreender como o protótipo foi
construído, você poderá desenvolver aplicações maiores.
A aplicação proposta contará com as seguintes funcionalidades:
• Cadastro de cidades – Oferecer um cadastro de cidades com informações
como nome da cidade e estado.
• Cadastro de fabricantes – Oferecer um cadastro de fabricantes com
informações como nome e site.
• Cadastro de produtos – Oferecer um cadastro de produtos com informações
como descrição, estoque, preço de custo, preço de venda, fabricante,
tipo (máquina, acessório) e unidade de medida.
• Cadastro de pessoas – Oferecer um cadastro de pessoas com informações
como nome, endereço, bairro, telefone, email, cidade e grupo (cliente,
fornecedor, revendedor, colaborador).
• Registro de vendas – Oferecer uma tela para registro das vendas ocorridas,
podendo informar uma série de itens (produtos) vendidos com suas
respectivas quantidades, e permitir nalizar a venda informando os
dados do cliente, os descontos, os acréscimos, alguma observação e o
parcelamento nanceiro.
• Relatório de vendas – Oferecer um relatório de vendas, permitindo ltrar
as vendas ocorridas por datas, e listar cada venda ocorrida agrupada
por cliente, totalizando o valor da venda.
• Relatório de contas – Oferecer um relatório de contas a receber, permitindo
ltrar as contas por datas, e listar cada uma das contas com
informações como emissão, vencimento, cliente, valor, se está paga etc.
• Relatório de produtos – Oferecer um relatório de produtos com suas
informações básicas, bem como um código de barras e um código
QRCode.
• Relatório de pessoas – Oferecer um relatório de pessoas contendo
informações básicas como código, nome, telefone, e-mail, bem como
colunas com informações nanceiras, como o total em contas e o total
em aberto.
• Grá co de vendas por mês – Oferecer um grá co de barras com o total de
vendas mês a mês.
• Grá co de vendas por tipo – Oferecer um grá co de pizza com o total de
vendas por tipo (por exemplo, acessórios, componentes, máquinas).
• Dashboard – Oferecer um painel resumido com mais de um grá co.

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>

8.1.3 Iniciando o projeto


Neste capítulo, vamos criar o projeto de um sistema para controle de
vendas que utilizará boa parte das classes criadas ao longo do livro. Você
poderá baixar esse projeto, bem como todos os exemplos de outros
capítulos do livro, diretamente no site da Novatec Editora.
Além disso, você poderá utilizar essas mesmas classes em seus novos
projetos se quiser. Sempre que você quiser iniciar um novo projeto
baseado nas classes criadas ao longo do livro, poderá fazê-lo de duas
formas. A primeira é baixar os exemplos do site da editora,
principalmente relativos a este capítulo, que contém uma aplicação
completa desenvolvida, e em seguida apagar os arquivos especí cos
(classes de modelo, arquivos de con guração etc.) localizados
principalmente na pasta App. A segunda forma é baixar uma estrutura
pronta que preparamos para a criação de novos projetos que contém
somente as classes criadas da pasta Lib, os arquivos do diretório principal
e a pasta App somente com os diretórios vazios. Para isso, basta clonar o
seguinte repositório público e iniciar o projeto a partir da estrutura
criada:
git clone https://github.com/pablodalloglio/phpoo4.git

8.1.4 Modelo de classes


Para desenvolver nossa aplicação de vendas proposta neste capítulo,
precisamos criar um modelo coerente e compreensível que facilite o seu
próprio desenvolvimento depois. Vamos então criar um modelo de classes
que demonstrará o relacionamento entre os objetos da aplicação e em
seguida convertê-lo em um modelo relacional, com os relacionamentos
entre as tabelas de um banco de dados. A gura 8.2 apresenta o modelo
de classes da aplicação proposta.

Figura 8.2 – Modelo de classes.


Temos a classe Pessoa, que armazenará os clientes do sistema. Pessoa tem
associação com Cidade, e esta, com Estado. Uma Pessoa poderá ter vários
grupos, por isso a agregação com a classe Grupo. Objetos da classe Conta
estarão associados à Pessoa para a qual aquela Conta deve ser paga.
Objetos da classe Venda estarão associados à Pessoa (cliente) para a qual
aquela Venda foi gerada. Uma Venda terá um ou vários itens (ItemVenda).
Cada item identi cará o Produto vendido, bem como a quantidade e o
preço praticado. Cada Produto terá associação com Tipo (acessório,
componente, suprimento), Fabricante e Unidade (centímetro, galão, fardo
etc.).
A seguir, veja cada uma das classes que formarão a aplicação.
• Pessoa – Representa uma pessoa que, por sua vez, pode ser cliente,
fornecedor, revendedor ou colaborador, conforme os grupos atribuídos
a ela. Uma pessoa terá associação com uma Cidade e uma agregação
com Grupo, podendo pertencer a vários grupos. Pessoa terá atributos
como nome, endereco, bairro, telefone e email.
• Cidade – Representa uma cidade. Terá o atributo nome e associação com
Estado.
• Estado – Representa um Estado. Terá como atributos sigla e nome.
• Grupo – Representa um grupo. Inicialmente os grupos serão cliente,
fornecedor, revendedor e colaborador. Cada grupo terá um nome. Uma
pessoa poderá ter vários grupos.
• Conta – Representa uma conta a receber que deve ser paga por uma
Pessoa. Está associada a uma Pessoa. Terá como atributos: dt_emissao,
dt_vencimento, valor e paga (indica se já foi paga).
• Venda – Representa uma venda feita para uma pessoa. Está associada a
uma Pessoa e é composta de um ou vários itens vendidos (ItemVenda).
Terá como atributos data_venda, valor_venda, desconto, acrescimos,
valor_final e obs.
• ItemVenda – Representa um item de uma Venda. Cada item identi ca a
quantidade vendida (quantidade) e o preço praticado (preco) no
momento da venda. Cada item está associado a um Produto.
• Produto – Representa um produto em estoque. Está associado a um
Tipo, Fabricante e Unidade. Terá como atributos descricao, estoque,
preco_custo e preco_venda.
• Tipo – Representa o tipo de um Produto. Poderá ser, entre outros,
máquina, acessório, insumo, componente ou suprimento. Terá como
atributo o campo nome.
• Fabricante – Representa o fabricante de um Produto. Terá como
atributos nome e site.
• Unidade – Representa a unidade de medida de um Produto. Poderá ser,
entre outras, centímetro, metro, quilograma, litro, peça, pacote ou fardo.
Terá como atributos sigla e nome.

8.1.5 Modelo relacional


Para armazenar os dados da aplicação de vendas, é fundamental
modelarmos a estrutura de dados. Para isso, foi de nido o modelo
relacional apresentado pela gura 8.3.
Figura 8.3 – Modelo relacional.
Para converter o modelo de classes no modelo relacional, é importante
utilizar técnicas de mapeamento objeto-relacional. Neste cenário
proposto, essas técnicas se resumem em aplicar os seguintes padrões:
• Chaves primárias – Cada classe, ao ser representada por uma tabela,
recebe uma chave primária. Assim, é possível visualizar a presença do
campo id em cada uma das tabelas geradas.
• Associações – Relacionamentos de associação, presente entre várias
classes como Pessoa e Cidade, Cidade e Estado, Produto e Tipo, e várias
outras, são convertidos automaticamente em chaves estrangeiras. Neste
modelo usamos o padrão id_<tabela>. A direção da chave estrangeira
segue a direção da associação, ou seja, parte sempre do objeto que
contém a referência para o outro.
• Composições – Como a presente entre as classes Venda e VendaItem, são
convertidas automaticamente em chaves estrangeiras, em que a direção
da chave estrangeira parte sempre do objeto que representa a “parte”
(VendaItem) para o objeto que representa o “todo” (Venda).
• Agregações – Como a presente entre as classes Pessoa e Grupo, precisam
necessariamente de uma tabela associativa. Como se trata de um
relacionamento de muitos para muitos, não é possível representá-lo
apenas com chaves estrangeiras em quaisquer dos lados do
relacionamento. Assim, é importante criarmos uma tabela de ligação,
que neste caso foi chamada de pessoa_grupo, que permite registrar vários
“pares” de chaves que ligam ambas as tabelas pessoa e grupo.
Para criar o modelo de dados físico proposto para a aplicação de vendas,
será utilizado o conjunto de instruções SQL para criação das tabelas a
seguir. Para facilitar a aplicação do modelo, o arquivo presente para
download no site da Novatec Editora (livro-sqlite.sql) também virá com
dados pré-cadastrados. As instruções a seguir foram testadas com o banco
de dados SQLite, mas devem funcionar com outros sistemas
gerenciadores de bancos de dados como PostgreSQL e MySQL.

 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)
);

8.2 As classes de modelo


No capítulo 5, estudamos vários design patterns voltados para o
armazenamento de informações em tabelas do banco de dados, como
Table Data Gateway e Active Record. Ao nal, optamos por utilizar o
padrão active record, e para isso criamos a classe Record. Nesta seção,
reproduziremos cada uma das classes de modelo criadas para formar a
nova aplicação. Posteriormente, demonstraremos como essas classes
poderão ser utilizadas.

8.2.1 Código-fonte das classes


8.2.1.1 Classe Estado
A classe Estado representará a tabela estado. 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/Estado.php
<?php
use Livro\Database\Record;
class Estado extends Record {
const TABLENAME = 'estado';
}

8.2.1.2 Classe Cidade


A classe Cidade representará a tabela cidade. Por ser subclasse de Record, já
contém métodos como store(), load(), delete(), all(), find() e outros. O
método get_estado() será executado automaticamente quando o
desenvolvedor acessar o atributo $cidade->estado, sendo que, nesse caso,
retornará o objeto Estado correspondente. O método get_nome_estado()
será executado automaticamente quando o desenvolvedor acessar o
atributo $cidade->nome_estado; nesse caso, retornará o nome do Estado
correspondente.

 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;
}
}

8.2.1.3 Classe Grupo


A classe Grupo representará a tabela grupo. 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/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';
}

8.2.1.5 Classe Unidade


A classe Unidade representará a tabela unidade. 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/Unidade.php
<?php
use Livro\Database\Record;
class Unidade extends Record {
const TABLENAME = 'unidade';
}

8.2.1.6 Classe Tipo


A classe Tipo representará a tabela tipo. 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/Tipo.php
<?php
use Livro\Database\Record;
class Tipo extends Record {
const TABLENAME = 'tipo';
}

8.2.1.7 Classe Pessoa


A classe Pessoa representará a tabela pessoa. Por ser subclasse de Record, já
contém métodos como store(), load(), delete(), all(), find() e outros.
Terá adicionalmente os seguintes métodos:
• O método get_nome_cidade() é executado automaticamente quando o
desenvolvedor acessa o atributo $pessoa->nome_cidade, sendo que, nesse
caso, retornará o nome da cidade correspondente à pessoa.
• O método addGrupo() é responsável por adicionar um objeto Grupo à
Pessoa. Para isso, utilizará a classe PessoaGrupo para gerar um novo
registro de vinculação entre Pessoa e Grupo. Ele vinculará o id do grupo
passado como parâmetro ($grupo->id) com o id da pessoa atual ($this-
>id).
• O método delGrupos() é responsável por eliminar os vínculos existentes
entre uma pessoa e seus grupos. Para isso, irá excluir todos os registros
de PessoaGrupo que estejam vinculados com a Pessoa ($this->id)
utilizando o método delete() da classe Repository.
• O método getGrupos() é responsável por retornar um array de objetos
do tipo Grupo vinculados à Pessoa. Para isso, ele irá carregar primeiro em
memória todos os objetos do tipo PessoaGrupo vinculados à pessoa
($this->id). Para cada objeto PessoaGrupo encontrado, irá instanciar o
objeto correspondente da classe Grupo e acumular estes no vetor
$grupos.
• O método getIdsGrupos() é responsável por retornar um array
contendo os IDs dos grupos vinculados à Pessoa. Para isso, ele utiliza o
método já existente getGrupos() e percorre cada um dos objetos
retornados, acumulando o seu ID ($grupo->id) em um vetor
($grupos_ids).
• O método getContasEmAberto() é responsável por retornar as contas em
aberto daquela Pessoa. Para isso, ele utiliza o método
Conta::getByPessoa(), identi cando o ID da pessoa ($this->id). Toda a
lógica de carregamento das contas está na classe Conta, pois ela é
responsável por essa informação.
• O método totalDebitos() é responsável por retornar o montante em
débitos daquela Pessoa. Para isso, ele utiliza o método
Conta::debitosPorPessoa(), identi cando o ID da pessoa ($this->id).
Toda a lógica de cálculo dos débitos está na classe Conta, pois ela é
responsável por essa informação.
 App/Model/Pessoa.php
<?php
use Livro\Database\Record;
use Livro\Database\Criteria;
use Livro\Database\Repository;
class Pessoa extends Record {
const TABLENAME = 'pessoa';
private $cidade;
public function get_cidade() {
if (empty($this->cidade))
$this->cidade = new Cidade($this->id_cidade);
return $this->cidade;
}
public function get_nome_cidade() {
if (empty($this->cidade))
$this->cidade = new Cidade($this->id_cidade);
return $this->cidade->nome;
}
public function addGrupo(Grupo $grupo) {
$pg = new PessoaGrupo;
$pg->id_grupo = $grupo->id;
$pg->id_pessoa = $this->id;
$pg->store();
}
public function delGrupos() {
$criteria = new Criteria;
$criteria->add('id_pessoa', '=', $this->id);
$repo = new Repository('PessoaGrupo');
return $repo->delete($criteria);
}
public function getGrupos() {
$grupos = array();
$criteria = new Criteria;
$criteria->add('id_pessoa', '=', $this->id);
$repo = new Repository('PessoaGrupo');
$vinculos = $repo->load($criteria);
if ($vinculos) {
foreach ($vinculos as $vinculo) {
$grupos[] = new Grupo($vinculo->id_grupo);
}
}
return $grupos;
}
public function getIdsGrupos() {
$grupos_ids = array();
$grupos = $this->getGrupos();
if ($grupos) {
foreach ($grupos as $grupo) {
$grupos_ids[] = $grupo->id;
}
}
return $grupos_ids;
}
public function getContasEmAberto() {
return Conta::getByPessoa($this->id);
}
public function totalDebitos() {
return Conta::debitosPorPessoa($this->id);
}
}

8.2.1.8 Classe PessoaGrupo


A classe PessoaGrupo representará a tabela pessoa_grupo, que é uma tabela
de ligação utilizada no relacionamento de agregação entre as classes Pessoa
e Grupo. 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/PessoaGrupo.php
<?php
use Livro\Database\Record;
class PessoaGrupo extends Record {
const TABLENAME = 'pessoa_grupo';
}

8.2.1.9 Classe Conta


A classe Conta representará a tabela conta. Por ser subclasse de Record, já
contém métodos como store(), load(), delete(), all(), find() e outros.
Terá adicionalmente os seguintes métodos:
• O método get_cliente() será executado automaticamente quando o
desenvolvedor acessar o atributo $conta->cliente, sendo que nesse caso
retornará um objeto da classe Pessoa vinculado à Conta.
• O método getByPessoa() é um método estático responsável por retornar
todos os objetos Conta com o status paga<>'S' (não pagas) vinculados a
uma pessoa ($id_pessoa). Pode ser executado com a sintaxe $contas =
Pessoa::getByPessoa(1). Utiliza o método load() da classe Repository
para carregar e retornar um array de objetos.
• O método debitosPorPessoa() é responsável por retornar o valor total
das contas em aberto de uma pessoa. Para isso, utiliza o método já
desenvolvido getByPessoa() para retornar todas as contas de uma
pessoa. Em seguida, percorre uma a uma, somando o seu valor e
retornando-o ao nal.
• O método geraParcelas() é um método estático responsável por gerar
um conjunto de parcelas (contas a receber) para uma determinada
pessoa. Para isso, ele recebe como parâmetros $id_cliente (código da
pessoa), $delay (carência inicial em dias para a primeira parcela), $valor
(valor total da conta) e $parcelas (quantidade de parcelas). Esse método
gerará uma Conta para cada parcela.

 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);

$repo = new Repository('Conta');


return $repo->load($criteria);
}
public static function debitosPorPessoa($id_pessoa) {
$total = 0;
$contas = self::getByPessoa($id_pessoa);
if ($contas) {
foreach ($contas as $conta) {
$total += $conta->valor;
}
}
return $total;
}
public static function geraParcelas($id_cliente, $delay, $valor,
$parcelas) {
$date = new DateTime(date('Y-m-d'));
$date->add(new DateInterval('P'.$delay.'D'));
for ($n=1; $n<=$parcelas; $n++) {
$conta = new self;
$conta->id_cliente = $id_cliente;
$conta->dt_emissao = date('Y-m-d');
$conta->dt_vencimento = $date->format('Y-m-d');
$conta->valor = $valor / $parcelas;
$conta->paga = 'N';
$conta->store();
$date->add(new DateInterval('P1M'));
}
}
}

8.2.1.10 Classe Venda


A classe Venda representará a tabela venda. Por ser subclasse de Record, já
contém métodos como store(), load(), delete(), all(), find() e outros.
Terá adicionalmente os seguintes métodos:
• O método set_cliente() atribui um cliente (objeto Pessoa) à Venda. Ele
será executado automaticamente quando o desenvolvedor de nir valor
para o atributo $conta->cliente = new Cliente(1).
• O método get_cliente() é executado automaticamente quando o
desenvolvedor acessa o atributo $conta->cliente, sendo que, nesse caso,
retorna um objeto da classe Pessoa vinculado à classe Conta.
• O método addItem() é utilizado para adicionar um item (objeto
ItemVenda) à Venda. Para isso, esse método recebe o objeto Produto, bem
como a quantidade vendida. A partir desses parâmetros, cria o objeto
ItemVenda correspondente e o acumula-o em um vetor interno ($this-
>itens). Os itens serão salvos com os dados da Venda no momento da
execução do método store().
• O método store() é utilizado para armazenar a Venda, bem como seus
itens (objetos ItemVenda) na base de dados. O método store()
inicialmente irá executar o método-padrão da classe Record
(parent::store()), onde armazenará os dados da Venda na base de dados.
Em seguida, percorrerá os itens da Venda, armazenando um a um na
base de dados, garantindo que o item aponte para a Venda atual ($item-
>id_venda = $this->id). Os itens são armazenados depois da Venda para
garantir que a Venda já tenha sido armazenada e já tenha garantido um
id ($this->id).
• O método get_itens() retorna os itens (objetos ItemVenda) de uma
Venda. Para isso, ele utiliza o método load() da classe Repository sobre a
classe ItemVenda, tendo como critério (id_venda = $this->id).

 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
}
}

8.2.1.11 Classe ItemVenda


A classe ItemVenda representará a tabela item_venda. Por ser subclasse de
Record, já contém métodos como store(), load(), delete(), all(), find() e
outros. Terá adicionalmente os seguintes métodos:
• O método set_produto() – que será executado automaticamente
quando o desenvolvedor sobrescrever o atributo $itemvenda->produto.
Nesse caso, irá gravar o objeto internamente, bem como seu código.
• O método get_produto() – que será executado automaticamente
quando o desenvolvedor acessar o atributo $itemvenda->produto, sendo
que, nesse caso, retornará o Produto correspondente àquele item.

 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;
}
}

8.2.1.12 Classe Produto


A classe Produto representará a tabela produto. Por ser subclasse de Record,
já contém métodos como store(), load(), delete(), all(), find() e outros.
O método get_nome_fabricante() é executado automaticamente quando o
desenvolvedor acessar o atributo $produto->nome_fabricante, sendo que,
nesse caso, retornará o nome do fabricante correspondente àquele
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;
}
}

8.2.2 Testando as classes de modelo


Para demonstrar a utilização das classes de modelo criadas até o
momento, vamos desenvolver alguns scripts para fazer testes unitários e
demonstrar o comportamento de alguns métodos criados.
Posteriormente, essas mesmas classes serão utilizadas dentro de rotinas
como registro de vendas e relatórios que serão criados dentro do sistema
proposto.

8.2.2.1 Lazy Initialization em associações


O primeiro script de testes demonstrará a utilização de métodos getters
para busca de informações relacionadas. No programa a seguir, após a
abertura da transação, carregamos a Cidade de ID 12 para a memória. Em
seguida, exibimos o seu atributo nome. Depois acessamos o atributo -
>estado->nome. Como não há um atributo chamado estado,
automaticamente a classe Record (superclasse de Cidade) busca algum
método chamado get_estado(), que é executado pelo método mágico
__get(). Como esse método é encontrado, ele é executado, retornando o
objeto vinculado da classe Estado, que é instanciado somente nesse
momento, ou seja, sob demanda. A seguir, acessamos o atributo
nome_estado, que também não existe. Porém há um método chamado
get_nome_estado() que faz a busca da informação correspondente.
Em seguida, carregamos para a memória o objeto a Pessoa de ID 12 para a
memória. Em seguida, exibimos o seu atributo nome. Acessamos o
atributo nome_cidade. Como não há um atributo chamado nome_cidade,
automaticamente a classe Record (superclasse de Pessoa) busca algum
método chamado get_nome_cidade(). Como esse método existe, ele
retornará o nome da cidade correspondente. Depois, exibimos o nome da
cidade relativa à pessoa, o que dispara o método get_cidade(). Então,
exibimos o nome do estado, que dispara o método get_estado() sobre o
objeto Cidade resultante. Como se trata do mesmo objeto ($p1), o objeto
Cidade somente é carregado da primeira vez; nas vezes subsequentes, o
mesmo objeto é retornado.
Sempre que carregamos um objeto relacionado sob demanda, como
ocorreu quando o método get_estado() foi automaticamente executado ao
acessarmos o atributo estado, estamos na verdade utilizando um padrão
de projetos chamado Lazy Initialization. Frequentemente, precisamos
navegar por entre os relacionamentos dos objetos, o que torna necessário
que esses objetos relacionados estejam disponíveis (instanciados).
Poderíamos carregar automaticamente todos os relacionamentos de um
objeto quando o carregamos da base de dados. Porém, dependendo do
nosso objeto de ponto de partida, poderíamos carregar quase todo o
banco de dados nessa operação, consumindo muita memória.
Para evitar esse tipo de situação, o ideal seria que os objetos relacionados
fossem instanciados somente quando eles fossem necessários para a
aplicação. Essa é exatamente a proposta do design pattern lazy
initialization, também conhecido por “inicialização tardia”. Utiliza-se o
termo “tardia” porque os relacionamentos somente são disponibilizados
para aplicação quando são necessários, evitando uma carga inicial
desnecessária.
Quando declaramos um método em uma superclasse, ele é válido para
todas as classes lhas. Dessa forma, a classe Record contém o método
__get(), que será executado sempre que tentarmos obter uma de suas
propriedades. Esse método, que recebe o nome da propriedade que está
sendo requerida, está programado para detectar a ocorrência de um
método nomeado por get_<propriedade> na classe atual. Se tentarmos
obter a propriedade estado, automaticamente o método __get() irá
veri car a existência do método get_estado() na mesma classe. Caso esse
método exista, ele será executado; caso contrário, simplesmente será
retornado o valor da propriedade.
 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=ModelTest1.

 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();
}
}
}

 Observação: o resultado da execução pode variar conforme os dados


previamente cadastrados.

 Resultado:
João Pessoa
Paraíba
Paraíba
Isaac Morrison
Recife
Recife
Pernambuco

8.2.2.2 Agregação entre pessoa e grupo


O objetivo deste exemplo é demonstrar os métodos criados para gerenciar
o relacionamento de agregação entre as classes Pessoa e Grupo. Para isso
serão demonstrados os seguintes métodos: delGrupos(), que exclui os
grupos de uma Pessoa; addGrupo(), que adiciona um grupo a uma Pessoa; e
getGrupos(), que retorna os grupos de uma Pessoa.
O programa a seguir inicia com o carregamento do objeto Pessoa com ID 1
da base de dados por meio do método find(). Em seguida, é executado o
método delGrupos(), que exclui todos os grupos vinculados àquela pessoa
que estão registrados na tabela associativa pessoa_grupo. Em seguida, são
adicionados dois grupos (1,3) à pessoa.
O método addGrupo() está preparado para receber o objeto Grupo e no
mesmo instante criar o objeto PessoaGrupo e armazená-lo (na tabela
pessoa_grupo). Por m, é usado o método getGrupos(), que retorna todos os
objetos Grupo vinculados à pessoa. Esses objetos são percorridos, e
algumas informações (id, nome) são exibidas. O que o método getGrupos()
faz é descobrir todos os registros feitos para a pessoa na tabela
pessoa_grupo e transformá-los em objetos da classe Grupo para então
retorná-los.
 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=ModelTest2.

 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();
}
}
}

 Observação: o resultado da execução pode variar conforme os dados


previamente cadastrados.

 Resultado:
1 - Cliente
3 - Revendedor

8.2.2.3 Composição entre venda e itens


O objetivo do próximo exemplo é demonstrar o funcionamento das
classes envolvidas no processo de vendas, mais especi camente na
composição entre a venda e seus itens, que posteriormente serão
utilizadas pelo programa criado neste capítulo. No exemplo proposto,
será criado um objeto Venda a partir de alguns valores absolutos
declarados no próprio escopo do programa. Porém, quando construirmos
o programa real de registro de vendas, esses dados virão de um formulário
preenchido pelo usuário.
O programa inicia com a criação do objeto Venda e a de nição de alguns
atributos como data_venda, valor_venda, desconto, acrescimos e obs. Ao
de nirmos o valor do atributo cliente, será executado internamente o
método set_cliente(), que armazenará o objeto cliente (new Pessoa(3)).
Depois de de nir alguns atributos da venda, executamos por duas vezes o
método addItem(), que recebe como parâmetro o produto e a quantidade
vendida. Cada vez que é executado, o método addItem() cria internamente
um objeto do tipo ItemVenda e o acumula em um array interno de itens do
objeto Venda. É importante observar que o método addItem() não
armazena a informação neste instante na base de dados. Neste caso,
optamos por armazenar a informação somente no momento da execução
do método store(), quando são armazenadas as informações do objeto
Venda, e também dos objetos ItemVenda relacionados. Antes do store(), o
valor nal da venda é calculado.
 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=ModelTest3.
 App/Control/Tests/ModelTest3.php
<?php
use Livro\Control\Page;
use Livro\Database\Transaction;
class ModelTest3 extends Page {
public function show() {
try {
Transaction::open('livro');
// define atributos da venda
$venda = new Venda;
$venda->cliente = new Pessoa(3);
$venda->data_venda = date('Y-m-d');
$venda->valor_venda = 0;
$venda->desconto = 0;
$venda->acrescimos = 0;
$venda->obs = 'obs';
// adiciona itens
$venda->addItem(new Produto(3), 2);
$venda->addItem(new Produto(4), 1);
// atualiza valor
$venda->valor_final = $venda->valor_venda + $venda->acrescimos -
$venda->desconto;
// grava venda e itens
$venda->store();
Transaction::close();
}
catch (Exception $e) {
echo $e->getMessage();
}
}
}

8.2.2.4 Coesão e responsabilidade


No exemplo a seguir, procuramos demonstrar um dos conceitos
fundamentais da orientação a objetos, que é a responsabilidade. Uma
classe deve ser “responsável” por determinado assunto gerenciado por ela
e deve prover modos de encapsular esse comportamento por meio de
métodos. Cada classe deve ter uma única responsabilidade e deve ser
coesa. A coesão ocorre quando as atividades desempenhadas por uma
parte do sistema (classe) estão relacionadas a um mesmo conceito. A
coesão mede a diversidade dos tópicos (assuntos) gerenciados por uma
classe. Quanto menor essa diversidade, maior a coesão.
Dentro de nosso modelo de negócios, precisaremos saber quanto
determinada pessoa possui em débitos. É plausível imaginar um método
$pessoa->totalDebitos() para retornar essa informação. Porém a
informação sobre os débitos da Pessoa está na tabela conta, que por sua
vez é gerenciada pela classe Conta, não na classe Pessoa. Então a classe
Conta deve conter métodos e regras de negócio que nos devolvam essa
informação, para que possamos manter a coesão. A nal, é ali que a
informação nanceira está localizada. Quando mantemos todas as
informações relativas à classe Conta (e consequentemente à tabela conta)
em apenas um lugar, temos maior facilidade de manutenção caso mude a
estrutura do banco de dados, já que precisaremos somente alterar uma
classe (Conta), e não várias outras no sistema. Assim, também é plausível
termos um método Conta::debitosPorPessoa($id_pessoa) na classe Conta
para calcular os débitos de uma pessoa, recebendo como parâmetro o
código dessa pessoa.
É importante que o código que irá de fato carregar as informações a partir
do banco de dados que concentrado na classe que gerencia aquele
assunto. Assim, primeiro, optamos por criar um método
Conta::debitosPorPessoa($id_pessoa) na classe Conta para fazer os ltros e
chamar as classes necessárias para fazer o “trabalho sujo”, que é carregar
os objetos e fazer o cálculo necessário. Assim, a regra de negócio de
cálculo cou na classe Conta. Porém, como vimos anteriormente, é
plausível e legível imaginarmos um método $pessoa->totalDebitos() para
retornar de maneira rápida essa informação. Para não perdermos a
legibilidade de código e mantermos a coesão, criamos um método
$pessoa->totalDebitos() simplesmente fazendo um direcionamento de
chamada para Conta::debitosPorPessoa($this->id). Assim, temos um
facilitador, que é o método totalDebitos(), que atua sobre o objeto Pessoa
carregado em memória, e temos o método debitosPorPessoa(), que de fato
efetua o trabalho e os cálculos. Caso mude a estrutura da tabela conta,
somente nos preocuparemos em atualizar métodos da classe Conta,
porque ali está a complexidade.
O programa a seguir carrega para a memória a Pessoa de ID 1 e em seguida
exibe o resultado do método totalDebitos(), que por sua vez executa
internamente o método Conta::debitosPorPessoa($this->id). Em seguida,
executamos o método getContasEmAberto(), que internamente redireciona
essa chamada para o método Conta::getByPessoa($this->id). Para cada
Conta retornada, são exibidas algumas informações.

 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=ModelTest4.

 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();
}
}
}

 Observação: o resultado da execução pode variar conforme os dados


previamente cadastrados.

 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.

8.3.1 Cadastro de pessoas


8.3.1.1 Formulário de pessoas
Agora podemos iniciar a implementação de nosso formulário de cadastro
de pessoas. O objetivo é criar um formulário que permita o cadastro bem
como a edição de pessoas. Para isso, vamos criar a classe PessoasForm. No
método construtor da classe, criaremos o formulário utilizando a classe
Form, acompanhada de FormWrapper para apresentação, bem como
criaremos os campos do formulário utilizando componentes como Entry,
Combo e CheckGroup. A classe terá ainda o método onSave(), que 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 feito 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=PessoasForm.
A classe inicia pela de nição de quais classes serão utilizadas por esse
programa, como Page, Action, Transaction, Form e outras devidamente
importadas com seus namespaces. No seu método construtor, criamos o
formulário por meio da classe Form com o nome form_pessoas e utilizamos
a classe FormWrapper para fazer sua apresentação. Em seguida, de nimos o
título do formulário e ainda declaramos cada um dos campos do
formulário: id, nome, endereco, bairro, telefone, email, id_cidade e
ids_grupos. 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, precisamos preencher os
campos para Cidade (Combo) e Grupos (CheckGroup). Em ambos os casos,
carregamos os dados a partir de suas respectivas tabelas da base de dados.
Inicialmente, abrimos uma transação com o banco de dados e usamos o
método all() da classe Record para carregar todos os objetos do tipo
Cidade. Os objetos são percorridos, e é montado um vetor ($items)
indexado pelo ID do objeto. Esse vetor é usado para preencher a combo de
cidades por meio do método addItems() da combo. O mesmo processo é
repetido com os grupos.
Após preenchermos as cidades e os grupos, os campos são adicionados ao
formulário pelo método addField(). Em seguida, alguns métodos são
executados para modi car alguns campos. O campo “código” é
desabilitado para edição e outros têm seu tamanho de nido. Por m, a
ação de salvamento é adicionada ao formulário pelo método addAction().
Essa ação estará vinculada ao método onSave().
 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.

 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.

Figura 8.4 – Formulário de pessoas.

8.3.1.2 Listagem de pessoas


Após criarmos o formulário de cadastro de pessoas, o próximo passo é
de nir a datagrid para a listagem que permitirá a busca, exclusão e edição
(que redirecionará para o formulário de pessoas). Para isso, vamos
construir uma nova página (PessoasList). No método construtor da classe,
de niremos um formulário ($this->form) que será usado para fazer buscas
nos dados de pessoas. Esse formulário terá um único campo (nome) e duas
ações: “buscar”, que executará o método onReload() sempre que o usuário
zer uma busca pelo nome, e “novo”, que executará o método onEdit() de
PessoasForm, carregando um novo formulário para cadastro de uma
pessoa.
Em seguida, temos a instância da classe Datagrid e suas colunas
(DatagridColumn), que são criadas e adicionadas à datagrid por meio do
método addColumn(). É importante lembrar que os parâmetros recebidos
na instanciação de datagrid são: nome da coluna, rótulo do título,
alinhamento e largura.
Após adicionarmos as colunas, duas ações são criadas, sendo que a
primeira representa a ação de edição de registro, e a segunda, a ação de
exclusão de registro. A ação de edição estará vinculada ao método onEdit()
da classe PessoasForm. Neste caso, o atributo passado como parâmetro da
ação será o id da pessoa (terceiro parâmetro). Então, quando o usuário
acionar essa ação, será redirecionado para outra página (PessoasForm) por
meio da URL index.php?class=PessoasForm&method=onEdit&id=1. Nesse
momento, o formulário de edição será exibido em tela. Já a outra ação
está vinculada ao método onDelete() da própria classe. O objetivo desse
método é perguntar ao usuário se ele quer excluir o registro para só então
excluí-lo. O último parâmetro do método addAction() representa o ícone
da ação, que utiliza a biblioteca Font Awesome. Ao nal, o formulário de
buscas e a datagrid são acrescentados à página, mas antes são
“empacotados” dentro de um contêiner do tipo Vbox.
 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=PessoasList.

 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.

8.3.2 Criando traits para ações comuns


Ao criarmos programas para cadastrar e listar registros do banco de
dados, como foi o caso dos programas PessoasForm e PessoasList, criamos
uma série de métodos para executar ações comuns. No formulário de
cadastro, criamos ações para salvar (onSave) e editar (onEdit) registros. Na
listagem, criamos métodos para carregar (onReload) e excluir (onDelete,
Delete) registros. Ao analisar os códigos desenvolvidos, podemos concluir
que é possível reaproveitar grandes porções de código-fonte desses
métodos criados para criar novas classes. Podemos concluir que bastaria
apenas “copiar e colar” (perdão pelo uso da expressão) essas classes
alterando algumas pequenas de nições, como é o caso do nome da
conexão (livro), que pode ser diferente em outro caso, bem como o nome
da classe de modelo (Pessoa), que certamente será diferente em outros
casos. Porém “copiar e colar” certamente não é uma boa estratégia para
ganhar produtividade, pois, mesmo ganhando velocidade inicial, à
medida que precisarmos fazer uma manutenção, teremos de lembrar de
fazê-la em inúmeros locais. Uma estratégia melhor para reaproveitar
trechos são os traits.
Como vimos no capítulo 4, para atender à necessidade de compartilhar
pequenos comportamentos entre diferentes classes, independentemente
da hierarquia (superclasses), é implementado no PHP o conceito de traits
(traços). Um trait é formado por um conjunto de métodos que
representam 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
por intermédio de comportamentos absorvidos a partir de traits.

8.3.2.1 Trait para salvar dados


O primeiro comportamento que vamos transformar em trait será o
método de salvamento de dados de um formulário. Para isso, vamos criar
um trait chamado SaveTrait, contendo um método onSave(). O conteúdo
do trait é exatamente o método onSave(), que já desenvolvemos em outras
situações, como no cadastro ProdutosForm. Entretanto, substituímos as
partes variantes, como o nome da conexão e o nome da classe de modelo,
por atributos variáveis que deverão ser de nidos na classe que
“incorporar” o trait. Neste caso, o atributo responsável por conter o nome
da conexão será $this->connection e o responsável por conter o nome da
classe de modelo será $this->activeRecord. Ambos precisarão estar
previamente de nidos no método construtor da classe receptora para ser
utilizados.
O método onSave() abrirá uma transação com o banco de nido em $this-
>connection. Em seguida, lerá os dados do formulário por meio do
método getData(). Depois, instanciará um objeto da classe de nida em
$this->activeRecord e a alimentará com os dados do formulário pelo
método fromArray() para então armazenar os dados pelo método store().
 Observação: é importante observar que o comportamento definido no trait não
trata situações específicas como conversão de formato de datas e gravação de
campo CheckGroup, entre outras.

 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());
}
}
}

8.3.2.2 Trait para editar dados


O segundo comportamento que vamos transformar em trait será o
método de edição de dados em um formulário, responsável por receber o
código do registro a ser editado pela URL e carregar o formulário com os
dados do banco de dados. Para isso, vamos criar um trait chamado
EditTrait contendo um método onEdit().
O conteúdo do trait é exatamente o método onEdit() que já
desenvolvemos em outras situações, como no cadastro ProdutosForm.
Entretanto, substituímos o nome da conexão pelo atributo $this-
>conection e o nome da classe de modelo pelo atributo $this-
>activeRecord. Ambos precisarão estar previamente de nidos no método
construtor da classe receptora para ser utilizados.
O método onEdit() será acionado por meio da URL no formato index.php?
class=ClasseControle&method=onEdit&id=5 para carregar o formulário com os
dados do registro de ID=5. Assim, ele deve ler na URL a variável ID e
carregar o registro correspondente. Para isso, deverá abrir uma transação
com a base de dados e usar a classe prede nida pelo programador ($this-
>activeRecord) para acionar seu método find() de maneira estática
($class::find()) e localizar o registro correspondente, retornando um
objeto Active Record. Em seguida, o objeto será usado para preencher o
formulário por meio do método setData() e a transação será encerrada.
 Lib/Livro/Traits/EditTrait.php
<?php
namespace Livro\Traits;
use Livro\Database\Transaction;
use Livro\Widgets\Dialog\Message;
use Exception;
trait EditTrait {
function onEdit($param) {
try {
if (isset($param['id'])) {
$id = $param['id']; // obtém a chave do registro
Transaction::open( $this->connection ); // inicia transação
$class = $this->activeRecord; // classe de Active Record
$object = $class::find($id); // instancia o Active Record
$this->form->setData($object); // lança os dados no formulário
Transaction::close(); // finaliza a transação
}
}
catch (Exception $e) {
new Message('error', $e->getMessage());
Transaction::rollback();
}
}
}

8.3.2.3 Trait para carregar dados


O terceiro comportamento que vamos transformar em trait será o método
de carregamento de dados em uma datagrid, responsável por alimentar
uma datagrid com os dados do banco de dados. Para isso, vamos criar um
trait chamado ReloadTrait, contendo um método onReload().
O conteúdo do trait é exatamente o método onReload() que já
desenvolvemos em outras situações, como na listagem ProdutosList.
Entretanto, substituímos o nome da conexão pelo atributo $this-
>conection e o nome da classe de modelo pelo atributo $this-
>activeRecord. Ambos precisarão estar previamente de nidos no método
construtor da classe receptora para ser utilizados. Além disso, como em
alguns casos as datagrids precisam receber “ ltros”, deixamos preparado
um atributo chamado $this->filters. Caso esse atributo seja de nido
antes da execução do método onReload(), então esse ltro será usado
também.
O método onReload() será utilizado em datagrids sempre antes da
exibição da página e deverá carregar a datagrid com dados vindos do
banco de dados. Para isso, ele abre uma transação com a base de dados
($this->connection) e usa a classe prede nida pelo programador ($this-
>activeRecord) para criar um repositório que será utilizado para o
carregamento dos dados. Caso o programador tenha prede nido ltros
($this->filters), estes serão usados para compor o critério de seleção,
sendo adicionados pelo método add() da classe Criteria. Posteriormente,
veremos a listagem de produtos em que esse ltro será usado.
Os objetos são carregados pelo método load(). Em seguida, os objetos são
percorridos e adicionados à datagrid pelo método addItem(). Por m, a
transação com a base de dados é encerrada.

 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());
}
}
}

8.3.2.4 Trait para excluir dados


O quarto comportamento que vamos transformar em trait será o método
de exclusão de dados em uma datagrid, responsável por excluir um
determinado registro do banco de dados. Para isso, vamos criar um trait
chamado DeleteTrait contendo os métodos onDelete() e Delete().
O trait contém exatamente os métodos onDelete() e Delete() que já
desenvolvemos em outras situações, como na listagem ProdutosList.
Entretanto, substituímos o nome da conexão pelo atributo $this-
>conection e o nome da classe de modelo pelo atributo $this-
>activeRecord. Ambos precisarão estar previamente de nidos no método
construtor da classe receptora para ser utilizados.
O método onDelete() será acionado a partir da ação de exclusão de
registros em datagrids e simplesmente fará uma pergunta ao usuário por
meio da classe Question. Como ele recebe dos parâmetros da URL a
variável id, repassa essa variável ao método Delete() caso o usuário
responda a rmativamente. O método Delete() abre uma transação com a
base de dados prede nida ($this->conection) e usa a classe prede nida
pelo programador ($this->activeRecord) para acionar seu método find()
de maneira estática ($class::find()) e localizar o registro correspondente,
retornando um objeto Active Record. Em seguida, executa-se o método
delete() sobre o objeto retornado e a datagrid é recarregada.
 Lib/Livro/Traits/DeleteTrait.php
<?php
namespace Livro\Traits;
use Livro\Control\Action;
use Livro\Database\Transaction;
use Livro\Widgets\Dialog\Message;
use Livro\Widgets\Dialog\Question;
use Exception;
trait DeleteTrait {
function onDelete($param) {
$id = $param['id']; // obtém o parâmetro id
$action1 = new Action(array($this, 'Delete')); // cria ação
$action1->setParameter('id', $id);
new Question('Deseja realmente excluir o registro?', $action1);
}
function Delete($param) {
try {
$id = $param['id']; // obtém a chave do registro
Transaction::open( $this->connection ); // inicia transação
$class = $this->activeRecord; // classe Active Record
$object = $class::find($id); // instancia objeto
$object->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());
}
}
}

 Observação: com os traits definidos, podemos utilizá-los em seguida para


compor novas classes, aumentando o reaproveitamento de código e nos poupando
de muitas linhas novas para dar manutenção.

8.3.3 Cadastro de produtos


8.3.3.1 Formulário de produtos
Na seção anterior, aprendemos a criar traits para atender à necessidade de
compartilhar pequenos comportamentos entre diferentes classes. Nesse
sentido, criamos traits que podem ser usados em formulários como o
SaveTrait (onSave) para salvar os dados do formulário na tabela, o
EditTrait (onEdit) para carregar o formulário com um registro, e também
criamos traits para serem usados em listagens, como o ReloadTrait
(onReload) para carregar uma datagrid com objetos vindos do banco de
dados, e o DeleteTrait (onDelete, Delete) para questionar o usuário e
excluir registros por meio de uma datagrid.
A partir do momento em que de nimos os traits com comportamentos
básicos, como salvar e editar registros, podemos usá-los para construir
formulários de maneira mais rápida. Assim, no próximo exemplo, vamos
construir um formulário para cadastro e edição de produtos. Como
vamos usar traits já de nidos para agregar comportamento, só
precisaremos de nir seu método construtor com a de nição dos campos e
sua disposição em tela. Os traits usados nesse exemplo serão SaveTrait
(onSave), para salvar os dados do formulário na tabela, e o EditTrait
(onEdit), para carregar o formulário com o registro para a edição.
O método construtor inicia com a de nição das classes em uso com seus
namespaces. Aqui, devemos prestar atenção na importação dos traits
SaveTrait e EditTrait. À medida que iniciarmos a declaração da classe,
antes do construtor, observe as de nições use SaveTrait e use EditTrait. O
comando use utilizado dentro do escopo da classe “importa” o código do
trait como se aqueles métodos tivessem na verdade sido declarados aqui.
Após importar os traits, iniciamos a de nição do método construtor.
Logo no início deste, precisamos de nir as propriedades $this->connection
(contém o nome da conexão) e $this->activeRecord (contém o nome da
classe active record). Ambos os atributos são usados pelos traits, que
precisarão dessas informações para armazenar e carregar os dados do
lugar correto.
Após de nirmos as propriedades $this->connection e $this->activeRecord,
criamos o formulário, utilizando o wrapper FormWrapper, e em seguida
criamos vários objetos que farão parte do formulário. Então, abrimos
uma transação com a base de dados para carregar dados das classes
Fabricante, Tipo e Unidade para montar vetores ($items) que serão usados
para alimentar as três combos para seleção. En m, os campos são
adicionados ao formulário.
Após a criação dos campos, é criada uma ação “salvar”. Essa ação está
vinculada ao método onSave(), que não consta na classe atual, mas sim no
trait importado (SaveTrait). Em seguida, o formulário é adicionado à
página.
Além de permitir salvar registros, o formulário também poderá ser usado
para edição, pois o trait EditTrait também foi importado. O EditTrait
contém o método onEdit(), cuja nalidade é “carregar” os dados do
formulário com um registro já feito na base de dados. Assim, para
executar o método onEdit(), preenchendo o formulário, devemos acionar a
URL index.php?class=ProdutosForm&method=onEdit&id=5 para carregar o
formulário com os dados do registro de ID=5.
 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=ProdutosForm.

 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')));

// adiciona o formulário na página


parent::add($this->form);
}
}
A gura 8.6 demonstra a utilização do programa recém-criado.

Figura 8.6 – Formulário de produtos.


Para acioná-lo, basta o acessarmos por meio da URL index.php?
class=ProdutosForm, onde quer que esteja rodando nosso servidor de
páginas.

8.3.3.2 Listagem de produtos


Agora que já usamos traits para agilizar a criação de formulários, vamos
fazer o mesmo para listagens. Na seção anterior, aprendemos a criar traits
que podem ser usados em listagens, como o ReloadTrait (onReload), para
carregar uma datagrid com objetos vindos do banco de dados, e o
DeleteTrait (onDelete, Delete), para questionar o usuário a respeito da
exclusão de registros por meio de uma datagrid.
A partir do momento em que temos de nidos os traits com
comportamentos básicos, como carregar e excluir registros, podemos usá-
los para construir listagens de maneira mais rápida. Assim, no próximo
exemplo, vamos construir uma listagem para localização, exclusão e
edição (redirecionamento para o formulário) de produtos. Os traits
usados nesse exemplo serão ReloadTrait (onReload), para carregar uma
datagrid com objetos vindos do banco de dados, e o DeleteTrait (onDelete,
Delete), para questionar o usuário e excluir registros por meio de uma
datagrid.
O método construtor inicia com a de nição das classes em uso com seus
namespaces. Aqui, devemos prestar atenção na importação dos traits
DeleteTrait e ReloadTrait. À medida que iniciarmos a declaração da classe,
antes do construtor, veja as de nições use DeleteTrait e use ReloadTrait. O
comando use utilizado dentro do escopo da classe “importa” o código do
trait como se aqueles métodos estivessem na verdade sido declarados
aqui. Ao importarmos o trait ReloadTrait, aproveitamos para rede nir o
método onReload() disponibilizado pelo trait antes de “incorporá-lo” à
classe. Assim, o método é incorporado com um novo nome:
onReloadTrait(). Faremos isso porque precisaremos criar um método com
o nome onReload(). Assim, para evitar con ito de nomes, optamos por
renomear o método importado do trait, que por sua vez será chamado
por meio do método onReload() que criaremos.
Após importar os traits, iniciamos a de nição do método construtor.
Logo no início, precisamos de nir as propriedades $this->connection
(contém o nome da conexão) e $this->activeRecord (contém o nome da
classe Active Record). Ambos os atributos são usados pelos traits, que
precisarão dessas informações para carregar e excluir os dados do lugar
correto.
Após de nirmos as propriedades $this->connection e $this->activeRecord,
criamos o formulário de buscas utilizando o wrapper FormWrapper. Esse
formulário terá um campo chamado descricao, para localização de
produtos, e duas ações: “Buscar”, que estará vinculada ao método
onReload(), e “Cadastrar”, que estará vinculada ao método onEdit() da
classe ProdutosForm.
Após de nirmos o formulário de buscas, criamos a datagrid usando o
wrapper DatagridWrapper e criamos suas colunas usando a classe
DatagridColumn. Em seguida, as colunas são adicionadas à datagrid. A
datagrid terá duas ações: “Editar”, vinculada ao método onEdit() da classe
ProdutosForm (criada anteriormente), e “Deletar”, vinculada ao método
onDelete(), que não existe nessa classe, mas foi importado pelo trait
DeleteTrait. Ao nal do construtor, tanto o formulário quanto a listagem
são empacotados verticalmente por meio do container VBox, antes de este
ser incorporado à página.
 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=ProdutosList.

 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.

8.3.4 Cadastro de cidades


Na seção anterior, aprendemos a utilizar os traits criados para reaproveitar
pequenos comportamentos para formulários e listagens. Assim, quando
criamos o formulário de produtos, usamos traits como o SaveTrait
(onSave) para salvar os dados do formulário no banco de dados e o
EditTrait (onEdit) para carregar o formulário com um registro, e, quando
criamos a listagem de produtos, usamos traits como o ReloadTrait
(onReload) para carregar uma datagrid com objetos vindos do banco de
dados, e o DeleteTrait (onDelete, Delete), para questionar o usuário e
excluir registros por meio de uma datagrid.
O objetivo do próximo exemplo é combinar diferentes traits entre os
citados anteriormente para formar uma interface que possibilite a
listagem, a exclusão, a edição e a inserção de registros na mesma tela.
Dessa forma, não precisaremos construir duas classes para manutenção
de um cadastro. Para isso, criaremos um cadastro de cidades no qual a
primeira metade da tela (superior) terá um formulário de cadastro e
edição de registros. Já a segunda metade da tela (inferior) terá uma
listagem dos registros já feitos. Essa abordagem tem algumas limitações.
Como o formulário será utilizado para o cadastro, não teremos um
formulário para buscas de registros. Assim, essa abordagem é
recomendada para manutenção de cadastros com uma quantidade menor
de registros que não precise de buscas nem de paginação.
Como os traits oferecem comportamentos básicos prede nidos como
salvar, editar registros e carregar dados para uma datagrid, poderemos
combiná-los para construir uma interface que concentre ao mesmo tempo
a funcionalidade de cadastro (formulário) bem como de listagem
(datagrid). Para isso, vamos criar um cadastro completo de cidades
chamado CidadesFormList.
O método construtor inicia com a de nição das classes em uso, com seus
namespaces. Neste exemplo, serão utilizados os traits EditTrait,
DeleteTrait, ReloadTrait e SaveTrait. Veja que estamos colocando todos os
traits criados em uso. Na medida em que iniciamos a declaração da classe,
antes do construtor, veja as de nições use EditTrait e use DeleteTrait. O
comando use utilizado dentro do escopo da classe “importa” o código do
trait como se aqueles métodos estivessem na verdade sido declarados
aqui. Ao importarmos o trait ReloadTrait, aproveitamos para mudar o
nome do método onReload() para onReloadTrait(). O mesmo fazemos ao
importarmos o SaveTrait, mudando o nome do método onSave() para
onSaveTrait(). Faremos isso porque queremos criar novos métodos
exatamente com esses nomes, chamando internamente os métodos dos
traits, agregando comportamento.
Após importar os traits, iniciamos a de nição do método construtor.
Logo no início deste, precisamos de nir as propriedades $this->connection
(contém o nome da conexão) e $this->activeRecord (contém o nome da
classe Active Record). Ambos os atributos são usados pelos traits, que
precisarão dessas informações para armazenar e carregar os dados do
lugar correto.
Após de nirmos as propriedades $this->connection e $this->activeRecord,
criamos o formulário para cadastro de cidades utilizando o wrapper
FormWrapper e em seguida criamos vários objetos que farão parte do
formulário. Então, abrimos uma transação com a base de dados para
carregar dados da classe Estado para montar vetores ($items) que serão
usados para alimentar a combo para seleção de Estado. Depois, os
campos são adicionados ao formulário. Após a criação dos campos, é
gerada uma ação “salvar”, vinculada ao método onSave(), que será
declarado posteriormente.
Depois de de nirmos o formulário de cadastro, criamos a datagrid
usando o wrapper DatagridWrapper e criamos suas colunas usando a classe
DatagridColumn. Em seguida, as colunas são adicionadas à datagrid. A
datagrid terá duas ações: “Editar”, vinculada ao método onEdit() (que virá
do trait EditTrait), e “Deletar”, vinculada ao método onDelete() (que virá
do trait DeleteTrait). Depois, ambos, o formulário e a datagrid, são
empacotados em um contêiner VBox, que é então adicionado à página.
 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=CidadesFormList.

 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.

 Observação: o cadastro de fabricantes (FabricantesFormList), disponibilizado


com o cadastro de cidades, utiliza a mesma técnica de construção e os mesmos
traits que o cadastro de cidades. Dessa forma, não colocaremos a explicação sobre
o cadastro de fabricantes aqui para não ficar muito repetitivo.

8.3.5 Manipulação de sessões


A próxima funcionalidade que desenvolveremos em nossa aplicação de
vendas será a tela para registro de vendas. Nessa tela, o usuário poderá
informar vários produtos para comprar, antes de dar procedimento à
nalização da venda. Enquanto o usuário não nalizar a venda, os
produtos que ele registrar estarão só “em memória”. Para manter os dados
dos produtos “em memória” utilizaremos o recurso de sessões.
Requisições de páginas (que ocorrem geralmente via método POST ou GET)
são interações curtas entre o cliente e o servidor. Quando uma página é
requisitada, as variáveis declaradas ocorrem somente durante a
requisição. Uma forma de preservar o conteúdo de uma variável entre o
cliente e o servidor durante um tempo maior é por meio do uso de
sessões. Uma sessão é um meio de armazenamento de informações no
lado do servidor em que podemos armazenar valores que mantêm o seu
estado mesmo após sucessivas requisições de páginas, estabelecendo um
vínculo entre o cliente e o servidor. Como o conteúdo de uma sessão
depende do usuário que está interagindo com a aplicação, geralmente
utilizam-se sessões para controle de login, de modo que podemos
armazenar na sessão quem está logado para veri car página por página se
esse usuário tem permissões de acesso. Também podemos armazenar na
sessão preferências do usuário, bem como conteúdos dependentes de uma
interação.

8.3.5.1 Registry Pattern


Em diversas situações no desenvolvimento de aplicações de negócio
precisamos compartilhar algumas informações por meio de várias etapas
de um mesmo processo dentro do sistema. Para isso, precisamos de um
local visível globalmente que sirva para o armazenamento compartilhado
de informações. Poderíamos utilizar variáveis globais, mas essa técnica
contraria uma série de princípios da orientação a objetos, como o
encapsulamento. Então, para implementarmos isso, muitas vezes, usamos
um objeto que atua como um registrador de informações, não tendo
relações diretas com outros objetos de negócio, como associações,
agregações ou heranças. Tais objetos registradores existem apenas para
armazenar e retornar determinados valores. Pense em uma memória com
determinadas posições utilizadas para alocar valores.
Um objeto que implementa o Registry Pattern geralmente utiliza métodos
estáticos para atribuir e retornar a informação que desejamos armazenar
em seus registradores. Não faz sentido termos várias instâncias de objetos
registry no sistema, visto que estaremos falando de um local
compartilhado para armazenamento de informações. Em nossa aplicação,
usaremos um objeto registry para lidar com sessões. Antes, porém,
estudaremos um pouco mais sobre sessões.
O protocolo HTTP é, por natureza, stateless, ou seja, ele não mantém o
seu estado no decorrer das várias interações de um usuário com o
sistema, o que torna difícil identi carmos um usuário com unicidade ou
mesmo passarmos valores de variáveis de uma página a outra durante a
navegação do usuário no sistema. Podemos passar pela URL (método
GET), assim como via submissão de formulários (método POST). No
entanto, torna-se excessivamente trabalhoso lembrar sempre de passar
uma variável adiante no sistema. Uma sessão torna esse trabalho muito
mais simples.
Uma sessão pode ser de nida como o tempo decorrido durante uma
interação do usuário com o sistema. A sessão para o PHP representa um
espaço físico localizado no servidor no qual podemos armazenar variáveis
que se mantêm persistentes mesmo durante sucessivos acessos ao sistema.
Durante essa interação do usuário com o sistema, podemos armazenar
valores de variáveis nesse espaço chamado sessão. Cada visitante do
sistema recebe um código de sessão único, chamado de SESSION_ID, que
geralmente é armazenado em um cookie.
Uma sessão no PHP é representada por um array chamado $_SESSION. Esse
array é visível por toda a aplicação, e os dados armazenados nele
persistem durante toda a interação do usuário, mesmo passando por
diferentes páginas. O que faremos é criar uma interface orientada a
objetos (um Registry Pattern) que trate de registrar e obter valores a partir
desse array. Essa interface será representada pela classe Session, que em
seu método construtor inicializará a sessão. Essa inicialização deverá ser
feita no início de cada página que desejar fazer uso dos dados da sessão. A
classe Session também terá o método setValue(), que registra um valor em
uma determinada posição da sessão (identi cado pelo primeiro
parâmetro). Já a função getValue() retorna o valor atribuído a uma
determinada posição da sessão por meio do método setValue().
Terminamos com o método freeSession(), que destrói todos os dados
contidos em uma sessão.

 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();
}
}

8.3.6 Registro de vendas


O próximo passo no desenvolvimento de nossa aplicação é criar uma
interface para o registro de vendas. O processo de vendas será dividido em
duas etapas. A primeira etapa terá uma interface que permitirá ao usuário
adicionar itens (produtos) à uma lista de objetos armazenada na sessão. A
gura 8.9 demonstra essa interface em que o usuário informa o código do
produto e a quantidade e adiciona-os em uma lista armazenada em
sessão. Essa classe será chamada de VendasForm.

Figura 8.9 – Formulário de registro de venda.


A partir do momento em que o usuário clicar no botão “Terminar”, ele
será direcionado à outra interface, que por sua vez permitirá que o
usuário informe detalhes da transação para que esta seja armazenada no
banco de dados. Nesta etapa, ele informará cliente, descontos, acréscimos,
parcelamento e observação. A gura 8.10 demonstra essa tela de
nalização, que irá registrar a venda na base de dados, bem como gerar as
parcelas nanceiras. Esta classe será chamada de ConcluiVendaForm.

Figura 8.10 – Formulário de conclusão de venda.

8.3.6.1 Registro de itens da venda


Iniciaremos o desenvolvimento pela primeira tela, na qual o usuário
informará os produtos e suas respectivas quantidades, que serão
armazenados na sessão e exibidos em uma datagrid. Essa classe inicia
com a de nição das classes necessárias, bem como com a de seus
namespaces. No método construtor, começamos iniciando a sessão ao
instanciarmos a classe Session. Em seguida, criamos um formulário que
será utilizado para informar os produtos que serão vendidos. Esse
formulário terá dois campos – id_produto e quantidade – e duas ações –
“Adicionar”, que está vinculada ao método onAdiciona(), que receberá os
dados do formulário e os adicionará à sessão, e “Terminar”, que está
vinculada ao método onLoad() da classe ConcluiVendaForm. Como esse
método pertence à outra classe, provocará uma troca de tela no momento
do clique, ou seja, o usuário será transportado para a classe
ConcluiVendaForm.
Após a de nição do formulário no qual são informados os itens, é criada
uma datagrid que será utilizada para exibir os itens que foram
adicionados à sessão. Essa datagrid (new DatagridWrapper(new Datagrid))
terá colunas para código do produto (id_produto), descrição (descricao),
quantidade (quantidade) e preço (preco). Como a coluna de preço precisa
de formatação, ela passará pela função de transformação formata_money(),
de nida pelo setTransformer(). As colunas são adicionadas à datagrid
(addColumn) e em seguida é de nida uma ação “Deletar”, vinculada ao
método onDelete(), que permite excluir um item da Datagrid. Ao nal do
método construtor, o formulário ($this->form) e a datagrid ($this-
>datagrid) são empacotados em uma caixa vertical (VBox), que é então
adicionada à página.
 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=VendasForm.

 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();
}

8.3.6.2 Finalização da venda


Ao criarmos a classe anterior (VendasForm), de nimos no formulário um
botão chamado “Terminar”, que por sua vez estava vinculado à classe
ConcluiVendaForm. O objetivo da classe ConcluiVendaForm é fazer a nalização
da venda solicitando ao usuário informações como acréscimos, descontos
e parcelamento. Na gura 8.11 temos a tela de conclusão de venda com
alguns dados preenchidos.

Figura 8.11 – Formulário de conclusão de venda.


A tela de conclusão de vendas basicamente solicita essas informações
“ nais” ao usuário e em seguida registra a venda, bem como cria as
parcelas nanceiras conforme o parcelamento informado.
O formulário de conclusão de vendas inicia pelo método construtor com
a de nição de campos para código do cliente (id_cliente), valor parcial da
venda (valor_venda), descontos (desconto), acréscimos (acrescimos), valor
nal calculado (valor_final), parcelamento escolhido (parcelas) e
observações (obs).
Após de nirmos os campos do formulário, são acrescentadas três opções
de parcelamento à combo de parcelas e é de nida uma ação JavaScript no
evento onBlur dos campos para acréscimo e desconto. Essa ação JavaScript
calcula o valor nal com base no valor nal somado aos acréscimos,
subtraindo os descontos e atualizando o campo com o valor nal
(valor_final).
Por m, ainda no método construtor, os campos são adicionados ao
formulário (addField) e também é de nida a ação do formulário, que
contará com um botão “salvar”, vinculado ao método onGravaVenda(). O
formulário é então acrescentado à página.
 Observação: a classe ConcluiVendaForm será executada somente a partir da
classe VendasForm.

 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());
}
}
}

8.3.7 Relatório de contas


Nas últimas seções, construímos programas que fazem cadastros, como o
cadastro de produtos (ProdutosForm), e que também registram atividades
de um processo, como o registro de vendas (VendasForm, ConcluiVendaForm).
Agora chegou o momento de prepararmos a “saída” de informações para o
usuário por meio de relatórios. Para isso, construiremos a classe
ContasReport, que terá como responsabilidade gerar um relatório de contas
com base em um intervalo de datas informadas pelo usuário. O relatório
será gerado em tela, bem como no formato PDF.
 Observação: neste livro, abordamos de maneira superficial a geração de
relatórios. Caso queira ver em maior grau de profundidade, procure pelo livro
Criando relatórios com PHP também publicado pela Novatec Editora. Ele aborda
bibliotecas para geração de relatórios HTML, PDF, RTF e gráficos, relatórios
tabulares, com filtros, seleção de colunas e ordenação, relatórios hierárquicos
(quebras) e matriciais (cross-tab reports), gráficos gerenciais reais e documentos
(notas fiscais e cartas), entre outros.
Para gerar um relatório de contas no formato HTML, usaremos a
biblioteca de templates Twig, já abordada anteriormente. Também será
utilizada a biblioteca DomPDF para conversão para o formato PDF. A
interface do programa será como demonstrado na gura 8.12.
Na parte superior da tela, teremos um formulário perguntando duas
datas (inicial e nal de vencimento). Com base nas datas informadas (que
são opcionais), o programa gera uma listagem no formato HTML sobre as
contas com vencimento dentro do período informado. Caso somente seja
informada a primeira data (inicial), serão listadas as contas a vencer após
a data. Caso somente seja informada a segunda data ( nal), serão listadas
as contas a vencer antes da data. Caso sejam informadas ambas as datas
(inicial e nal), serão listadas as contas a vencer entre as datas informadas.
Figura 8.12 – Relatório de contas.
Para implementar o relatório de contas, a princípio, precisamos construir
um formulário para solicitar as datas ao usuário. Vamos construir um
formulário com dois campos (data_ini e data_fim). O formulário terá
também uma ação chamada “gerar”, que está vinculada ao método
onGera(), que por sua vez fará o carregamento do HTML com as contas a
vencer no período apresentado na tela, e também uma ação chamada
PDF, que está vinculada ao método onGeraPDF(), que converterá o
relatório e o baixará no formato PDF. Por m, o formulário é adicionado à
página.
 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=ContasReport.

 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>

8.3.8 Relatório de produtos


O objetivo deste próximo relatório é criar um relatório na tela que
apresente uma lista de produtos, bem como seus respectivos código de
barras e QRCode. Para criar a visualização em tela, usaremos a biblioteca
de templates Twig, já utilizada em exemplos anteriores. Para gerar o
código de barras, usaremos uma biblioteca publicamente disponível
chamada Picqer Barcode. Já para gerar QRCode, usaremos a biblioteca
também disponível publicamente BaconQrCode.
Para instalar as bibliotecas utilizadas aqui, recorremos ao Composer:
php composer.phar require bacon/bacon-qr-code
php composer.phar require picqer/php-barcode-generator
A gura 8.13 demonstra o relatório depois de gerado.
O relatório será gerado pela classe ProdutosReport, que inicia em seu
método com a instanciação da classe Twig_Loader_Filesystem, que indica o
diretório onde estão os templates, e o método loadTemplate(), que carrega
o template.
Para gerar códigos de barras, precisamos instanciar um $generator, que é
uma instância de Picqer\Barcode\BarcodeGeneratorHTML. Já para gerar
QRCodes, precisamos criar um $renderer, que é uma instância de
BaconQrCode\Renderer\Image\Svg, e um Writer, que é uma instância de
BaconQrCode\Writer.

Figura 8.13 – Relatório de produtos.


Depois de instanciar os objetos responsáveis pela criação dos códigos de
barras e QRCode, iniciamos uma transação com a base de dados,
carregamos todos os produtos (Produto::all()) e percorremos os produtos
por meio de um foreach. Dentro do laço de repetições, criamos as posições
barcode e qrcode. A posição barcode conterá o resultado do método
getBarcode(), que é o método responsável por gerar o código de barras,
baseado no id do produto. A posição qrcode conterá o resultado do
método writeString(), que gerará o QRCode, baseado no id e na descricao
do produto.
O vetor de produtos é então atribuído ao vetor $replaces, que será
injetado em um template HTML ao nal pelo método render(). O
resultado do processamento do HTML é então adicionado a um painel, e
este é adicionado à página.

 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.

Figura 8.14 – Relatório de pessoas.


O primeiro passo é criar a view na base de dados. O código a seguir
demonstra as instruções que utilizamos para criar a view. Ela é baseada
na tabela de pessoas, mas também tem duas colunas que são subselects
que buscam os valores totais, e os valores em abertos para cada pessoa do
select principal.

 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>

8.3.10 Grá co de vendas por mês


Neste próximo exemplo, criaremos um grá co de vendas por mês, tal
como apresentado na gura 8.15. Para cada mês é apresentada uma barra
com o total vendido. Para gerar o grá co, utilizaremos uma biblioteca
Javascript chamada ChartJs. Para executar a biblioteca, utilizaremos
novamente templates HTML.

Figura 8.15 – Grá co de vendas por mês.


Antes de gerar o relatório, criaremos um método na classe Venda, para
retornar um vetor com o valor vendido por mês. Esse método será o
getVendasMes() e, para isso, realizará uma query no banco de dados, mais
precisamente sobre a tabela venda, agrupando os resultados por mês. Ao
nal, é formado um vetor contendo o nome do mês e, na respectiva
posição do vetor, o valor vendido. O vetor $meses é utilizado para
convertermos o número do mês em seu respectivo nome.

 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.

 A biblioteca ChartJs é importada no template geral do sistema


App/Templates/template.html nos cabeçalhos da página.

 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.

Figura 8.16 – Dashboard de vendas.


Para construir o dashboard, utilizaremos simplesmente classes de grá cos
já criadas anteriormente e somente decidiremos sua disposição em tela.
Neste caso, usaremos as classes VendasMesChart, que é responsável pela
montagem do grá co de vendas por mês em formato de barras, e
VendasTipoChart, que é responsável pela montagem do grá co de vendas
por tipo de produto em formato de pizza. Estas classes podem ser
acionadas individualmente pelo menu, mas também podem ser agregadas
no formato de um painel.
Para montar o dashboard, utilizaremos uma caixa horizontal (HBox), mas
também poderíamos usar uma caixa vertical (VBox), ou mesmo um
template HTML. Neste caso, simplesmente adicionamos as classes
VendasMesChart e VendasTipoChart à caixa horizontal, sobrepondo seu estilo,
mais precisamente sua largura (40%).

 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);
}
}

8.3.12 Controle de login


Até o momento, todos os acessos aos programas desenvolvidos foram
realizados por meio do index.php sem controle algum de autenticação.
Propositalmente, deixamos o login para o m do processo de criação da
aplicação para não atrapalhar o acesso aos exemplos construídos. Porém
agora criaremos um controle simpli cado de login para impedir que
usuários não autenticados acessem a aplicação.
Para fazer o controle de login, precisaremos modi car o arquivo de acesso
à aplicação, o index.php. Para isso, deixamos junto aos arquivos de exemplo
do livro um arquivo chamado index-login.php. Para implementar o controle
de login, basta renomear o arquivo index-login.php para index.php, usando
esse novo conteúdo a partir de então. Por meio do novo index.php com
controle de acesso, a página demonstrada na gura 8.17 deverá ser exibida
ao acessar o sistema.
Figura 8.17 – Tela de login.

8.3.12.1 Um novo index


Como vimos, é necessário um novo index para implementarmos um
controle de login. O início do “novo” index continua da mesma maneira
que sua versão anterior. Inicialmente é carregada a classe ClassLoader,
responsável pelo carregamento automático (autoloader) das classes
armazenadas sob o diretório Lib/, especi camente a pasta Lib/Livro, em que
se encontram as classes do namespace Livro. Já a classe AppLoader é
responsável pelo carregamento automático (autoloader) das classes
armazenadas sob o diretório App/, especi camente App/Control e App/Model.
Em seguida, é iniciada uma nova sessão (Session) e então é veri cado se o
usuário está logado por meio de uma variável de sessão chamada logged.
Caso o usuário esteja logado (o que não será verdadeiro no primeiro
acesso), é carregado o template.html (template com menus); caso contrário
(primeiro acesso), será carregado o login.html (template sem menus). Além
disso, caso o usuário não esteja logado, será automaticamente carregada a
classe LoginForm. É nessa classe que construiremos o formulário de
autenticação (usuário e senha), responsável por validar o acesso do
usuário ao sistema.
Em seguida, veri ca-se se há uma classe informada na URL
($_GET['class']). Se houver classe na URL e o usuário estiver logado, essa
será a classe exibida. O código seguinte é igual ao que já havíamos
construído no capítulo 6, sendo que a classe identi cada é instanciada
(new) e exibida (show). Todo o conteúdo da página exibido a partir da
função ob_start() é lido pela função ob_get_contents() e posteriormente
“inserido” no template ($output) por meio da substituição de conteúdo
(str_replace).

 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;

8.3.12.2 Um formulário de login


Como vimos durante a de nição do novo index.php, caso o usuário não
esteja logado, o que é determinado por uma variável de sessão
(Session::getValue('logged')), automaticamente ele será direcionado para
a classe LoginForm. O objetivo da classe LoginForm é solicitar usuário e
senha, veri cá-los e, caso as credenciais informadas forem válidas, gravar
a variável de sessão logged e recarregar o index.php para que o usuário
visualize o menu de opções completo, tendo acesso a todo tipo de
programa.
 Observação: vamos implementar um controle bastante simplificado. Somente
será verificado se o usuário e a senha informados são admin/admin. Na prática, ao
desenvolver seu próprio sistema, você só precisará substituir esse trecho por uma
verificação em alguma tabela de usuários de seu sistema.
O formulário de login inicia com a criação de um formulário (Form) com
dois campos: login e password. Esse formulário terá uma única ação
chamada “Login”, que estará vinculada ao método onLogin(). Por m, o
formulário é adicionado à página.

 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>";
}
}

8.4 Considerações nais


A orientação a objetos é um tópico muito extenso. Trabalho diariamente
com desenvolvimento orientado a objetos desde 1998, e a cada dia
aprendo algo novo. Seria loucura pensar em escrever um livro sobre esse
assunto que abordasse todos os seus aspectos de forma completa. O
objetivo deste livro é mostrar a você, leitor, alguns aspectos de um sistema
orientado a objetos focado em aplicações de negócio. Por isso a ênfase em
design patterns que pudessem ser utilizados para persistência de objetos
em bases de dados e construção de componentes como formulários e
listagens, conceitos que podem ser adotados em praticamente toda
aplicação de negócios. Ao mesmo tempo, procuramos abordar temas
introdutórios à linguagem PHP em si (no capítulo 1) e em relação à
orientação a objetos (no capítulo 2), tornando a leitura deste livro um
processo evolutivo que poderia ser acompanhado por qualquer
programador, mesmo sem o prévio conhecimento de orientação a objetos.
Espero que você tenha conseguido absorver alguns conceitos de maneira
leve e que consiga aplicar alguns aspectos no desenvolvimento de novas
aplicações. Caso tenha gostado da maneira como os componentes foram
construídos, saiba que esses conceitos originaram o “Adianti Framework
para PHP”, framework que lancei em 2012. Mais informações em
www.adianti.com.br/framework.
Desenvolvendo com Laravel
Stauffer, Matt
9788575227718
480 páginas

Compre agora e leia

O que diferencia o Laravel de outros frameworks PHP? Para


começar, velocidade e simplicidade. Este framework veloz de
desenvolvimento de aplicativos e seu vasto ecossistema de
ferramentas permitem construir rapidamente novos sites e
aplicativos com código limpo e legível. Com este guia prático, Matt
Stauffer – um dos principais mentores e desenvolvedores da
comunidade Laravel – fornece a introdução definitiva a um dos
frameworks web mais populares da atualidade. A visão geral de alto
nível e os exemplos concretos do livro ajudarão desenvolvedores
PHP experientes a começar a usar o Laravel imediatamente.
Quando você chegar à última página, se sentirá à vontade para criar
um aplicativo inteiro no Laravel a partir do zero. Vários recursos do
framework são apresentados, entre eles: •Blade, a ferramenta
poderosa e personalizada do Laravel para a manipulação de
templates •Ferramentas para a coleta, validação, normalização e
filtragem de dados fornecidos pelo usuário •O ORM Eloquent do
Laravel para o trabalho com os bancos de dados do aplicativo •O
objeto de solicitação Illuminate e seu papel no ciclo de vida do
aplicativo •PHPUnit, Mockery e PHPSpec para o teste de seu código
PHP •Ferramentas do Laravel para a criação de APIs JSON e
RESTful •Interfaces para acesso ao sistema de arquivos, sessões,
cookies, caches e busca •Ferramentas para a implementação de
filas, jobs, eventos e publicação de eventos por WebSockets
•Pacotes especializados do Laravel: Scout, Passport, Cashier, Echo,
Elixir, Valet e Socialite

Compre agora e leia


Padrões para Kubernetes
Ibryam, Bilgin
9788575228159
272 páginas

Compre agora e leia

O modo como os desenvolvedores projetam, desenvolvem e


executam software mudou significativamente com a evolução dos
microsserviços e dos contêineres. Essas arquiteturas modernas
oferecem novas primitivas distribuídas que exigem um conjunto
diferente de práticas, distinto daquele com o qual muitos
desenvolvedores, líderes técnicos e arquitetos estão acostumados.
Este guia apresenta padrões comuns e reutilizáveis, além de
princípios para o design e a implementação de aplicações nativas
de nuvem no Kubernetes. Cada padrão inclui uma descrição do
problema e uma solução específica no Kubernetes. Todos os
padrões acompanham e são demonstrados por exemplos concretos
de código. Este livro é ideal para desenvolvedores e arquitetos que
já tenham familiaridade com os conceitos básicos do Kubernetes, e
que queiram aprender a solucionar desafios comuns no ambiente
nativo de nuvem, usando padrões de projeto de uso comprovado.
Você conhecerá as seguintes classes de padrões: • Padrões
básicos, que incluem princípios e práticas essenciais para
desenvolver aplicações nativas de nuvem com base em contêineres.
• Padrões comportamentais, que exploram conceitos mais
específicos para administrar contêineres e interações com a
plataforma. • Padrões estruturais, que ajudam você a organizar
contêineres em um Pod para tratar casos de uso específicos. •
Padrões de configuração, que oferecem insights sobre como tratar
as configurações das aplicações no Kubernetes. • Padrões
avançados, que incluem assuntos mais complexos, como
operadores e escalabilidade automática (autoscaling).

Compre agora e leia


Candlestick
Debastiani, Carlos Alberto
9788575225943
200 páginas

Compre agora e leia

A análise dos gráficos de Candlestick é uma técnica amplamente


utilizada pelos operadores de bolsas de valores no mundo inteiro.
De origem japonesa, este refinado método avalia o comportamento
do mercado, sendo muito eficaz na previsão de mudanças em
tendências, o que permite desvendar fatores psicológicos por trás
dos gráficos, incrementando a lucratividade dos investimentos.
Candlestick – Um método para ampliar lucros na Bolsa de Valores é
uma obra bem estruturada e totalmente ilustrada. A preocupação do
autor em utilizar uma linguagem clara e acessível a torna leve e de
fácil assimilação, mesmo para leigos. Cada padrão de análise
abordado possui um modelo com sua figura clássica, facilitando a
identificação. Depois das características, das peculiaridades e dos
fatores psicológicos do padrão, é apresentado o gráfico de um caso
real aplicado a uma ação negociada na Bovespa. Este livro possui,
ainda, um índice resumido dos padrões para pesquisa rápida na
utilização cotidiana.

Compre agora e leia


Avaliando Empresas, Investindo em
Ações
Debastiani, Carlos Alberto
9788575225974
224 páginas

Compre agora e leia


Avaliando Empresas, Investindo em Ações é um livro destinado a
investidores que desejam conhecer, em detalhes, os métodos de
análise que integram a linha de trabalho da escola fundamentalista,
trazendo ao leitor, em linguagem clara e acessível, o conhecimento
profundo dos elementos necessários a uma análise criteriosa da
saúde financeira das empresas, envolvendo indicadores de balanço
e de mercado, análise de liquidez e dos riscos pertinentes a fatores
setoriais e conjunturas econômicas nacional e internacional. Por
meio de exemplos práticos e ilustrações, os autores exercitam os
conceitos teóricos abordados, desde os fundamentos básicos da
economia até a formulação de estratégias para investimentos de
longo prazo.

Compre agora e leia


Manual de Análise Técnica
Abe, Marcos
9788575227022
256 páginas

Compre agora e leia

Este livro aborda o tema Investimento em Ações de maneira inédita


e tem o objetivo de ensinar os investidores a lucrarem nas mais
diversas condições do mercado, inclusive em tempos de crise.
Ensinará ao leitor que, para ganhar dinheiro, não importa se o
mercado está em alta ou em baixa, mas sim saber como operar em
cada situação. Com o Manual de Análise Técnica o leitor aprenderá:
- os conceitos clássicos da Análise Técnica de forma diferenciada,
de maneira que assimile não só os princípios, mas que desenvolva
o raciocínio necessário para utilizar os gráficos como meio de
interpretar os movimentos da massa de investidores do mercado; -
identificar oportunidades para lucrar na bolsa de valores, a longo e
curto prazo, até mesmo em mercados baixistas; um sistema de
investimentos completo com estratégias para abrir, conduzir e fechar
operações, de forma que seja possível maximizar lucros e minimizar
prejuízos; - estruturar e proteger operações por meio do
gerenciamento de capital. Destina-se a iniciantes na bolsa de
valores e investidores que ainda não desenvolveram uma
metodologia própria para operar lucrativamente.

Compre agora e leia

Você também pode gostar