These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Task which does the actual checking of updates |
||
4 | * |
||
5 | * Originally from https://github.com/XploreNet/silverstripe-composerupdates |
||
6 | * |
||
7 | * @author Matt Dwen |
||
8 | * @license MIT |
||
9 | */ |
||
10 | class CheckComposerUpdatesTask extends BuildTask |
||
0 ignored issues
–
show
|
|||
11 | { |
||
12 | /** |
||
13 | * @var string |
||
14 | */ |
||
15 | protected $title = 'Composer update checker'; |
||
16 | |||
17 | /** |
||
18 | * @var string |
||
19 | */ |
||
20 | protected $description = 'Checks if any composer dependencies can be updated.'; |
||
21 | |||
22 | /** |
||
23 | * Deserialized JSON from composer.lock |
||
24 | * |
||
25 | * @var object |
||
26 | */ |
||
27 | protected $composerLock; |
||
28 | |||
29 | /** |
||
30 | * Minimum required stability defined in the site composer.json |
||
31 | * |
||
32 | * @var string |
||
33 | */ |
||
34 | protected $minimumStability; |
||
35 | |||
36 | /** |
||
37 | * Whether or not to prefer stable packages |
||
38 | * |
||
39 | * @var bool |
||
40 | */ |
||
41 | protected $preferStable; |
||
42 | |||
43 | /** |
||
44 | * Known stability values |
||
45 | * |
||
46 | * @var array |
||
47 | */ |
||
48 | protected $stabilityOptions = array( |
||
49 | 'dev', |
||
50 | 'alpha', |
||
51 | 'beta', |
||
52 | 'rc', |
||
53 | 'stable' |
||
54 | ); |
||
55 | |||
56 | /** |
||
57 | * Whether to write all log messages or not |
||
58 | * |
||
59 | * @var bool |
||
60 | */ |
||
61 | protected $extendedLogging = true; |
||
62 | |||
63 | /** |
||
64 | * Retrieve an array of primary composer dependencies from composer.json |
||
65 | * |
||
66 | * @return array |
||
0 ignored issues
–
show
|
|||
67 | */ |
||
68 | protected function getPackages() |
||
69 | { |
||
70 | $composerPath = BASE_PATH . '/composer.json'; |
||
71 | if (!file_exists($composerPath)) { |
||
72 | return null; |
||
73 | } |
||
74 | |||
75 | // Read the contents of composer.json |
||
76 | $composerFile = file_get_contents($composerPath); |
||
77 | |||
78 | // Parse the json |
||
79 | $composerJson = json_decode($composerFile); |
||
80 | |||
81 | ini_set('display_errors', 1); |
||
82 | error_reporting(E_ALL); |
||
83 | |||
84 | // Set the stability parameters |
||
85 | $this->minimumStability = (isset($composerJson->{'minimum-stability'})) |
||
86 | ? $composerJson->{'minimum-stability'} |
||
87 | : 'stable'; |
||
88 | |||
89 | $this->preferStable = (isset($composerJson->{'prefer-stable'})) |
||
90 | ? $composerJson->{'prefer-stable'} |
||
91 | : true; |
||
92 | |||
93 | $packages = array(); |
||
94 | foreach ($composerJson->require as $package => $version) { |
||
95 | // Ensure there's a / in the name, probably not an addon with it |
||
96 | if (!strpos($package, '/')) { |
||
97 | continue; |
||
98 | } |
||
99 | |||
100 | $packages[] = $package; |
||
101 | } |
||
102 | |||
103 | return $packages; |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Return an array of all Composer dependencies from composer.lock |
||
108 | * |
||
109 | * @return array(package => hash) |
||
0 ignored issues
–
show
The doc-type
array(package could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.
Loading history...
|
|||
110 | */ |
||
111 | protected function getDependencies() |
||
112 | { |
||
113 | $composerPath = BASE_PATH . '/composer.lock'; |
||
114 | if (!file_exists($composerPath)) { |
||
115 | return null; |
||
116 | } |
||
117 | |||
118 | // Read the contents of composer.json |
||
119 | $composerFile = file_get_contents($composerPath); |
||
120 | |||
121 | // Parse the json |
||
122 | $dependencies = json_decode($composerFile); |
||
123 | |||
124 | $packages = array(); |
||
125 | |||
126 | // Loop through the requirements |
||
127 | foreach ($dependencies->packages as $package) { |
||
128 | $packages[$package->name] = $package->version; |
||
129 | } |
||
130 | |||
131 | $this->composerLock = $dependencies; |
||
132 | |||
133 | return $packages; |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Check if an available version is better than the current version, |
||
138 | * considering stability requirements |
||
139 | * |
||
140 | * Returns FALSE if no update is available. |
||
141 | * Returns the best available version if an update is available. |
||
142 | * |
||
143 | * @param string $currentVersion |
||
144 | * @param string $availableVersions |
||
145 | * @return bool|string |
||
0 ignored issues
–
show
|
|||
146 | */ |
||
147 | protected function hasUpdate($currentVersion, $availableVersions) |
||
148 | { |
||
149 | $currentVersion = strtolower($currentVersion); |
||
150 | |||
151 | // Check there are some versions |
||
152 | if (count($availableVersions) < 1) { |
||
153 | return false; |
||
154 | } |
||
155 | |||
156 | // If this is dev-master, compare the hashes |
||
157 | if ($currentVersion === 'dev-master') { |
||
158 | return $this->hasUpdateOnDevMaster($availableVersions); |
||
159 | } |
||
160 | |||
161 | // Loop through each available version |
||
162 | $currentStability = $this->getStability($currentVersion); |
||
163 | $bestVersion = $currentVersion; |
||
164 | $bestStability = $currentStability; |
||
165 | $availableVersions = array_reverse($availableVersions, true); |
||
166 | foreach ($availableVersions as $version => $details) { |
||
167 | // Get the stability of the version |
||
168 | $versionStability = $this->getStability($version); |
||
169 | |||
170 | // Does this meet minimum stability |
||
171 | if (!$this->isStableEnough($this->minimumStability, $versionStability)) { |
||
172 | continue; |
||
173 | } |
||
174 | |||
175 | if ($this->preferStable) { |
||
176 | // A simple php version compare rules out the dumb stuff |
||
177 | if (version_compare($bestVersion, $version) !== -1) { |
||
178 | continue; |
||
179 | } |
||
180 | } else { |
||
181 | // We're doing a straight version compare |
||
182 | $pureBestVersion = $this->getPureVersion($bestVersion); |
||
183 | $pureVersion = $this->getPureVersion($version); |
||
184 | |||
185 | // Checkout the version |
||
186 | $continue = false; |
||
187 | switch (version_compare($pureBestVersion, $pureVersion)) { |
||
188 | case -1: |
||
189 | // The version is better, take it |
||
190 | break; |
||
191 | |||
192 | case 0: |
||
193 | // The version is the same. |
||
194 | // Do another straight version compare to rule out rc1 vs rc2 etc... |
||
195 | if ($bestStability == $versionStability) { |
||
196 | if (version_compare($bestVersion, $version) !== -1) { |
||
197 | $continue = true; |
||
198 | break; |
||
199 | } |
||
200 | } |
||
201 | break; |
||
202 | |||
203 | case 1: |
||
204 | // The version is worse, ignore it |
||
205 | $continue = true; |
||
206 | break; |
||
207 | } |
||
208 | |||
209 | if ($continue) { |
||
210 | continue; |
||
211 | } |
||
212 | } |
||
213 | |||
214 | $bestVersion = $version; |
||
215 | $bestStability = $versionStability; |
||
216 | } |
||
217 | |||
218 | if ($bestVersion !== $currentVersion || $bestStability !== $currentStability) { |
||
219 | if ($bestStability === 'stable') { |
||
220 | return $bestVersion; |
||
221 | } |
||
222 | |||
223 | return $bestVersion . '-' . $bestStability; |
||
224 | } |
||
225 | |||
226 | return false; |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Check the latest hash on the dev-master branch, and return it if different to the local hash |
||
231 | * |
||
232 | * FALSE is returned if the hash is the same. |
||
233 | * |
||
234 | * @param $availableVersions |
||
235 | * @return bool|string |
||
236 | */ |
||
237 | protected function hasUpdateOnDevMaster($availableVersions) |
||
238 | { |
||
239 | // Get the dev-master version |
||
240 | $devMaster = $availableVersions['dev-master']; |
||
241 | |||
242 | // Sneak the name of the package |
||
243 | $packageName = $devMaster->getName(); |
||
244 | |||
245 | // Get the local package details |
||
246 | $localPackage = $this->getLocalPackage($packageName); |
||
247 | |||
248 | // What's the current hash? |
||
249 | $localHash = $localPackage->source->reference; |
||
250 | |||
251 | // What's the latest hash in the available versions |
||
252 | $remoteHash = $devMaster->getSource()->getReference(); |
||
253 | |||
254 | // return either the new hash or false |
||
255 | return ($localHash != $remoteHash) ? $remoteHash : false; |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Return details from composer.lock for a specific package |
||
260 | * |
||
261 | * @param string $packageName |
||
262 | * @return object |
||
263 | * @throws Exception if package cannot be found in composer.lock |
||
264 | */ |
||
265 | protected function getLocalPackage($packageName) |
||
266 | { |
||
267 | foreach ($this->composerLock->packages as $package) { |
||
268 | if ($package->name == $packageName) { |
||
269 | return $package; |
||
270 | } |
||
271 | } |
||
272 | |||
273 | throw new Exception('Cannot locate local package ' . $packageName); |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Retrieve the pure numerical version |
||
278 | * |
||
279 | * @param string $version |
||
280 | * @return string|null |
||
281 | */ |
||
282 | protected function getPureVersion($version) |
||
283 | { |
||
284 | $matches = array(); |
||
285 | |||
286 | preg_match("/^(\d+\\.)?(\d+\\.)?(\\*|\d+)/", $version, $matches); |
||
287 | |||
288 | if (count($matches) > 0) { |
||
289 | return $matches[0]; |
||
290 | } |
||
291 | |||
292 | return null; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Determine the stability of a given version |
||
297 | * |
||
298 | * @param string $version |
||
299 | * @return string |
||
300 | */ |
||
301 | protected function getStability($version) |
||
302 | { |
||
303 | $version = strtolower($version); |
||
304 | |||
305 | foreach ($this->stabilityOptions as $option) { |
||
306 | if (strpos($version, $option) !== false) { |
||
307 | return $option; |
||
308 | } |
||
309 | } |
||
310 | |||
311 | return 'stable'; |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Return a numerical representation of a stability |
||
316 | * |
||
317 | * Higher is more stable |
||
318 | * |
||
319 | * @param string $stability |
||
320 | * @return int |
||
0 ignored issues
–
show
|
|||
321 | * @throws Exception If the stability is unknown |
||
322 | */ |
||
323 | protected function getStabilityIndex($stability) |
||
324 | { |
||
325 | $stability = strtolower($stability); |
||
326 | |||
327 | $index = array_search($stability, $this->stabilityOptions, true); |
||
328 | |||
329 | if ($index === false) { |
||
330 | throw new Exception("Unknown stability: $stability"); |
||
331 | } |
||
332 | |||
333 | return $index; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Check if a stability meets a given minimum requirement |
||
338 | * |
||
339 | * @param $currentStability |
||
340 | * @param $possibleStability |
||
341 | * @return bool |
||
342 | */ |
||
343 | protected function isStableEnough($currentStability, $possibleStability) |
||
344 | { |
||
345 | $minimumIndex = $this->getStabilityIndex($currentStability); |
||
346 | $possibleIndex = $this->getStabilityIndex($possibleStability); |
||
347 | |||
348 | return ($possibleIndex >= $minimumIndex); |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Record package details in the database |
||
353 | * |
||
354 | * @param string $package Name of the Composer Package |
||
355 | * @param string $installed Currently installed version |
||
356 | * @param string|boolean $latest The latest available version |
||
357 | */ |
||
358 | protected function recordUpdate($package, $installed, $latest) |
||
359 | { |
||
360 | // Is there a record already for the package? If so find it. |
||
361 | $packages = ComposerUpdate::get()->filter(array('Name' => $package)); |
||
362 | |||
363 | // if there is already one use it otherwise create a new data object |
||
364 | if ($packages->count() > 0) { |
||
365 | $update = $packages->first(); |
||
366 | } else { |
||
367 | $update = new ComposerUpdate(); |
||
368 | $update->Name = $package; |
||
0 ignored issues
–
show
The property
Name does not exist on object<ComposerUpdate> . Since you implemented __set , maybe consider adding a @property annotation.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
369 | } |
||
370 | |||
371 | // If installed is dev-master get the hash |
||
372 | if ($installed === 'dev-master') { |
||
373 | $localPackage = $this->getLocalPackage($package); |
||
374 | $installed = $localPackage->source->reference; |
||
375 | } |
||
376 | |||
377 | // Set the new details and save it |
||
378 | $update->Installed = $installed; |
||
379 | $update->Available = $latest; |
||
380 | $update->write(); |
||
381 | } |
||
382 | |||
383 | /** |
||
384 | * runs the actual steps to verify if there are updates available |
||
385 | * |
||
386 | * @param SS_HTTPRequest $request |
||
387 | */ |
||
388 | public function run($request) |
||
389 | { |
||
390 | // Retrieve the packages |
||
391 | $packages = $this->getPackages(); |
||
392 | $dependencies = $this->getDependencies(); |
||
393 | |||
394 | // Load the Packagist API |
||
395 | $packagist = new Packagist\Api\Client(); |
||
396 | |||
397 | // run through the packages and check each for updates |
||
398 | foreach ($packages as $package) { |
||
0 ignored issues
–
show
The expression
$packages of type null|array is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
399 | // verify that we need to check this package. |
||
400 | if (!isset($dependencies[$package])) { |
||
401 | continue; |
||
402 | } else { |
||
403 | // get information about this package from packagist. |
||
404 | try { |
||
405 | $latest = $packagist->get($package); |
||
406 | } catch (Guzzle\Http\Exception\ClientErrorResponseException $e) { |
||
0 ignored issues
–
show
The class
Guzzle\Http\Exception\ClientErrorResponseException does not exist. Did you forget a USE statement, or did you not list all dependencies?
Scrutinizer analyzes your It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.
Loading history...
|
|||
407 | SS_Log::log($e->getMessage(), SS_Log::WARN); |
||
408 | continue; |
||
409 | } |
||
410 | |||
411 | // Check if there is a newer version |
||
412 | $currentVersion = $dependencies[$package]; |
||
413 | $result = $this->hasUpdate($currentVersion, $latest->getVersions()); |
||
0 ignored issues
–
show
$latest->getVersions() is of type array<integer,object<Pac...esult\Package\Version>> , but the function expects a string .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
414 | |||
415 | // Check if there is a newer version and if so record the update |
||
416 | if ($result !== false) { |
||
417 | $this->recordUpdate($package, $currentVersion, $result); |
||
418 | } |
||
419 | } |
||
420 | } |
||
421 | |||
422 | // finished message |
||
423 | $this->message('The task finished running. You can find the updated information in the database now.'); |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * prints a message during the run of the task |
||
428 | * |
||
429 | * @param string $text |
||
430 | */ |
||
431 | protected function message($text) |
||
432 | { |
||
433 | if (!Director::is_cli()) { |
||
434 | $text = '<p>' . $text . '</p>'; |
||
435 | } |
||
436 | |||
437 | echo $text . PHP_EOL; |
||
438 | } |
||
439 | } |
||
440 |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.