Asset::getPackageJson()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 12
c 0
b 0
f 0
rs 10
cc 3
nc 3
nop 2
1
<?php
2
3
4
namespace carono\yii2bower;
5
6
use yii\helpers\ArrayHelper;
7
use yii\helpers\Inflector;
8
use yii\helpers\StringHelper;
9
use yii\helpers\VarDumper;
10
use yii\web\AssetBundle;
11
use yii\web\View;
12
13
class Asset extends AssetBundle
14
{
15
    public $packageNamespace = 'app\runtime\bower';
16
    public $packages = [];
17
    public $alias = '@bower';
18
    public $config = 'bower.json';
19
    protected static $_installedPackages = [];
20
21
    public function init()
22
    {
23
        $timestamp = $this->getFileTimestamp(static::getClassFile(get_class($this)));
24
        static::loadPackages($this->alias, $this->config);
25
        foreach ($this->packages as $idx => $value) {
26
            $package = is_numeric($idx) ? $value : $idx;
27
            $files = is_numeric($idx) ? [] : $value;
28
            if (!static::packageInstalled($package, $this->alias) && !static::packageAsFolder($package, $this->alias)) {
29
                continue;
30
            }
31
            if (!$this->assetExists($package) || $this->needRewrite($package)) {
32
                if ($classFile = static::createAssetClass($package, $this->packageNamespace, $this->alias, $files)) {
33
                    touch($classFile, (int)$timestamp);
34
                }
35
            };
36
            $this->depends[] = static::getPackageClass($package, $this->packageNamespace);
37
        }
38
        parent::init();
39
    }
40
41
    /**
42
     * @param $package
43
     * @param $namespace
44
     * @return string
45
     */
46
    public static function getPackageClass($package, $namespace)
47
    {
48
        return $namespace . '\\' . static::formClassNameFromPackage($package);
49
    }
50
51
    /**
52
     * @param $package
53
     * @return string
54
     */
55
    public static function formClassNameFromPackage($package)
56
    {
57
        return Inflector::camelize($package);
58
    }
59
60
    /**
61
     * @param $package
62
     * @param $namespace
63
     * @return string
64
     */
65
    protected static function getPackageFile($package, $namespace)
66
    {
67
        return static::getClassFile(static::getPackageClass($package, $namespace));
68
    }
69
70
    public static function packageAsFolder($package, $alias)
71
    {
72
        return is_dir(\Yii::getAlias("$alias/$package"));
0 ignored issues
show
Bug introduced by
It seems like Yii::getAlias($alias.'/'.$package) can also be of type false; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

72
        return is_dir(/** @scrutinizer ignore-type */ \Yii::getAlias("$alias/$package"));
Loading history...
73
    }
74
75
    public static function packageInstalled($package, $alias, $config = 'bower.json')
76
    {
77
        if (!static::$_installedPackages) {
0 ignored issues
show
Bug Best Practice introduced by
The expression static::_installedPackages of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
78
            static::loadPackages($alias, $config);
79
        }
80
        return isset(static::$_installedPackages[$package]);
81
    }
82
83
    public static function loadPackages($alias, $config)
84
    {
85
        static::$_installedPackages = [];
86
        foreach (glob(\Yii::getAlias("$alias/**/$config"), GLOB_BRACE) as $file) {
0 ignored issues
show
Bug introduced by
It seems like Yii::getAlias($alias.'/**/'.$config) can also be of type false; however, parameter $pattern of glob() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

86
        foreach (glob(/** @scrutinizer ignore-type */ \Yii::getAlias("$alias/**/$config"), GLOB_BRACE) as $file) {
Loading history...
87
            $json = json_decode(file_get_contents($file), true);
88
            if (($name = ArrayHelper::getValue($json, 'name'))) {
89
                static::$_installedPackages[$name] = $file;
90
            }
91
        }
92
    }
93
94
    public static function getPackageJson($package, $alias)
95
    {
96
        if (isset(static::$_installedPackages[$package])) {
97
            $file = static::$_installedPackages[$package];
98
            return json_decode(file_get_contents($file), true);
99
        }
100
101
        if (static::packageAsFolder($package, $alias)) {
102
            return [];
103
        }
104
105
        return null;
106
    }
107
108
    public static function getPackageDir($package, $alias)
109
    {
110
        if (isset(static::$_installedPackages[$package])) {
111
            $file = static::$_installedPackages[$package];
112
            return basename(dirname($file));
113
        }
114
115
        if (static::packageAsFolder($package, $alias)) {
116
            return $package;
117
        }
118
119
        return null;
120
    }
121
122
    protected static function filesToConfig($files)
123
    {
124
        $config = [];
125
        foreach ($files as $key => $file) {
126
            if (!is_numeric($key)) {
127
                $config[$key] = $file;
128
                continue;
129
            }
130
            if (StringHelper::endsWith($file, '.css')) {
131
                $config['css'][] = ltrim($file, "./\\");
132
            }
133
            if (StringHelper::endsWith($file, '.js')) {
134
                $config['js'][] = ltrim($file, "./\\");
135
            }
136
        }
137
        return $config;
138
    }
139
140
    /**
141
     * @param $package
142
     * @param $namespace
143
     * @param $alias
144
     * @param array $files
145
     * @return mixed|string
146
     */
147
    public static function createAssetClass($package, $namespace, $alias, $files = [])
148
    {
149
        $configJson = static::getPackageJson($package, $alias);
150
151
        if ($configJson === null) {
152
            \Yii::warning("Bower $package package not found");
153
            return false;
154
        }
155
156
        $main = ArrayHelper::getValue($configJson, 'main');
157
158
        $dir = static::getPackageDir($package, $alias);
159
160
        $defaultConfig = [
161
            'baseUrl' => '@web',
162
            'jsOptions' => ['position' => View::POS_END]
163
        ];
164
        $packageConfig = self::filesToConfig($files ?: (array)$main);
165
        $sourcePath = implode('/', array_filter([
166
            $alias,
167
            $dir,
168
            trim((string)ArrayHelper::remove($packageConfig, 'sourcePath', ''), '/')
169
        ]));
170
        $config = ArrayHelper::merge($defaultConfig, $packageConfig, ['sourcePath' => $sourcePath]);
171
        $str = '';
172
        foreach ($config as $key => $value) {
173
            $str .= '    public $' . $key . ' = ' . VarDumper::export($value) . ";\n";
174
        }
175
        $class = static::formClassNameFromPackage($package);
176
        $template = <<<PHP
177
<?php
178
179
namespace $namespace;
180
181
182
use yii\web\AssetBundle;
183
use yii\web\View;
184
185
class $class extends AssetBundle
186
{
187
$str
188
}
189
PHP;
190
        $dir = \Yii::getAlias('@' . str_replace('\\', '/', ltrim($namespace, '\\')));
191
        if (!is_dir($dir)) {
0 ignored issues
show
Bug introduced by
It seems like $dir can also be of type false; however, parameter $filename of is_dir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

191
        if (!is_dir(/** @scrutinizer ignore-type */ $dir)) {
Loading history...
192
            if (!mkdir($dir, 0777, true) && !is_dir($dir)) {
0 ignored issues
show
Bug introduced by
It seems like $dir can also be of type false; however, parameter $directory of mkdir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

192
            if (!mkdir(/** @scrutinizer ignore-type */ $dir, 0777, true) && !is_dir($dir)) {
Loading history...
193
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
0 ignored issues
show
Bug introduced by
It seems like $dir can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
                throw new \RuntimeException(sprintf('Directory "%s" was not created', /** @scrutinizer ignore-type */ $dir));
Loading history...
194
            }
195
        }
196
        $file = $dir . DIRECTORY_SEPARATOR . $class . '.php';
0 ignored issues
show
Bug introduced by
Are you sure $dir of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

196
        $file = /** @scrutinizer ignore-type */ $dir . DIRECTORY_SEPARATOR . $class . '.php';
Loading history...
197
        file_put_contents($file, $template);
198
        return $file;
199
    }
200
201
    /**
202
     * @param $package
203
     * @return bool
204
     */
205
    protected function assetExists($package)
206
    {
207
        return class_exists(static::getPackageClass($package, $this->packageNamespace));
208
    }
209
210
    /**
211
     * @param $package
212
     * @return bool
213
     */
214
    protected function needRewrite($package)
215
    {
216
        $timestamp = $this->getFileTimestamp(static::getPackageFile($package, $this->packageNamespace));
217
        if ($timestamp) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $timestamp of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
218
            $assetTimestamp = $this->getFileTimestamp(static::getClassFile(get_class($this)));
219
            return $timestamp != $assetTimestamp;
220
        }
221
        return false;
222
    }
223
224
    /**
225
     * @param $class
226
     * @return string
227
     */
228
    protected static function getClassFile($class)
229
    {
230
        return \Yii::getAlias('@' . ltrim(str_replace('\\', '/', $class), '/')) . '.php';
0 ignored issues
show
Bug introduced by
Are you sure Yii::getAlias('@' . ltri...\', '/', $class), '/')) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

230
        return /** @scrutinizer ignore-type */ \Yii::getAlias('@' . ltrim(str_replace('\\', '/', $class), '/')) . '.php';
Loading history...
231
    }
232
233
    /**
234
     * @param $file
235
     * @return bool|int
236
     */
237
    protected function getFileTimestamp($file)
238
    {
239
        if ($timestamp = @filemtime($file)) {
240
            $timestamp = (int)$timestamp / 100 * 100;
241
        }
242
        return $timestamp;
243
    }
244
}