sexta-feira, 26 de fevereiro de 2010

Gerando arquivos .INI com Composite Design Pattern e PHP

Analisando um arquivo .ini é possível perceber que se trata de uma composição de comentários e seções onde cada seção possui um ou mais parâmetros e cada parâmetro é composto pelo par nome=valor.

Para criar esse tipo de arquivo é possível utilizar um padrão de projeto chamado composite:






Apesar de termos conhecimento de que o arquivo .ini é de fato uma composição, cada item dessa composição possui regras próprias e, para que não tenhamos que conhecer essas regras em um único objeto deixamos com que cada item da composição faça seu trabalho permitindo que tratemos todos os elementos de uma forma comum.

Para o nosso arquivo .ini os participantes são:

Comentários e valor de um parâmetro -> Leaf -> São elementos finais, que não possuem nós filhos.
Nome de um parâmetro, seção e o próprio arquivo.ini -> Composite -> Possuem um ou mais nós filhos.

Uma implementação simples em PHP ficaria assim:



INIFormat.php
<?php
 /**
 * Define a interface do Component
 */
interface INIFormat extends IteratorAggregate {
        /**
         * Adiciona um nó filho ao componente
         * @param INIFormat $leaf
         */
        public function add( INIFormat $leaf );
       
        /**
         * Grava o componente no arquivo
         * @param IFileObject $fo
         * @param string $eol Delimitador de fim de linha
         */
        public function write( IFileObject $fo , $eol = PHP_EOL );
}


AbstractINIComposite.php
<?php
 /**
 * Define a interface da composição
 * @abstract
 */
abstract class AbstractINIComposite implements INIFormat {
        private $leafs;

        /**
         * Inicializa o objeto da composição
         */
        public function __construct(){
                $this->leafs = new ArrayIterator();
        }

        /**
         * Adiciona um nó filho ao componente
         * @param INIFormat $leaf
         */
        public function add( INIFormat $leaf ){
                $this->leafs[] = $leaf;
        }

        /**
         * Recupera um Iterator com os nós filhos da composição
         * @return ArrayIterator
         */
        public function getIterator(){
                return $this->leafs;
        }
}


AbstractINILeaf.php
<?php
 /**
 * Define a interface para os nós que não possuem filhos
 * @abstract
 */
abstract class AbstractINILeaf implements INIFormat {
        /**
         * @param INIFormat $leaf
         * @throws LogicException Como uma folha não pode ter nós filhos, sempre dispara um LogicException
         */
        public function add( INIFormat $leaf ){
                throw new LogicException( 'Um nó final não pode ter nós filhos.' );
        }

        /**
         * Como uma folha não pode ter nós filhos, sempre retorna um Iterator vazio
         * @return ArrayIterator
         */
        public function getIterator(){
                static $iterator = null;

                if ( $iterator == null ) $iterator = new ArrayIterator();

                return $iterator;
        }
}


INIParameter.php
<?php
/**
 * Parâmetro de uma seção
 */
class INIParameter extends AbstractINIComposite {
        /**
         * @var string
         */
        private $name;

        /**
         * Constroi um novo parâmetro de seção
         * @param string $name O nome do parâmetro
         */
        public function __construct( $name ){
                parent::__construct();
                $this->name =& $name;
        }

        /**
         * Implementa o método write para gravar no arquivo
         * @param IFileObject $fo Objeto que será usado para gravar no arquivo
         * @param string $eol Delimitador de fim de linha
         */
        public function write( IFileObject $fo , $eol = PHP_EOL ){
                $iterator = $this->getIterator();
                $name = $this->name;

                if ( ( $total = $iterator->count() ) >= 1 ){
                        if ( $total > 1 ){
                                $name = sprintf( '%s[]' , $name );
                        }

                        foreach ( $iterator as $leaf ){
                                $fo->write( sprintf( '%s=' , $name ) );
                                $leaf->write( $fo , $eol );
                        }
                } else {
                        throw new LogicException( sprintf( 'O parâmetro %s precisa possuir pelo menos 1 valor.' , $this->name ) );
                }
        }
}


INIValue.php
<?php
/**
 * Valor de um parâmetro
 */
class INIValue extends AbstractINILeaf {
        private $value;

