file_get_contentsが間違った結果を得る


10

更新

問題を解決して回答を投稿しました。ただし、私のソリューションは100%理想的ではありません。私はむしろのみ除去するであろうsymlinkからcacheclearstatcache(true, $target)か、clearstatcache(true, $link)それは動作しません。

また、シンボリックリンクのキャッシュを最初から防ぐか、シンボリックリンクを生成した直後にキャッシュから削除します。残念ながら、私はそれで運がありませんでした。何らかの理由でclearstatcache(true)シンボリックリンクを作成しても機能しない場合でも、キャッシュされます。

私は私の答えを改善し、それらの問題を解決できるすべての人に賞金を喜んで授与します。

編集する

clearstatcache実行するたびにファイルを生成してコードを最適化しようとしたので、シンボリックリンクごとに1回だけキャッシュをクリアする必要があります。何らかの理由で、これは機能しません。clearstatcachesymlinkパスに含まれるたびに呼び出す必要がありますが、なぜですか?私のソリューションを最適化する方法がなければなりません。


で使用PHP 7.3.5していnginx/1.16.0ます。file_get_contentsを使用すると、誤った値が返されることがありますsymlink。問題は、シンボリックリンクを削除して再作成した後、その古い値がキャッシュに残っていることです。正しい値が返されることもあれば、古い値が返されることもあります。ランダムに見えます。

私はキャッシュをクリアするか、キャッシュを防止しようとしました:

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

キャッシュを無効にしたくないのですが、file_get_contentsで100%の精度が必要です。

編集する

私のソースコードは長すぎて複雑であるため、投稿できません。問題を再現する最小限の再現可能な例(index.php)を作成しました。

<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>

Nginx構成に問題がある可能性が高いようです。これらの行がないと問題が発生する可能性があります。

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

これが私のNginx設定です(上記の行が含まれていることがわかります)。

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;
}

現在、上記の例をhttps://www.websemantica.co.ukで公開しています

フォームにいくつかの値を追加してみてください。Success!毎回青色で表示されるはずです。時々Failure!赤で表示されます。からSuccess!Failure!またはその逆に変更するには、かなりの数のページの更新が必要な場合があります。最終的にはSuccess!毎回表示されるため、何らかのキャッシュの問題があるはずです。


私は同じケースを見回していて、realpath関数ページに非常に役立つコメントを見つけました。多分それはあなたを助けることができます。
marv255

@ marv255 幸運なしで使っrealpathてみましたfile_get_conents。それでもキャッシュから読み込まれることがあります。
Dan Bray、

2
だけrealpathでなく、次のような意味でもありますclearstatcache(true); file_get_conents(realpath($fileName));
marv255

linux.die.net/man/8/updatedb を試して、連続する呼び出しの間にコマンドを実行します。これが事実である場合、phpで問題を解決する方法はわかりませんが。
Jannes Botis

回答:


3

OSレベルに依存しすぎです。では、箱を考えてみてはどうでしょう。でファイルの実際の場所を読み取って、readlinkその実際の場所のパスを使用してみてはどうでしょうか。

$realPath = shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);

それは(箱から出して)十分ではないと思います。結局のところ、readlinkはOSレベルの呼び出しにも依存し、キャッシュの影響を受けます。
Bahram Ardalan、

3

これは、あなたがこれを見ることができますPHPの所望の動作です、ここで PHPを使用しているため、realpath_cache原因に店舗にファイルパスをパフォーマンスの向上、それはディスクの操作を減らすことができるように。

この動作を回避するにrealpath_cacheは、get_file_contents関数を使用する前にをクリアしてみてください

あなたはこのようなことを試すことができます:


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

PHPのドキュメントでclearstatcacheの詳細を確認できます。


2

2つのキャッシュがあります。

最初にOSキャッシュ、次にPHPキャッシュ。

ほとんどの場合clearstatcache(true)前にfile_get_contents(...)仕事をしていません。

ただし、OSキャッシュをクリアする必要がある場合もあります。Linuxの場合、2か所を明確にする必要があります。PageCache(1)およびdentries / inodes(2)。

これにより、両方がクリアされます。

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

