$file->eof() sempre retornando false ao usar o SplFileObject do PHP no modo 'r'
-
13-12-2019 - |
Pergunta
Por que meu script PHP está travado?
$path = tempnam(sys_get_temp_dir(), '').'.txt';
$fileInfo = new \SplFileInfo($path);
$fileObject = $fileInfo->openFile('a');
$fileObject->fwrite("test line\n");
$fileObject2 = $fileInfo->openFile('r');
var_dump(file_exists($path)); // bool(true)
var_dump(file_get_contents($path)); // string(10) "test line
// "
var_dump(iterator_count($fileObject2)); // Hangs on this
Se eu excluir a última linha (iterator_count(...
) e substitua-o por isto:
$i = 0;
$fileObject2->rewind();
while (!$fileObject2->eof()) {
var_dump($fileObject2->eof());
var_dump($i++);
$fileObject2->next();
}
// Output:
// bool(false)
// int(0)
// bool(false)
// int(1)
// bool(false)
// int(2)
// bool(false)
// int(3)
// bool(false)
// int(4)
// ...
O $fileObject->eof()
sempre retorna falso, então recebo um loop infinito.
Por que essas coisas estão acontecendo?Preciso obter uma contagem de linhas.
Solução
Por que essas coisas estão acontecendo?
Você está experimentando uma peculiaridade na maneira como o SplFileObject
a aula está escrita.Sem ligar next()
e current()
métodos - usando o padrão (0
) sinalizadores — o iterador nunca avança.
O iterator_count()
função nunca chama current()
;verifica valid()
e chamadas next()
apenas.Seus loops personalizados chamam apenas um ou outro current()
e next()
.
Isso deve ser considerado um bug (seja no próprio PHP, ou uma falha na documentação) e o código a seguir deve (e não) funciona como esperado.Eu convido você para denunciar este mau comportamento.
// NOTE: This currently runs in an infinite loop!
$file = new SplFileObject(__FILE__);
var_dump(iterator_count($file));
Soluções alternativas
Um passo rápido para fazer as coisas andarem é definir o READ_AHEAD
bandeira no objeto.Isto fará com que next()
método para ler a próxima linha disponível.
$file->setFlags(SplFileObject::READ_AHEAD);
Se, por qualquer motivo, você não quiser que o Leia adiante comportamento, então você deve ligar para ambos next()
e current()
você mesmo.
De volta ao problema original de dois SplFileObjects
O seguinte agora deve funcionar conforme o esperado, permitindo anexar a um arquivo e ler sua contagem de linhas.
<?php
$info = new SplFileInfo(__FILE__);
$write = $info->openFile('a');
$write->fwrite("// new line\n");
$read = $info->openFile('r');
$read->setFlags(SplFileObject::READ_AHEAD);
var_dump(iterator_count($read));
Outras dicas
EDITADO 01
Se o que você precisa é do número de linhas dentro do arquivo:
<?php
$path = tempnam(sys_get_temp_dir(), '').'.txt';
$fileInfo = new SplFileInfo($path);
$fileObject = $fileInfo->openFile('a+');
$fileObject->fwrite("Foo".PHP_EOL);
$fileObject->fwrite("Bar".PHP_EOL);
echo count(file($path)); // outputs 2
?>
EDITADO 02
Este é o seu código acima, mas sem entrar em loops infinitos devido ao ponteiro do arquivo:
<?php
$path = tempnam(sys_get_temp_dir(), '').'.txt';
$fileInfo = new SplFileInfo($path);
$fileObject = $fileInfo->openFile('a+');
$fileObject->fwrite("Foo".PHP_EOL);
$fileObject->fwrite("Bar");
foreach($fileObject as $line_num => $line) {
echo 'Line: '.$line_num.' "'.$line.'"'."<br/>";
}
echo 'Total Lines:' . $fileObject->key();
?>
Resultados
Linha:0 "Foo"
Linha:1 "Barra"
Total de linhas: 2
RESPOSTA ORIGINAL
A lógica aplicada estava um pouco errada.Simplifiquei o código:
<?php
// set path to tmp with random file name
echo $path = tempnam(sys_get_temp_dir(), '').'.txt';
echo "<br/>";
// new object
$fileInfo = new \SplFileInfo($path);
// open to write
$fileObject = $fileInfo->openFile('a');
// write two lines
$fileObject->fwrite("Foo".PHP_EOL);
$fileObject->fwrite("Bar".PHP_EOL);
// open to read
$fileObject2 = $fileInfo->openFile('r');
// output contents
echo "File Exists: " .file_exists($path);
echo "<br/>";
echo "File Contents: " . file_get_contents($path);
echo "<br/>";
// foreach line get line number and line contents
foreach($fileObject2 as $line_num => $line) {
echo 'Line: '.$line_num;
echo ' With: "'.$line.'" is the end? '.($fileObject2->eof()?'yes':'no')."<br>";
}
?>
Saídas:
/tmp/EAdklY.txt
O arquivo existe:1
Conteúdo do arquivo:Foo Bar
Linha:0 Com:"Foo" é o fim?não
Linha:1 Com:"Bar" é o fim?não
Linha:2 Com:"" é o fim?sim
Embora possa parecer contra-intuitivo, com o PHP 5.3.9, isto:
<?php
$f = new SplFileObject('test.txt', 'r');
while (!$f->eof()) {
$f->next();
}
while seja um loop infinito e nunca saia.
O seguinte será encerrado quando o final do arquivo for atingido:
<?php
$f = new SplFileObject('test.txt', 'r');
while (!$f->eof()) {
$f->current();
}
Então:
$i = 0;
$fileObject2->rewind();
while (!$fileObject2->eof()) {
var_dump($fileObject2->eof());
var_dump($i++);
$fileObject2->next();
}
deve ser reescrito como:
$fileObject2->rewind();
while (!$fileObject2->eof()) {
$fileObject2->current();
}
$i = $fileObject2->key();