Completed
Push — master ( 8be8b4...c2dd3a )
by Tom
10:49 queued 05:44
created

ScriptLoader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 3
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
     * @param string $location
105
     * @param array $paths
106
     */
107
    private function addFolders($location, array $paths)
108
    {
109
        foreach ($paths as $path) {
110
            $this->addFolder($location, $path);
111
        }
112
    }
113
114
    private function filterInvalidFolders()
115
    {
116
        foreach ($this->folders as $path => $type) {
117
            if (!is_dir($path)) {
118
                unset($this->folders[$path]);
119
            }
120
        }
121
    }
122
123
    private function getFolderPaths()
124
    {
125
        return array_keys($this->folders);
126
    }
127
128
    /**
129
     * @return array
130
     */
131
    private function findScriptFiles()
132
    {
133
        $scriptFiles = array();
134
135
        $folders = $this->getFolderPaths();
136
137
        if (!$folders) {
138
            return $scriptFiles;
139
        }
140
141
        $finder = $this->createScriptFilesFinder($folders);
142
143
        foreach ($finder as $file) {
144
            $filename = $file->getFilename();
145
            $scriptFiles[$filename] = $this->createScriptFile($file);
146
        }
147
148
        ksort($scriptFiles, SORT_STRING);
149
150
        return $scriptFiles;
151
    }
152
153
    /**
154
     * @param array $folders to search for magerun scripts in
155
     * @return Finder|\Symfony\Component\Finder\SplFileInfo[]
156
     */
157
    private function createScriptFilesFinder(array $folders)
158
    {
159
        /* @var $finder Finder */
160
        $finder = Finder::create()
161
            ->files()
162
            ->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...
163
            ->ignoreUnreadableDirs(true)
164
            ->name('*' . AbstractRepositoryCommand::MAGERUN_EXTENSION)
165
            ->in($folders);
166
167
        return $finder;
168
    }
169
170
    private function createScriptFile(SplFileInfo $file)
171
    {
172
        $pathname = $file->getPathname();
173
174
        $scriptFile = array(
175
            'fileinfo'    => $file,
176
            'description' => $this->readDescriptionFromFile($pathname),
177
            'location'    => $this->getLocation($pathname),
178
        );
179
180
        return $scriptFile;
181
    }
182
183
    /**
184
     * Reads the first line. If it's a comment return it.
185
     *
186
     * @param string $file
187
     *
188
     * @return string comment text or zero-length string if no comment was given
189
     */
190
    private function readDescriptionFromFile($file)
191
    {
192
        $line = $this->readFirstLineOfFile($file);
193
        if (null === $line) {
194
            return '';
195
        }
196
197
        if (isset($line[0]) && $line[0] != '#') {
198
            return '';
199
        }
200
201
        return trim(substr($line, 1));
202
    }
203
204
    /**
205
     * Read first line of a file w/o line-separators and end-of-line whitespace
206
     *
207
     * @param string $file
208
     *
209
     * @return string|null first line or null, if it was not possible to obtain a first line
210
     */
211
    private function readFirstLineOfFile($file)
212
    {
213
        $handle = @fopen($file, 'r');
214
        if (!$handle) {
215
            return null;
216
        }
217
218
        $buffer = fgets($handle);
219
        fclose($handle);
220
221
        if (false === $file) {
222
            return null;
223
        }
224
225
        return rtrim($buffer);
226
    }
227
228
    /**
229
     * @param string $pathname
230
     *
231
     * @return string
232
     */
233
    private function getLocation($pathname)
234
    {
235
        if (null !== $location = $this->detectLocationViaFolderByPathname($pathname)) {
236
            return $location;
237
        }
238
239
        if (null !== $location = $this->detecLocationModuleByPathname($pathname)) {
240
            return $location;
241
        }
242
243
        return self::LOCATION_SYSTEM;
244
    }
245
246
    /**
247
     * private helper function to detect type by pathname with the help of initialized folders
248
     *
249
     * @see init()
250
     *
251
     * @param string $pathname
252
     * @return mixed|null
253
     */
254
    private function detectLocationViaFolderByPathname($pathname)
255
    {
256
        foreach ($this->folders as $path => $type) {
257
            if (0 === strpos($pathname, $path . '/')) {
258
                return $type;
259
            }
260
        }
261
262
        return null;
263
    }
264
265
    /**
266
     * private helper function to detect if a script is from within a module
267
     *
268
     * @param string $pathname
269
     * @return null|string
270
     */
271
    private function detecLocationModuleByPathname($pathname)
272
    {
273
        $pos = strpos($pathname, '/' . $this->basename . '/' . self::BASENAME_MODULES . '/');
274
        if (false !== $pos) {
275
            return $this::LOCATION_MODULE;
276
        }
277
278
        return null;
279
    }
280
281
    /**
282
     * @return array
283
     */
284
    public function getFiles()
285
    {
286
        if (null === $this->scriptFiles) {
287
            $this->init();
288
        }
289
290
        return $this->scriptFiles;
291
    }
292
}
293