注:これはトラブルシューティングに適していますが、本番環境での頻繁な呼び出しには適していません。OSキャッシュ全体がクリアされ、システムにキャッシュの再読み込みに数分の時間がかかるためです。


これは機能しません。キャッシュされた値がまだ読み込まれることがあり、本番環境での頻繁な呼び出しに適したソリューションが必要です。
Dan Bray、

2
@DanBray、時々の性質についてもっと知るために物事をログに記録できますか?
Bahram Ardalan、

1
@DanBray、そしてどのように古い値の出現を検出しますか?他のテスト条件が原因で、値が実際に変更されているにもかかわらず、テストが古い値を返す可能性がありますか?
Bahram Ardalan、

2

「問題は、シンボリックリンクを削除して再作成した後です」

シンボリックリンクをどのように削除しますか?ファイル(またはシンボリックリンク)を削除すると、自動的にキャッシュがクリアされます。

それ以外の場合は、次のようにするとどうなるかを確認できます。

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

これで問題が解決しない場合、それはおそらくのようにnginxのに問題がある可能性があり、この問題は

すべての操作をログファイルに記録して、実際に起きているかを確認してください。

または多分...

... シンボリックリンクなしで実行できますか?たとえば、「ファイル名」と「実際のシンボリックリンクターゲット」の間のマッピングをデータベース、memcache、SQLiteファイル、またはJSONファイルに保存します。たとえば、redisまたは他のキーストアを使用して、「ファイル名」を実際のsymlinkターゲットに関連付け、OSの解決を完全にバイパスできます。

ユースケースによっては、シンボリックリンクを使用するよりも高速になる場合もあります。


phpプロセスとローカルファイルシステムの間にhttpが存在しないように見えるので、これがnginxにどのように関係するかはわかりませんでした。親プロセスであることがnginxを何らかの形で関連づけていますか?
Bahram Ardalan、

@BahramArdalan事実は、問題がどのように診断されたか、シンボリックリンクが何であるか、またはそれらがどのように使用されているはわかりません。そのため、コンテンツの不一致がnginxの下流で検出され、実際にはPHPとは無関係である可能性があります。SCCCEは非常に役立ちます。
LSerni

はい。その「方法」について少し掘り下げる必要があります。
Bahram Ardalan、

1

問題の原因となった2つの問題がありました。

創刊

私はすでに質問として投稿し、編集しています。Nginxの設定に問題があります。

これらの行:

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

置き換えが必要:

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

第二号

2つ目の問題は、電話をかけるclearstatcache前に電話する必要があったことですfile_get_contentsclearstatcacheどうしても必要なときにだけ呼び出したいので、ディレクトリにが含まれている場合にのみキャッシュをクリアする関数を作成しましたsymlink

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

1

最初の回答は有効な回答であるため、そのままにしておきます。clearstatcache(true、$ filename)を実装することで、@ DanBrayの回答を改善しています。

問題の原因となった2つの問題がありました。

創刊

私はすでに質問として投稿し、編集しています。Nginxの設定に問題があります。

これらの行:

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

置き換えが必要:

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

第二号

2番目の問題は、file_get_contentsを呼び出す前にclearstatcacheを呼び出す必要があったことです。絶対に必要な場合にのみclearstatcacheを呼び出したいので、ディレクトリにシンボリックリンクが含まれている場合にのみキャッシュをクリアする関数を作成しました。

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);
}

上記のコードを自分のWebサーバーでテストしましたが、うまくいきました。


1
残念ながら、私のWebサーバーでは動作しません。
Dan Bray、

さて私はドローリングボードに戻ります。@DanBray
JTS

1
誠にありがとうございましたが、残念ながら、賞金期間が終了するまでの時間はほとんどありません。しかし、あなたが私が100%満足している解決策について考えるなら、私は追加の賞金を授与します。また、これfile_get_contents1は私が作成したフレームワークの一部であるため、頻繁に使用されるため、最適化が重要になります。
Dan Bray、

$dir_go=readdir("$realPath")nullを返します。
Dan Bray、

While($dir_go!==null)@DanBrayに変更する必要があるかもしれません
JTS

0

Jqueryを使用して継続的に更新されている要素内にコードを配置し、再検証を強制して静的キャッチをクリアしてみてください。このコードは、@ 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 );
 ?>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.