Completed
Push — master ( 852c67...0a8133 )
by Craig
07:11
created

getUnsatisfiedExtensionDependencies()   C

Complexity

Conditions 11
Paths 6

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 23
c 0
b 0
f 0
nc 6
nop 1
dl 0
loc 35
rs 5.2653

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Zikula package.
5
 *
6
 * Copyright Zikula Foundation - http://zikula.org/
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zikula\ExtensionsModule\Helper;
13
14
use vierbergenlars\SemVer\expression;
15
use vierbergenlars\SemVer\version;
16
use Zikula\Bundle\CoreBundle\Bundle\MetaData;
17
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaHttpKernelInterface;
18
use Zikula\ExtensionsModule\Api\ExtensionApi;
19
use Zikula\ExtensionsModule\Entity\ExtensionDependencyEntity;
20
use Zikula\ExtensionsModule\Entity\ExtensionEntity;
21
use Zikula\ExtensionsModule\Entity\Repository\ExtensionDependencyRepository;
22
use Zikula\ExtensionsModule\Entity\RepositoryInterface\ExtensionRepositoryInterface;
23
use Zikula\ExtensionsModule\Exception\ExtensionDependencyException;
24
25
class ExtensionDependencyHelper
26
{
27
    /**
28
     * @var ExtensionDependencyRepository
29
     */
30
    private $extensionDependencyRepo;
31
32
    /**
33
     * @var ExtensionRepositoryInterface
34
     */
35
    private $extensionEntityRepo;
36
37
    /**
38
     * @var ZikulaHttpKernelInterface
39
     */
40
    private $kernel;
41
42
    /**
43
     * @var array
44
     */
45
    private $installedPackages = [];
0 ignored issues
show
Unused Code introduced by
The property $installedPackages is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
46
47
    /**
48
     * ExtensionDependencyHelper constructor.
49
     *
50
     * @param ExtensionDependencyRepository $extensionDependencyRepo
51
     * @param ExtensionRepositoryInterface $extensionEntityRepo
52
     * @param ZikulaHttpKernelInterface $kernel
53
     */
54
    public function __construct(
55
        ExtensionDependencyRepository $extensionDependencyRepo,
56
        ExtensionRepositoryInterface $extensionEntityRepo,
57
        ZikulaHttpKernelInterface $kernel
58
    ) {
59
        $this->extensionDependencyRepo = $extensionDependencyRepo;
60
        $this->extensionEntityRepo = $extensionEntityRepo;
61
        $this->kernel = $kernel;
62
    }
63
64
    /**
65
     * Get an array of ExtensionEntities that are dependent on the $extension.
66
     *
67
     * @param ExtensionEntity $extension
68
     * @return ExtensionEntity[]
69
     */
70
    public function getDependentExtensions(ExtensionEntity $extension)
71
    {
72
        $requiredDependents = [];
73
        /** @var ExtensionDependencyEntity[] $dependents */
74
        $dependents = $this->extensionDependencyRepo->findBy([
75
            'modname' => $extension->getName(),
76
            'status' => MetaData::DEPENDENCY_REQUIRED
77
        ]);
78
        foreach ($dependents as $dependent) {
79
            $foundExtension = $this->extensionEntityRepo->findOneBy([
80
                'id' => $dependent->getModid(),
81
                'state' => ExtensionApi::STATE_ACTIVE
82
            ]);
83
            if (!is_null($foundExtension)) {
84
                $requiredDependents[] = $foundExtension;
85
            }
86
        }
87
88
        return $requiredDependents;
89
    }
90
91
    /**
92
     * Get an array of dependencies that are not currently met by the system and active extensions.
93
     *
94
     * @param ExtensionEntity $extension
95
     * @return ExtensionDependencyEntity[]
96
     */
97
    public function getUnsatisfiedExtensionDependencies(ExtensionEntity $extension)
98
    {
99
        $unsatisfiedDependencies = [];
100
        $dependencies = $this->extensionDependencyRepo->findBy(['modid' => $extension->getId()]);
101
        /** @var ExtensionDependencyEntity[] $dependencies */
102
        foreach ($dependencies as $dependency) {
103
            if ($this->bundleDependencySatisfied($dependency)) {
104
                continue;
105
            }
106
            $foundExtension = $this->extensionEntityRepo->get($dependency->getModname());
107
            if (!is_null($foundExtension)
108
                && ExtensionApi::STATE_ACTIVE == $foundExtension->getState()
109
                && $this->meetsVersionRequirements($dependency->getMinversion(), $dependency->getMaxversion(), $foundExtension->getVersion())) {
110
                continue;
111
            }
112
            $this->checkForFatalDependency($dependency);
113
            // get and set reason from bundle metaData temporarily
114
            if ($dependency->getReason() === false) {
115
                $bundle = $this->kernel->getModule($dependency->getModname());
116
                if (null !== $bundle) {
117
                    $bundleDependencies = $bundle->getMetaData()->getDependencies();
118
                    foreach ($bundleDependencies as $bundleDependency) {
119
                        if ($bundleDependency['modname'] == $dependency->getModname()) {
120
                            $reason = isset($dependency['reason']) ? $dependency['reason'] : '';
121
                            $dependency->setReason($reason);
122
                        }
123
                    }
124
                }
125
                $dependency->setReason('');
126
            }
127
            $unsatisfiedDependencies[$dependency->getId()] = $dependency;
128
        }
129
130
        return $unsatisfiedDependencies;
131
    }
132
133
    /**
134
     * Check for 'fatal' dependency.
135
     *
136
     * @param ExtensionDependencyEntity $dependency
137
     * @throws ExtensionDependencyException
138
     */
139
    private function checkForFatalDependency(ExtensionDependencyEntity $dependency)
140
    {
141
        $foundExtension = $this->extensionEntityRepo->get($dependency->getModname());
142
        if ($dependency->getStatus() == MetaData::DEPENDENCY_REQUIRED
143
            && (is_null($foundExtension) // never in the filesystem
144
                || $foundExtension->getState() == ExtensionApi::STATE_MISSING
145
                || $foundExtension->getState() == ExtensionApi::STATE_INVALID
146
                || $foundExtension->getState() > 10 // not compatible with current core
147
            )) {
148
            throw new ExtensionDependencyException(sprintf('Could not find a core-compatible, required dependency: %s.', $dependency->getModname()));
149
        }
150
        if (!is_null($foundExtension) && !$this->meetsVersionRequirements($dependency->getMinversion(), $dependency->getMaxversion(), $foundExtension->getVersion())) {
151
            $versionString = ($dependency->getMinversion() == $dependency->getMaxversion()) ? $dependency->getMinversion() : $dependency->getMinversion() . ' - ' . $dependency->getMaxversion();
152
            throw new ExtensionDependencyException(sprintf('A required dependency is found, but does not meet version requirements: %s (%s)', $dependency->getModname(), $versionString));
153
        }
154
    }
155
156
    /**
157
     * Compute if bundle requirements are met.
158
     *
159
     * @param ExtensionDependencyEntity $dependency
160
     * @return bool
161
     */
162
    private function bundleDependencySatisfied(ExtensionDependencyEntity &$dependency)
163
    {
164
        if ($dependency->getModname() == "php") {
165
            // Do not use PHP_VERSION constant, because it might throw off
166
            // the semver parser.
167
            $phpVersion = new version(PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "." . PHP_RELEASE_VERSION);
168
            $requiredVersionExpression = new expression($dependency->getMinversion());
169
170
            if (!$requiredVersionExpression->satisfiedBy($phpVersion)) {
171
                throw new \InvalidArgumentException('This module requires a higher version of PHP than you currently have installed.');
172
            }
173
174
            return true;
175
        }
176
        if (strpos($dependency->getModname(), 'composer/') !== false) {
0 ignored issues
show
Unused Code introduced by
This if statement and the following return statement are superfluous as you return always true.
Loading history...
177
            // @todo this specifically is for `composer/installers` but will catch all with `composer/`
178
            return true;
179
        }
180
181
        return true;
182
        /**
183
         * The section below is disabled because it doesn't work with dependencies that are in the module's own vendor directory.
184
         */
185
//        if (strpos($dependency->getModname(), '/') !== false) {
186
//            if ($this->kernel->isBundle($dependency->getModname())) {
187
//                if (empty($this->installedPackages)) {
188
//                    // create and cache installed packages from composer.lock file
189
//                    $appPath = $this->kernel->getRootDir();
190
//                    $composerLockPath = realpath($appPath . '/../') . 'composer.lock';
191
//                    $packages = json_decode(file_get_contents($composerLockPath), true);
192
//                    foreach ($packages as $package) {
193
//                        $this->installedPackages[$package['name']] = $package;
194
//                    }
195
//                }
196
//                $bundleVersion = new version($this->installedPackages[$dependency->getModname()]['version']);
197
//                $requiredVersionExpression = new expression($dependency->getMinversion());
198
//
199
//                if ($requiredVersionExpression->satisfiedBy($bundleVersion)) {
200
//                    return true;
201
//                }
202
//            }
203
//
204
//            throw new \InvalidArgumentException(sprintf('This dependency can only be resolved by adding %s to the core\'s composer.json file and running `composer update`.', $dependency->getModname()));
205
//        }
206
//
207
//        return false;
208
    }
209
210
    /**
211
     * Determine if a $current value is between $requiredMin and $requiredMax.
212
     *
213
     * @param $requiredMin
214
     * @param $requiredMax
215
     * @param $current
216
     * @return bool
217
     */
218
    private function meetsVersionRequirements($requiredMin, $requiredMax, $current)
219
    {
220
        if (($requiredMin == $requiredMax) || empty($requiredMax)) {
221
            $compatibilityString = (preg_match("/>|=|</", $requiredMin)) ? $requiredMin : ">=$requiredMin";
222
        } else {
223
            $compatibilityString = "$requiredMin - $requiredMax";
224
        }
225
        $requiredVersionExpression = new expression($compatibilityString);
226
227
        return $requiredVersionExpression->satisfiedBy(new version($current));
228
    }
229
}
230