如何使用 PHP 在文件中添加或更新值

How to add or update values in a file using PHP

我正在开发一个小的 class,它将允许我将队列数据写入文件。类似于 PHP $_SESSION.

我认为以下步骤可以解决问题

  1. 'a+' 模式下使用 fopen() 打开文件
  2. 使用 flock() 和 LOCK_EX 类型锁定文件以防止另一个进程使用同一文件
  3. 使用fread()读取文件的现有内容。然后使用 json_decode(array, true)
  4. 将数据放入数组
  5. 现在数据在数组中。如果键存在于数组中更新它的值,否则将键插入数组。
  6. 在创建一个包含文件中需要的所有数据的数组后,我使用 ftruncate() 截断文件并使用 fwrite()
  7. 将新数据写入文件
  8. 使用 flock() 类型 LOCK_UN 解锁文件以允许另一个进程使用它。
  9. 最后关闭文件

我相信我在 updateCache() 方法中编写了满足上述步骤的代码。但它似乎无法正常工作。它不跟踪数据。

这是我的 class

<?php

namespace ICWS;

use \ICWS\showVar;
use \DirectoryIterator;
/**
 * CacheHandler
 *
 * @package ICWS
 */
class CacheHandler {

    /* Max time second allowed to keep a session File */
    private $maxTimeAllowed = 300;


    private $filename = '';
    private $handle;

    public function __construct( $filename )
    {

        $this->filename = $filename;

        // 5% chance to collect garbage when the class is initialized.
        if(mt_rand(1, 100) <= 5){
            $this->collectGarbage();
        }
    }

    private function print_me($a)
    {
        echo '<pre>';
        print_r($a);
        echo '</pre>';
    }

    /**
    * Add/Update the cached array in the session
    *
    * @param string $key
    * @param bigint $id
    * @param array $field
    * @return void
    */  
    public function updateCache($key, $id, array $field)
    {

        $currentVal = $field;
        $this->openFile();

        $this->lockFile();
        $storage = $this->readFile();

        //create a new if the $id does not exists in the cache
        if( isset($storage[$key]) && array_key_exists($id, $storage[$key]) ){
            $currentVal = $storage[$key][$id];

            foreach($field as $k => $v){
                $currentVal[$k] = $v; //update existing key or add a new one
            }

        }

        $storage[$key][$id] =  $currentVal; 

        $this->updateFile($storage);

        $this->unlockFile();

        $this->closeFile();

    }

    /**
    * gets the current cache/session for a giving $key
    *
    * @param string $key. If null is passed it will return everything in the cache
    * @return object or boolean
    */  
    public function getCache($key = null)
    {
        $value = false;
        $this->openFile();
        rewind($this->handle);
        $storage = $this->readFile();

        if(!is_null($key) && isset($storage[$key])){
            $value = $storage[$key];
        }


        if(is_null($key)){
            $value = $storage;
        }

        $this->closeFile();

        return $value;
    }

    /**
    * removes the $id from the cache/session
    *
    * @param string $key
    * @param bigint $id
    * @return boolean
    */      
    public function removeCache($key, $id)
    {

        $this->openFile();

        $this->lockFile();
        $storage = $this->readFile();

        if( !isset($storage[$key][$id])){
            $this->unlockFile();
            $this->closeFile();
            return false;
        }

        unset($storage[$key][$id]);
        $this->updateFile($storage);
        $this->unlockFile();
        $this->closeFile();
        return true;
    }

    /**
    * unset a session
    *
    * @param argument $keys
    * @return void
    */  
    public function truncateCache()
    {
        if(file_exists($this->filename)){
            unlink($this->filename);
        }
    }

    /**
    * Open a file in a giving mode
    *
    * @params string $mode 
    * @return void
    */
    private function openFile( $mode = "a+")
    {
        $this->handle = fopen($this->filename, $mode);

        if(!$this->handle){
            throw new exception('The File could not be opened!');
        }
    }

    /**
    * Close the file
    * @return void
    */
    private function closeFile()
    {
        fclose($this->handle);
        $this->handle = null;
    }

    /**
    * Update the file with array data
    *
    * @param array $data the array in that has the data
    * @return void
    */
    private function updateFile(array $data = array() )
    {
        $raw = json_encode($data);
        $this->truncateFile();
        fwrite($this->handle, $raw);
    }

