AssetPackage   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 12.3%

Importance

Changes 0
Metric Value
wmc 51
lcom 1
cbo 8
dl 0
loc 317
ccs 15
cts 122
cp 0.123
rs 7.92
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A normalizeName() 0 4 1
A normalizeScopedName() 0 4 1
A __construct() 0 13 3
A getRegistry() 0 4 1
A checkType() 0 4 2
A checkName() 0 4 1
A getFullName() 0 4 1
A buildNormalName() 0 4 1
A buildFullName() 0 4 1
A splitFullName() 0 7 1
A fromFullName() 0 5 1
A getType() 0 4 1
A getNormalName() 0 4 1
A getName() 0 4 1
A getHash() 0 4 1
A findOne() 0 7 1
A load() 0 9 2
A update() 0 7 1
B prepareReleases() 0 48 11
A prepareVersion() 0 8 2
A convertPatchToRC() 0 4 1
A prepareRequire() 0 10 2
A prepareUid() 0 6 2
A getReleases() 0 4 1
A getRelease() 0 4 2
A getSaved() 0 8 2
A getStorage() 0 4 1
A getUpdateTime() 0 4 1
A canBeUpdated() 0 4 1
A canAutoUpdate() 0 4 1
A isAvailable() 0 6 1
A __sleep() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like AssetPackage 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 AssetPackage, and based on these observations, apply Extract Interface, too.

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