Passed
Push — master ( 69774c...de7969 )
by Sebastian
02:46
created

CheckLockFile::getLockFileHash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * This file is part of CaptainHook
5
 *
6
 * (c) Sebastian Feldmann <[email protected]>
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 CaptainHook\App\Hook\Composer\Action;
13
14
use CaptainHook\App\Config;
15
use CaptainHook\App\Console\IO;
16
use CaptainHook\App\Hook\Action;
17
use Exception;
18
use SebastianFeldmann\Git\Repository;
19
20
/**
21
 * Class CheckLockFile
22
 *
23
 * @package CaptainHook
24
 * @author  Sebastian Feldmann <[email protected]>
25
 * @link    https://github.com/captainhookphp/captainhook
26
 * @since   Class available since Release 1.0.1
27
 */
28
class CheckLockFile implements Action
29
{
30
    /**
31
     * Composer configuration keys that are relevant for the 'content-hash' creation
32
     *
33
     * @var array<string>
34
     */
35
    private $relevantKeys = [
36
        'name',
37
        'version',
38
        'require',
39
        'require-dev',
40
        'conflict',
41
        'replace',
42
        'provide',
43
        'minimum-stability',
44
        'prefer-stable',
45
        'repositories',
46
        'extra',
47
    ];
48
49
    /**
50
     * Executes the action
51
     *
52
     * @param  \CaptainHook\App\Config           $config
53
     * @param  \CaptainHook\App\Console\IO       $io
54
     * @param  \SebastianFeldmann\Git\Repository $repository
55 4
     * @param  \CaptainHook\App\Config\Action    $action
56
     * @throws \Exception
57 4
     */
58 4
    public function execute(Config $config, IO $io, Repository $repository, Config\Action $action): void
59 4
    {
60 4
        $path           = $action->getOptions()->get('path', getcwd());
61 2
        $name           = $action->getOptions()->get('name', 'composer');
62
        $pathname       = $path . DIRECTORY_SEPARATOR . $name;
63 2
        $lockFileHash   = $this->getLockFileHash($pathname . '.lock');
64 1
        $configFileHash = $this->getConfigFileHash($pathname . '.json');
65
66
        if ($lockFileHash !== $configFileHash) {
67 1
            throw new Exception('Your composer.lock file is out of date');
68 1
        }
69
70
        $io->write('<info>Your composer.lock file is up to date</info>');
71
    }
72
73
    /**
74
     * Read the composer.lock file and extract the composer.json hash
75
     *
76
     * @param  string $composerLock
77 4
     * @return string
78
     * @throws \Exception
79 4
     */
80 3
    private function getLockFileHash(string $composerLock): string
81
    {
82 3
        $lockFile = json_decode($this->loadFile($composerLock));
83 1
        $hashKey  = 'content-hash';
84
85
        if (!isset($lockFile->$hashKey)) {
86 2
            throw new Exception('could not find content hash, please update composer to the latest version');
87
        }
88
89
        return $lockFile->$hashKey;
90
    }
91
92
    /**
93
     * Read the composer.json file and create a md5 hash on its relevant content
94
     *
95
     * This more or less is composer internal code to generate the content-hash so this might not be the best idea
96
     * and will be removed in the future.
97
     *
98
     * @param  string $composerJson
99 2
     * @return string
100
     * @throws \Exception
101 2
     */
102 2
    private function getConfigFileHash(string $composerJson): string
103
    {
104 2
        $content         = json_decode($this->loadFile($composerJson), true);
105 2
        $relevantContent = [];
106
107 2
        foreach (array_intersect($this->relevantKeys, array_keys($content)) as $key) {
108 1
            $relevantContent[$key] = $content[$key];
109
        }
110 2
        if (isset($content['config']['platform'])) {
111
            $relevantContent['config']['platform'] = $content['config']['platform'];
112 2
        }
113
        ksort($relevantContent);
114
115
        return md5((string)json_encode($relevantContent));
116
    }
117
118
    /**
119
     * Load a composer file
120
     *
121
     * @param  string $file
122 4
     * @return string
123
     * @throws \Exception
124 4
     */
125 1
    private function loadFile(string $file): string
126
    {
127 3
        if (!file_exists($file)) {
128
            throw new Exception($file . ' not found');
129
        }
130
        return (string)file_get_contents($file);
131
    }
132
}
133