    /**
    * Delete the file content;
    *
    * @return void
    */
    private function truncateFile( )
    {
        ftruncate($this->handle, 0);
        rewind($this->handle);
    }


    /**
    * Read the data from the opned file
    *
    * @params string $mode 
    * @return array of the data found in the file
    */
    private function readFile()
    {
        $length = filesize($this->filename);
        if($length > 0){
            rewind($this->handle);
            $raw = fread($this->handle, $length);
            return json_decode($raw, true);
        }

        return array();
    }


    /**
    * Lock the file
    *
    * @return void
    */  
    private function lockFile( $maxAttempts = 100, $lockType = LOCK_EX)
    {

        $i = 0;
        while($i <= $maxAttempts){

            // acquire an exclusive lock
            if( flock($this->handle, LOCK_EX) ){
                break;
            }

            ++$i;
        }
    }


    /**
    * Unlock the file
    *
    * @return void
    */  
    private function unlockFile()
    {

        fflush($this->handle);
        flock($this->handle, LOCK_UN);
    }

    /**
    * Remove add cache files
    *
    * @return void
    */  
    private function collectGarbage()
    {
        $mydir = dirname($this->filename);

        $dir = new DirectoryIterator( $mydir );
        $time = strtotime("now");
        foreach ($dir as $file) {

            if (  !$file->isDot()
                && $file->isFile()
                && ($time - $file->getATime()) > $this->maxTimeAllowed
                && $file->getFilename() != 'index.html'
            ) {
                unlink($file->getPathName());

            }

        }
    }

    function addSlashes($str)
    {
        return addslashes($str);
    }


}

我是这样使用的

<?php
    require 'autoloader.php';
    try {
        $cache = new \ICWS\CacheHandler('cache/879');

        $field = array('name' => 'Mike A', 'Address' => '123 S Main', 'phone' => '2152456245', 'ext' => 123);
        $cache->updateCache('NewKey', 'first', $field);

        $cache->updateCache('NewKey', 'second', $field);

        $field = array('Address' => '987 S Main', 'phone' => '000000000000', 'ext' => 5555);
        $cache->updateCache('NewKey', 'first', $field);

    $field = array('locations' => array('S' => 'South', 'N' => 'North', 'E' => 'East'));
    $cache->updateCache('NewKey', 'first', $field);

        echo '<pre>';
        print_r($cache->getCache());
        echo '</pre>';  


    } catch (exception $e){

        echo $e->getMessage();
    }

?>

我希望数组看起来像这样

Array
(
    [first] => stdClass Object
        (
            [name] => Mike A
            [Address] => 987 S Main
            [phone] => 000000000000
            [ext] => 5555
            [locations] => Array
                (
                    [S] => South
                    [N] => North
                    [E] => East
                )

        )

    [second] => stdClass Object
        (
            [name] => Mike A
            [Address] => 123 S Main
            [phone] => 2152456245
            [ext] => 123
        )

)

但是第一次执行后,我得到了空白数组。然后在我再次执行脚本后,我得到以下数组。

Array
(
    [NewKey] => Array
        (
            [first] => Array
                (
                    [locations] => Array
                        (
                            [S] => South
                            [N] => North
                            [E] => East
                        )

                )

        )

)

什么可能导致数据无法正确更新?

我使用 $_SESSION 编写了 updateCache(),这给了我正确的输出,但我需要在没有会话的情况下执行此操作

这是我的会话基础方法

public function updateCache($key, $id, array $field)
{

    $currentVal = (object) $field;

    //create a new if the $id does not exists in the cache
    if( isset($_SESSION[$key]) && array_key_exists($id, $_SESSION[$key]) ){

        $currentVal = (object) $_SESSION[$key][$id];

        foreach($field as $k => $v){
            $currentVal->$k = $v; //update existing key or add a new one
        }

    }

    $_SESSION[$key][$id] =  $currentVal;    
}

问题出在这里:

private function readFile()
{
    $length = filesize($this->filename);
    if($length > 0){
        rewind($this->handle);
        $raw = fread($this->handle, $length);
        return json_decode($raw, true);
    }

    return array();
}

filesize() 的文档指出:

Note: The results of this function are cached. See clearstatcache() for more details.

因为文件在您启动时是空的,所以它会缓存该信息并且您的分支将被完全跳过,直到下一个脚本执行。这应该可以解决它:

private function readFile()
{
    rewind($this->handle);
    $raw = stream_get_contents($this->handle);
    return json_decode($raw, true) ?: [];
}