Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Fala galera, tudo certo? Hoje sou eu precisando de uma ajuda aqui.
Estou montando uma aplicação que vai ler alguns arquivos e suas linhas. Abaixo parte do código:
public function processa() {
while ( ! feof ( $this->_arquivo ) ) {
list ( $tipoRegistro, $linha ) = fscanf ( $this->_arquivo, '%02d%248[ -~]' );
// o restante do código
}
fclose ( $this->_arquivo );
}
Um exemplo de linha que preciso ler:
02 LUCIANE 1150 OUTROS SERVIÇOS/PACOTE RSNTI/Instalação de hardwar000000000000000
Preciso pegar primeiro os 2 primeiros caracteres do arquivo (%02d), que está OK. Depois, preciso pegar todo o resto (o que fazer com ele depende do valor dos 2 primeiros caracteres), mas quando tem qualquer caractere acentuado, o fscanf para de contar a linha daí em diante. Se eu der um var_dump em $linha:
string(192) " LUCIANE 1150 OUTROS SERVI"
// quando o esperado era
string(248) " LUCIANE 1150 OUTROS SERVIÇOS/PACOTE RSNTI/Instalação de hardwar000000000000000 "
Procurei no Oráculo e encontrei outras pessoas com o mesmo problema, mas nenhuma solução. Alguém já passou por isto e conseguiu resolver?
Todos os arquivos estão salvos em UTF-8.
Carlos Eduardo
Uhn... pior que não funcionou. Tentei com \n e \r\n e nada, continua parando no acento. Provavelmente no servidor (Linux) vai funcionar, porque aqui no Windows não funcionou. Tem alguma outra sugestão do que posso colocar ali?
Carlos Eduardo
Falando contigo pelo MSN, você me sugeriu que eu utilizasse fgets ou fread. Só que depois desta separação, para cada $tipoRegistro encontrada eu preciso buscar um novo padrão dentro do restante ($linha). Para isto estou usando o sscanf, que apresenta o mesmo problema do fscanf.
Teria alguma outra sugestão para me ajudar neste caso?
Carlos Eduardo
E se você combinasse o list() com um preg_split() ou fizesse uma preg_match_all() analisando manualmente? Não resolveria?
Pelo menos é sabido que ER não encrenca com acentos (pelo menos por enquanto).
>
Falando contigo pelo MSN, você me sugeriu que eu utilizasse fgets ou fread. Só que depois desta separação, para cada $tipoRegistro encontrada eu preciso buscar um novo padrão dentro do restante ($linha). Para isto estou usando o sscanf, que apresenta o mesmo problema do fscanf.
>
Teria alguma outra sugestão para me ajudar neste caso?
Veja Matias,
Não sei exatamente quantos algorítimos você tem hoje mas, e se amanhã surgir um novo ?
Ou se algum desses algorítimos mudar ?
Se eu com esse mesmo problema, a primeira coisa que eu faria seria desacoplar os algorítimos, o desacoplamento causaria, de imediato, a facilidade de manutenção futura desses algorítimos e a possibilidade de escalá-los conforme as necessidades.
Você poderia simplesmente adicionar ou remover um algorítimo sem ter que modificar seu código.
Para resolver esse tipo de problema, existe um padrão bastante interessante chamado Strategy, que permite que você defina uma família de algorítimos, encapsule cada um deles e os torne intercambiáveis, ou seja, permite que você possa variá-los independentemente do cliente que os utiliza.
Strategy :seta: Comportamental
Conhecido também como:
Policy
Motivação:
Se você definir os algorítimos diretamente no cliente, você terá um código grande, complexo e difícil de manter.
Muitas vezes, algorítimos diferentes são necessários em situações diferentes, no seu caso, por exemplo, você tem uma forma de separação diferente para cada linha de um arquivo e essa separação é orientada pelos 2 primeiros bytes de cada linha.
Se você definir esses algorítimos no cliente, você terá um código não escalável, se um novo algorítimo surgir, você precisará editar seu cliente e adicionar o novo algorítimo, da mesma forma, caso algum algorítimo deixe de ser utilizado ou então precise ser modificado, você precisará editar constantemente o cliente que o utiliza.
Esses problemas podem ser resolvidos se você encapsular os algorítimos em objetos responsáveis exclusivamente pela sua lógica:
Aplicabilidade:
switch ( $type ) {
case '01' :
case '02' :
case '03' :
//...
case '99' :
}
Com Strategy você utiliza apenas a interface do algorítimo, por exemplo:
$algorithm->execute();
**Estrutura**:
Participantes:
Strategy:
Define a interface que é comum para todos os algorítimos. Context utiliza essa interface para executar o algorítimo definido por um ConcreteStrategy.
ConcreteStrategy:
Implementa um algorítimo específico utilizando a interface definida por Strategy.
Context:
É configurado com um objeto ConcreteStrategy.
Mantém uma referência ao objeto Strategy.
Pode definir uma interface que permite que o Strategy acesse seus dados.
Exemplo:
Bom, como estamos lidando com arquivos, o primeiro passo é remover a responsabilidade de trabalho com arquivos do cliente, para isso definiremos uma classe File, específica para esse fim:
File.php
<?php
/**
* Representação de um arquivo
*/
class File {
/**
* @var resource
*/
private $fh;
/**
* @var string
*/
private $filename;
/**
* @var string
*/
private $mode = 'r';
/**
* @var boolean
*/
private $opened = false;
/**
* Constroi o objeto que representa um arquivo.
* @param string $filename
* @throws RuntimeException
*/
public function __construct( $filename ) {
if ( is_file( $filename ) && is_readable( $filename ) ) {
if ( is_writable( $filename ) ) {
$this->mode = 'r+';
}
} else if ( is_writable( dirname( $filename ) ) ) {
$this->mode = 'w+';
} else {
throw new RuntimeException( 'Sem permissões para criar o arquivo.' );
}
$this->filename = $filename;
}
/**
* Destroi o objeto e fecha o arquivo se estiver aberto.
*/
public function __destruct() {
if ( is_resource( $this->fh ) ) {
$this->close();
}
}
/**
* Verifica se o ponteiro de arquivo está no início.
* @return boolean
*/
public function bof() {
if ( $this->opened ) {
return $this->tell() == 0;
}
return true;
}
/**
* Fecha o arquivo.
*/
public function close() {
if ( $this->opened ) {
fclose( $this->fh );
$this->opened = false;
}
}
/**
* Verifica se o ponteiro de arquivo está no final.
* @return boolean
*/
public function eof() {
if ( $this->opened ) {
return feof( $this->fh );
}
return true;
}
/**
* Recupera uma linha do arquivo.
* @param integer $length
* @return integer
*/
public function gets( $length = 1024 ) {
if ( $this->opened ) {
return fgets( $this->fh , $length );
}
}
/**
* Abre o arquivo.
* @return boolean
* @throws RuntimeException
*/
public function open() {
if ( !$this->opened ) {
$fh = fopen( $this->filename , $this->mode );
if ( is_resource( $fh ) ) {
$this->fh = $fh;
} else {
throw new RuntimeException( 'Não foi possível abrir o arquivo.' );
}
return $this->opened = true;
}
return false;
}
/**
* Lê uma porção de bytes do arquivo.
* @param integer $length
*/
public function read( $length = 1 ) {
if ( $this->opened ) {
return fread( $this->fh , $length );
}
}
/**
* Define a posição do ponteiro do arquivo.
* @param integer $offset
* @param integer $whence
*/
public function seek( $offset , $whence = SEEK_CUR ) {
if ( $this->opened ) {
return fseek( $this->fh , $offset , $whence ) == 0;
}
return false;
}
/**
* Recupera a posição do ponteiro do arquivo.
* @return integer
*/
public function tell() {
if ( $this->opened ) {
$tell = ftell( $this->fh );
return $tell === false ? -1 : $tell;
}
return -1;
}
/**
* Escreve no arquivo.
* @param string $data
* @return integer
*/
public function write( $data ) {
if ( $this->opened ) {
return fwrite( $this->fh , $data , strlen( $data ) );
}
return 0;
}
}
Com o objeto que representa um arquivo definido, vamos definir o Container:
Container.php
<?php
require_once 'File.php';
/**
* Implementação de um container que contém os dados a serem importados.
*/
class Container implements Countable {
/**
* @var array
*/
private $data = array();
/**
* @var File
*/
private $file;
/**
* @var Algorithm
*/
private $algorithm;
/**
* Constroi o objeto do container para um arquivo específico
* @param string $filename
*/
public function __construct( $filename ) {
$this->file = new File( $filename );
$this->file->open();
}
/**
* Adiciona uma linha que acaba de ser interpretada por um
* algorítimo qualquer.
* @param array $line
*/
public function addLine( $line ) {
$this->data[] = $line;
}
/**
* Configura o container com um algorítimo.
* @param Algorithm $algorithm
*/
public function configure( Algorithm $algorithm ) {
$this->algorithm = $algorithm;
$this->algorithm->accept( $this );
}
/**
* Conta o total de linhas já importadas.
* @return integer
* @see Countable::count()
*/
public function count() {
return count( $this->data );
}
/**
* Executa a importação utilizando um algorítimo previamente
* configurado.
*/
public function execute() {
$this->algorithm->execute();
}
/**
* Finaliza a importação.
*/
public function finalize() {
$this->file->close();
printf( "Finalizando\n" );
var_dump( $this->data );
}
/**
* Recupera uma linha inteira do arquivo.
* @return string
*/
public function gets() {
return $this->file->gets();
}
/**
* Lê uma porção de bytes do arquivo.
* @param integer $length
* @return string
*/
public function read( $length ) {
return $this->file->read( $length );
}
public function seek( $offset , $whence = SEEK_CUR ) {
$this->file->seek( $offset , $whence );
}
/**
* Verifica se o container ainda é válido (o arquivo já chegou no fim).
* @return boolean
*/
public function valid() {
return !$this->file->eof();
}
}
O Container é nosso Context, ele utilizará a interface do Strategy para executar a importação utilizando o algorítimo adequado.
Algorithm.php
<?php
/**
* Interface para definição de um algorítimo de importação
*/
interface Algorithm {
/**
* Define o contexto do algorítimo.
* @param Container $container
* @return boolean
*/
public function accept( Container $container );
/**
* Executa o algorítimo
*/
public function execute();
}
AbstractAlgorithm.php
<?php
require_once 'Algorithm.php';
/**
* Base para implementação do algorítimo de importação.
*/
abstract class AbstractAlgorithm implements Algorithm {
/**
* @var boolean
*/
protected $acceptable = false;
/**
* @var Container
*/
protected $container;
/**
* @var string
*/
protected $type;
/**
* @param Container $container
* @return boolean
*/
public function accept( Container $container ) {
if ( $container->read( 2 ) != $this->type ) {
$container->seek( -2 );
return $this->acceptable = false;
}
$this->container = $container;
return $this->acceptable = true;
}
}
Com a interface de um algorítimo definida, partimos para as implementações de cada algorítimo específico:
Algorithm01.php
<?php
require_once 'AbstractAlgorithm.php';
class Algorithm01 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '01';
/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
$line = array();
$this->container->seek( 4 );
$line[ 'oco_dataHoraAbertura' ] = vsprintf(
'%4d-%02d-%02d %02d:%02d:%02d' , sscanf(
$this->container->read( 14 ),
'%04d%02d%02d%02d%02d%02d'
)
);
$line[ 'oco_numeroChamadoCaixa' ] = trim( $this->container->read( 20 ) );
$line[ 'oco_codigoChamadoCaixa' ] = $line[ 'oco_numeroChamadoCaixa' ];
$line[ 'oco_codigoAtividade' ] = $this->container->read( 3 );
$this->container->seek( 3 );
$line[ 'oco_descricao' ] = vsprintf(
'UNIDADE DA OCORRÊNCIA => Código Unidade - %d, Tipo Unidade - %d, Nome - %s, Endereço - %s, %s - %s, Correspondente - ',
array_map( 'trim' , array(
$this->container->read( 10 ),
$this->container->read( 30 ),
$this->container->read( 40 ),
$this->container->read( 60 ),
$this->container->read( 40 ),
$this->container->read( 2 ),
$this->container->read( 10 )
) )
);
$this->container->addLine( $line );
$this->container->gets();
}
}
}
Algorithm02.php
<?php
require_once 'AbstractAlgorithm.php';
class Algorithm02 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '02';
/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
$this->container->addLine( array(
'oco_descricao' => implode( ',' , array(
'EQUIPAMENTO DA OCORRÊNCIA => ' . trim( $this->container->read( 40 ) ),
'Número de Série - ' . trim( $this->container->read( 40 ) ),
'Nome lógico na rede - ' . trim( $this->container->read( 40 ) ),
'Contato - ' . trim( $this->container->read( 40 ) ),
'Telefone - ' . trim( $this->container->read( 20 ) ),
'Tipo de Serviço para a Caixa - ' . trim( $this->container->read( 50 ) )
) ) ) );
$this->container->gets();
}
}
}
Algorithm03.php
<?php
require_once 'AbstractAlgorithm.php';
class Algorithm03 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '03';
/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
$this->container->addLine( array(
'oco_descricao' => $this->container->gets()
) );
}
}
}
Algorithm99.php
<?php
require_once 'AbstractAlgorithm.php';
class Algorithm99 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '99';
/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
if ( $this->container->count() + 1 != (int) $this->container->read( 5 ) ) {
throw new RuntimeException( 'A quantidade de linhas no arquivo não bate com o existente no trailer' );
}
$this->container->finalize();
}
}
}
Com os algorítimos encapsulados, preciamos apenas definir o Client:
Importer.php
<?php
require_once 'Container.php';
require_once 'Algorithm01.php';
require_once 'Algorithm02.php';
require_once 'Algorithm03.php';
require_once 'Algorithm99.php';
/**
* Implementação de um importador que aceita vários
* algorítimos de importação.
*/
class Importer {
/**
* @var array[Algorithm]
*/
private $algorithms;
/**
* Adiciona um algorítimo de importação.
* @param Algorithm $algorithm
*/
public function addAlgorithm( Algorithm $algorithm ) {
$this->algorithms[] = $algorithm;
}
/**
* Importa os dados de um determinado container.
* @param Container $container
* @throws BadMethodCallException
*/
public function import( Container $container ) {
if ( count( $this->algorithms ) > 0 ) {
while ( $container->valid() ) {
foreach ( $this->algorithms as $algorithm ) {
$container->configure( $algorithm );
$container->execute();
}
}
} else {
throw new BadMethodCallException( 'Nenhum algorítimo de importação definido.' );
}
}
}
Usando isso ai:
<?php
require_once 'Importer.php';
$i = new Importer();
$i->addAlgorithm( new Algorithm01() );
$i->addAlgorithm( new Algorithm02() );
$i->addAlgorithm( new Algorithm03() );
$i->addAlgorithm( new Algorithm99() );
$i->import( new Container( '00000000000053217462.txt' ) );
E pronto, se amanhã algum algorítimo mudar, basta ajustar o próprio algorítimo.
Se você precisar adicionar mais algorítimos, remover alguns, utilizar algorítimos específicos para arquivos específicos, enfim...
;)
Show de bola!!!! Segunda feira vou analisar com calma e implementar. Se surgir alguma dúvida eu volto a perguntar.
Como sempre, João, ótimo exemplo.
Carlos Eduardo
kra isso não é uma resposta, é uma lição de vida heheheheheheheh muito bom!!!
Matias,
Quando se trabalha com padrões de strings, você tem dois caminhos a seguir:
1. Especificação
2. Restrição
No fragmento abaixo, você está trabalhando com especificação:
>
fscanf ( $this->_arquivo, '%02d%248[ -~]' );
Que significa, "qualquer caractere imprimível com comprimento de 248 caracteres"
>
quando tem qualquer caractere acentuado, o fscanf para de contar a linha daí em diante. Se eu der um var_dump em $linha:
Todos os arquivos estão salvos em UTF-8.
Quando você se depara com um acento ou um cedilha, você encontrou o seguinte:
0xC3 0x87 => Ç
0xC3 0xA7 => ç
Bom, você disse para o fscanf que quer caracteres imprimíveis, mas o 0xC3 (que compõe o multibyte ç e outros caracteres latinos) não é imprimível e, como o fscanf é obediente ele pensou: "O cara não quer isso não, paro por aqui".
Qual a solução ?
Deixar de especificar e passar a restringir:
fscanf( $fh , "%02d%248[^\n]" )
Que significa: *"qualquer caractere **que não seja** uma quebra de linha, com comprimento de 248 caracteres"*
A saída:
array(2) {
[0]=>
int(2)
[1]=>
string(248) " LUCIANE 1150 OUTROS SERVIÇOS/PACOTE RSNTI/Instalação de hardwar000000000000000"
}
;)