file_get_contents obtient des résultats erronés

10

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 ne retirer que symlinkle cacheavec clearstatcache(true, $target)ou clearstatcache(true, $link)mais cela ne fonctionne pas.

Je préfère également 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, la clearstatcache(true)création d'un lien symbolique ne fonctionne pas, il est toujours mis en cache.

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

Éditer

J'ai essayé d'optimiser mon code en générant un fichier à chaque clearstatcacheexécution, de sorte que je n'ai besoin d'effacer le cache qu'une seule fois pour chaque lien symbolique. Pour une raison quelconque, cela ne fonctionne pas. clearstatcachedoit être appelé à chaque fois que a symlinkest inclus dans le chemin, mais pourquoi? Il doit y avoir un moyen d'optimiser la solution que j'ai.


J'utilise PHP 7.3.5avec nginx/1.16.0. file_get_contentsRetourne parfois la mauvaise valeur lors de l'utilisation de a 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é de vider 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.

Éditer

Je ne peux pas poster 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 de Nginxconfiguration. 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 Nginxconfiguration (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 devrait s'afficher Success!en bleu à chaque fois. Est parfois affiché Failure!en rouge. Le changement de Success!vers Failure!ou vice-versa peut prendre quelques rafraîchissements de page . Finalement, il s'affichera à Success!chaque fois, donc il doit y avoir une sorte de problème de mise en cache.

Dan Bray
la source
Je regardais autour du même cas et j'ai trouvé un commentaire très utile sur la realpathpage de fonction . Cela pourrait peut-être vous aider.
marv255
@ marv255 J'ai essayé d'utiliser realpathavec file_get_conentset pas de chance. Il se charge encore parfois à partir du cache.
Dan Bray
2
Je veux dire non seulement realpath, mais quelque chose commeclearstatcache(true); file_get_conents(realpath($fileName));
marv255
Essayez linux.die.net/man/8/updatedb exécutez la commande entre des appels consécutifs. Bien que je ne sais pas comment résoudre le problème en php si tel est le cas.
Jannes Botis

Réponses:

3

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 readlinket d'utiliser ce chemin d'accès réel?

$realPath = shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);
Vo Kim Nguyen
la source
Je ne pense pas que cela soit suffisant (hors boîte), après tout, readlink dépend également des appels au niveau du système d'exploitation et est affecté par le cache.
Bahram Ardalan
3

C'est le comportement souhaité de PHP, vous pouvez le voir ici parce que PHP utilise realpath_cachepour stocker les chemins de fichiers en raison des améliorations de performances afin de réduire les opérations sur disque.

Pour éviter ce comportement, vous pouvez peut-être essayer de supprimer le realpath_cacheavant d'utiliser leget_file_contents fonction

Vous pouvez essayer quelque chose comme ceci:


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

Vous pouvez en savoir plus pour clearstatcache sur PHP doc.

Touqeer Shafi
la source
2

Il y a deux caches.

D'abord le cache OS puis le cache PHP.

Dans la plupart des cas, le travail est effectué clearstatcache(true)auparavant file_get_contents(...).

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.

Bahram Ardalan
la source
Cela ne fonctionne pas, il charge parfois la valeur mise en cache et j'ai besoin d'une solution adaptée aux appels fréquents en production.
Dan Bray
2
@DanBray, pourriez-vous enregistrer des choses pour en savoir plus sur la nature de parfois ?
Bahram Ardalan
1
@DanBray, et comment détectez-vous l' apparence de l'ancienne valeur? Se pourrait-il que votre test renvoie l'ancienne valeur en raison d'autres conditions de test alors que la valeur y a vraiment changé?
Bahram Ardalan
2

"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 réellement .

ou peut-être...

... pourriez-vous vous passer complètement de 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". En utilisant par exemple redis ou d'autres magasins de clés, vous pouvez associer le "nom de fichier" à la cible réelle du lien symbolique et contourner complètement la résolution du système d'exploitation.

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

LSerni
la source
Je ne pouvais pas voir comment cela peut être lié à nginx car il ne semble pas y avoir de chose http entre le processus php et le système de fichiers local. Le fait d'être le processus parent rend-il nginx pertinent?
Bahram Ardalan
@BahramArdalan le fait est que nous ne savons pas comment le problème a été diagnostiqué ni quels sont les liens symboliques ou comment ils sont utilisés. Il est donc concevable que la non-concordance de contenu ait été détectée en aval de nginx et puisse en fait être indépendante de PHP. Un SCCCE serait d'une grande aide.
LSerni
Oui. Nous devons creuser un peu dans ce "comment".
Bahram Ardalan
1

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 clearstatcacheavant d'appeler file_get_contents. Je ne veux appeler que clearstatcachelorsque c'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);
}
Dan Bray
la source
1

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 $ racine_document $ 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 j'avais besoin d'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é.

JTS
la source
1
Malheureusement, cela ne fonctionne pas pour moi sur mon serveur Web.
Dan Bray
Eh bien, je reviens au tableau des traînées. @DanBray
JTS
1
Merci beaucoup, mais malheureusement, il reste très peu de temps avant l'expiration de la période de prime. Cependant, si vous pensez à une solution dont je suis 100% satisfait, je vous octroierai une prime supplémentaire. En outre, file_get_contents1fait partie du cadre que j'ai créé, il est donc beaucoup utilisé, ce qui rend l'optimisation importante.
Dan Bray
$dir_go=readdir("$realPath")renvoie null.
Dan Bray
Cela peut While($dir_go!==null)devoir être changé en @DanBray
JTS
0

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é à partir de la réponse originale @naveed .

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 );
 ?>
JTS
la source