Completed
Push — master ( de3f3f...b726a3 )
by Andrii
03:59
created

Plugin::saveFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 9
ccs 0
cts 8
cp 0
rs 9.6666
cc 2
eloc 6
nc 2
nop 2
crap 6
1
<?php
2
3
/*
4
 * Composer plugin for Yii extensions
5
 *
6
 * @link      https://github.com/hiqdev/composer-extension-plugin
7
 * @package   composer-extension-plugin
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\composerextensionplugin;
13
14
use Composer\Composer;
15
use Composer\EventDispatcher\EventSubscriberInterface;
16
use Composer\IO\IOInterface;
17
use Composer\Package\PackageInterface;
18
use Composer\Package\RootPackageInterface;
19
use Composer\Plugin\PluginInterface;
20
use Composer\Script\Event;
21
use Composer\Script\ScriptEvents;
22
use Composer\Util\Filesystem;
23
24
/**
25
 * Plugin class.
26
 *
27
 * @author Andrii Vasyliev <[email protected]>
28
 */
29
class Plugin implements PluginInterface, EventSubscriberInterface
30
{
31
    const PACKAGE_TYPE     = 'yii2-extension';
32
    const EXTRA_CONFIG     = 'yii2-extraconfig';
33
    const EXTENSIONS_FILE  = 'yiisoft/extensions.php';
34
    const EXTRACONFIG_FILE = 'yiisoft/yii2-extraconfig.php';
35
    const BASE_DIR_ALIAS   = '<base-dir>';
36
    const VENDOR_DIR_ALIAS = '<base-dir>/vendor';
37
38
    /**
39
     * @var PackageInterface[] the array of active composer packages
40
     */
41
    protected $packages;
42
43
    /**
44
     * @var string absolute path to the package base directory.
45
     */
46
    protected $baseDir;
47
48
    /**
49
     * @var string absolute path to vendor directory.
50
     */
51
    protected $vendorDir;
52
53
    /**
54
     * @var Filesystem utility
55
     */
56
    protected $filesystem;
57
58
    /**
59
     * @var array extra configuration
60
     */
61
    protected $extraconfig = [
62
        'aliases' => [
63
            '@vendor' => self::VENDOR_DIR_ALIAS,
64
        ],
65
    ];
66
67
    /**
68
     * @var array extensions
69
     */
70
    protected $extensions = [];
71
72
    /**
73
     * @var Composer instance
74
     */
75
    protected $composer;
76
77
    /**
78
     * @var IOInterface
79
     */
80
    public $io;
81
82
    /**
83
     * Initializes the plugin object with the passed $composer and $io.
84
     * @param Composer $composer
85
     * @param IOInterface $io
86
     * @void
87
     */
88 2
    public function activate(Composer $composer, IOInterface $io)
89
    {
90 2
        $this->composer = $composer;
91 2
        $this->io = $io;
92 2
    }
93
94
    /**
95
     * Returns list of events the plugin is subscribed to.
96
     * @return array list of events
97
     */
98 1
    public static function getSubscribedEvents()
99
    {
100
        return [
101 1
            ScriptEvents::POST_AUTOLOAD_DUMP => [
102 1
                ['onPostAnything', 0],
103 1
            ],
104 1
        ];
105
    }
106
107
    /**
108
     * Simply rewrites extensions file from scratch.
109
     * @param Event $event
110
     * @void
111
     */
112
    public function onPostAnything(Event $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
113
    {
114
        $this->io->writeError('<info>Generating yii2 config files</info>');
115
        $this->processPackage($this->composer->getPackage());
116
        foreach ($this->getPackages() as $package) {
117
            if ($package instanceof \Composer\Package\CompletePackageInterface
118
            && ($package->getType() === self::PACKAGE_TYPE || $this->extractConfigPath($package))) {
119
                $this->processPackage($package);
120
            }
121
        }
122
123
        $this->saveFile(static::EXTENSIONS_FILE, $this->extensions);
124
        $this->saveFile(static::EXTRACONFIG_FILE, $this->extraconfig);
125
    }
126
127
    /**
128
     * Writes file.
129
     * @param string $file
130
     * @param array $data
131
     * @void
132
     */
133
    protected function saveFile($file, array $data)
134
    {
135
        $path = $this->getVendorDir() . '/' . $file;
136
        if (!file_exists(dirname($path))) {
137
            mkdir(dirname($path), 0777, true);
138
        }
139
        $array = str_replace("'" . self::BASE_DIR_ALIAS, '$baseDir . \'', var_export($data, true));
140
        file_put_contents($path, "<?php\n\n\$baseDir = dirname(dirname(__DIR__));\n\nreturn $array;\n");
141
    }
142
143
    /**
144
     * Scans the given package and collects extensions data.
145
     * @param PackageInterface $package
146
     * @void
147
     */
148
    public function processPackage(PackageInterface $package)
149
    {
150
        $extension = [
151
            'name'    => $package->getName(),
152
            'version' => $package->getVersion(),
153
        ];
154
        if ($package->getVersion() === '9999999-dev') {
155
            $reference = $package->getSourceReference() ?: $package->getDistReference();
156
            if ($reference) {
157
                $extension['reference'] = $reference;
158
            }
159
        }
160
        $this->extensions[$package->getName()] = $extension;
161
162
        $this->extraconfig['aliases'] = array_merge(
163
            $this->extraconfig['aliases'],
164
            $this->prepareAliases($package, 'psr-0'),
165
            $this->prepareAliases($package, 'psr-4')
166
        );
167
168
        $path = $this->extractConfigPath($package);
169
        if ($path) {
170
            $this->extraconfig = array_merge_recursive($this->extraconfig, $this->readExtraConfig($package, $path));
171
        }
172
    }
173
174
    public function extractConfigPath(PackageInterface $package)
175
    {
176
        $extra = $package->getExtra();
177
        return isset($extra[static::EXTRA_CONFIG]) ? $extra[static::EXTRA_CONFIG] : null;
178
    }
179
180
    /**
181
     * Read extra config.
182
     * @param string $file
183
     * @return array
184
     */
185
    protected function readExtraConfig(PackageInterface $package, $file)
186
    {
187
        $path = $this->preparePath($package, $file);
188
        if (!file_exists($path)) {
189
            $this->io->writeError('<error>Non existent extraconfig file</error> ' . $file . ' in ' . $package->getName());
190
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method readExtraConfig() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
191
        }
192
        return require $path;
193
    }
194
195
    /**
196
     * Prepare aliases.
197
     *
198
     * @param PackageInterface $package
199
     * @param string 'psr-0' or 'psr-4'
200
     * @return array
201
     */
202
    protected function prepareAliases(PackageInterface $package, $psr)
203
    {
204
        $autoload = $package->getAutoload();
205
        if (empty($autoload[$psr])) {
206
            return [];
207
        }
208
209
        $aliases = [];
210
        foreach ($autoload[$psr] as $name => $path) {
211
            if (is_array($path)) {
212
                // ignore psr-4 autoload specifications with multiple search paths
213
                // we can not convert them into aliases as they are ambiguous
214
                continue;
215
            }
216
            $name = str_replace('\\', '/', trim($name, '\\'));
217
            $path = $this->preparePath($package, $path);
218
            $path = $this->substitutePath($path, $this->getBaseDir(), self::BASE_DIR_ALIAS);
219
            if ('psr-0' === $psr) {
220
                $path .= '/' . $name;
221
            }
222
            $aliases["@$name"] = $path;
223
        }
224
225
        return $aliases;
226
    }
227
228
    /**
229
     * Substitute path with alias if applicable.
230
     * @param string $path
231
     * @param string $dir
232
     * @param string $alias
233
     * @return string
234
     */
235
    public function substitutePath($path, $dir, $alias)
236
    {
237
        return (substr($path, 0, strlen($dir) + 1) === $dir . '/') ? $alias . substr($path, strlen($dir)) : $path;
238
    }
239
240
    public function preparePath(PackageInterface $package, $path)
241
    {
242
        if (!$this->getFilesystem()->isAbsolutePath($path)) {
243
            $prefix = $package instanceof RootPackageInterface ? $this->getBaseDir() : $this->getVendorDir() . '/' . $package->getPrettyName();
244
            $path = $prefix . '/' . $path;
245
        }
246
247
        return $this->getFilesystem()->normalizePath($path);
248
    }
249
250
    /**
251
     * Sets [[packages]].
252
     * @param PackageInterface[] $packages
253
     * @void
254
     */
255 2
    public function setPackages(array $packages)
256
    {
257 2
        $this->packages = $packages;
258 2
    }
259
260
    /**
261
     * Gets [[packages]].
262
     * @return \Composer\Package\PackageInterface[]
263
     */
264 1
    public function getPackages()
265
    {
266 1
        if ($this->packages === null) {
267
            $this->packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
268
        }
269
270 1
        return $this->packages;
271
    }
272
273
    /**
274
     * Get absolute path to package base dir.
275
     * @return string
276
     */
277
    public function getBaseDir()
278
    {
279
        if ($this->baseDir === null) {
280
            $this->baseDir = dirname($this->getVendorDir());
281
        }
282
283
        return $this->baseDir;
284
    }
285
286
    /**
287
     * Get absolute path to composer vendor dir.
288
     * @return string
289
     */
290
    public function getVendorDir()
291
    {
292
        if ($this->vendorDir === null) {
293
            $dir = $this->composer->getConfig()->get('vendor-dir', '/');
294
            $this->vendorDir = $this->getFilesystem()->normalizePath($dir);
295
        }
296
297
        return $this->vendorDir;
298
    }
299
300
    /**
301
     * Getter for filesystem utility.
302
     * @return Filesystem
303
     */
304
    public function getFilesystem()
305
    {
306
        if ($this->filesystem === null) {
307
            $this->filesystem = new Filesystem();
308
        }
309
310
        return $this->filesystem;
311
    }
312
}
313