web-dev-qa-db-fra.com

file_get_contents obtient des résultats erronés

Mise à jour

J'ai résolu le problème et publié une réponse. Cependant, ma solution n'est pas idéale à 100%. Je préfère de loin supprimer uniquement le symlink du cache avec clearstatcache(true, $target) ou clearstatcache(true, $link) mais cela ne fonctionne pas.

Je préférerais aussi de beaucoup empêcher la mise en cache des liens symboliques en premier lieu ou supprimer le lien symbolique du cache immédiatement après l'avoir généré. Malheureusement, je n'ai pas eu de chance avec ça. Pour une raison quelconque, clearstatcache(true) après avoir créé un lien symbolique ne fonctionne pas, il est toujours mis en cache.

Je remets volontiers la prime à toute personne qui peut améliorer ma réponse et résoudre ces problèmes.

Modifier

J'ai essayé d'optimiser mon code en générant un fichier chaque fois que clearstatcache est exécuté, de sorte que je n'ai besoin de vider le cache qu'une seule fois pour chaque lien symbolique. Pour une raison quelconque, cela ne fonctionne pas. clearstatcache doit être appelé chaque fois qu'un symlink est inclus dans le chemin d'accès, mais pourquoi? Il doit y avoir un moyen d'optimiser la solution que j'ai.


J'utilise PHP 7.3.5 avec nginx/1.16.0. Quelquefois file_get_contents renvoie la mauvaise valeur lors de l'utilisation d'un symlink. Le problème est qu'après avoir supprimé et recréé un lien symbolique, son ancienne valeur reste dans le cache. Parfois, la valeur correcte est renvoyée, parfois l'ancienne valeur. Cela semble aléatoire.

J'ai essayé d'effacer le cache ou d'empêcher la mise en cache avec:

function symlink1($target, $link)
{
    realpath_cache_size(0);
    symlink($target, $link);
    //clearstatcache(true);
}

Je ne veux pas vraiment désactiver la mise en cache mais j'ai toujours besoin d'une précision de 100% avec file_get_contents.

Modifier

Je ne suis pas en mesure de publier mon code source, car il est beaucoup trop long et complexe, j'ai donc créé un exemple reproductible minimal (index.php) qui recrée le problème:

