1 | <?php |
||
41 | abstract class TemplateEngine { |
||
42 | |||
43 | /** |
||
44 | * An array of loaded template engine instances. |
||
45 | * @var array<\ntentan\honam\TemplateEngine> |
||
46 | */ |
||
47 | private static $loadedInstances; |
||
48 | |||
49 | /** |
||
50 | * The path to the template file to be used when generate is called. |
||
51 | * @var string |
||
52 | */ |
||
53 | protected $template; |
||
54 | |||
55 | /** |
||
56 | * An instance of the helpers loader used for loading the helpers. |
||
57 | * @var \ntentan\honam\template_engines\HelpersLoader |
||
58 | */ |
||
59 | private $helpersLoader; |
||
60 | |||
61 | /** |
||
62 | * The array which holds the template path heirachy. |
||
63 | * |
||
64 | * @var array<string> |
||
65 | */ |
||
66 | private static $path = array(); |
||
67 | private static $tempDirectory = '.'; |
||
68 | |||
69 | /** |
||
70 | * Append a directory to the end of the template path heirachy. |
||
71 | * |
||
72 | * @param string $path |
||
73 | */ |
||
74 | 27 | public static function appendPath($path) { |
|
75 | 27 | self::$path[] = $path; |
|
76 | 27 | } |
|
77 | |||
78 | /** |
||
79 | * Prepend a directory to the beginning of the template path heirachy. |
||
80 | * |
||
81 | * @param string $path |
||
82 | */ |
||
83 | 2 | public static function prependPath($path) { |
|
84 | 2 | array_unshift(self::$path, $path); |
|
85 | 2 | } |
|
86 | |||
87 | /** |
||
88 | * Return the template hierachy as an array. |
||
89 | * |
||
90 | * @return array<string> |
||
91 | */ |
||
92 | 34 | public static function getPath() { |
|
93 | 34 | return self::$path; |
|
94 | } |
||
95 | |||
96 | 8 | private static function getEngineClass($engine) { |
|
97 | 8 | return "ntentan\\honam\\template_engines\\" . \ntentan\utils\Text::ucamelize($engine); |
|
98 | } |
||
99 | |||
100 | 33 | private static function getEngineInstance($engine) { |
|
101 | 33 | if (!isset(self::$loadedInstances[$engine])) { |
|
102 | 8 | $engineClass = self::getEngineClass($engine); |
|
103 | 8 | if (class_exists($engineClass)) { |
|
104 | 7 | $engineInstance = new $engineClass(); |
|
105 | } else { |
||
106 | 1 | throw new \ntentan\honam\exceptions\TemplateEngineNotFoundException("Could not load template engine class [$engineClass]"); |
|
107 | } |
||
108 | 7 | self::$loadedInstances[$engine] = $engineInstance; |
|
109 | } |
||
110 | |||
111 | 32 | return self::$loadedInstances[$engine]; |
|
112 | } |
||
113 | |||
114 | /** |
||
115 | * Loads a template engine instance to be used for rendering a given |
||
116 | * template file. It determines the engine to be loaded by using the filename. |
||
117 | * The extension of the file determines the engine to be loaded. |
||
118 | * |
||
119 | * @param string $template The template file |
||
120 | * @return \ntentan\honam\TemplateEngine |
||
121 | * @throws \ntentan\honam\exceptions\TemplateEngineNotFoundException |
||
122 | */ |
||
123 | 33 | private static function getEngineInstanceWithTemplate($template) { |
|
124 | 33 | $engine = pathinfo($template, PATHINFO_EXTENSION); |
|
125 | 33 | $engineInstance = self::getEngineInstance($engine); |
|
126 | 32 | $engineInstance->template = $template; |
|
127 | 32 | return $engineInstance; |
|
128 | } |
||
129 | |||
130 | public static function canRender($file) { |
||
131 | return class_exists(self::getEngineClass(pathinfo($file, PATHINFO_EXTENSION))); |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * Returns the single instance of the helpers loader that is currently |
||
136 | * stored in this class. |
||
137 | * |
||
138 | * @return \ntentan\honam\template_engines\HelpersLoader |
||
139 | */ |
||
140 | 32 | public function getHelpersLoader() { |
|
141 | 32 | if ($this->helpersLoader == null) { |
|
142 | 7 | $this->helpersLoader = new HelpersLoader(); |
|
|
|||
143 | } |
||
144 | 32 | return $this->helpersLoader; |
|
145 | } |
||
146 | |||
147 | 33 | private static function testTemplateFile($testTemplate, $paths, $extension) { |
|
148 | 33 | $templateFile = ''; |
|
149 | 33 | foreach ($paths as $path) { |
|
150 | 33 | $newTemplateFile = "$path/$testTemplate.$extension"; |
|
151 | 33 | if (file_exists($newTemplateFile)) { |
|
152 | 32 | $templateFile = $newTemplateFile; |
|
153 | 33 | break; |
|
154 | } |
||
155 | } |
||
156 | 33 | return $templateFile; |
|
157 | } |
||
158 | |||
159 | 5 | private static function testNoEngineTemplateFile($testTemplate, $paths) { |
|
160 | 5 | $templateFile = ''; |
|
161 | 5 | foreach ($paths as $path) { |
|
162 | 5 | $newTemplateFile = "$path/$testTemplate.*"; |
|
163 | 5 | $files = glob($newTemplateFile); |
|
164 | 5 | if (count($files) == 1) { |
|
165 | 5 | $templateFile = $files[0]; |
|
166 | 5 | break; |
|
167 | 5 | } else if (count($files) > 1) { |
|
168 | $templates = implode(", ", $files); |
||
169 | 5 | throw new TemplateResolutionException("Multiple templates ($templates) resolved for request"); |
|
170 | } |
||
171 | } |
||
172 | 5 | return $templateFile; |
|
173 | } |
||
174 | |||
175 | 34 | private static function searchTemplateDirectory($template, $ignoreEngine = false) { |
|
176 | 34 | $templateFile = ''; |
|
177 | 34 | $paths = self::getPath(); |
|
178 | |||
179 | // Split the filename on the dots. The first part before the first dot |
||
180 | // would be used to implement the file breakdown. The other parts are |
||
181 | // fused together again and appended during the evaluation of the |
||
182 | // breakdown. |
||
183 | |||
184 | 34 | if ($ignoreEngine) { |
|
185 | 5 | $breakDown = explode('_', $template); |
|
186 | } else { |
||
187 | 33 | $splitOnDots = explode('.', $template); |
|
188 | 33 | $breakDown = explode('_', array_shift($splitOnDots)); |
|
189 | 33 | $extension = implode(".", $splitOnDots); |
|
190 | } |
||
191 | |||
192 | 34 | for ($i = 0; $i < count($breakDown); $i++) { |
|
193 | 34 | $testTemplate = implode("_", array_slice($breakDown, $i, count($breakDown) - $i)); |
|
194 | |||
195 | 34 | if ($ignoreEngine) { |
|
196 | 5 | $templateFile = self::testNoEngineTemplateFile($testTemplate, $paths); |
|
197 | } else { |
||
198 | 33 | $templateFile = self::testTemplateFile($testTemplate, $paths, $extension); |
|
199 | } |
||
200 | |||
201 | 34 | if ($templateFile != '') { |
|
202 | 33 | break; |
|
203 | } |
||
204 | } |
||
205 | |||
206 | 34 | return $templateFile; |
|
207 | } |
||
208 | |||
209 | /** |
||
210 | * Resolve a template file by running through all the directories in the |
||
211 | * template heirachy till a file that matches the template is found. |
||
212 | * |
||
213 | * @param string $template |
||
214 | * @return string |
||
215 | * @throws \ntentan\honam\exceptions\FileNotFoundException |
||
216 | */ |
||
217 | 34 | protected static function resolveTemplateFile($template) { |
|
218 | 34 | if ($template == '') { |
|
219 | throw new TemplateResolutionException("Empty template file requested"); |
||
220 | } |
||
221 | |||
222 | 34 | $templateFile = self::searchTemplateDirectory($template, pathinfo($template, PATHINFO_EXTENSION) === ''); |
|
223 | |||
224 | 34 | if ($templateFile == null) { |
|
225 | 1 | $pathString = "[" . implode('; ', self::getPath()) . "]"; |
|
226 | 1 | throw new TemplateResolutionException( |
|
227 | 1 | "Could not find a suitable template file for the current request '{$template}'. Current template path $pathString" |
|
228 | ); |
||
229 | } |
||
230 | |||
231 | 33 | return $templateFile; |
|
232 | } |
||
233 | |||
234 | /** |
||
235 | * Renders a given template reference with associated template data. This render |
||
236 | * function combs through the template directory heirachy to find a template |
||
237 | * file which matches the given template reference and uses it for the purpose |
||
238 | * of rendering the view. |
||
239 | * |
||
240 | * @param string $template The template reference file. |
||
241 | * @param array $templateData The data to be passed to the template. |
||
242 | * @return string |
||
243 | * @throws \ntentan\honam\exceptions\FileNotFoundException |
||
244 | */ |
||
245 | 34 | public static function render($template, $templateData) { |
|
246 | 34 | return self::getEngineInstanceWithTemplate(self::resolveTemplateFile($template))->generate($templateData); |
|
247 | } |
||
248 | |||
249 | /** |
||
250 | * Renders a given template file with the associated template data. This |
||
251 | * render function loads the appropriate engine and passes the file to it |
||
252 | * for rendering. |
||
253 | * |
||
254 | * @param string $templatePath A path to the template file |
||
255 | * @param string $templateData An array of the template data to be rendered |
||
256 | * @return string |
||
257 | */ |
||
258 | public static function renderFile($templatePath, $templateData) { |
||
259 | return self::getEngineInstanceWithTemplate($templatePath)->generate($templateData); |
||
260 | } |
||
261 | |||
262 | public static function renderString($templateString, $engine, $templateData) { |
||
263 | return self::getEngineInstance($engine)->generateFromString($templateString, $templateData); |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * Resets the path heirachy. |
||
268 | */ |
||
269 | 2 | public static function reset() { |
|
270 | 2 | self::$path = array(); |
|
271 | 2 | self::$loadedInstances = array(); |
|
272 | 2 | } |
|
273 | |||
274 | /* public function generateFromString($string, $data) |
||
275 | { |
||
276 | //$this->template = "data://text/plain," . urlencode($string); |
||
277 | file_put_contents("php://temp", $string); |
||
278 | $this->template = "php://temp"; |
||
279 | var_dump(file_get_contents("php://temp")); |
||
280 | return $this->generate($data); |
||
281 | } */ |
||
282 | |||
283 | 2 | public static function getTempDirectory() { |
|
284 | 2 | return self::$tempDirectory; |
|
285 | } |
||
286 | |||
287 | /** |
||
288 | * Passes the data to be rendered to the template engine instance. |
||
289 | */ |
||
290 | abstract protected function generate($data); |
||
291 | |||
292 | /** |
||
293 | * Passes a template string and data to be rendered to the template engine |
||
294 | * instance. |
||
295 | */ |
||
296 | abstract protected function generateFromString($string, $data); |
||
297 | } |
||
298 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..