Passed
Push — master ( 91737d...b67073 )
by Sam
08:30
created

Module::getComposerName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Manifest;
4
5
use Exception;
6
use InvalidArgumentException;
7
use Serializable;
8
use SilverStripe\Core\Path;
9
use SilverStripe\Dev\Deprecation;
10
11
/**
12
 * Abstraction of a PHP Package. Can be used to retrieve information about SilverStripe modules, and other packages
13
 * managed via composer, by reading their `composer.json` file.
14
 */
15
class Module implements Serializable
16
{
17
    /**
18
     * @deprecated 4.1.0:5.0.0 Use Path::normalise() instead
19
     */
20
    const TRIM_CHARS = ' /\\';
21
22
    /**
23
     * Full directory path to this module with no trailing slash
24
     *
25
     * @var string
26
     */
27
    protected $path = null;
28
29
    /**
30
     * Base folder of application with no trailing slash
31
     *
32
     * @var string
33
     */
34
    protected $basePath = null;
35
36
    /**
37
     * Cache of composer data
38
     *
39
     * @var array
40
     */
41
    protected $composerData = null;
42
43
    /**
44
     * Loaded resources for this module
45
     *
46
     * @var ModuleResource[]
47
     */
48
    protected $resources = [];
49
50
    /**
51
     * Construct a module
52
     *
53
     * @param string $path Absolute filesystem path to this module
54
     * @param string $basePath base path for the application this module is installed in
55
     */
56
    public function __construct($path, $basePath)
57
    {
58
        $this->path = Path::normalise($path);
59
        $this->basePath = Path::normalise($basePath);
60
        $this->loadComposer();
61
    }
62
63
    /**
64
     * Gets name of this module. Used as unique key and identifier for this module.
65
     *
66
     * If installed by composer, this will be the full composer name (vendor/name).
67
     * If not insalled by composer this will default to the basedir()
68
     *
69
     * @return string
70
     */
71
    public function getName()
72
    {
73
        return $this->getComposerName() ?: $this->getShortName();
74
    }
75
76
    /**
77
     * Get full composer name. Will be null if no composer.json is available
78
     *
79
     * @return string|null
80
     */
81
    public function getComposerName()
82
    {
83
        if (isset($this->composerData['name'])) {
84
            return $this->composerData['name'];
85
        }
86
        return null;
87
    }
88
89
    /**
90
     * Get list of folders that need to be made available
91
     *
92
     * @return array
93
     */
94
    public function getExposedFolders()
95
    {
96
        if (isset($this->composerData['extra']['expose'])) {
97
            return $this->composerData['extra']['expose'];
98
        }
99
        return [];
100
    }
101
102
    /**
103
     * Gets "short" name of this module. This is the base directory this module
104
     * is installed in.
105
     *
106
     * If installed in root, this will be generated from the composer name instead
107
     *
108
     * @return string
109
     */
110
    public function getShortName()
111
    {
112
        // If installed in the root directory we need to infer from composer
113
        if ($this->path === $this->basePath && $this->composerData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->composerData 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...
114
            // Sometimes we customise installer name
115
            if (isset($this->composerData['extra']['installer-name'])) {
116
                return $this->composerData['extra']['installer-name'];
117
            }
118
119
            // Strip from full composer name
120
            $composerName = $this->getComposerName();
121
            if ($composerName) {
122
                list(, $name) = explode('/', $composerName);
123
                return $name;
124
            }
125
        }
126
127
        // Base name of directory
128
        return basename($this->path);
129
    }
130
131
    /**
132
     * Name of the resource directory where vendor resources should be exposed as defined by the `extra.resources-dir`
133
     * key in the composer file. A blank string will will be returned if the key is undefined.
134
     *
135
     * Only applicable when reading the composer file for the main project.
136
     * @return string
137
     */
138
    public function getResourcesDir()
139
    {
140
        return isset($this->composerData['extra']['resources-dir'])
141
            ? $this->composerData['extra']['resources-dir']
142
            : '';
143
    }
144
145
    /**
146
     * Get base path for this module
147
     *
148
     * @return string Path with no trailing slash E.g. /var/www/module
149
     */
150
    public function getPath()
151
    {
152
        return $this->path;
153
    }
154
155
    /**
156
     * Get path relative to base dir.
157
     * If module path is base this will be empty string
158
     *
159
     * @return string Path with trimmed slashes. E.g. vendor/silverstripe/module.
160
     */
161
    public function getRelativePath()
162
    {
163
        if ($this->path === $this->basePath) {
164
            return '';
165
        }
166
        return substr($this->path, strlen($this->basePath) + 1);
167
    }
168
169
    public function serialize()
170
    {
171
        return json_encode([$this->path, $this->basePath, $this->composerData]);
172
    }
173
174
    public function unserialize($serialized)
175
    {
176
        list($this->path, $this->basePath, $this->composerData) = json_decode($serialized, true);
177
        $this->resources = [];
178
    }
179
180
    /**
181
     * Activate _config.php for this module, if one exists
182
     */
183
    public function activate()
184
    {
185
        $config = "{$this->path}/_config.php";
186
        if (file_exists($config)) {
187
            requireFile($config);
188
        }
189
    }
190
191
    /**
192
     * @throws Exception
193
     */
194
    protected function loadComposer()
195
    {
196
        // Load composer data
197
        $path = "{$this->path}/composer.json";
198
        if (file_exists($path)) {
199
            $content = file_get_contents($path);
200
            $result = json_decode($content, true);
201
            if (json_last_error()) {
202
                $errorMessage = json_last_error_msg();
203
                throw new Exception("$path: $errorMessage");
204
            }
205
            $this->composerData = $result;
206
        }
207
    }
208
209
    /**
210
     * Get resource for this module
211
     *
212
     * @param string $path
213
     * @return ModuleResource
214
     */
215
    public function getResource($path)
216
    {
217
        $path = Path::normalise($path, true);
218
        if (empty($path)) {
219
            throw new InvalidArgumentException('$path is required');
220
        }
221
        if (isset($this->resources[$path])) {
222
            return $this->resources[$path];
223
        }
224
        return $this->resources[$path] = new ModuleResource($this, $path);
225
    }
226
227
    /**
228
     * @deprecated 4.0.0:5.0.0 Use getResource($path)->getRelativePath() instead
229
     * @param string $path
230
     * @return string
231
     */
232
    public function getRelativeResourcePath($path)
233
    {
234
        Deprecation::notice('5.0', 'Use getResource($path)->getRelativePath() instead');
235
        return $this
236
            ->getResource($path)
237
            ->getRelativePath();
238
    }
239
240
    /**
241
     * @deprecated 4.0.0:5.0.0 Use ->getResource($path)->getPath() instead
242
     * @param string $path
243
     * @return string
244
     */
245
    public function getResourcePath($path)
246
    {
247
        Deprecation::notice('5.0', 'Use getResource($path)->getPath() instead');
248
        return $this
249
            ->getResource($path)
250
            ->getPath();
251
    }
252
253
    /**
254
     * @deprecated 4.0.0:5.0.0 Use ->getResource($path)->getURL() instead
255
     * @param string $path
256
     * @return string
257
     */
258
    public function getResourceURL($path)
259
    {
260
        Deprecation::notice('5.0', 'Use getResource($path)->getURL() instead');
261
        return $this
262
            ->getResource($path)
263
            ->getURL();
264
    }
265
266
    /**
267
     * @deprecated 4.0.0:5.0.0 Use ->getResource($path)->exists() instead
268
     * @param string $path
269
     * @return string
270
     */
271
    public function hasResource($path)
272
    {
273
        Deprecation::notice('5.0', 'Use getResource($path)->exists() instead');
274
        return $this
275
            ->getResource($path)
276
            ->exists();
277
    }
278
}
279
280
/**
281
 * Scope isolated require - prevents access to $this, and prevents module _config.php
282
 * files potentially leaking variables. Required argument $file is commented out
283
 * to avoid leaking that into _config.php
284
 *
285
 * @param string $file
286
 */
287
function requireFile()
288
{
289
    require_once func_get_arg(0);
290
}
291