ImportNode::getPath()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 0
1
<?php
2
3
/*
4
 * This file is part of the ILess
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace ILess\Node;
11
12
use ILess\Context;
13
use ILess\Exception\Exception;
14
use ILess\FileInfo;
15
use ILess\Node;
16
use ILess\Output\OutputInterface;
17
use ILess\Util;
18
use ILess\Visitor\VisitorInterface;
19
use LogicException;
20
21
/**
22
 * Import.
23
 */
24
class ImportNode extends Node implements \Serializable
25
{
26
    /**
27
     * Node type.
28
     *
29
     * @var string
30
     */
31
    protected $type = 'Import';
32
33
    /**
34
     * The path.
35
     *
36
     * @var QuotedNode|UrlNode
37
     */
38
    public $path;
39
40
    /**
41
     * Current file index.
42
     *
43
     * @var int
44
     */
45
    public $index = 0;
46
47
    /**
48
     * Array of options.
49
     *
50
     * @var array
51
     */
52
    public $options = [
53
        'inline' => false,
54
    ];
55
56
    /**
57
     * The features.
58
     *
59
     * @var Node
60
     */
61
    public $features;
62
63
    /**
64
     * Import CSS flag.
65
     *
66
     * @var bool
67
     */
68
    public $css = false;
69
70
    /**
71
     * Skip import?
72
     *
73
     * @var bool
74
     *
75
     * @see Visitor_Import::visitImport
76
     */
77
    public $skip = false;
78
79
    /**
80
     * The root node.
81
     *
82
     * @var Node
83
     */
84
    public $root;
85
86
    /**
87
     * Error.
88
     *
89
     * @var Exception
90
     */
91
    public $error;
92
93
    /**
94
     * Imported filename.
95
     *
96
     * @var string
97
     */
98
    public $importedFilename;
99
100
    /**
101
     * Constructor.
102
     *
103
     * @param Node $path The path
104
     * @param Node $features The features
105
     * @param array $options Array of options
106
     * @param int $index The index
107
     * @param FileInfo $currentFileInfo Current file info
108
     */
109
    public function __construct(
110
        Node $path,
111
        Node $features = null,
112
        array $options = [],
113
        $index = 0,
114
        FileInfo $currentFileInfo = null
115
    ) {
116
        $this->path = $path;
0 ignored issues
show
Documentation Bug introduced by
It seems like $path of type object<ILess\Node> is incompatible with the declared type object<ILess\Node\Quoted...ect<ILess\Node\UrlNode> of property $path.

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...
117
        $this->features = $features;
118
        $this->options = array_merge($this->options, $options);
119
        $this->index = $index;
120
        $this->currentFileInfo = $currentFileInfo;
121
        if (isset($this->options['less']) || $this->options['inline']) {
122
            $this->css = !isset($this->options['less']) || !$this->options['less'] || $this->options['inline'];
123
        } else {
124
            $path = $this->getPath();
125
            if ($path && preg_match('/[#\.\&\?\/]css([\?;].*)?$/', $path)) {
126
                $this->css = true;
127
            }
128
        }
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function accept(VisitorInterface $visitor)
135
    {
136
        if ($this->features) {
137
            $this->features = $visitor->visit($this->features);
138
        }
139
140
        $this->path = $visitor->visit($this->path);
141
        if (!$this->getOption('plugin') && !$this->getOption('inline') && $this->root) {
142
            $this->root = $visitor->visit($this->root);
143
        }
144
    }
145
146
    /**
147
     * Returns the path.
148
     *
149
     * @return string|null
150
     */
151
    public function getPath()
152
    {
153
        return $this->path instanceof UrlNode ? $this->path->value->value : $this->path->value;
154
    }
155
156
    /**
157
     * @return bool
158
     */
159
    public function isVariableImport()
160
    {
161
        $path = $this->path;
162
        if ($path instanceof UrlNode) {
163
            $path = $path->value;
164
        }
165
166
        if ($path instanceof QuotedNode) {
167
            return $path->containsVariables();
168
        }
169
170
        return true;
171
    }
172
173
    /**
174
     * Compiles the node.
175
     *
176
     * @param Context $context The context
177
     * @param array|null $arguments Array of arguments
178
     * @param bool|null $important Important flag
179
     *
180
     * @return array|ImportNode|MediaNode
181
     *
182
     * @throws Exception
183
     * @throws LogicException
184
     */
185
    public function compile(Context $context, $arguments = null, $important = null)
186
    {
187
        if ($this->getOption('plugin')) {
188
            $registry = isset($context->frames[0]) && $context->frames[0]->functionRegistry ?
189
                $context->frames[0]->functionRegistry : null;
190
            if ($registry && $this->root && $this->root->functions) {
191
                /* @var $registry FunctionRegistry */
192
                foreach ($this->root->functions as $funcFile) {
193
                    $registry->loadPlugin($funcFile);
194
                }
195
            }
196
197
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type declared by the interface ILess\Node\CompilableInterface::compile of type ILess\Node.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
198
        }
199
200
        if ($this->skip) {
201
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type declared by the interface ILess\Node\CompilableInterface::compile of type ILess\Node.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
202
        }
203
204
        if ($this->getOption('inline')) {
205
            $contents = new AnonymousNode($this->root, 0, new FileInfo([
206
                'filename' => $this->importedFilename,
207
                'reference' => $this->path->currentFileInfo->reference,
208
            ]), true, true, false);
209
210
            return $this->features ? new MediaNode([$contents], $this->features->value) : [$contents];
0 ignored issues
show
Documentation introduced by
$this->features->value is of type object<ILess\Node>|string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug Compatibility introduced by
The expression $this->features ? new \I...ue) : array($contents); of type ILess\Node\MediaNode|ILess\Node\AnonymousNode[] adds the type ILess\Node\AnonymousNode[] to the return on line 210 which is incompatible with the return type declared by the interface ILess\Node\CompilableInterface::compile of type ILess\Node.
Loading history...
211
        } elseif ($this->css) {
212
            $features = $this->features ? $this->features->compile($context) : null;
213
            $import = new self($this->compilePath($context), $features, $this->options, $this->index);
214
215
            if (!$import->css && $this->hasError()) {
216
                throw $this->getError();
217
            }
218
219
            return $import;
220
        } else {
221
            $ruleset = new RulesetNode([], $this->root ? $this->root->rules : []);
222
            $ruleset->compileImports($context);
223
224
            return $this->features ? new MediaNode($ruleset->rules, $this->features->value) : $ruleset->rules;
0 ignored issues
show
Documentation introduced by
$this->features->value is of type object<ILess\Node>|string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug Compatibility introduced by
The expression $this->features ? new \I...lue) : $ruleset->rules; of type ILess\Node\MediaNode|array adds the type array to the return on line 224 which is incompatible with the return type declared by the interface ILess\Node\CompilableInterface::compile of type ILess\Node.
Loading history...
225
        }
226
    }
