Completed
Push — master ( 02b122...2a8558 )
by James Ekow Abaka
02:43
created

TemplateEngine::testNoEngineTemplateFile()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 2
dl 0
loc 15
ccs 11
cts 12
cp 0.9167
crap 4.0092
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * View Templating System
5
 * Copyright (c) 2008-2015 James Ekow Abaka Ainooson
6
 * 
7
 * Permission is hereby granted, free of charge, to any person obtaining
8
 * a copy of this software and associated documentation files (the
9
 * "Software"), to deal in the Software without restriction, including
10
 * without limitation the rights to use, copy, modify, merge, publish,
11
 * distribute, sublicense, and/or sell copies of the Software, and to
12
 * permit persons to whom the Software is furnished to do so, subject to
13
 * the following conditions:
14
 * 
15
 * The above copyright notice and this permission notice shall be
16
 * included in all copies or substantial portions of the Software.
17
 * 
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
25
 */
26
27
namespace ntentan\honam;
28
29
use ntentan\honam\exceptions\TemplateResolutionException;
30
31
/**
32
 * The TemplateEngine class does the work of resolving templates, loading template files,
33
 * loading template engines and rendering templates. The `ntentan/views` package takes a reference to a 
34
 * template and tries to find a specific template file to be used for the rendering. 
35
 * `ntentan/views` does not expect to find all view templates in a single directory.
36
 * It uses a heirachy of directories which it searches for the template to render.
37
 * Templates in directories closer to the beginning of the array have a higher
38
 * priority over those closer to the end of the array.
39
 * 
40
 */
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();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \ntentan\honam\HelpersLoader() of type object<ntentan\honam\HelpersLoader> is incompatible with the declared type object<ntentan\honam\tem..._engines\HelpersLoader> of property $helpersLoader.

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..

Loading history...
143
        }
144 32
        return $this->helpersLoader;
145
    }
146
147 33
    private static function testTemplateFile($testTemplate, $paths, $extension) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
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) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
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) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
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++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable $extension does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
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)
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
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);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
297
}
298