<h1>Symlink Problem</h1>
<?php
    $dir = getcwd();
    if (isset($_POST['clear-all']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        foreach ($nos as $no)
        {
            unlink($dir.'/nos/'.$no.'/id.txt');
            rmdir($dir.'/nos/'.$no);
        }
        foreach (array_values(array_diff(scandir($dir.'/ids'), array('..', '.'))) as $id)
            unlink($dir.'/ids/'.$id);
    }
    if (!is_dir($dir.'/nos'))
        mkdir($dir.'/nos');
    if (!is_dir($dir.'/ids'))
        mkdir($dir.'/ids');
    if (isset($_POST['submit']) && !empty($_POST['id']) && ctype_digit($_POST['insert-after']) && ctype_alnum($_POST['id']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        $total = count($nos);
        if ($total <= 100)
        {
            for ($i = $total; $i >= $_POST['insert-after']; $i--)
            {
                $id = file_get_contents($dir.'/nos/'.$i.'/id.txt');
                unlink($dir.'/ids/'.$id);
                symlink($dir.'/nos/'.($i + 1), $dir.'/ids/'.$id);
                rename($dir.'/nos/'.$i, $dir.'/nos/'.($i + 1));
            }
            echo '<br>';
            mkdir($dir.'/nos/'.$_POST['insert-after']);
            file_put_contents($dir.'/nos/'.$_POST['insert-after'].'/id.txt', $_POST['id']);
            symlink($dir.'/nos/'.$_POST['insert-after'], $dir.'/ids/'.$_POST['id']);
        }
    }
    $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
    $total = count($nos) + 1;
    echo '<h2>Ids from nos directory</h2>';
    foreach ($nos as $no)
    {
        echo ($no + 1).':'.file_get_contents("$dir/nos/$no/id.txt").'<br>';
    }
    echo '<h2>Ids from using symlinks</h2>';
    $ids = array_values(array_diff(scandir($dir.'/ids'), array('..', '.')));
    if (count($ids) > 0)
    {
        $success = true;
        foreach ($ids as $id)
        {
            $id1 = file_get_contents("$dir/ids/$id/id.txt");
            echo $id.':'.$id1.'<br>';
            if ($id !== $id1)
                $success = false;
        }
        if ($success)
            echo '<b><font color="blue">Success!</font></b><br>';
        else
            echo '<b><font color="red">Failure!</font></b><br>';
    }
?>
<br>
<h2>Insert ID after</h2>
<form method="post" action="/">
    <select name="insert-after">
        <?php
            for ($i = 0; $i < $total; $i++)
                echo '<option value="'.$i.'">'.$i.'</option>';
        ?>
    </select>
    <input type="text" placeholder="ID" name="id"><br>
    <input type="submit" name="submit" value="Insert"><br>
</form>
<h2>Clear all</h2>
<form method="post" action="/">
    <input type="submit" name="clear-all" value="Clear All"><br>
</form>
<script>
    if (window.history.replaceState)
    {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

Cela semblait très probablement être un problème avec la configuration de Nginx. Ne pas avoir ces lignes peut provoquer le problème:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

Voici ma configuration Nginx (vous pouvez voir que j'ai inclus les lignes ci-dessus):

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.websemantica.co.uk;
    root "/path/to/site/root";
    index index.php;

    location / {
        try_files $uri $uri/ $uri.php$is_args$query_string;
    }

    location ~* \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_param   QUERY_STRING            $query_string;
        fastcgi_param   REQUEST_METHOD          $request_method;
        fastcgi_param   CONTENT_TYPE            $content_type;
        fastcgi_param   CONTENT_LENGTH          $content_length;

        fastcgi_param   SCRIPT_FILENAME         $realpath_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME             $fastcgi_script_name;
        fastcgi_param   PATH_INFO               $fastcgi_path_info;
        fastcgi_param   PATH_TRANSLATED         $realpath_root$fastcgi_path_info;
        fastcgi_param   REQUEST_URI             $request_uri;
        fastcgi_param   DOCUMENT_URI            $document_uri;
        fastcgi_param   DOCUMENT_ROOT           $realpath_root;
        fastcgi_param   SERVER_PROTOCOL         $server_protocol;

        fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
        fastcgi_param   SERVER_SOFTWARE         nginx/$nginx_version;

        fastcgi_param   REMOTE_ADDR             $remote_addr;
        fastcgi_param   REMOTE_PORT             $remote_port;
        fastcgi_param   SERVER_ADDR             $server_addr;
        fastcgi_param   SERVER_PORT             $server_port;
        fastcgi_param   SERVER_NAME             $server_name;

        fastcgi_param   HTTPS                   $https;

        # PHP only, required if PHP was built with --enable-force-cgi-redirect
        fastcgi_param   REDIRECT_STATUS         200;

        fastcgi_index index.php;
        fastcgi_read_timeout 3000;
    }

    if ($request_uri ~ (?i)^/([^?]*)\.php($|\?)) {
        return 301 /$1$is_args$args;
    }
    rewrite ^/index$ / permanent;
    rewrite ^/(.*)/$ /$1 permanent;
}

Actuellement, j'ai l'exemple ci-dessus en direct sur https://www.websemantica.co.uk .

Essayez d'ajouter quelques valeurs dans le formulaire. Il doit afficher Success! en bleu à chaque fois. Parfois, c'est des spectacles Failure! en rouge. Il peut falloir quelques rafraîchissements de page pour passer de Success! à Failure! ou vice versa. Finalement, il affichera Success! à chaque fois, il doit donc y avoir une sorte de problème de mise en cache.

10
Dan Bray

Deux problèmes ont causé le problème.

Premier numéro

J'ai déjà posté et édité dans la question. C'est un problème avec la configuration Nginx.

Ces lignes:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;

nécessaire remplacé par:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

Deuxième numéro

Le deuxième problème était que je devais appeler clearstatcache avant d'appeler file_get_contents. Je ne veux appeler clearstatcache que lorsque cela est absolument nécessaire, j'ai donc écrit une fonction qui n'efface le cache que lorsque le répertoire contient un symlink.

function file_get_contents1($dir)
{
    $realPath = realpath($dir);
    if ($realPath === false)
        return '';
    if ($dir !== $realPath)
    {
        clearstatcache(true);
    }
    return file_get_contents($dir);
}
1
Dan Bray

C'est le comportement souhaité de PHP vous pouvez le voir ici car PHP utilise realpath_cache pour stocker les chemins d'accès aux fichiers en raison de améliorations des performances afin de réduire les opérations sur disque.

Pour éviter ce problème, vous pouvez peut-être essayer d'effacer le realpath_cache avant d'utiliser le get_file_contents fonction

Vous pouvez essayer quelque chose comme ceci:


clearstatcache();
$data = file_get_contents("Your File");

Vous pouvez en savoir plus pour clearstatcache on PHP doc.

3
Touqeer Shafi

Cela dépend trop du niveau du système d'exploitation. Alors que diriez-vous d'essayer de penser à la boîte. Que diriez-vous d'essayer de lire l'emplacement réel du fichier par readlink et d'utiliser ce chemin d'accès réel?

$realPath = Shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);
3
Vo Kim Nguyen

"Le problème est après avoir supprimé et recréé un lien symbolique"

Comment supprimez-vous le lien symbolique? La suppression d'un fichier (ou d'un lien symbolique) devrait automatiquement vider le cache.

Sinon, vous pourriez voir ce qui se passe si vous le faites:

// This has "race condition" written all around it
unlink($link);
touch($link);
unlink($link); // Remove the empty file
symlink($target, $link);

Si cela ne résout pas le problème, pourrait-il s'agir d'un problème avec nginx comme dans ce problème ?

