Completed
Push — develop ( dc4ca1...3dd7cf )
by Tom
05:25
created

ScriptLoader::init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 17
rs 9.4285
cc 1
eloc 6
nc 1
nop 0
1
<?php
2
3
namespace N98\Magento\Command\Script\Repository;
4
5
use N98\Util\OperatingSystem;
6
use Symfony\Component\Finder\Finder;
7
use Symfony\Component\Finder\SplFileInfo;
8
9
final class ScriptLoader
10
{
11
    const LOCATION_PROJECT = 'project';
12
    const LOCATION_PERSONAL = 'personal';
13
    const LOCATION_MODULE = 'module';
14
    const LOCATION_SYSTEM = 'system';
15
16
    const BASENAME_MODULES = 'modules';
17
    const BASENAME_SCRIPTS = 'scripts';
18
19
    /**
20
     * @var string basename, e.g. "n98-magerun2"
21
     */
22
    private $basename;
23
24
    /**
25
     * @var string magento root folder
26
     */
27
    private $magentoRootFolder;
28
29
    /**
30
     * @var array collection of folders, key is the folder (normalized), value is the type of it
31
     */
32
    private $folders;
33
34
    /**
35
     * @var array
36
     */
37
    private $scriptFolders;
38
39
    /**
40
     * @var array
41
     */
42
    private $scriptFiles;
43
44
    /**
45
     * @param array $scriptFolders provided from the config file (config: script.folders, see YAML for details)
46
     * @param string $basename
47
     * @param string $magentoRootFolder
48
     */
49
    public function __construct(array $scriptFolders, $basename, $magentoRootFolder)
50
    {
51
        $this->basename = $basename;
52
        $this->magentoRootFolder = $magentoRootFolder;
53
        $this->scriptFolders = $scriptFolders;
54
    }
55
56
    /**
57
     * initialize folders by type. folders is the main concept of this class
58
     */
59
    private function init()
60
    {
61
        // add Magento root folder
62
        $this->addFolder(self::LOCATION_PROJECT, $this->magentoRootFolder);
63
64
        // add home-dir folder
65
        $this->addPersonalFolder();
66
67
        // add Magerun config folders
68
        $this->addFolders(self::LOCATION_SYSTEM, $this->scriptFolders);
69
70
        // remove folders again which do not exist
71
        $this->filterInvalidFolders();
72
73
        // finally find all *.magerun scripts in so far initialized folders
74
        $this->scriptFiles = $this->findScriptFiles();
75
    }
76
77
    /**
78
     * @param string $location
79
     * @param string $path
80
     */
81
    private function addFolder($location, $path)
82
    {
83
        $normalized = rtrim($path, '/');
84
        $this->folders[$normalized] = $location;
85
    }
86
87
    /**
88
     * Add home-dir folder(s), these are multiple on windows due to backwards compatibility
89
     */
90
    private function addPersonalFolder()
91
    {
92
        $basename = $this->basename;
93
        $homeDir = OperatingSystem::getHomeDir();
94
95
        if (false !== $homeDir) {
96
            if (OperatingSystem::isWindows()) {
97
                $this->addFolder(self::LOCATION_PERSONAL, $homeDir . '/' . $basename . '/' . self::BASENAME_SCRIPTS);
98
            }
99
            $this->addFolder(self::LOCATION_PERSONAL, $homeDir . '/.' . $basename . '/' . self::BASENAME_SCRIPTS);
100
        }
101
102
    }
103
104
    /**
105
     * @param string $location
106
     * @param array $paths
107
     */
108
    private function addFolders($location, array $paths)
109
    {
110
        foreach ($paths as $path) {
111
            $this->addFolder($location, $path);
112
        }
113
    }
114
115
    private function filterInvalidFolders()
116
    {
117
        foreach ($this->folders as $path => $type) {
118
            if (!is_dir($path)) {
119
                unset($this->folders[$path]);
120
            }
121
        }
122
    }
123
124
    private function getFolderPaths()
125
    {
126
        return array_keys($this->folders);
127
    }
128
129
    /**
130
     * @return array
131
     */
132
    private function findScriptFiles()
133
    {
134
        $scriptFiles = array();
135
136
        $folders = $this->getFolderPaths();
137
138
        if (!$folders) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $folders of type array<integer|string> 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...
139
            return $scriptFiles;
140
        }
141
142
        $finder = $this->createScriptFilesFinder($folders);
143
144
        foreach ($finder as $file) {
145
            $filename = $file->getFilename();
146
            $scriptFiles[$filename] = $this->createScriptFile($file);
147
        }
148
149
        ksort($scriptFiles, SORT_STRING);
150
151
        return $scriptFiles;
152
    }
