Completed
Pull Request — master (#88)
by
unknown
02:12
created

AssetPackage::__sleep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * Asset Packagist.
4
 *
5
 * @link      https://github.com/hiqdev/asset-packagist
6
 * @package   asset-packagist
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2016-2017, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\assetpackagist\models;
12
13
use Composer\Package\Link;
14
use Exception;
15
use hiqdev\assetpackagist\components\Storage;
16
use hiqdev\assetpackagist\registry\RegistryFactory;
17
use hiqdev\assetpackagist\repositories\PackageRepository;
18
use Yii;
19
use yii\base\Object;
20
21
class AssetPackage extends Object
0 ignored issues
show
Deprecated Code introduced by
The class yii\base\Object has been deprecated with message: since 2.0.13, the class name `Object` is invalid since PHP 7.2, use [[BaseObject]] instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
22
{
23
    protected $_type;
24
    protected $_name;
25
    protected $_hash;
26
    /**
27
     * @var array
28
     */
29
    protected $_releases = [];
30
    protected $_saved;
31
32
    /**
33
     * @var integer UNIX Epoch timestamp of the latest package update
34
     */
35
    protected $_updateTime;
36
37
    public static function normalizeName($name)
38
    {
39
        return strtolower(static::normalizeScopedName($name));
40
    }
41
42
    public static function normalizeScopedName($name)
43
    {
44
        return preg_replace("#@(.+?)/#", '${1}--', $name);
45
    }
46
47
    /**
48
     * AssetPackage constructor.
49
     * @param string $type
50
     * @param string $name
51
     * @param array $config
52
     * @throws Exception
53
     */
54 1
    public function __construct($type, $name, $config = [])
55
    {
56 1
        parent::__construct($config);
57
58 1
        if (!$this->checkType($type)) {
59
            throw new Exception('wrong type');
60
        }
61 1
        if (!$this->checkName($name)) {
62
            throw new Exception('wrong name');
63
        }
64 1
        $this->_type = $type;
65 1
        $this->_name = $name;
66 1
    }
67
68
    /**
69
     * @return RegistryFactory
70
     */
71
    public function getRegistry()
72
    {
73
        return Yii::$app->get('registryFactory');
74
    }
75
76 1
    public function checkType($type)
77
    {
78 1
        return $type === 'bower' || $type === 'npm';
79
    }
80
81 1
    public function checkName($name)
82
    {
83 1
        return strlen($name) > 0;
84
    }
85
86 1
    public function getFullName()
87
    {
88 1
        return static::buildFullName($this->_type, $this->_name);
89
    }
90
91
    public static function buildNormalName($type, $name)
92
    {
93
        return static::buildFullName($type, static::normalizeName($name));
94
    }
95
96 1
    public static function buildFullName($type, $name)
97
    {
98 1
        return $type . '-asset/' . $name;
99
    }
100
101
    public static function splitFullName($full)
102
    {
103
        list($temp, $name) = explode('/', $full);
104
        list($type) = explode('-', $temp);
105
106
        return [$type, $name];
107
    }
108
109
    /**
110
     * @param string $full package name
111
     * @return static
112
     */
113
    public static function fromFullName($full)
114
    {
115
        list($type, $name) = static::splitFullName($full);
116
        return new static($type, $name);
117
    }
118
119
    public function getType()
120
    {
121
        return $this->_type;
122
    }
123
124
    public function getNormalName()
125
    {
126
        return static::buildNormalName($this->_type, $this->_name);
127
    }
128
129
    public function getName()
130
    {
131
        return $this->_name;
132
    }
133
134
    public function getHash()
135
    {
136
        return $this->_hash;
137
    }
138
139
    /**
140
     * findOne.
141
     *
142
     * @param string $type
143
     * @param string $name
144
     * @return static|null
145
     */
146
    public static function findOne($type, $name)
147
    {
148
        $package = new static($type, $name);
149
        $package->load();
150
151
        return $package;
152
    }
153
154
    public function load()
155
    {
156
        $data = $this->getStorage()->readPackage($this);
157
        if ($data !== null) {
158
            $this->_hash = $data['hash'];
159
            $this->_releases = $data['releases'];
160
            $this->_updateTime = $data['updateTime'];
161
        }
162
    }
163
164
    public function update()
165
    {
166
        $pool = $this->getRegistry()->getPool();
167
        $this->_releases = $this->prepareReleases($pool);
168
        $this->getStorage()->writePackage($this);
169
        $this->load();
170
    }
171
172
    /**
173
     * @param \Composer\DependencyResolver\Pool $pool
174
     * @return array
175
     */
176
    public function prepareReleases($pool)
177
    {
178
        $releases = [];
179
180
        foreach ($pool->whatProvides($this->getFullName()) as $package) {
181
            if ($package instanceof \Composer\Package\AliasPackage) {
182
                continue;
183
            }
184
185
            $version = $this->prepareVersion($package->getPrettyVersion());
186
            $require = $this->prepareRequire($package->getRequires());
187
            $release = [
188
                'uid' => $this->prepareUid($version),
189
                'name' => $this->getNormalName(),
190
                'version' => $version,
191
                'version_normalized' => $this->prepareVersion($package->getVersion()),
192
                'type' => $this->getType() . '-asset',
193
            ];
194
            if ($require) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $require 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...
195
                $release['require'] = $require;
196
            }
197
            if ($package->getDistUrl()) {
198
                $release['dist'] = [
199
                    'type' => $package->getDistType(),
200
                    'url' => $package->getDistUrl(),
201
                    'reference' => $package->getDistReference(),
202
                ];
203
            }
204
            if ($package->getLicense()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Composer\Package\PackageInterface as the method getLicense() does only exist in the following implementations of said interface: Composer\Package\AliasPackage, Composer\Package\CompletePackage, Composer\Package\RootAliasPackage, Composer\Package\RootPackage, Fxp\Composer\AssetPlugin...ractLazyCompletePackage, Fxp\Composer\AssetPlugin...age\LazyCompletePackage.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
205
                $packageLicense = $package->getLicense();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Composer\Package\PackageInterface as the method getLicense() does only exist in the following implementations of said interface: Composer\Package\AliasPackage, Composer\Package\CompletePackage, Composer\Package\RootAliasPackage, Composer\Package\RootPackage, Fxp\Composer\AssetPlugin...ractLazyCompletePackage, Fxp\Composer\AssetPlugin...age\LazyCompletePackage.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
206
                $licenses = [];
207
                if (is_array($packageLicense)) {
208
                    // For packages with an array license, validate values.
209
                    foreach ($packageLicense as $license) {
210
                        // If the license follows older npm spec follow extract
211
                        // url or project name. Otherwise, skip it.
212
                        if (is_array($license)) {
213
                            if (array_key_exists('url', $license)) {
214
                                $licenses[] = $license['url'];
215
                            } else if (array_key_exists('type', $license)) {
216
                                $licenses[] = $license['type'];
217
                            } else {
218
                                continue;
219
                            }
220
                        } else {
221
                            // Keep non array licenses.
222
                            $licenses[] = $license;
223
                        }
224
                    }
225
                } else {
226
                    $licenses[] = $packageLicense;
227
                }
228
                $release['license'] = $licenses;
229
            }
230
            if ($package->getSourceUrl()) {
231
                $release['source'] = [
232
                    'type' => $package->getSourceType(),
233
                    'url' => $package->getSourceUrl(),
234
                    'reference' => $package->getSourceReference(),
235
                ];
236
            }
237
            if ((isset($release['dist']) && $release['dist']) || (isset($release['source']) && $release['source'])) {
238
                $releases[$version] = $release;
239
            }
240
        }
241
242
        //Sort before save
243
        \hiqdev\assetpackagist\components\PackageUtil::sort($releases);
244
245
        return $releases;
246
    }
247
248
    protected function prepareVersion($version)
249
    {
250
        if ($this->getNormalName() === 'bower-asset/angular') {
251
            return $this->convertPatchToRC($version);
252
        }
253
254
        return $version;
255
    }
256
257
    protected function convertPatchToRC($version)
258
    {
259
        return preg_replace('/-patch(.+)/', '-RC${1}', $version);
260
    }
261
262
    /**
263
     * Prepares array of requires: name => constraint.
264
     * @param Link[] array of package requires
265
     * @return array
266
     */
267
    public function prepareRequire(array $links)
268
    {
269
        $requires = [];
270
        foreach ($links as $name => $link) {
271
            /** @var Link $link */
272
            $requires[$name] = $link->getPrettyConstraint();
273
        }
274
275
        return $requires;
276
    }
277
278
    public function prepareUid($version)
279
    {
280
        $known = $this->getSaved()->getRelease($version);
281
282
        return isset($known['uid']) ? $known['uid'] : $this->getStorage()->getNextId();
283
    }
284
285
    /**
286
     * @return array
287
     */
288
    public function getReleases()
289
    {
290
        return $this->_releases;
291
    }
292
293
    /**
294
     * @param $version
295
     * @return array
296
     */
297
    public function getRelease($version)
298
    {
299
        return isset($this->_releases[$version]) ? $this->_releases[$version] : [];
300
    }
301
302
    public function getSaved()
303
    {
304
        if ($this->_saved === null) {
305
            $this->_saved = static::findOne($this->getType(), $this->getName());
306
        }
307
308
        return $this->_saved;
309
    }
310
311
    /**
312
     * @return Storage
313
     */
314
    public function getStorage()
315
    {
316
        return Yii::$app->get('packageStorage');
317
    }
318
319
    /**
320
     * Returns the latest update time (UNIX Epoch).
321
     * @return int|null
322
     */
323
    public function getUpdateTime()
324
    {
325
        return $this->_updateTime;
326
    }
327
328
    /**
329
     * Package can be updated not more often than once in 10 min.
330
     * @return bool
331
     */
332
    public function canBeUpdated()
333
    {
334
        return time() - $this->getUpdateTime() > 60 * 10; // 10 min
335
    }
336
337
    /**
338
     * Whether tha package should be auth-updated (if it is older than 1 day).
339
     * @return bool
340
     */
341
    public function canAutoUpdate()
342
    {
343
        return time() - $this->getUpdateTime() > 60 * 60 * 24; // 1 day
344
    }
345
346
    public function isAvailable()
347
    {
348
        $repository = Yii::createObject(PackageRepository::class, []);
349
350
        return $repository->exists($this);
351
    }
352
353
    /**
354
     * @return array
355
     */
356
    public function __sleep()
357
    {
358
        return ['_type', '_name', '_hash'];
359
    }
360
}
361