Journal
PHP : fread + filesize = danger
Je n'aime pas trop taper sur PHP, mais quand la documentation elle même n'est pas fiable et me fait perdre une bonne demi-heure, ça a tendance à me gonfler.
La documentation de fread (http://www.php.net/manual/fr/function.fread.php) donne un exemple (un vrai exemple hein, officiel, pas un commentaire) pour lire un fichier en entier :
$contents = fread($handle, filesize($filename));
Alors oui, sauf que dans la documentation de filesize il y a une petite note qui dit "les résultats de cette fonction sont mis en cache" (http://www.php.net/manual/fr/function.filesize.php).
Ce que ça signifie c'est que si vous écrivez/lisez plusieurs fois d’affilée dans un fichier, la taille du fichier retournée par filesize ne changera pas (et donc, fread lira trop ou pas assez de données).
C'est génial quand on ne le sait pas, parce que c'est sincèrement la dernière chose à laquelle on pourrait penser. Une fonction qui s'appelle "filesize", on ne peut pas imaginer qu'il y ait un piège idiot, qui plus est lorsque c'est la documentation qui vous donne le bout de code en question.
Au début, je n'y ai pas cru, alors j'ai écrit un test qui crée deux chaînes, une de 500 caractères et une de 1000:
1/ Les 500 caractères sont écrits dans le fichier puis lus.
2/ Les 1000 caractères sont écrits dans le fichier puis lus.
3/ 1 et 2 sont réitérés avec cette fois l'utilisation de clearstatcache(), qui vide le cache des tailles, entre autres.
<?php
save('file_test', str_repeat('x', 500));
echo strlen(load('file_test', false)) . ' ';
save('file_test', str_repeat('x', 1000));
echo strlen(load('file_test', false)) . ' ';
save('file_test', str_repeat('x', 500));
echo strlen(load('file_test', true)) . ' ';
save('file_test', str_repeat('x', 1000));
echo strlen(load('file_test', true)) . ' ';
function save($file, $data)
{
$fh = fopen($file, 'w');
fwrite($fh, $data);
fclose($fh);
}
function load($file, $clear)
{
if($clear)
clearstatcache();
$fh = fopen($file, 'r');
$data = fread($fh, filesize($file));
fclose($fh);
return $data;
}
?>
Et ce test, il affiche bien "500 500 500 1000", ce qui signifie que le fichier n'a été lu qu'à moitié dans l'étape 2.
Ce genre de problème est usant. Je ne critique pas ici le fait que filesize utilise un cache (c'est juste une propriété de la fonction), mais plutôt que la documentation de fread propose un exemple de code dangereux sans qu'à aucun moment ne soit indiqué que celui-ci peut avoir un comportement aberrant.
Si vous voulez vérifier par vous même, j'ai fait une copie des pages de la documentation ici : http://benjaminbillet.fr/media/php_fread_filesize_doc.7z
Bref, utilisez file_get_contents...
La documentation de fread (http://www.php.net/manual/fr/function.fread.php) donne un exemple (un vrai exemple hein, officiel, pas un commentaire) pour lire un fichier en entier :
$contents = fread($handle, filesize($filename));
Alors oui, sauf que dans la documentation de filesize il y a une petite note qui dit "les résultats de cette fonction sont mis en cache" (http://www.php.net/manual/fr/function.filesize.php).
Ce que ça signifie c'est que si vous écrivez/lisez plusieurs fois d’affilée dans un fichier, la taille du fichier retournée par filesize ne changera pas (et donc, fread lira trop ou pas assez de données).
C'est génial quand on ne le sait pas, parce que c'est sincèrement la dernière chose à laquelle on pourrait penser. Une fonction qui s'appelle "filesize", on ne peut pas imaginer qu'il y ait un piège idiot, qui plus est lorsque c'est la documentation qui vous donne le bout de code en question.
Au début, je n'y ai pas cru, alors j'ai écrit un test qui crée deux chaînes, une de 500 caractères et une de 1000:
1/ Les 500 caractères sont écrits dans le fichier puis lus.
2/ Les 1000 caractères sont écrits dans le fichier puis lus.
3/ 1 et 2 sont réitérés avec cette fois l'utilisation de clearstatcache(), qui vide le cache des tailles, entre autres.
<?php
save('file_test', str_repeat('x', 500));
echo strlen(load('file_test', false)) . ' ';
save('file_test', str_repeat('x', 1000));
echo strlen(load('file_test', false)) . ' ';
save('file_test', str_repeat('x', 500));
echo strlen(load('file_test', true)) . ' ';
save('file_test', str_repeat('x', 1000));
echo strlen(load('file_test', true)) . ' ';
function save($file, $data)
{
$fh = fopen($file, 'w');
fwrite($fh, $data);
fclose($fh);
}
function load($file, $clear)
{
if($clear)
clearstatcache();
$fh = fopen($file, 'r');
$data = fread($fh, filesize($file));
fclose($fh);
return $data;
}
?>
Et ce test, il affiche bien "500 500 500 1000", ce qui signifie que le fichier n'a été lu qu'à moitié dans l'étape 2.
Ce genre de problème est usant. Je ne critique pas ici le fait que filesize utilise un cache (c'est juste une propriété de la fonction), mais plutôt que la documentation de fread propose un exemple de code dangereux sans qu'à aucun moment ne soit indiqué que celui-ci peut avoir un comportement aberrant.
Si vous voulez vérifier par vous même, j'ai fait une copie des pages de la documentation ici : http://benjaminbillet.fr/media/php_fread_filesize_doc.7z
Bref, utilisez file_get_contents...
Ce journal est basé sur Ginger, un gestionnaire de lien minimaliste développé dans le cadre d'un stage de perfectionnement. Pour plus d'informations, consulter le wiki consacré à mes projets personnels.