Completed
Branch dev (374206)
by James Ekow Abaka
06:04
created

TemplateFileResolver::resolveTemplateFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
namespace ntentan\honam;
4
5
use ntentan\honam\exceptions\TemplateResolutionException;
6
7
class TemplateFileResolver
8
{
9
    /**
10
     * The array which holds the template path heirachy.
11
     *
12
     * @var array<string>
13
     */
14
    private $pathHierarchy = array();
15
16
    /**
17
     * Append a directory to the end of the template path heirachy.
18
     *
19
     * @param string $path
20
     */
21
    public function appendToPathHierarchy($path)
22
    {
23
        $this->pathHierarchy[] = $path;
24
    }
25
26
    /**
27
     * Prepend a directory to the beginning of the template path heirachy.
28
     *
29
     * @param string $path
30
     */
31
    public function prependToPathHierarchy($path)
32
    {
33
        array_unshift($this->pathHierarchy, $path);
34
    }
35
36
    private 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...
37
    {
38
        $templateFile = '';
39
        foreach ($paths as $path) {
40
            $newTemplateFile = "$path/$testTemplate.$extension";
41
            if (file_exists($newTemplateFile)) {
42
                $templateFile = $newTemplateFile;
43
                break;
44
            }
45
        }
46
        return $templateFile;
47
    }
48
49
    private function testNoEngineTemplateFile($testTemplate, $paths)
50
    {
51
        $templateFile = '';
52
        foreach ($paths as $path) {
53
            $newTemplateFile = "$testTemplate.*";
54
            $files = array_filter(
55
                scandir($path),
56
                function($file) use($newTemplateFile) {
57
                    return fnmatch($newTemplateFile, $file);
58
                });
59
            if (count($files) == 1) {
60
                $templateFile = $path . "/" . reset($files);
61
                break;
62
            } else if (count($files) > 1) {
63
                $templates = implode(", ", $files);
64
                throw new TemplateResolutionException("Multiple templates were resolved for the request '$testTemplate'. Please ensure that only one supported template type of the name '$testTemplate' exists in the path '$path'. Files found: $templates");
65
            }
66
        }
67
        return $templateFile;
68
    }
69
70
    private 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...
71
    {
72
        $templateFile = '';
73
74
        // Split the filename on the dots. The first part before the first dot
75
        // would be used to implement the file breakdown. The other parts are
76
        // fused together again and appended during the evaluation of the
77
        // breakdown.
78
79
        if ($ignoreEngine) {
80
            $breakDown = explode('_', $template);
81
        } else {
82
            $splitOnDots = explode('.', $template);
83
            $breakDown = explode('_', array_shift($splitOnDots));
84
            $extension = implode(".", $splitOnDots);
85
        }
86
87
        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...
88
            $testTemplate = implode("_", array_slice($breakDown, $i, count($breakDown) - $i));
89
90
            if ($ignoreEngine) {
91
                $templateFile = $this->testNoEngineTemplateFile($testTemplate, $this->pathHierarchy);
92
            } else {
93
                $templateFile = $this->testTemplateFile($testTemplate, $this->pathHierarchy, $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...
94
            }
95
96
            if ($templateFile != '') {
97
                break;
98
            }
99
        }
100
101
        return $templateFile;
102
    }
103
104
    /**
105
     * Resolve a template file by running through all the directories in the
106
     * template heirachy till a file that matches the template is found.
107
     *
108
     * @param string $template
109
     * @return string
110
     * @throws \ntentan\honam\exceptions\FileNotFoundException
111
     */
112
    public function resolveTemplateFile($template)
113
    {
114
        if ($template == '') {
115
            throw new TemplateResolutionException("Empty template file requested");
116
        }
117
118
        $templateFile = $this->searchTemplateDirectory($template, pathinfo($template, PATHINFO_EXTENSION) === '');
119
120
        if ($templateFile == null) {
121
            $pathString = "[" . implode('; ', $this->pathHierarchy) . "]";
122
            throw new TemplateResolutionException(
123
                "Could not find a suitable template file for the current request '{$template}'. Current template path $pathString"
124
            );
125
        }
126
127
        return $templateFile;
128
    }
129
}
130