Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP file creation/write within destructor

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.

like image 979
Brandon Lockaby Avatar asked Jul 08 '11 18:07

Brandon Lockaby


3 Answers

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);
like image 173
hakre Avatar answered Oct 20 '22 00:10

hakre


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

like image 44
ComFreek Avatar answered Oct 20 '22 01:10

ComFreek


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;
like image 1
Justin Avatar answered Oct 20 '22 02:10

Justin