Este artigo ilustra diversas maneiras de criar aplicativos PHP configuráveis. O artigo também explora os pontos de configuração ideais em uma aplicação e busca um equilíbrio entre uma aplicação ser muito configurável e muito fechada.
Se você planeja disponibilizar seu aplicativo PHP para outras pessoas ou empresas, você precisa ter certeza de que o aplicativo é configurável. No mínimo, permita que os usuários definam logins e senhas de banco de dados de maneira segura, para que o material contido neles não seja tornado público.
Este artigo demonstra diversas técnicas para armazenar definições de configuração e editar essas definições. Além disso, o artigo também fornece orientações sobre quais elementos precisam ser configuráveis e como evitar cair no dilema da configuração excessiva ou insuficiente.
Configuração usando arquivos INI
O PHP possui suporte integrado para arquivos de configuração. Isso é conseguido através de um mecanismo de arquivo de inicialização (INI), como o arquivo php.ini, onde são definidas constantes como tempos limite de conexão do banco de dados ou como as sessões são armazenadas. Se desejar, você pode personalizar a configuração da sua aplicação neste arquivo php.ini. Para ilustrar, adicionei as seguintes linhas de código ao arquivo php.ini.
myapptempdir=foo
Em seguida, escrevi um pequeno script PHP para ler esse item de configuração, conforme mostrado na Listagem 1.
Listagem 1. ini1.php
<?php
função get_template_directory()
{
$v = get_cfg_var("meuapptempdir");
retornar ($v == nulo)? "tempdir": $v;
}
echo(get_template_directory()."n" );
?>
Ao executar este código na linha de comando, você obtém os seguintes resultados:
% php ini1.php
foo
%
maravilhoso. Mas por que não podemos usar a função INI padrão para obter o valor do item de configuração myapptempdir? Fiz algumas pesquisas e descobri que, na maioria dos casos, os itens de configuração personalizados não podem ser obtidos usando esses métodos. No entanto, é acessível usando a função get_cfg_var.
Para simplificar essa abordagem, encapsule o acesso à variável em uma segunda função que usa o nome da chave de configuração e um valor padrão como parâmetros, conforme mostrado abaixo.
Listagem 2.
função ini2.php get_ini_value( $n, $dv )
{
$c = get_cfg_var($n);
retornar ($c == nulo)? $dv: $c;
}
função get_template_directory()
{
return get_ini_value("myapptempdir", "tempdir");
}
Esta é uma boa visão geral de como acessar o arquivo INI, portanto, se você quiser usar um mecanismo diferente ou armazenar o arquivo INI em outro lugar, não precisará se dar ao trabalho de alterar muitas funções.
Não recomendo usar arquivos INI para configuração de aplicativos, por dois motivos. Primeiro, embora isso facilite a leitura do arquivo INI, torna quase impossível gravar o arquivo INI com segurança. Portanto, isso só é adequado para itens de configuração somente leitura. Segundo, o arquivo php.ini é compartilhado por todos os aplicativos no servidor, então não acho que itens de configuração específicos do aplicativo devam ser gravados nesse arquivo.
O que você precisa saber sobre arquivos INI? O mais importante é como redefinir o caminho de inclusão para adicionar itens de configuração, conforme mostrado abaixo.
Listagem 3. ini3.php
<?php
echo( ini_get("include_path")."n" );
ini_set("include_path",
ini_get("include_path").":./mylib" );
echo( ini_get("include_path")."n" );
?>
Neste exemplo, adicionei meu diretório local mylib ao caminho de inclusão, para poder solicitar arquivos PHP desse diretório sem adicionar o caminho à instrução require.
Configuração em PHP
Uma alternativa comum para armazenar entradas de configuração em um arquivo INI é usar um script PHP simples para persistir os dados. Abaixo está um exemplo.
Listagem 4. config.php
<?php
# Especifique a localização do diretório temporário
#
$TEMPLATE_DIRECTORY = "tempdir";
?>
O código que usa esta constante é o seguinte.
Listagem 5. php.php
<?php
require_once 'config.php'
função get_template_directory()
{
$TEMPLATE_DIRECTORY global;
retornar $TEMPLATE_DIRECTORY;
}
echo(get_template_directory()."n" );
?>
O código primeiro contém o arquivo de configuração (config.php), e então você pode usar essas constantes diretamente.
Existem muitas vantagens em usar esta tecnologia. Primeiro, se alguém apenas navegar no arquivo config.php, a página ficará em branco. Assim você pode colocar config.php no mesmo arquivo que a raiz da sua aplicação web. Em segundo lugar, ele pode ser editado em qualquer editor, e alguns editores ainda possuem funções de coloração e verificação de sintaxe.
A desvantagem desta tecnologia é que ela é uma tecnologia somente leitura, como os arquivos INI. Extrair os dados deste arquivo é muito fácil, mas ajustar os dados no arquivo PHP é difícil e, em alguns casos, até impossível.
A alternativa a seguir mostra como escrever um sistema de configuração que seja de natureza legível e gravável.
Os dois exemplos anteriores dearquivos de texto
são adequados para entradas de configuração somente leitura, mas e os parâmetros de configuração que são tanto de leitura quanto de gravação? Primeiro, dê uma olhada no arquivo de configuração de texto na Listagem 6.
Listagem 6. config.txt
# Arquivo de configuração da minha aplicação
Título=Meu aplicativo
TemplateDirectory=tempdir
Este é o mesmo formato de arquivo do arquivo INI, mas eu escrevi minha própria ferramenta auxiliar. Para fazer isso, criei minha própria classe Configuration conforme mostrado abaixo.
Listagem 7. text1.php
<?php
configuração de classe
{
private $configFile = 'config.txt';
private $itens = array();
function __construct() { $this->parse() };
função __get($id) { return $this->items[ $id ];
análise de função()
{
$fh = fopen( $this->configFile, 'r' );
enquanto($l = fgets($fh))
{
if (preg_match( '/^#/', $l ) == falso )
{
preg_match( '/^(.*?)=(.*?)$/', $l, $encontrado );
$this->items[ $found[1] ] = $found[2];
}
}
fclose($fh);
}
}
$c = nova configuração()
echo( $c->TemplateDirectory."n" );
?>
Este código primeiro cria um objeto Configuration. Em seguida, o construtor lê config.txt e define a variável local $items com o conteúdo do arquivo analisado.
O script então procura TemplateDirectory, que não está definido diretamente no objeto. Portanto, o método mágico __get é chamado com $id definido como 'TemplateDirectory', que retorna o valor no array $items para essa chave.
Este método __get é específico para o ambiente PHP V5, portanto este script deve ser executado no PHP V5. Na verdade, todos os scripts neste artigo precisam ser executados no PHP V5.
Ao executar este script na linha de comando, você verá os seguintes resultados:
% php text1.php
tempdir
%
Tudo é esperado, o objeto lê o arquivo config.txt e obtém o valor correto para o item de configuração TemplateDirectory.
Mas o que você deve fazer para definir um valor de configuração? Ao criar um novo método e algum novo código de teste nesta classe, você pode obter essa funcionalidade, conforme mostrado abaixo.
Listagem 8. text2.php
<?php
configuração de classe
{
...
função __get($id) { return $this->items[ $id ];
função __set($id,$v) { $this->items[ $id ] = $v }
função analisar() { ... }
}
$c = nova configuração();
echo($c->TemplateDirectory."n" );
$c->TemplateDirectory = 'foobar';
echo($c->TemplateDirectory."n" );
?>
Agora existe uma função __set, que é a "prima" da função __get. Esta função não obtém o valor de uma variável de membro. Esta função é chamada quando uma variável de membro deve ser definida. O código de teste na parte inferior define o valor e imprime o novo valor.
Aqui está o que acontece quando você executa este código na linha de comando:
% php text2.php
tempdir
foobar
%
Muito bom! Mas como posso salvá-lo em um arquivo para que a alteração seja corrigida? Para fazer isso, você precisa gravar o arquivo e lê-lo. Nova função para gravação de arquivos conforme mostrado abaixo.
Listagem 9. text3.php
<?php
configuração de classe
{
...
função salvar()
{
$nf = '';
$fh = fopen( $this->configFile, 'r' );
enquanto($l = fgets($fh))
{
if (preg_match( '/^#/', $l ) == falso )
{
preg_match( '/^(.*?)=(.*?)$/', $l, $encontrado );
$nf .= $found[1]."=".$this->items[$found[1]]."n";
}
outro
{
$nf.= $l;
}
}
fclose($fh);
copiar($this->configFile, $this->configFile.'.bak' );
$fh = fopen( $this->configFile, 'w' );
fwrite($fh, $nf);
fclose($fh);
}
}
$c = nova configuração();
echo($c->TemplateDirectory."n" );
$c->TemplateDirectory = 'foobar';
echo($c->TemplateDirectory."n" );
$c->salvar();
?>
A nova função de salvamento manipula habilmente o config.txt. Em vez de apenas reescrever o arquivo com os itens de configuração atualizados (o que removeria os comentários), li o arquivo e reescrevi de forma flexível o conteúdo da matriz $items. Dessa forma, os comentários do arquivo são preservados.
Execute o script na linha de comando e gere o conteúdo do arquivo de configuração de texto. Você pode ver a seguinte saída.
Listagem 10. Salvando a saída da função
%php text3.php
tempdir
foobar
% gato config.txt
#Arquivo de configuração da minha aplicação
Título=Meu aplicativo
TemplateDirectory=foobar
%
O arquivo config.txt original agora está atualizado com os novos valores.
Arquivos de configuração XML
Embora os arquivos de texto sejam fáceis de ler e editar, eles não são tão populares quanto os arquivos XML. Além disso, existem vários editores disponíveis para XML que entendem marcação, escape de símbolos especiais e muito mais. Então, como seria a versão XML do arquivo de configuração? A Listagem 11 mostra o arquivo de configuração em formato XML.
Listagem 11. config.xml
<?xml version="1.0"?>
<configuração>
<Título>Meu aplicativo</Título>
<TemplateDirectory>tempdir</TemplateDirectory>
</config>
A Listagem 12 mostra uma versão atualizada da classe Configuration que usa XML para carregar definições de configuração.
Listagem 12. xml1.php
<?php
configuração de classe
{
private $configFile = 'config.xml';
private $itens = array();
function __construct() { $this->parse() };
função __get($id) { return $this->items[ $id ];
análise de função()
{
$doc = new DOMDocument();
$doc->carregar( $this->configFile );
$cn = $doc->getElementsByTagName( "config");
$nodes = $cn->item(0)->getElementsByTagName( "*" );
foreach($nós as $node)
$this->items[ $node->nodeName ] = $node->nodeValue;
}
}
$c = nova configuração();
echo($c->TemplateDirectory."n" );
?>
Parece que o XML tem outro benefício: o código é mais simples e fácil que a versão em texto. Para salvar este XML, é necessária outra versão da função save, que salva o resultado em formato XML em vez de formato de texto.
Listagem 13. xml2.php
...
função salvar()
{
$doc = new DOMDocument();
$doc->formatOutput = true;
$r = $doc->createElement( "config");
$doc->appendChild( $r );
foreach( $this->items as $k => $v )
{
$kn = $doc->createElement( $k );
$kn->appendChild( $doc->createTextNode( $v ) );
$r->appendChild($kn);
}
copiar($this->configFile, $this->configFile.'.bak' )
;
}
...
Este código cria um novo modelo de objeto de documento XML (DOM) e, em seguida, salva todos os dados na matriz $ items neste modelo. Depois de concluir isso, use o método save para salvar o XML em um arquivo.
Uma alternativa final
ao uso de um banco de dados
é usar um banco de dados para armazenar os valores dos elementos de configuração.A primeira etapa é usar um esquema simples para armazenar dados de configuração. Abaixo está um padrão simples.
Listagem 14.
Configurações do schema.sql DROP TABLE IF EXISTS;
Configurações de CRIAR TABELA (
id MEDIUMINT NÃO NULO AUTO_INCREMENT,
nome TEXTO,
valor TEXTO,
CHAVE PRIMÁRIA(id)
);
Isso requer alguns ajustes com base nos requisitos da aplicação. Por exemplo, se desejar que o elemento de configuração seja armazenado por usuário, será necessário incluir o ID do usuário como uma coluna adicional.
Para ler e gravar dados, escrevi a classe Configuration atualizada mostrada na Figura 15.
Listagem 15. db1.php
<?php
require_once('DB.php');
$dsn = 'mysql://root:senha@localhost/config';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage() }
classe Configuração
);
{
private $configFile = 'config.xml';
private $itens = array();
function __construct() { $this->parse() };
função __get($id) { return $this->items[ $id ];
função __set($id,$v)
{
$db global;
$este->itens[ $id ] = $v;
$sth1 = $db->prepare( 'DELETE FROM configurações WHERE nome=?' );
$db->executar( $sth1, $id );
if (PEAR::isError($db)) { morrer($db->getMessage() });
$sth2 = $db->prepare('INSERT INTO configurações ( id, nome, valor ) VALUES ( 0, ?, ? )' );
$db->execute( $sth2, array( $id, $v ) );
if (PEAR::isError($db)) { morrer($db->getMessage() });
}
função analisar()
{
$db global;
$doc = new DOMDocument();
$doc->carregar( $this->configFile );
$cn = $doc->getElementsByTagName( "config");
$nodes = $cn->item(0)->getElementsByTagName( "*" );
foreach($nodes como $node)
$this->items[ $node->nodeName ] = $node->nodeValue;
$res = $db->query( 'SELECIONE nome, valor das configurações' );
if (PEAR::isError($db)) { morrer($db->getMessage() });
while( $res->fetchInto( $row ) ) {
$this->items[ $row[0] ] = $row[1];
}
}
}
$c = nova configuração();
echo($c->TemplateDirectory."n" );
$c->TemplateDirectory = 'novo foo';
echo($c->TemplateDirectory."n" );
?>
Esta é na verdade uma solução híbrida de texto/banco de dados. Por favor, dê uma olhada mais de perto no método de análise. Esta classe primeiro lê o arquivo de texto para obter o valor inicial e, em seguida, lê o banco de dados para atualizar a chave para o valor mais recente. Após definir um valor, a chave é removida do banco de dados e um novo registro é adicionado com o valor atualizado.
É interessante ver como a classe Configuration funciona em diversas versões deste artigo. Ela pode ler dados de arquivos de texto, XML e bancos de dados, mantendo a mesma interface. Eu encorajo você a usar interfaces com a mesma estabilidade em seu desenvolvimento também. Exatamente como isso funciona não está claro para o cliente do objeto. A chave é o contrato entre o objeto e o cliente.
O que é configuração e como configurá-la
Encontrar o meio-termo certo entre muitas opções de configuração e configuração insuficiente pode ser difícil. Com certeza, qualquer configuração de banco de dados (por exemplo, nome do banco de dados, usuário e senha do banco de dados) deve ser configurável. Além disso, tenho alguns itens básicos de configuração recomendados.
Nas configurações avançadas, cada recurso deve ter uma opção separada de ativar/desativar. Permita ou desative essas opções com base na importância delas para o aplicativo. Por exemplo, em um aplicativo de fórum da Web, o recurso de atraso está ativado por padrão. No entanto, as notificações por e-mail estão desabilitadas por padrão, pois parece exigir personalização.
As opções da interface do usuário (IU) devem ser todas definidas em um único local. A estrutura da interface (por exemplo, locais de menu, itens de menu adicionais, URLs com links para elementos específicos da interface, logotipos usados, etc.) deve ser definida em um único local. Eu recomendo fortemente não especificar entradas de fonte, cor ou estilo como itens de configuração. Eles devem ser definidos por meio de Cascading Style Sheets (CSS), e o sistema de configuração deve especificar qual arquivo CSS usar. CSS é uma maneira eficiente e flexível de definir fontes, estilos, cores e muito mais. Existem muitas ferramentas CSS excelentes por aí, e seu aplicativo deve fazer bom uso do CSS, em vez de tentar definir o padrão sozinho.
Dentro de cada recurso, recomendo definir de 3 a 10 opções de configuração. Essas opções de configuração devem ser nomeadas de forma significativa. Se as opções de configuração puderem ser definidas por meio da UI, os nomes das opções em arquivos de texto, arquivos XML e bancos de dados deverão estar diretamente relacionados ao título do elemento da interface. Além disso, todas essas opções devem ter valores padrão claros.
Em geral, as seguintes opções devem ser configuráveis: endereços de e-mail, qual CSS usar, a localização dos recursos do sistema referenciados nos arquivos e os nomes dos arquivos dos elementos gráficos.
Para elementos gráficos, você pode querer criar um tipo de perfil separado chamado skin, que contém configurações para o perfil, incluindo o posicionamento de arquivos CSS, o posicionamento de gráficos e esses tipos de coisas. Em seguida, deixe o usuário escolher entre vários arquivos de skin. Isso simplifica alterações em grande escala na aparência do seu aplicativo. Isso também oferece aos usuários a oportunidade de personalizar o aplicativo entre diferentes instalações de produtos. Este artigo não cobre esses arquivos de skin, mas o básico que você aprenderá aqui tornará o suporte a arquivos de skin muito mais simples.
Conclusão
A configurabilidade é uma parte vital de qualquer aplicação PHP e deve ser uma parte central do design desde o início. Espero que este artigo forneça ajuda na implementação de sua arquitetura de configuração e forneça algumas orientações sobre quais opções de configuração você deve permitir.