Implement template PreCache-ing in the TemplateLoader.
[hoplite.git] / views / template_loader.php
1 <?php
2 // Hoplite
3 // Copyright (c) 2011 Blue Static
4 //
5 // This program is free software: you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 // more details.
13 //
14 // You should have received a copy of the GNU General Public License along with
15 // this program. If not, see <http://www.gnu.org/licenses/>.
16
17 namespace hoplite\views;
18
19 use \hoplite\base\Profiling;
20
21 require_once HOPLITE_ROOT . '/base/profiling.php';
22 require_once HOPLITE_ROOT . '/views/template.php';
23
24 /*!
25 The TemplateLoader manages the reading of templates from the file system.
26
27 It can also work with a CacheBackend to store the compiled template data to
28 increase performance.
29
30 Internally it also maintains a cache that it will consult after a template is
31 loaded for the first time.
32 */
33 class TemplateLoader
34 {
35 /*! @var TemplateLoader Singleton instance */
36 static private $instance = NULL;
37
38 /*! @var string Base path for loading the template file. Use %s to indicate
39 where the name (passed to the constructor) should be
40 substituted.
41 */
42 protected $template_path = '%s.tpl';
43
44 /*! @var CacheBackend A cache for compiled template data. */
45 protected $cache_backend = NULL;
46
47 /*! @var array An array of Template objects, keyed by the template name. */
48 protected $cache = array();
49
50 /*! @var array Array of template usage counts, keyed by name. Only used
51 when profiling.
52 */
53 protected $usage = array();
54
55 /*! Gets the singleton instance. */
56 static public function GetInstance()
57 {
58 if (!self::$instance) {
59 $class = get_called_class();
60 self::$instance = new $class();
61 }
62 return self::$instance;
63 }
64 /*! Sets the singleton instance. */
65 static public function SetInstance($instance) { self::$instance = $instance; }
66
67 /*! Accessors */
68 public function set_template_path($path) { $this->template_path = $path; }
69 public function template_path() { return $this->template_path; }
70
71 public function set_cache_backend(CacheBackend $backend) { $this->cache_backend = $backend; }
72 public function cache_backend() { return $this->cache_backend; }
73
74 /*!
75 Loads a template from a file, creates a Template object, and returns a copy
76 of that object.
77
78 @param string Template name, with wich the template plath is formatted.
79
80 @return Template Clone of the cached template.
81 */
82 public function Load($name)
83 {
84 if (Profiling::IsProfilingEnabled() && !isset($this->usage[$name]))
85 $this->usage[$name] = 0;
86
87 $tpl_path = $this->_TemplatePath($name);
88 if (!file_exists($tpl_path))
89 throw new TemplateException("Template $name does not exist at path $tpl_path");
90
91 // First check the memory cache.
92 if (isset($this->cache[$name]))
93 return clone $this->cache[$name];
94
95 // Then check if the cache backend has it.
96 $template = $this->_QueryCache($name, $tpl_path);
97 if ($template) {
98 $this->cache[$name] = $template;
99 return clone $template;
100 }
101
102 // Finally, parse and cache the template.
103 $template = $this->_Load($name);
104 $this->cache[$name] = $template;
105 return clone $template;
106 }
107
108 /*!
109 Warms up the template cache with a set of templates. If the cache backend
110 supports multiple simultaneous fetches, this can greatly improve performance.
111 */
112 public function PreCache(Array $templates)
113 {
114 if (!$this->cache_backend)
115 return;
116
117 $fetch_templates = array();
118 foreach ($templates AS $name) {
119 // Do not re-cache templates that have already been cached.
120 if (isset($this->cache[$name]))
121 continue;
122
123 $tpl_path = $this->_TemplatePath($name);
124 $fetch_templates[$name] = filemtime($tpl_path);
125 }
126
127 $profile = Profiling::IsProfilingEnabled();
128
129 $cached_templates = $this->cache_backend->GetMultipleTemplates($fetch_templates);
130 foreach ($cached_templates AS $name => $data) {
131 $this->cache[$name] = Template::NewWithCompiledData($name, $data);
132 if ($profile)
133 $this->usage[$name] = 0;
134 }
135 }
136
137 /*! Convenience function for loading templates. */
138 static public function Fetch($name)
139 {
140 return self::GetInstance()->Load($name);
141 }
142
143 /*! Marks a template as having been used. */
144 public function MarkTemplateRendered($name)
145 {
146 if (!isset($this->usage[$name]))
147 throw new \InvalidArgumentException("Template $name has not been loaded through this instance");
148
149 $this->usage[$name]++;
150 }
151
152 /*!
153 Queries the optional CacheBackend for a template.
154
155 @param string Template name
156 @param string Template path
157
158 @return Template|NULL
159 */
160 protected function _QueryCache($name, $tpl_path)
161 {
162 if (!$this->cache_backend)
163 return NULL;
164
165 $data = $this->cache_backend->GetTemplateDataForName($name, filemtime($tpl_path));
166 if (!$data)
167 return NULL;
168
169 return Template::NewWithCompiledData($name, $data);
170 }
171
172 /*!
173 Loads a raw template from the file system and compiles it. If the optional
174 CacheBackend is present, it will cache the compiled data.
175
176 @param string Template name.
177
178 @return Template
179 */
180 protected function _Load($name)
181 {
182 $tpl_path = $this->_TemplatePath($name);
183
184 $data = @file_get_contents($tpl_path);
185 if ($data === FALSE)
186 throw new TemplateLoaderException('Could not load template ' . $name);
187
188 $template = Template::NewWithData($name, $data);
189
190 if ($this->cache_backend) {
191 $this->cache_backend->StoreCompiledTemplate(
192 $name, filemtime($tpl_path), $template->template());
193 }
194
195 return $template;
196 }
197
198 /*! Returns the template path for a given template name. */
199 protected function _TemplatePath($name)
200 {
201 return sprintf($this->template_path, $name);
202 }
203 }
204
205 class TemplateLoaderException extends \Exception {}