A aplicação desenvolvida na Aula 9 era suficiente para exibir o currículo de uma pessoa. Mas se ela é suficiente para exibir um currículo, ela tem que ser adaptável para exibir quantos currículos nós quisermos. Imagine uma equipe, querendo exibir o currículo de cada um de seus desenvolvedores e designers, por exemplo.
Na aplicação que nós construímos, nós começamos definindo diversos arrays com os dados que precisávamos exibir. Nós poderíamos simplesmente copiar o arquivo do programa para cada um dos membros da equipe, e depois ir adaptando cada um deles. Isso seria simples, mas tem dois problemas: primeiro que copiar arquivos pra uma equipe de 10 pessoas ia ficar muito chato. Segundo que toda vez que alguma coisa fosse mudar no programa, seria necessário mudar cada um dos arquivos.
Para nosso time de 10 pessoas, separar as informações em um arquivo separado vai ser suficiente.
Então o arquivo capi.php
vai ter apenas as linhas dos dados, assim:
<?php
$nome = 'Capi Etheriel';
$profissao = 'Designer de Jogos';
$especialidade = 'Jogos Digitais e Design Colaborativo';
$contato = ['Email' => 'mailto:barraponto@gmail.com',
'Twitter' => 'http://twitter.com/barraponto',
'Linkedin' => 'http://linkedin.com/in/barraponto',
'Github' => 'http://github.com/barraponto'];
$formacao = [['inicio' => 2006,
'termino' => 2012,
'instituicao' => 'Universidade Estadual de Campinas',
'ocupacao' => 'Bacharel em Midialogia'],
['inicio' => '2013',
'termino' => FALSE,
'instituicao' => 'TimTec',
'ocupacao' => 'Desenvolvimento PHP']];
$experiencia = [['inicio' => 2013,
'termino' => FALSE,
'instituicao' => 'Rodada Hacker',
'ocupacao' => 'Hacker'],
['inicio' => '2013',
'termino' => FALSE,
'instituicao' => 'Quequere Jogos',
'ocupacao' => 'Designer de Jogos']];
$portfolio = [['titulo' => 'Rodada Hacker São Paulo',
'foto' => 'rodada-sp.jpg',
'ano' => 2013],
['titulo' => 'Cara a Cara do Legislativo',
'url' => 'http://quequere.com.br/caraacara',
'ano' => 2013]];
$profhabil = ['html' => 100,
'css' => 100,
'javascript' => 60,
'python' => 50];
Preste bastante atenção no fim desse arquivo: nós abrimos a tag <?php
lá em
cima, para inserir código PHP, mas no fim do arquivo nós não fechamos essa tag.
Isso é uma prática que evita problemas com conteúdo depois do fechamento da
tag. Infelizmente muitos editores de texto ou de código vão inserir uma linha
em branco no fim do arquivo, e se isso estiver fora da tag do PHP, essa linha
vai ser impressa.
Agora nós vamos usar uma função do PHP para inserir essas variáveis no nosso programa original, que vai ficar assim:
<?php require_once 'capi.php' ;>
<!doctype html>
<html lang="en">
...
</html>
Todo o código continua aí exceto os dados que estão definidos no capi.php
. A
função require_once
é uma das funções do PHP responsáveis por incluir o
código do arquivo na execução. Ela procura o arquivo indicado e inclui o código
naquele ponto da execução, mas se não encontrar o arquivo, então ela trava a
execução do programa. Afinal, sem as variáveis definidas, nós não teríamos nada
para mostrar ao usuário.
Essa função difere da função include_once
justamente por parar o programa
caso o arquivo não seja encontrado. E o require_once
difere da função
require
ao inserir o código apenas uma vez. Isto é, se em algum momento nós
escrevermos de novo require_once 'capi.php'
, nada vai acontecer.
Bom, agora que nós temos os dados separados em outros arquivos, podemos copiar o arquivo de dados e alterar os valores para outro membro da equipe. Suponha que daí resultem os arquivos 'gus.php', 'gabi.php' e 'guilherme.php'. Como podemos fazer para que o programa carregue o currículo certo?
Bom, uma opção razoável é passar a opção na URL. A URL
http://localhost/curriculo.php?membro=gus
carregaria o currículo do Gus,
enquanto a URL http://localhost/curriculo.php?membro=gabi
carregaria o
currículo da Gabi.
Por onde começamos? Primeiro, antes do require_once
, precisamos checar se
esse parâmetro foi enviado. E para evitar que abusem do nosso programa, vamos
checar se esse parâmetro corresponde a algum dos membros da equipe. Só então,
vamos usar esse parâmetro para procurar e carregar o arquivo.
<?php
$membros = ['capi', 'gus', 'gabi'];
if (!empty($_GET['membro']) && in_array($_GET['membro'], $membros)) {
require_once $_GET['membro'] . '.php';
}
Mas aqui temos um problema: o que acontece se o parâmetro não estiver presente ou não corresponder a um membro da equipe? Nesse caso deveríamos interromper a execução -- de preferência dando ao usuário uma opção razoável, como por exemplo exibindo links para os membros da equipe.
Mas isso implica em ter que escrever um outro HTML, para essa outra condição, o
que faria nosso código ficar muito comprido e difícil de dar manutenção. Então
vamos jogar o nosso HTML original inteiro em outro arquivo, digamos o arquivo
curriculo-membro.php
.
<?php
$membros = ['capi', 'gus', 'gabi'];
if (!empty($_GET['membro']) && in_array($_GET['membro'], $membros)) {
require_once $_GET['membro'] . '.php';
require_once 'curriculo-membro.php';
}
Como você vê, nosso código fica muito mais simples de ler assim, embora agora
tenhamos que abrir um outro arquivo para ver o HTML, o curriculo-membro.php
.
Por outro lado, quem for responsável pelo HTML e CSS (o frontend) agora só
precisa se preocupar com esse arquivo (que costumamos chamar de Template).
Não podemos esquecer de colocar o nosso else (que também vai carregar o arquivo com o HTML das opções):
else {
require_once 'curriculo-opcoes.php';
}
Esse arquivo vai precisar ter um código HTML completo, com as tags <HTML>
,
<HEAD>
, <BODY>
, etc. A vantagem de usar um formulário é que usando os
atributos certos, esse formulário vai gerar requisições para o programa
curriculo.php
com os parâmetro certos na URL, porque o formulário é do tipo
GET
.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $nome ?></title>
<link rel="stylesheet" href="css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<form action="curriculo.php" method="GET">
<label for="membro">
Selecione um dos membros para ver seu curriculo.
</label>
<select class="form-control" name="membro">
<?php foreach ($membros as $membro): ?>
<option value="<?= $membro ?>">
<?= ucwords($membro) ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div><!-- /container -->
</body>
</html>
Como você pode ver, separar o código em arquivos separados ajuda a facilitar a leitura do código, especialmente conforme ele vai ficando mais complexo. Complexidade é o grande inimigo do programador, mas é inevitável conforme nós queremos fazer mais e mais com os nossos programas.
Nós começamos com o código para exibir um currículo, aproveitando estilos compartilhados do Bootstrap. Agora nós estamos tentando reaproveitar o código para exibir os currículos de vários membros de uma equipe. Este processo se chama Abstração.
Conforme a abstração aumenta, a complexidade vai aumentar também. Existem muitas ferramentas desenhadas para manter a complexidade sob controle, como a inclusão de arquivos que vimos nesta unidade. Ainda podemos separar as partes repetitivas do código em funções e especializar os nossos templates, para evitar a repetição (que é outra inimiga do programador).
Mais para a frente no curso, nós ainda vamos tirar esses arquivos de dados daí e guardar os dados em um ambiente especializado que é o banco de dados. A partir daí, vamos poder fazer um aplicativo capaz de gerenciar currículos para milhares de pessoas -- o que pode ser útil tanto para quem procura emprego quanto para quem procura novos empregados.
Mas vamos com calma, um passo de cada vez.
No fim da unidade anterior, encontramos um problema: tivemos que repetir a estrutura do HTML nos nossos templates. Isso é ruim pelo mesmo motivo de sempre: porque vai dar mais trabalho.
Por exemplo, se quisermos linkar mais um arquivo de CSS nos nossos HTMLs, vamos ter que modificar cada um dos arquivos. Além disso, por enquanto temos apenas dois tipos de página, mas podemos ter muitos mais no futuro. Sem contar que podemos querer adicionar cabeçalhos, rodapés, etc.
A solução ideal é usar um template para imprimir essa parte repetida do HTML, e outro para imprimir um conteúdo. Especializando cada template em uma preocupação só, facilitamos a manutenção do código no futuro.
O PHP tem uma solução muito boa para isso, mas para aproveitá-la é preciso entender como o PHP escreve a resposta que vai enviar para o navegador.
Quando você fecha a tag do PHP e começa a escrever HTML, esse conteúdo vai
direto pro corpo da resposta que o servidor vai enviar para o navegador. O mesmo acontece quando você usa as funções print
, echo
e afins.
Existe, no PHP, um jeito de interromper esse fluxo, e salvar o conteúdo dele em uma variável, como se fosse uma string qualquer. Usando esse truque, é fácil guardar o que seria o resultado de um arquivo de template -- que geralmente sai imprimindo o HTML.
A função ob_start
abre um Registro de Saída, ou Output Buffer como é
conhecido em inglês. Na prática, ele segura tudo que normalmente iria para o
corpo da resposta, como o resultado de um print
ou código html fora das tags.
Depois basta chamar a função ob_get_clean
para obter todo o conteúdo que foi
segurado. Esse conteúdo aparece como uma string, que inclusive pode ser
manipulada, com substituições, cortes, enfim. É uma string como qualquer outra.
Pode inclusive ser impressa no corpo da resposta.
No nosso caso, o primeiro que temos que fazer é tirar a parte repetida toda e
jogar em um arquivo separado, que vamos chamar de layout.php
. Esse template
vai ter o doctype, a tag HTML com a tag <HEAD>
inteira, com todo o conteúdo e
a tag <BODY>
apenas com uma tag <DIV>
com a classe container
, pra não ter
que ficar repetindo.
O <HEAD>
fica assim. Perceba que ele ainda imprime a variável $nome
. Essa
variável tem que estar definida quando esse arquivo for incluído.
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $nome ?></title>
<link rel="stylesheet" href="css/bootstrap.min.css" />
</head>
Já a tag <BODY>
fica assim. Repare que nela eu coloquei impressa a variável
$conteudo
.
<body>
<div class="container">
<?= $conteudo ?>
</div><!-- /container -->
</body>
Finalmente, nos arquivos curriculo-membro.php
e curriculo-opcoes
, nossos
templates, todas essas linhas tem que ser eliminadas. Isso reduz bastante o
tamanho desses arquivos.
Agora vem a parte legal, que usa os registros. Vamos mudar o arquivo principal,
o curriculo.php
. Primeiro, nós abrimos o registro com a função ob_start
,
depois nós verificamos o contexto e usamos os templates correspondentes.
Só então, finalmente, nós pegamos o que quer que tenha sido guardado no
registro com a função ob_get_clean
e associamos esse valor à variável
$conteudo
. Que vai ser impressa no arquivo layout.php
, finalizando nosso
programa.
<?php
$membros = ['capi', 'gus', 'gabi'];
ob_start();
if (!empty($_GET['membro']) && in_array($_GET['membro'], $membros)) {
require_once $_GET['membro'] . '.php';
require_once 'curriculo-membro.php';
} else {
$name = 'Confira os curriculos da nossa equipe';
require_once 'curriculo-opcoes.php';
}
$conteudo = ob_get_clean();
require 'layout.php';
Se parece que ficou mais complicado agora, lembre que essa abstração está
abrindo caminho para o nosso programa. Agora podemos adicionar mais conteúdos
sem precisar repetir a estrutura do HTML, que está salva no template
layout.php
.
Existe um motivo muito sério para se usar registros de saída: uma vez que o servidor começa a enviar o contéudo da resposta, ele não pode mais mandar cabeçalhos com meta-informação sobre a resposta.
Isto está diretamente relacionado ao formato de uma resposta HTTP. HTTP é texto puro, e começa sempre com uma linha que diz a versão usada do HTTP e o status da resposta. Por exemplo:
HTTP/1.1 200 OK
Esses códigos de status são padronizados. O 200 significa que tudo foi como o esperado, 404 significa que não foi encontrado nenhum conteúdo para a URL requisitada, 500 significa que alguma coisa deu errada no servidor. Ninguém decora a tabela de status, mas esses 3 códigos que eu citei são os mais comuns e a gente acaba decorando.
Depois da primeira linha da resposta, começam linhas de cabeçalho. Elas indicam o tipo de conteúdo, data da resposta, origem da resposta, instruções de cache, detalhes do servidor...
HTTP/1.1 200 OK
Server: Apache
Date: Thu, 28 Nov 2013 23:20:07 GMT
Content-Type: text/html; charset=utf-8
Content-Language: en
Content-Length: 22849
Cache-Control: public, max-age=0
X-Powered-By: PHP/5.3.27
Finalmente, depois dos cabeçalhos, vem uma linha em branco e o conteúdo da resposta (normalmente HTML, mas pode ser um download, pode ser um CSS)...
HTTP/1.1 200 OK
Server: Apache
Date: Thu, 28 Nov 2013 23:20:07 GMT
Content-Type: text/html; charset=utf-8
Content-Language: en
Content-Length: 22849
Cache-Control: public, max-age=0
X-Powered-By: PHP/5.3.27
<!doctype html>
<html>
...
</html>
O que isso tem a ver com o registro de saída? Bom, quando nós mandamos o PHP imprimir algo -- ou quando saímos do PHP e voltamos pro HTML, nós estamos escrevendo exatamente aí, no corpo da resposta. Sabendo que a resposta é um arquivo simples, de texto, uma vez que você começa a escrever o conteúdo, como você volta a escrever cabeçalhos? Simples: você não volta.
Então se você quiser escrever qualquer coisa nos cabeçalhos durante a execução do seu programa, tem que ser antes de escrever qualquer coisa na resposta. Usando registros de saída, os output buffers, nós evitamos escrever o conteúdo da resposta até o último segundo, no final do programa.
Se você precisar escrever um cabeçalho, o PHP tem a função header
que é baba
de usar. Este código, por exemplo, manda um cabeçalho de redirecionamento, para
outra página:
header('Location: http://exemplo.com')
Nós já usamos algumas funções do PHP: ucwords
, header
, ob_start
... Uma
função do PHP é um pedaço de código que faz alguma coisa que precisamos
recorrentemente, como por exemplo trocar a inicial de cada palavra em uma
string por sua maiúscula -- que é o que a ucwords
faz.
Quando queremos usar uma função em PHP, simplesmente escrevemos o nome da função seguido de parênteses (). Entre esses parênteses, podemos passar valores para a função operar, inclusive detalhes de como ela deve operar.
Por exemplo, a função trim
recebe como parâmetro uma string, de onde ela vai
remover os espaços, tabs, quebras de linha que aparecerem no começo e/ou no
final da string:
<?php
$texto = ' Texto ';
print trim($texto); // Imprime 'Texto'
Já a função explode cria um array com pedaços da string, a partir de um separador:
<?php
$hashtags = explode(' ', '#programacao #php #funcoes');
print_r($hashtags);
// Imprime array('#programacao', '#php', '#funcoes')
Quando uma função é executada, ela pode retornar um valor. No caso da função
explode
, ela retorna um array com os pedaços da string. Nós podemos pegar o
retorno de uma função e salvá-lo diretamente, como fazemos neste exemplo. Ou
podemos passar o valor como parâmetro para outra função:
<?php
print_r(explode(' ', 'a b c')); // Imprime array('a', 'b', 'c');
Além das funções que o PHP nos disponibiliza, nós podemos escrever nossas próprias funções, pra coisas que não são recorrentes para todos os usuários do PHP (porque se fossem, viriam prontas junto com o PHP) mas que são recorrentes para o nosso projeto.
Por exemplo, imagine uma função que recebe o array $experiencia
, do nosso
exemplo, e retorna apenas os empregos em que a pessoa ainda estiver trabalhando
-- ou seja, aqueles que tem na chave termino
o valor False
.
Primeiro definimos o nome da função, que vamos chamar de empregos_atuais
.
<?php
function empregos_atuais() {
}
Essa função precisa operar sobre um array de experiencia como o que usamos no nosso aplicativo de exemplo.
<?php
function empregos_atuais($experiencia) {
}
Quando ela receber esse array, ela precisa checar quais valores nesse array tem
a chave termino
com o valor False
. Quando encontrar algum $item
sem ano
de termino
, isto é, que ainda está acontecendo, copiamos o item para dentro
do array na variável $atuais
e ao final da função, retornamos esse array
usando o comando return
.
<?php
function empregos_atuais($experiencia) {
$atuais = array();
foreach ($experiencia as $item) {
if (empty($item['termino'])) {
$atuais[] = $item;
}
}
return $atuais.
}
Pronto, agora se nós quisermos ver o emprego atual de um dos membros da equipe,
podemos usar nossa função para filtar a experiência. Se quisermos inserir isto
no fluxo do nosso aplicativo de currículos, podemos checar o parâmetro atuais
e, se ele estiver na URL, exibir apenas os empregos atuais:
<?php
require_once 'funcoes-auxiliares.php';
$membros = ['capi', 'gus', 'gabi'];
if (!empty($_GET['membro']) && in_array($_GET['membro'], $membros)) {
require_once $_GET['membro'] . '.php';
if (!empty($_GET['atuais'])) {
$experiencia = empregos_atuais($experiencia);
}
require_once 'curriculo-membro.php';
} else {
require_once 'curriculo-opcoes.php';
}
Note que eu começo o programa importando o arquivo funcoes-auxiliares.php
,
porque salvei a função empregos_atuais
nele. Outra opção seria deixar a
definição da função neste arquivo mesmo, mas eu acho mais organizado ter as
funções num arquivo só delas.
Repare também que a array de empregos atuais, retornada pela função
empregos_atuais
, ficou salva na variável $experiencia, para ser impressa no
template curriculo-membro
.
E se nós quiséssemos que esse parâmetro atuais
na URL também mostrasse apenas
os estudos atuais da pessoa? Poderíamos passar o array $formacao
como
parametro para a função empregos_atuais
:
<?php
require_once 'funcoes-auxiliares.php';
$membros = ['capi', 'gus', 'gabi'];
if (!empty($_GET['membro']) && in_array($_GET['membro'], $membros)) {
require_once $_GET['membro'] . '.php';
if (!empty($_GET['atuais'])) {
$experiencia = empregos_atuais($experiencia);
$formacao = empregos_atuais($formacao);
}
require_once 'curriculo-membro.php';
} else {
require_once 'curriculo-opcoes.php';
}
Note que na definição da função, nós chamamos o parâmetro recebido de
$experiencia
. Durante a execução da função, o valor passado nesse parâmetro
vai estar disponível com esse nome. Se tivéssemos chamado o parâmetro de
$itens
, a função funcionaria do mesmo jeito, desde que nós mudássemos a
referência para $itens
na função, consistentemente.
<?php
function empregos_atuais($itens) {
$atuais = array();
foreach ($itens as $item) {
if (empty($item['termino'])) {
$atuais[] = $item;
}
}
return $atuais;
}
Aliás, o nome empregos_atuais
não faz tanto sentido se vamos filtrar a
formação também. Vamos renomear a função para itens_atuais
.
<?php
function itens_atuais($itens) {
$atuais = array();
foreach ($itens as $item) {
if (empty($item['termino'])) {
$atuais[] = $item;
}
}
return $atuais.
}
Isso exige, é claro, renomear as chamadas feitas à função no arquivo curriculo.php
<?php
require_once 'funcoes-auxiliares.php';
$membros = ['capi', 'gus', 'gabi'];
if (!empty($_GET['membro']) && in_array($_GET['membro'], $membros)) {
require_once $_GET['membro'] . '.php';
if (!empty($_GET['atuais'])) {
$experiencia = itens_atuais($experiencia);
$formacao = itens_atuais($formacao);
}
require_once 'curriculo-membro.php';
} else {
require_once 'curriculo-opcoes.php';
}
Um potencial problema aqui é esquecer de passar um valor. O PHP vai reclamar
com um aviso de erro que diz que a função itens_atuais
espera um parametro,
mas foi chamada com 0 parametros em alguma linha do seu código (no caso, na
linha 123).
PHP Warning: itens_atuais() expects at least 1 parameter, 0 given in curriculo.php(123).
Outro potencial problema é o array não estar no formato esperado: como arrays
podem ter qualquer chave, pode ser que alguém na equipe use a chave 'fim' no
lugar da chave 'termino'... e a função itens_atuais
vai acabar se confundindo
e filtrando errado. Para evitar esse tipo de problema, e facilitar nossa
organização de código e dados, podemos usar Classes e Objetos, que são assunto
da próxima aula.