227
228
    /**
229
     * Compiles the path.
230
     *
231
     * @param Context $context
232
     *
233
     * @return UrlNode
234
     */
235
    public function compilePath(Context $context)
236
    {
237
        $path = $this->path->compile($context);
238
        if (!($path instanceof UrlNode)) {
239
            $rootPath = $this->currentFileInfo && $this->currentFileInfo->rootPath ? $this->currentFileInfo->rootPath : false;
240
            if ($rootPath) {
241
                $pathValue = $path->value;
242
                // Add the base path if the import is relative
243
                if ($pathValue && Util::isPathRelative($pathValue)) {
244
                    $path->value = $rootPath . $pathValue;
245
                }
246
            }
247
            $path->value = Util::normalizePath($path->value);
248
        }
249
250
        return $path;
251
    }
252
253
    /**
254
     * Compiles the node for import.
255
     *
256
     * @param Context $context
257
     *
258
     * @return ImportNode
259
     */
260
    public function compileForImport(Context $context)
261
    {
262
        $path = $this->path;
263
        if ($this->path instanceof UrlNode) {
264
            $path = $path->value;
265
        }
266
267
        return new self($path->compile($context),
268
            $this->features, $this->options, $this->index, $this->currentFileInfo);
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274
    public function generateCSS(Context $context, OutputInterface $output)
275
    {
276
        $isNotReference = !isset($this->path->currentFileInfo) || !$this->path->currentFileInfo->reference;
277
        if ($this->css && $isNotReference) {
278
            $output->add('@import ');
279
            $this->path->generateCSS($context, $output);
280
            if ($this->features) {
281
                $output->add(' ');
282
                $this->features->generateCSS($context, $output);
283
            }
284
            $output->add(';');
285
        }
286
287
        return $output;
288
    }
289
290
    /**
291
     * Returns the option with given $name.
292
     *
293
     * @param string $name The option name
294
     * @param string $default The default value
295
     *
296
     * @return mixed
297
     */
298
    public function getOption($name, $default = null)
299
    {
300
        return isset($this->options[$name]) ? $this->options[$name] : $default;
301
    }
302
303
    /**
304
     * Has the import an error?
305
     *
306
     * @return bool
307
     */
308
    public function hasError()
309
    {
310
        return $this->error !== null;
311
    }
312
313
    /**
314
     * Returns the error.
315
     *
316
     * @return null|Exception
317
     */
318
    public function getError()
319
    {
320
        return $this->error;
321
    }
322
323
    public function serialize()
324
    {
325
        $vars = get_object_vars($this);
326
        unset($vars['skip'], $vars['error']);
327
328
        return Util\Serializer::serialize($vars);
329
    }
330
331
    public function unserialize($serialized)
332
    {
333
        $unserialized = Util\Serializer::unserialize($serialized);
334
        foreach ($unserialized as $var => $val) {
335
            $this->$var = $val;
336
        }
337
    }
338
}
339