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

AssetPackage::prepareReleaseLicense()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 0
cts 12
cp 0
rs 8.439
c 0
b 0
f 0
cc 6
eloc 16
nc 2
nop 1
crap 42
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 mixed $releaseLicense
174
     * @return array
175
     */
176
    public function prepareReleaseLicense($releaseLicense) {
177
        $licenses = [];
178
        if (is_array($releaseLicense)) {
179
            // For packages with an array license, validate values.
180
            foreach ($releaseLicense as $license) {
181
                // If the license follows older npm spec follow extract
182
                // url or project name. Otherwise, skip it.
183
                if (is_array($license)) {
184
                    if (array_key_exists('url', $license)) {
185
                        $licenses[] = $license['url'];
186
                    } else if (array_key_exists('type', $license)) {
187
                        $licenses[] = $license['type'];
188
                    } else {
189
                        continue;
190
                    }
191
                } else {
192
                    // Keep non array licenses.
193
                    $licenses[] = $license;
194
                }
195
            }
196
        } else {
197
            $licenses[] = $releaseLicense;
198
        }
199
        return $licenses;
200
    }
201
202
    /**
203
     * @param \Composer\DependencyResolver\Pool $pool
204
     * @return array
205
     */
206
    public function prepareReleases($pool)
207
    {
208
        $releases = [];
209
210
        foreach ($pool->whatProvides($this->getFullName()) as $package) {
211
            if ($package instanceof \Composer\Package\AliasPackage) {
212
                continue;
213
            }
214
215
            $version = $this->prepareVersion($package->getPrettyVersion());
216
            $require = $this->prepareRequire($package->getRequires());
217
            $release = [
218
                'uid' => $this->prepareUid($version),
219
                'name' => $this->getNormalName(),
220
                'version' => $version,
221
                'version_normalized' => $this->prepareVersion($package->getVersion()),
222
                'type' => $this->getType() . '-asset',
223
            ];
224
            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...
225
                $release['require'] = $require;
226
            }
227
            if ($package->getDistUrl()) {
228
                $release['dist'] = [
229
                    'type' => $package->getDistType(),
230
                    'url' => $package->getDistUrl(),
231
                    'reference' => $package->getDistReference(),
232
                ];
233
            }
234
            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...
235
                $release['license'] = $this->prepareReleaseLicense($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...
236
            }
237
            if ($package->getSourceUrl()) {
238
                $release['source'] = [
239
                    'type' => $package->getSourceType(),
240
                    'url' => $package->getSourceUrl(),
241
                    'reference' => $package->getSourceReference(),
242
                ];
243
            }
244
            if ((isset($release['dist']) && $release['dist']) || (isset($release['source']) && $release['source'])) {
245
                $releases[$version] = $release;
246
            }
247
        }
248
249
        //Sort before save
250
        \hiqdev\assetpackagist\components\PackageUtil::sort($releases);
251
252
        return $releases;
253
    }
254
255
    protected function prepareVersion($version)
256
    {
257
        if ($this->getNormalName() === 'bower-asset/angular') {
258
            return $this->convertPatchToRC($version);
259
        }
260
261
        return $version;
262
    }
263
264
    protected function convertPatchToRC($version)
265
    {
266
        return preg_replace('/-patch(.+)/', '-RC${1}', $version);
267
    }
268
269
    /**
270
     * Prepares array of requires: name => constraint.
271
     * @param Link[] array of package requires
272
     * @return array
273
     */
274
    public function prepareRequire(array $links)
275
    {
276
        $requires = [];
277
        foreach ($links as $name => $link) {
278
            /** @var Link $link */
279
            $requires[$name] = $link->getPrettyConstraint();
280
        }
281
282
        return $requires;
283
    }
284
285
    public function prepareUid($version)
286
    {
287
        $known = $this->getSaved()->getRelease($version);
288
289
        return isset($known['uid']) ? $known['uid'] : $this->getStorage()->getNextId();
290
    }
291
292
    /**
293
     * @return array
294
     */
295
    public function getReleases()
296
    {
297
        return $this->_releases;
298
    }
299
300
    /**
301
     * @param $version
302
     * @return array
303
     */
304
    public function getRelease($version)
305
    {
306
        return isset($this->_releases[$version]) ? $this->_releases[$version] : [];
307
    }
308
309
    public function getSaved()
310
    {
311
        if ($this->_saved === null) {
312
            $this->_saved = static::findOne($this->getType(), $this->getName());
313
        }
314
315
        return $this->_saved;
316
    }
317
318
    /**
319
     * @return Storage
320
     */
321
    public function getStorage()
322
    {
323
        return Yii::$app->get('packageStorage');
324
    }
325
326
    /**
327
     * Returns the latest update time (UNIX Epoch).
328
     * @return int|null
329
     */
330
    public function getUpdateTime()
331
    {
332
        return $this->_updateTime;
333
    }
334
335
    /**
336
     * Package can be updated not more often than once in 10 min.
337
     * @return bool
338
     */
339
    public function canBeUpdated()
340
    {
341
        return time() - $this->getUpdateTime() > 60 * 10; // 10 min
342
    }
343
344
    /**
345
     * Whether tha package should be auth-updated (if it is older than 1 day).
346
     * @return bool
347
     */
348
    public function canAutoUpdate()
349
    {
350
        return time() - $this->getUpdateTime() > 60 * 60 * 24; // 1 day
351
    }
352
353
    public function isAvailable()
354
    {
355
        $repository = Yii::createObject(PackageRepository::class, []);
356
357
        return $repository->exists($this);
358
    }
359
360
    /**
361
     * @return array
362
     */
363
    public function __sleep()
364
    {
365
        return ['_type', '_name', '_hash'];
366
    }
367
}
368