1<?php
2
3/**
4 * Simple wrapper to create a temporary file and guarantee it will be deleted on
5 * object destruction. Used like a string to path:
6 *
7 *    $temp = new TempFile();
8 *    Filesystem::writeFile($temp, 'Hello World');
9 *    echo "Wrote data to path: ".$temp;
10 *
11 * Throws Filesystem exceptions for errors.
12 *
13 * @task  create    Creating a Temporary File
14 * @task  config    Configuration
15 * @task  internal  Internals
16 */
17final class TempFile extends Phobject {
18
19  private $dir;
20  private $file;
21  private $preserve;
22  private $destroyed = false;
23
24/* -(  Creating a Temporary File  )------------------------------------------ */
25
26
27  /**
28   * Create a new temporary file.
29   *
30   * @param string? Filename hint. This is useful if you intend to edit the
31   *                file with an interactive editor, so the user's editor shows
32   *                "commit-message" instead of "p3810hf-1z9b89bas".
33   * @param string? Root directory to hold the file. If omitted, the system
34   *                temporary directory (often "/tmp") will be used by default.
35   * @task create
36   */
37  public function __construct($filename = null, $root_directory = null) {
38    $this->dir = Filesystem::createTemporaryDirectory(
39      '',
40      0700,
41      $root_directory);
42    if ($filename === null) {
43      $this->file = tempnam($this->dir, getmypid().'-');
44    } else {
45      $this->file = $this->dir.'/'.$filename;
46    }
47
48    // If we fatal (e.g., call a method on NULL), destructors are not called.
49    // Make sure our destructor is invoked.
50    register_shutdown_function(array($this, '__destruct'));
51
52    Filesystem::writeFile($this, '');
53  }
54
55
56/* -(  Configuration  )------------------------------------------------------ */
57
58
59  /**
60   * Normally, the file is deleted when this object passes out of scope. You
61   * can set it to be preserved instead.
62   *
63   * @param bool True to preserve the file after object destruction.
64   * @return this
65   * @task config
66   */
67  public function setPreserveFile($preserve) {
68    $this->preserve = $preserve;
69    return $this;
70  }
71
72
73/* -(  Internals  )---------------------------------------------------------- */
74
75
76  /**
77   * Get the path to the temporary file. Normally you can just use the object
78   * in a string context.
79   *
80   * @return string Absolute path to the temporary file.
81   * @task internal
82   */
83  public function __toString() {
84    return $this->file;
85  }
86
87
88  /**
89   * When the object is destroyed, it destroys the temporary file. You can
90   * change this behavior with @{method:setPreserveFile}.
91   *
92   * @task internal
93   */
94  public function __destruct() {
95    if ($this->destroyed) {
96      return;
97    }
98
99    if ($this->preserve) {
100      return;
101    }
102
103    Filesystem::remove($this->dir);
104
105    // NOTE: tempnam() doesn't guarantee it will return a file inside the
106    // directory you passed to the function, so we make sure to nuke the file
107    // explicitly.
108
109    Filesystem::remove($this->file);
110
111    $this->file = null;
112    $this->dir = null;
113    $this->destroyed = true;
114  }
115
116}
117