Essayez de consigner toutes les opérations dans un fichier journal pour voir ce qui se passe .

ou peut-être...

... pourriez-vous faire sans liens symboliques ? Par exemple, stockez dans une base de données, memcache, un fichier SQLite ou même un fichier JSON le mappage entre "nom de fichier" et "cible de lien symbolique réel". Utiliser par ex. redis ou d'autres magasins de clés, vous pouvez associer le "nom de fichier" à la véritable cible du lien symbolique et contourner complètement la résolution du système d'exploitation.

Selon le cas d'utilisation, cela pourrait même s'avérer plus rapide que l'utilisation de liens symboliques.

2
LSerni

Il y a deux caches.

D'abord le cache du système d'exploitation puis le cache PHP.

Dans la plupart des cas, clearstatcache(true) avant file_get_contents(...) fait le travail.

Mais parfois, vous devez également vider le cache du système d'exploitation. Dans le cas de Linux, je peux penser à deux endroits à nettoyer. PageCache (1) et denteries/inodes (2).

Cela efface à la fois:

Shell_exec('echo 3 > /proc/sys/vm/drop_caches')

Remarque: Ceci est bon pour le dépannage mais pas pour les appels fréquents en production car il efface tout le cache du système d'exploitation et coûte au système quelques instants de re-remplissage du cache.

2
Bahram Ardalan

Je laisse ma première réponse car c'est toujours une réponse valide. J'améliore la réponse @DanBray en implémentant clearstatcache (true, $ filename).

Deux problèmes ont causé le problème.

Premier numéro

J'ai déjà posté et édité dans la question. C'est un problème avec la configuration Nginx.

Ces lignes:

fastcgi_param SCRIPT_FILENAME $ document_root $ fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $ document_root;

nécessaire remplacé par:

fastcgi_param SCRIPT_FILENAME $ realpath_root $ fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $ realpath_root;

Deuxième numéro

Le deuxième problème était que je devais appeler clearstatcache avant d'appeler file_get_contents. Je ne veux appeler clearstatcache que lorsque cela est absolument nécessaire, j'ai donc écrit une fonction qui n'efface le cache que lorsque le répertoire comprend un lien symbolique.

function file_get_contents1234_hard_drives($dir_go_1){
    $realPath = realpath($dir_go_1);
        $myDirectory=opendir(dirname($realPath));        
        while($entryName=readdir($myDirectory)) {
          $dirArray[]=$entryName;
        }

        /* Finds extensions of files used for my site theelectronichandbook.tech
        function findexts ($filename) {
          $filename=strtolower($filename);
          $exts=split("[/\\.]", $filename);
          $n=count($exts)-1;
          $exts=$exts[$n];
          return $exts;
        }*/

        // Closes directory
        closedir($myDirectory);

        // Counts elements in array
        $indexCount=count($dirArray);
        for($ArPos=1;$ArPos<=$indexCount;$ArPos++){
            /*used for my site theelectronichandbook.tech
            if($_SERVER['QUERY_STRING']=="hidden"){
                $H="";
                $af="./";
                $atext="Hide";
            }else{
                $H=".";
                $af="./?hidden";
                $at="Show";
            }*/
            if(strpos($dirArray[$ArPos], "Symlink") !== false){
                clearstatcache(true,$dir_go_1);
            }
        }
    return file_get_contents($dir_go_1);
}

J'ai testé le code ci-dessus avec mon serveur Web et cela a fonctionné.

1
JTS

Essayez de placer le code à l'intérieur d'un élément qui est constamment actualisé à l'aide de Jquery ainsi que de forcer la revalidation et d'effacer les captures statiques. Ce code a été modifié depuis @naveed original answer .

form.php:

 <meta http-equiv="Cache-Control" content="no-store, must-revalidate" />
 <meta http-equiv="Expires" content="0"/>
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 <script> 
 jQuery(document).ready(function(){
    jQuery('.ajaxform').submit( function() {
        $.ajax({
            url     : $(this).attr('action'),
            type    : $(this).attr('method'),
            dataType: 'json',
            data    : $(this).serialize(),
            success : function( data ) {
                        // loop to set the result(value)
                        // in required div(key)
                        for(var id in data) {
                            jQuery('#' + id).html( data[id] );
                        }
                      }
        });
        return false;
    });
});
var timer, delay = 30;
timer = setInterval(function(){
    $.ajax({
      type    : 'POST',
      url     : 'profile.php',
      dataType: 'json',
      data    : $('.ajaxform').serialize(),
      success : function(data){
                  for(var id in data) {
                    jQuery('#' + id).html( data[id] );
                  }
                }
    }); }, delay);
 </script>
 <form action='profile.php' method='post' class='ajaxform'></form>
 <div id='result'></div>

profile.php:

 <?php
       // All form data is in $_POST
       // Now perform actions on form data here and create an result array something like this
       clearstatcache();
       $arr = array( 'result' => file_get_contents("./myfile.text") );
       echo json_encode( $arr );
 ?>
0
JTS