Creating / writing PHP files in destructor

advertisements

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.


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