153
154
    /**
155
     * @param array $folders to search for magerun scripts in
156
     * @return Finder|\Symfony\Component\Finder\SplFileInfo[]
157
     */
158
    private function createScriptFilesFinder(array $folders)
159
    {
160
        /* @var $finder Finder */
161
        $finder = Finder::create()
162
            ->files()
163
            ->followLinks(true)
0 ignored issues
show
Unused Code introduced by
The call to Finder::followLinks() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
164
            ->ignoreUnreadableDirs(true)
165
            ->name('*' . AbstractRepositoryCommand::MAGERUN_EXTENSION)
166
            ->in($folders);
167
168
        return $finder;
169
    }
170
171
    private function createScriptFile(SplFileInfo $file)
172
    {
173
        $pathname = $file->getPathname();
174
175
        $scriptFile = array(
176
            'fileinfo'    => $file,
177
            'description' => $this->readDescriptionFromFile($pathname),
178
            'location'    => $this->getLocation($pathname),
179
        );
180
181
        return $scriptFile;
182
    }
183
184
    /**
185
     * Reads the first line. If it's a comment return it.
186
     *
187
     * @param string $file
188
     *
189
     * @return string comment text or zero-length string if no comment was given
190
     */
191
    private function readDescriptionFromFile($file)
192
    {
193
        $line = $this->readFirstLineOfFile($file);
194
        if (null === $line) {
195
            return '';
196
        }
197
198
        if (isset($line[0]) && $line[0] != '#') {
199
            return '';
200
        }
201
202
        return trim(substr($line, 1));
203
    }
204
205
    /**
206
     * Read first line of a file w/o line-separators and end-of-line whitespace
207
     *
208
     * @param string $file
209
     *
210
     * @return string|null first line or null, if it was not possible to obtain a first line
211
     */
212
    private function readFirstLineOfFile($file)
213
    {
214
        $handle = @fopen($file, 'r');
215
        if (!$handle) {
216
            return null;
217
        }
218
219
        $buffer = fgets($handle);
220
        fclose($handle);
221
222
        if (false === $file) {
223
            return null;
224
        }
225
226
        return rtrim($buffer);
227
    }
228
229
    /**
230
     * @param string $pathname
231
     *
232
     * @return string
233
     */
234
    private function getLocation($pathname)
235
    {
236
        if (null !== $location = $this->detectLocationViaFolderByPathname($pathname)) {
237
            return $location;
238
        }
239
240
        if (null !== $location = $this->detecLocationModuleByPathname($pathname)) {
241
            return $location;
242
        }
243
244
        return self::LOCATION_SYSTEM;
245
    }
246
247
    /**
248
     * private helper function to detect type by pathname with the help of initialized folders
249
     *
250
     * @see init()
251
     *
252
     * @param $pathname
253
     * @return mixed|null
254
     */
255
    private function detectLocationViaFolderByPathname($pathname)
256
    {
257
        foreach ($this->folders as $path => $type) {
258
            if (0 === strpos($pathname, $path . '/')) {
259
                return $type;
260
            }
261
        }
262
263
        return null;
264
    }
265
266
    /**
267
     * private helper function to detect if a script is from within a module
268
     *
269
     * @param $pathname
270
     * @return null|string
271
     */
272
    private function detecLocationModuleByPathname($pathname)
273
    {
274
        $pos = strpos($pathname, '/' . $this->basename . '/' . self::BASENAME_MODULES . '/');
275
        if (false !== $pos) {
276
            return $this::LOCATION_MODULE;
277
        }
278
279
        return null;
280
    }
281
282
    /**
283
     * @return array
284
     */
285
    public function getFiles()
286
    {
287
        if (null === $this->scriptFiles) {
288
            $this->init();
289
        }
290
291
        return $this->scriptFiles;
292
    }
293
}
294