Completed
Pull Request — 1.2 (#12)
by David
07:52
created

FileBasedRenderer::findFile()   C

Complexity

Conditions 14
Paths 56

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 54
rs 6.2666
c 0
b 0
f 0
cc 14
nc 56
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * Copyright (c) 2013 David Negrier
4
 *
5
 * See the file LICENSE.txt for copying permission.
6
 */
7
8
namespace Mouf\Html\Renderer;
9
10
use Psr\Container\ContainerInterface;
11
use Mouf\MoufException;
12
use Mouf\Html\Renderer\Twig\MoufTwigExtension;
13
use Mouf\MoufManager;
14
use Psr\SimpleCache\CacheInterface;
15
16
/**
17
 * This class is a renderer that renders objects using a directory containing template files.
18
 * Each file should be a Twig file or PHP file named after the PHP class full name (respecting the PSR-0 notation).
19
 *
20
 * For instance, the class Mouf\Menu\Item would be rendered by the file Mouf\Menu\Item.twig or Mouf\Menu\Item.php
21
 *
22
 * If a context is passed, it will be appended after a double underscore to the file name.
23
 * If the file does not exist, we default to the base class.
24
 *
25
 * For instance, the class Mouf\Menu\Item with context "primary" would be rendered by the file Mouf\Menu\Item__primary.twig
26
 *
27
 * If the template for the class is not found, a test through the parents of the class is performed.
28
 *
29
 * If you are using PHP template files, the properties of the object are accessible using local vars.
30
 * For instance, if your object has a $a property, the property can be accessed using the $a variable.
31
 * Any property (even private properties can be accessed).
32
 * The object is accessible using the $object variable. Private methods or properties of the $object cannot
33
 * be accessed.
34
 *
35
 *
36
 * @author David Négrier <[email protected]>
37
 */
38
class FileBasedRenderer implements ChainableRendererInterface
39
{
40
41
    private $directory;
42
43
    private $cacheService;
44
45
    private $twig;
46
47
	private $tmpFileName;
48
	
49
	private $debugMode;
50
	
51
	private $debugStr;
52
	
53
	/**
54
	 * 
55
	 * @param string $directory The directory of the templates, relative to the project root. Does not start and does not finish with a /
56
	 * @param CacheInterface $cacheService This service is used to speed up the mapping between the object and the template.
57
	 * @param string $type The type of the renderer. Should be one of "custom", "template" or "package". Defaults to "custom" (see ChainableRendererInterface for more details)
0 ignored issues
show
Bug introduced by
There is no parameter named $type. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
58
	 * @param number $priority The priority of the renderer (within its type)
0 ignored issues
show
Bug introduced by
There is no parameter named $priority. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
59
	 */
60
    public function __construct(string $directory, CacheInterface $cacheService, ContainerInterface $container, Twig $twig = null)
61
    {
62
        $this->directory = $directory;
63
        $this->cacheService = $cacheService;
64
65
        $loader = new \Twig_Loader_Filesystem($this->directory);
66
        if (function_exists('posix_geteuid')) {
67
            $posixGetuid = posix_geteuid();
68
        } else {
69
            $posixGetuid = '';
70
        }
71
        $cacheFilesystem = new \Twig_Cache_Filesystem(rtrim(sys_get_temp_dir(),'/\\').'/mouftwigtemplatemain_'.$posixGetuid.'_'.$this->directory);
72
        if ($twig === null) {
73
74
            $this->twig = new \Twig_Environment($loader, array(
75
                // The cache directory is in the temporary directory and reproduces the path to the directory (to avoid cache conflict between apps).
76
                'cache' => $cacheFilesystem,
77
                'auto_reload' => true,
78
                'debug' => true
79
            ));
80
            $this->twig->addExtension(new MoufTwigExtension($container));
0 ignored issues
show
Compatibility introduced by
$container of type object<Psr\Container\ContainerInterface> is not a sub-type of object<Interop\Container\ContainerInterface>. It seems like you assume a child interface of the interface Psr\Container\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
81
            $this->twig->addExtension(new \Twig_Extension_Debug());
82
        } else {
83
            // We need to modify the loader of the twig environment.
84
            // Let's clone it.
85
            $this->twig = clone $twig;
86
            $this->twig->setLoader($loader);
87
            $this->twig->setCache($cacheFilesystem);
88
            $this->twig->setCompiler(new \Twig_Compiler($this->twig));
89
        }
90
	}
91
92
    /**
93
     * (non-PHPdoc)
94
     * @see \Mouf\Html\Renderer\RendererInterface::canRender()
95
     */
96
    public function canRender($object, $context = null)
97
    {
98
        $fileName = $this->getTemplateFileName($object, $context);
99
100
        if ($fileName) {
101
            return ChainableRendererInterface::CAN_RENDER_CLASS;
102
        } else {
103
            return ChainableRendererInterface::CANNOT_RENDER;
104
        }
105
    }
106
107
    /**
108
     * (non-PHPdoc)
109
     * @see \Mouf\Html\Renderer\ChainableRendererInterface::debugCanRender()
110
     */
111
    public function debugCanRender($object, $context = null)
112
    {
113
        $this->debugMode = true;
114
        $this->debugStr = "Testing renderer for directory '".$this->directory."'\n";
115
116
        $this->canRender($object, $context);
117
118
        $this->debugMode = false;
119
120
        return $this->debugStr;
121
    }
122
123
    /**
124
     * (non-PHPdoc)
125
     * @see \Mouf\Html\Renderer\RendererInterface::render()
126
     */
127
    public function render($object, $context = null)
128
    {
129
        $fileName = $this->getTemplateFileName($object, $context);
130
131
        if ($fileName != false) {
132
            if (method_exists($object, 'getPrivateProperties')) {
133
                $array = $object->getPrivateProperties();
134
            } else {
135
                $array = get_object_vars($object);
136
            }
137
            if ($fileName['type'] == 'twig') {
138
                if (!isset($array['this'])) {
139
                    $array['this'] = $object;
140
                }
141
                echo $this->twig->render($fileName['fileName'], $array);
142
            } else {
143
                // Let's store the filename into the object ($this) in order to avoid name conflict between
144
                // the variables.
145
                $this->tmpFileName = $fileName;
146
147
                extract($array);
148
                // Let's create a local variable
149
                /*foreach ($array as $var__tplt=>$value__tplt) {
150
                    $$var__tplt = $value__tplt;
151
                }*/
152
                include $this->directory.'/'.$this->tmpFileName['fileName'];
153
            }
154
        } else {
155
            throw new NoTemplateFoundException("Cannot render object of class ".get_class($object).". No template found.");
156
        }
157
    }
158
159
    /**
160
     * Returns the filename of the template or false if no file found.
161
     *
162
     * @param  object      $object
163
     * @param  string      $context
164
     * @return array<string,string>|null An array with 2 keys: "filename" and "type", or null if nothing found
165
     */
166
    private function getTemplateFileName($object, ?string $context = null): ?array
167
    {
168
        $fullClassName = get_class($object);
169
170
        // Optimisation: let's see if we already performed the file_exists checks.
171
        $cacheKey = md5("FileBasedRenderer_".$this->directory.'/'.$fullClassName.'/'.$context);
172
173
        $cachedValue = $this->cacheService->get($cacheKey);
174
        if ($cachedValue !== null && !$this->debugMode) {
175
            return $cachedValue;
176
        }
177
178
        $fileName = $this->findFile($fullClassName, $context);
179
        $parentClass = $fullClassName;
180
        // If no file is found, let's go through the parents of the object.
181
        while (true) {
182
            if ($fileName != false) {
183
                break;
184
            }
185
            $parentClass = get_parent_class($parentClass);
186
            if ($parentClass == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $parentClass of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
187
                break;
188
            }
189
            $fileName = $this->findFile($parentClass, $context);
190
        }
191
192
        // Still no objects? Let's browse the interfaces.
193
        if ($fileName == false) {
194
            $interfaces = class_implements($fullClassName);
195
            foreach ($interfaces as $interface) {
196
                $fileName = $this->findFile($interface, $context);
197
                if ($fileName != false) {
198
                    break;
199
                }
200
            }
201
        }
202
203
        $this->cacheService->set($cacheKey, $fileName);
204
205
        return $fileName;
206
    }
207
208
    /**
209
     * @param string $className
210
     * @param null|string $context
211
     * @return array<string,string>|null An array with 2 keys: "filename" and "type", or null if nothing found
212
     */
213
    private function findFile(string $className, ?string $context): ?array
214
    {
215
        $baseFileName = str_replace('\\', '/', $className);
216
        if ($context) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $context of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
217
            if (file_exists($this->directory.'/'.$baseFileName.'__'.$context.'.twig')) {
218
                if ($this->debugMode) {
219
                    $this->debugStr .= "  Found file: ".$this->directory.'/'.$baseFileName.'__'.$context.'.twig'."\n";
220
                }
221
222
                return array("fileName" => $baseFileName.'__'.$context.'.twig',
223
                    "type" => "twig", );
224
            }
225
            if ($this->debugMode) {
226
                $this->debugStr .= "  Tested file: ".$this->directory.'/'.$baseFileName.'__'.$context.'.twig'."\n";
227
            }
228
229
            if (file_exists($this->directory.'/'.$baseFileName.'__'.$context.'.php')) {
230
                if ($this->debugMode) {
231
                    $this->debugStr .= "  Found file: ".$this->directory.'/'.$baseFileName.'__'.$context.'.php'."\n";
232
                }
233
234
                return array("fileName" => $baseFileName.'__'.$context.'.php',
235
                    "type" => "php", );
236
            }
237
            if ($this->debugMode) {
238
                $this->debugStr .= "  Tested file: ".$this->directory.'/'.$baseFileName.'__'.$context.'.php'."\n";
239
            }
240
        }
241
        if (file_exists($this->directory.'/'.$baseFileName.'.twig')) {
242
            if ($this->debugMode) {
243
                $this->debugStr .= "  Found file: ".$this->directory.'/'.$baseFileName.'.twig'."\n";
244
            }
245
246
            return array("fileName" => $baseFileName.'.twig',
247
                    "type" => "twig", );
248
        }
249
        if ($this->debugMode) {
250
            $this->debugStr .= "  Tested file: ".$this->directory.'/'.$baseFileName.'.twig'."\n";
251
        }
252
253
        if (file_exists($this->directory.'/'.$baseFileName.'.php')) {
254
            if ($this->debugMode) {
255
                $this->debugStr .= "  Found file: ".$this->directory.'/'.$baseFileName.'.php'."\n";
256
            }
257
258
            return array("fileName" => $baseFileName.'.php',
259
                    "type" => "php", );
260
        }
261
        if ($this->debugMode) {
262
            $this->debugStr .= "  Tested file: ".$this->directory.'/'.$baseFileName.'.php'."\n";
263
        }
264
265
        return null;
266
    }
267
}
268