PHP file creation/write within destructor

2020-02-12 08:18发布

问题:

When calling file_put_contents() within a destructor, it causes files to be written in SERVER_ROOT... (Yikes!) Workarounds?

tldr:

I want to cache an array, probably containing serialized class instances. I figured, for now, I would write a class that implements the cache using unserialize()/file_get_contents() and serialize()/file_put_contents() and then hide its functionality behind a more generic Cache class. (I don't know if my client's host will have shared memory or PEAR, etc)

<?php
    class CacheFile {
        private $filename;
        private $data;
        private $dirty = false;

        function __construct($filename) {
            $this->filename = $filename;
            $this->load();
        }

        function __destruct() {
            // Calling file_put_contents within a destructor causes files to be written in SERVER_ROOT...
            $this->flush();
        }

        private function load() {
            if(!file_exists($this->filename)) {
                $this->data = array();
            }
            else {
                $this->data = unserialize(file_get_contents($this->filename));
                // todo
            }
            $this->dirty = false;
        }

        private function persist() {
            file_put_contents($this->filename, serialize($this->data));
            $this->dirty = false;
        }

        public function get($key) {
            if(array_key_exists($key, $this->data)) {
                return $this->data[$key];
            }
            else {
                return false;
            }
        }

        public function set($key, $value) {
            if(!array_key_exists($key, $this->data)) {
                $dirty = true;
            }
            else if($this->data[$key] !== $value) {
                $dirty = true;
            }
            if($dirty) {
                $this->dirty = true;
                $this->data[$key] = $value;
            }
        }

        public function flush() {
            if($this->dirty) {
                $this->persist();
            }
        }
    }


    $cache = new CacheFile("cache");
    var_dump( $cache->get("item") );
    $cache->set("item", 42);
    //$cache->flush();
    var_dump( $cache->get("item") );
?>

See the call to flush() in the destructor? I really don't want to have the public flush() function because it's implementation-specific.

回答1:

I assume you have not specified a full qualified path in $this->filename.

On some PHP configurations, when destructors are called (in the scripts shutdown phase), the working directory can change. Relative paths resolve to another location then.

Compare with the related note in the PHP Manual:

Note:

Destructors called during the script shutdown have HTTP headers already sent. The working directory in the script shutdown phase can be different with some SAPIs (e.g. Apache).

If you make that path absolute, it will work as expected.

Edit: As you've update your code, this is a simple way to ensure you've got the absolute path:

$cache = new CacheFile(realpath("cache"));

Or much better within the constructor:

$this->filename = realpath($filename);


回答2:

You could create a file handle in load() which you can use in __destruct() or flush().



回答3:

Are you using a relative path as $filename? I would pass in an absolute path to where you want the file. If you want it to be relative to where your script is you could use something like:

$filename = dirname($_SERVER['SCRIPT_FILENAME']) . PATH_SEPARATOR . $filename;