Projetando funções mais elegantes em PHP
Uma das facilidades óbvias que o PHP nos dá para definir funções é a possibilidade de se definir valores padrão para os parâmetros. Acredito, porém, que esse é o “lado negro” da força. Se uma função, por exemplo, deve receber uma grande quantidade de parâmetros, e ao menos 4 ou 5 são opcionais, é bem provável que você cairá no seguinte dilema: qual dos parâmetros aqui é mais importante?
Vamos a um exemplo nada típico para PHP. Imagine que você quer criar
uma biblioteca gráfica. Sua pequena biblioteca deverá ser capaz de
desenhar círculos, e para isso você decidiu implementar a função
draw_circ(). O que sabemos sobre um círculo? Todo círculo tem, por
exemplo, um raio. Isso já é alguma coisa. Então você escreve sua definição:
function draw_circ($radius) { ... }
Um círculo tem raio… ok… mas com essa definição todos os meus círculos serão idênticos. Precisamos de mais parâmetros nessa função. Podemos adicionar uma cor de fundo, uma cor para a “borda” e uma largura para a “borda”. Mas será que, sempre que utilizar minha nova função, desejarei especificar essas 4 características? Não, certamente não. Então podemos declarar esses 3 novos parâmetros como opcionais, pois daremos valores padrão a eles.
function draw_circ($radius, $bg_color='white', $border_color='black', $border_width='1px') { ... }
Mas acabamos de criar um problema do ponto de vista da beleza do código. Como poderemos criar um círculo de raio 3, com cor de fundo branca, cor da borda preta, mas com largura da borda de 3px? Ou seja, como criar um círculo com todos os parâmetros definidos com valores padrão, mas apenas com o quarto parâmetro assumindo um valor diferente? Não é difícil, é verdade:
draw_circ(3, null, null, '3px');
A beleza do código é comprometida com esses dois parâmetros null. Pois neste
caso nossa função recebe apenas 4 parâmetros. Mas na vida real muitas vezes
nossas funções recebem 8, 9, ou mais. Com que solução contamos, em PHP,
para deixar esse código mais apresentável?
Minha abordagem favorita (que você talvez não goste) é emprestada de Ruby. Ela consiste na utilização de um array associativo, que atua como um agregador de todos os parâmetros opcionais. Com essa nova abordagem podemos definir nossa função com apenas 2 parâmetros:
function draw_circ($radius, $options=array()) { ... }
Continuamos tendo o raio como o único parâmetro obrigatório. Os demais passam a ser opcionais, já que o nosso array associativo de opções é, ele próprio, opcional.
Mesmo com essa nova definição é possível escrever a implementação dessa função
de forma equivocada. Esta seria uma má implementação para o tratamento
das opções passadas para $options:
function draw_circ($radius, $options=array()) {
$options['bg_color'] = isset($options['bg_color']) ? $options['bg_color'] : 'white';
$options['border_color'] = isset($options['border_color']) ? $options['border_color'] : 'black';
$options['border_width'] = isset($options['border_width']) ? $options['border_width'] : '1px';
// ...
}
Por que ela é equivocada?
- É verbosa demais
- Trata os parâmetros de forma individual
- Deixa a legibilidade do código a desejar
O que essa implementação faz é o seguinte: para cada chave válida em $options, verifica se
há um valor definido para essa chave. Se há, então o atribui novamente na mesma chave de
$options. Senão, atribui o valor padrão àquela chave.
A função array_merge() chega, então, para salvar o dia e garantir a legibilidade desse código.
De acordo com a documentação oficial, essa função faz o seguinte:
array_merge() funde os elementos de um ou mais arrays para que os valores de um sejam adicionados no fim do array anterior. Ela retorna o array resultante.
Até aí nada de mais. Essa função parece legal, mas serve para nós? O texto continua:
Se os arrays de entrada possuem as mesmas chaves string, então o segundo valor definido para aquela chave irá sobrescrever o primeiro.
Opa! É disso que precisamos.
Veja como fica a nova implementação:
function draw_circ($radius, $options=array()) {
$defaults = array(
'bg_color' => 'white',
'border_color' => 'black',
'border_width' => '1px'
);
$options = array_merge($defaults, $options);
// ...
}
draw_circ(5, array('bg_color' => 'orange'));
A idéia é muito simples: $defaults é o nosso array associativo que possui
os valores padrão para cada uma das opções possíveis. É importante que todos
os valores padrão sejam declarados nesse array. Após sua definição utilizamos
array_merge() para sobrescrever cada um dos valores definidos em $defaults
pelos respectivos valores definidos em $options. Uma chave de $defaults
não definida por $options permanecerá com seu valor original (ou seja,
seu valor padrão ou default). Como array_merge() não modifica o array
original, precisamos atribuir seu resultado à antiga variável $options,
agora com valores sobrescritos.
Com essa implementação ganhamos muito em legibilidade e em beleza do código.
O grande problema fica para a notação de arrays literais em PHP. Enquanto
a definição da função draw_circ() fica muito legal uma vez que utilizamos
array_merge(), a chamada a essa função precisa ter a construção array().
Esse é o preço que se paga. Você pode escolher entre esta implementação ou
continuar com as dezenas de variáveis com valores padrão e, de vez em quando,
ver suas chamadas de função recebendo valores null intercalados com valores
verdadeiros.
Bonus
Ficou curioso para saber como draw_circ poderia ser implementada em Ruby?
Vamos ao código:
def draw_circ(radius, options={})
defaults = {
:bg_color => 'white',
:border_color => 'black',
:border_width => '1px'
}
options = defaults.merge(options)
options.each {|key,value| puts "#{key} => #{value}" }
end
draw_circ(5, :bg_color => 'orange')
A última linha da implementação não faz parte da função, foi colocada apenas para mostrar que a saída é aquela que esperamos:
bg_color => orange
border_color => black
border_width => 1px

December 21st, 2007 às 3:33 am
Bem interessante seu artigo, já usava o array options, mas o array_merge ajuda bastante
December 21st, 2007 às 9:20 pm
Raphael, só após entrar no seu blog descobri que o projeto PHPSpec existe
Valeu, adicionei teu blog aos meus feeds.
October 1st, 2008 às 2:05 pm
Muito bom o post. Muito útil.. vlw