        /**
         * Constroi o valor de um parâmetro
         * @param string $value
         */
        public function __construct( $value ){
                $this->value =& $value;
        }

        /**
         * Grava o valor no arquivo
         * @param IFileObject $fo
         * @param string $eol
         */
        public function write( IFileObject $fo , $eol = PHP_EOL ){
                $fo->write( sprintf( '%s%s' , $this->value , $eol ) );
        }
}


INIComment.php
<?php
/**
 * Comentário de um arquivo ou seção
 */
class INIComment extends AbstractINILeaf {
        /**
         * @var string
         */
        private $comment;

        /**
         * Constroi o comentário
         * @param string $comment
         */
        public function __construct( $comment ){
                $this->comment =& $comment;
        }

        /**
         * Grava o comentário no arquivo
         * @param IFileObject $fo
         * @param string $eol
         */
        public function write( IFileObject $fo , $eol = PHP_EOL ){
                $fo->write( sprintf( '; %s%s' , $this->comment , $eol ) );
        }
}


INISection.php
<?php
/**
 * Seção de parâmetros
 */
class INISection extends AbstractINIComposite {
        /**
         * @var string
         */
        private $name;

        /**
         * Constroi a nova seção
         * @param string $name
         */
        public function __construct( $name ){
                parent::__construct();
                $this->name =& $name;
        }

        /**
         * Grava a seção no arquivo
         * @param IFileObject $fo
         * @param string $eol
         */
        public function write( IFileObject $fo , $eol = PHP_EOL ){
                $fo->write( sprintf( '[%s]%s' , $this->name , $eol ) );

                foreach ( $this as $leaf ){
                        $leaf->write( $fo , $eol );
                }
        }
}


INIFile.php
<?php
/**
 * Arquivo INI
 */
class INIFile extends AbstractINIComposite {
        /**
         * @var string
         */
        private $file;

        /**
         * Constroi o novo arquivo
         * @param string $file
         */
        public function __construct( $file ){
                parent::__construct();
                $this->file =& $file;
        }

        /**
         * Grava o arquivo em disco
         * @param IFileObject $fo
         * @param string $eol
         */
        public function write( IFileObject $fo , $eol = PHP_EOL ){
                $fo->open( $this->file , 'w+' );

                foreach ( $this as $leaf ){
                        $leaf->write( $fo , $eol );
                }

                $fo->close();
        }
}


Para demonstrar o funcionamento, vamos usar um objeto para gravar em disco que na verdade irá apenas exibir o conteúdo do arquivo INI gerado:


IFileObject.php
<?php
interface IFileObject {
        public function close();
        public function open( $name , $method );
        public function write( $content );
}


FileObject.php
<?php
class FileObject implements IFileObject {
        public function close(){
                echo '--- Fechando o arquivo ---' , PHP_EOL;
        }

        public function open( $name , $method ){
                echo '--- Abrindo o arquivo: ' , $name , ' ---' , PHP_EOL;
        }

        public function write( $content ){
                echo $content;
        }
}


Usando isso tudo:
<?php
$ini = new INIFile( 'teste.ini' );
$ini->add( new INIComment( 'Abaixo uma nova seção' ) );

$imasters = new INISection( 'iMasters' );
$imasters->add( new INIComment( 'O parâmetro teste possui vários valores' ) );
$teste = new INIParameter( 'teste' );
$teste->add( new INIValue( 'valor 1' ) );
$teste->add( new INIValue( 'valor 2' ) );
$teste->add( new INIValue( 'valor 3' ) );
$imasters->add( $teste );

$imasters->add( new INIComment( 'O parâmetro outro possui só um valor' ) );
$outro = new INIParameter( 'outro' );
$outro->add( new INIValue( 'apenas um' ) );
$imasters->add( $outro );

$ini->add( $imasters );
$ini->write( new FileObject() );


A saída:
--- Abrindo o arquivo: teste.ini ---
; Abaixo uma nova seção[iMasters]
; O parâmetro teste possui vários valores
teste[]=valor 1
teste[]=valor 2
teste[]=valor 3
; O parâmetro outro possui só um valor
outro=apenas um
--- Fechando o arquivo ---


Agora é só implementar a classe FileObject para que ela faça o que tem que fazer (gravar em disco) que tudo estará funcionando.

;)

Nenhum comentário:

Postar um comentário