Completed
Push — master ( f4fae9...d97ad7 )
by Mihail
09:18
created

View   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 269
Duplicated Lines 7.06 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 22
Bugs 8 Features 0
Metric Value
wmc 45
c 22
b 8
f 0
lcom 1
cbo 13
dl 19
loc 269
rs 8.3673

8 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 22 4
B render() 0 39 5
C findViewer() 7 58 12
A show() 0 4 1
C renderSandbox() 6 31 7
A buildGlobal() 0 8 2
D showCodeLink() 6 29 10
A showPlainCode() 0 13 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like View often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use View, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Ffcms\Core\Arch;
4
5
use Ffcms\Core\App;
6
use Ffcms\Core\Exception\NativeException;
7
use Ffcms\Core\Exception\SyntaxException;
8
use Ffcms\Core\Helper\FileSystem\Directory;
9
use Ffcms\Core\Helper\FileSystem\File;
10
use Ffcms\Core\Helper\FileSystem\Normalize;
11
use Ffcms\Core\Helper\Type\Obj;
12
use Ffcms\Core\Helper\Type\Str;
13
use Ffcms\Core\Template\Variables;
14
use Ffcms\Core\Traits\DynamicGlobal;
15
16
class View
17
{
18
    use DynamicGlobal;
19
20
    /**
21
     * Global path for current environment theme
22
     * @var string
23
     */
24
    public $themePath;
25
26
    private $path;
27
    private $params;
28
29
    private $sourcePath;
30
31
    /**
32
     * Lets construct viewer
33
     * @throws NativeException
34
     */
35
    public function __construct()
36
    {
37
        // get theme config and build full path
38
        $themeConfig = App::$Properties->get('theme');
39
        $this->themePath = root . DIRECTORY_SEPARATOR . 'Apps' . DIRECTORY_SEPARATOR . 'View' . DIRECTORY_SEPARATOR . env_name;
40
        if (isset($themeConfig[env_name]) && Str::length($themeConfig[env_name]) > 0) {
41
            $this->themePath .=  DIRECTORY_SEPARATOR . $themeConfig[env_name];
42
        } else {
43
            $this->themePath .= DIRECTORY_SEPARATOR . 'default';
44
        }
45
46
        // check if theme is available
47
        if (!Directory::exist($this->themePath)) {
48
            throw new NativeException('Apps theme is not founded: ' . Str::replace(root, null, $this->themePath));
49
        }
50
51
        // get input args and build class properties
52
        $args = func_get_args();
53
        $this->path = array_shift($args);
54
        $this->params = array_shift($args);
55
        $this->sourcePath = array_shift($args);
56
    }
57
58
    /**
59
     * Render viewer based on construct or passed params. render($path, $params, $sourcePath)
60
     * @return string
61
     * @throws NativeException
62
     * @throws SyntaxException
63
     */
64
    public function render()
65
    {
66
        // get call arguments
67
        $arguments = func_get_args();
68
69
        // get params from constructor
70
        $path = $this->path;
71
        $params = $this->params;
72
        $source = $this->sourcePath;
73
74
        // if path is not defined - try to find it in arguments
75
        if ($path === null) {
76
            $path = array_shift($arguments);
77
        }
78
79
        // if arguments is not define - try to find in arguments
80
        if ($params === null) {
81
            $params = array_shift($arguments);
82
        }
83
84
        // if directory of caller is not defiend - lets find in argument
85
        if ($source === null) {
86
            $source = array_shift($arguments);
87
        }
88
89
        // path still not defined?
90
        if ($path === null) {
91
            throw new SyntaxException('Viewer is not founded: ' . App::$Security->strip_tags($path));
92
        }
93
94
        // cleanup from slashes on start/end
95
        $path = trim($path, '/\\');
96
97
        // lets find available viewer file
98
        $path = $this->findViewer($path, $source);
99
100
        // return response
101
        return $this->renderSandbox($path, $params);
102
    }
103
104
    /**
105
     * Try to find exist viewer full path
106
     * @param string $path
107
     * @param string|null $source
108
     * @return null|string
109
     * @throws NativeException
110
     */
111
    private function findViewer($path, $source = null)
112
    {
113
        $tmpPath = null;
0 ignored issues
show
Unused Code introduced by
$tmpPath is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
114
115
        // sounds like a relative path for current view theme
116
        if (Str::contains('/', $path)) {
117
            // lets try to get full path for current theme
118
            $tmpPath = $path;
119
            if (!Str::startsWith($this->themePath, $path)) {
120
                $tmpPath = Normalize::diskPath($this->themePath . '/' . $path . '.php');
121
            }
122
        } else { // sounds like a object-depended view call from controller or etc
123
            // get stack trace of callbacks
124
            $calledLog = debug_backtrace();
125
            $calledController = null;
126
127
            // lets try to find controller in backtrace
128
            foreach ($calledLog as $caller) {
129
                if (isset($caller['class']) && Str::startsWith('Apps\Controller\\', $caller['class'])) {
130
                    $calledController = (string)$caller['class'];
131
                }
132
            }
133
134
            // depended controller is not founded? Let finish
135
            if ($calledController === null) {
136
                throw new NativeException('View render is failed: callback controller not founded! Call with relative path: ' . $path);
137
            }
138
139
            // get controller name
140
            $controllerName = Str::substr($calledController, Str::length('Apps\\Controller\\' . env_name . '\\'));
141
            $controllerName = Str::lowerCase($controllerName);
142
            // get full path
143
            $tmpPath = $this->themePath . DIRECTORY_SEPARATOR . $controllerName . DIRECTORY_SEPARATOR . $path . '.php';
144
        }
145
146
        // check if builded view full path is exist
147
        if (File::exist($tmpPath)) {
148
            return $tmpPath;
149
        }
150
151
        // hmm, not founded. Lets try to find in caller directory (for widgets, apps packages and other)
152
        if ($source !== null) {
153
            $tmpPath = Normalize::diskPath($source . DIRECTORY_SEPARATOR . $path . '.php');
154 View Code Duplication
            if (File::exist($tmpPath)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
155
                // add notify for native views
156
                if (App::$Debug !== null) {
157
                    App::$Debug->addMessage('Render native viewer: ' . Str::replace(root, null, $tmpPath), 'info');
158
                }
159
                return $tmpPath;
160
            }
161
        }
162
163
        if (App::$Debug !== null) {
164
            App::$Debug->addMessage('Viewer not founded on rendering: ' . $path, 'warning');
165
        }
166
167
        return null;
168
    }
169
170
    /**
171
     * Alias of render
172
     * @deprecated
173
     * @return string|null
174
     */
175
    public function show()
176
    {
177
        return call_user_func_array([$this, 'render'], func_get_args());
178
    }
179
180
    /**
181
     * Render view in sandbox function
182
     * @param string $path
183
     * @param array|null $params
184
     * @return string
185
     */
186
    protected function renderSandbox($path, $params = null)
187
    {
188 View Code Duplication
        if ($path === null || !File::exist($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
            if (App::$Debug !== null) {
190
                App::$Debug->addMessage('Viewer is not founded: ' . $path, 'error');
191
            }
192
            return null;
193
        }
194
195
        // render defaults params as variables
196
        if (Obj::isArray($params) && count($params) > 0) {
197
            foreach ($params as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $params of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
198
                $$key = $value;
199
            }
200
        }
201
202
        $global = $this->buildGlobal();
203
        $self = $this;
204
        // turn on output buffer
205
        ob_start();
206
        include($path);
207
        $response = ob_get_contents();
208
        // turn off buffer
209
        ob_end_clean();
210
        // cleanup init params
211
        $this->path = null;
212
        $this->params = null;
213
        $this->sourcePath = null;
214
        // return response
215
        return $response;
216
    }
217
218
    /**
219
     * Build global variables in stdObject
220
     * @return \stdClass
221
     */
222
    public function buildGlobal()
223
    {
224
        $global = new \stdClass();
225
        foreach (Variables::instance()->getGlobalsObject() as $var => $value) {
226
            $global->$var = $value;
227
        }
228
        return $global;
229
    }
230
231
    /**
232
     * Show custom code library link
233
     * @param string $type - js or css allowed
234
     * @return array|null|string
235
     */
236
    public function showCodeLink($type)
237
    {
238
        $items = App::$Alias->getCustomLibraryArray($type);
239
        // check if custom library available
240 View Code Duplication
        if (null === $items || !Obj::isArray($items) || count($items) < 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
            return null;
242
        }
243
244
        $output = [];
245
        foreach ($items as $item) {
246
            $item = trim($item, '/');
247 View Code Duplication
            if (!Str::startsWith(App::$Alias->scriptUrl, $item) && !Str::startsWith('http', $item)) { // is local without proto and domain
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
                $item = App::$Alias->scriptUrl . '/' . $item;
249
            }
250
            $output[] = $item;
251
        }
252
253
        $clear = array_unique($output);
254
        $output = null;
255
        foreach ($clear as $row) {
256
            if ($type === 'css') {
257
                $output .= '<link rel="stylesheet" type="text/css" href="' . $row . '">' . "\n";
258
            } elseif ($type === 'js') {
259
                $output .= '<script src="' . $row . '"></script>' . "\n";
260
            }
261
        }
262
263
        return $output;
264
    }
265
266
    /**
267
     * Show plain code in template.
268
     * @param string $type
269
     * @return null|string
270
     */
271
    public function showPlainCode($type)
272
    {
273
        if (null === App::$Alias->getPlainCode($type) || !Obj::isArray(App::$Alias->getPlainCode($type))) {
274
            return null;
275
        }
276
277
        $code = null;
278
        foreach (App::$Alias->getPlainCode($type) as $row) {
0 ignored issues
show
Bug introduced by
The expression \Ffcms\Core\App::$Alias->getPlainCode($type) of type string is not traversable.
Loading history...
279
            $code .= $row . "\n";
280
        }
281
282
        return $code;
283
    }
284
}