Completed
Push — master ( 239a7d...29ee36 )
by Tom
9s
created

MagentoHelper::writeDebug()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
3
namespace N98\Util\Console\Helper;
4
5
use ArrayIterator;
6
use CallbackFilterIterator;
7
use N98\Magento\Application;
8
use N98\Magento\Application\DetectionResultInterface;
9
use RuntimeException;
10
use Symfony\Component\Console\Helper\Helper as AbstractHelper;
11
use Symfony\Component\Console\Input\ArgvInput;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Output\ConsoleOutput;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\Finder\Finder;
16
use UnexpectedValueException;
17
18
/**
19
 * Class MagentoHelper
20
 *
21
 * @package N98\Util\Console\Helper
22
 */
23
class MagentoHelper extends AbstractHelper implements DetectionResultInterface
24
{
25
    /**
26
     * @var string
27
     */
28
    protected $_magentoRootFolder = null;
29
30
    /**
31
     * @var int
32
     */
33
    protected $_magentoMajorVersion = \N98\Magento\Application::MAGENTO_MAJOR_VERSION_1;
34
35
    /**
36
     * @var bool
37
     */
38
    protected $_magentoEnterprise = false;
39
40
    /**
41
     * @var bool
42
     */
43
    protected $_magerunStopFileFound = false;
44
45
    /**
46
     * @var string
47
     */
48
    protected $_magerunStopFileFolder = null;
49
50
    /**
51
     * @var InputInterface
52
     */
53
    protected $input;
54
55
    /**
56
     * @var OutputInterface
57
     */
58
    protected $output;
59
60
    /**
61
     * @var array
62
     */
63
    protected $baseConfig = array();
64
65
    /**
66
     * @var string
67
     */
68
    protected $_customConfigFilename = 'n98-magerun2.yaml';
69
70
    /**
71
     * Returns the canonical name of this helper.
72
     *
73
     * @return string The canonical name
74
     *
75
     * @api
76
     */
77
    public function getName()
78
    {
79
        return 'magento';
80
    }
81
82
    /**
83
     * @param InputInterface $input
84
     * @param OutputInterface $output
0 ignored issues
show
Documentation introduced by
Should the type for parameter $output not be null|OutputInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
85
     */
86
    public function __construct(InputInterface $input = null, OutputInterface $output = null)
87
    {
88
        if (null === $input) {
89
            $input = new ArgvInput();
90
        }
91
92
        if (null === $output) {
93
            $output = new ConsoleOutput();
94
        }
95
96
        $this->input = $input;
97
        $this->output = $output;
98
    }
99
100
    /**
101
     * Start Magento detection
102
     *
103
     * @param string $folder
104
     * @param array $subFolders [optional] sub-folders to check
105
     * @return bool
106
     */
107
    public function detect($folder, array $subFolders = array())
108
    {
109
        $folders = $this->splitPathFolders($folder);
110
        $folders = $this->checkMagerunFile($folders);
111
        $folders = $this->checkModman($folders);
112
        $folders = array_merge($folders, $subFolders);
113
114
        foreach (array_reverse($folders) as $searchFolder) {
115
            if (!is_dir($searchFolder) || !is_readable($searchFolder)) {
116
                continue;
117
            }
118
119
            $found = $this->_search($searchFolder);
120
            if ($found) {
121
                return true;
122
            }
123
        }
124
125
        return false;
126
    }
127
128
    /**
129
     * @return string
130
     */
131
    public function getRootFolder()
132
    {
133
        return $this->_magentoRootFolder;
134
    }
135
136
    /**
137
     * @return bool
138
     */
139
    public function isEnterpriseEdition()
140
    {
141
        return $this->_magentoEnterprise;
142
    }
143
144
    /**
145
     * @return int
146
     */
147
    public function getMajorVersion()
148
    {
149
        return $this->_magentoMajorVersion;
150
    }
151
152
    /**
153
     * @return boolean
154
     */
155
    public function isMagerunStopFileFound()
156
    {
157
        return $this->_magerunStopFileFound;
158
    }
159
160
    /**
161
     * @return string
162
     */
163
    public function getMagerunStopFileFolder()
164
    {
165
        return $this->_magerunStopFileFolder;
166
    }
167
168
    /**
169
     * @param string $folder
170
     *
171
     * @return array
172
     */
173
    protected function splitPathFolders($folder)
174
    {
175
        $folders = array();
176
177
        $folderParts = explode('/', $folder);
178
        foreach ($folderParts as $key => $part) {
179
            $explodedFolder = implode('/', array_slice($folderParts, 0, $key + 1));
180
            if ($explodedFolder !== '') {
181
                $folders[] = $explodedFolder;
182
            }
183
        }
184
185
        return $folders;
186
    }
187
188
    /**
189
     * Check for modman file and .basedir
190
     *
191
     * @param array $folders
192
     *
193
     * @return array
194
     */
195
    protected function checkModman(array $folders)
196
    {
197
        foreach ($this->searchFolders($folders) as $searchFolder) {
198
            $finder = Finder::create();
199
            $finder
200
                ->files()
201
                ->ignoreUnreadableDirs(true)
202
                ->depth(0)
203
                ->followLinks()
204
                ->ignoreDotFiles(false)
205
                ->name('.basedir')
206
                ->in($searchFolder);
207
208
            $count = $finder->count();
209
            if ($count > 0) {
210
                $baseFolderContent = trim(file_get_contents($searchFolder . '/.basedir'));
211
                $this->writeDebug('Found modman .basedir file with content <info>' . $baseFolderContent . '</info>');
212
213
                if (!empty($baseFolderContent)) {
214
                    array_push($folders, $searchFolder . '/../' . $baseFolderContent);
215
                }
216
            }
217
        }
218
219
        return $folders;
220
    }
221
222
    /**
223
     * Check for magerun stop-file
224
     *
225
     * @param array $folders
226
     *
227
     * @return array
228
     */
229
    protected function checkMagerunFile(array $folders)
230
    {
231
        $stopFile = '.' . pathinfo($this->_customConfigFilename, PATHINFO_FILENAME);
232
233
        foreach ($this->searchFolders($folders) as $searchFolder) {
234
            $magerunFilePath = $searchFolder . '/' . $stopFile;
235
            if (is_link($magerunFilePath) && !file_exists($magerunFilePath)) {
236
                throw new \RuntimeException(
237
                    sprintf("Stopfile is broken symlink: '%s'", $magerunFilePath),
238
                    2
239
                );
240
            }
241
            if (!is_readable($magerunFilePath) || !is_file($magerunFilePath)) {
242
                continue;
243
            }
244
            $this->_magerunStopFileFound = true;
245
            $this->_magerunStopFileFolder = $searchFolder;
246
            $magerunFileContent = trim(file_get_contents($magerunFilePath));
247
            $message = sprintf(
248
                'Found stopfile \'%s\' file with content <info>%s</info> from \'%s\'',
249
                $stopFile,
250
                $magerunFileContent,
251
                $searchFolder
252
            );
253
            $this->writeDebug($message);
254
255
            array_push($folders, $searchFolder . '/' . $magerunFileContent);
256
            break;
257
        }
258
259
        return $folders;
260
    }
261
262
    /**
263
     * Turn an array of folders into a Traversable of readable paths.
264
     *
265
     * @param array $folders
266
     * @return CallbackFilterIterator Traversable of strings that are readable paths
267
     */
268
    private function searchFolders(array $folders)
269
    {
270
        $that = $this;
271
272
        $callback = function ($searchFolder) use ($that) {
273
            if (!is_readable($searchFolder)) {
274
                $that->writeDebug('Folder <info>' . $searchFolder . '</info> is not readable. Skip.');
275
276
                return false;
277
            }
278
279
            return true;
280
        };
281
282
        return new CallbackFilterIterator(
283
            new ArrayIterator(array_reverse($folders)),
284
            $callback
285
        );
286
    }
287
288
    /**
289
     * @param string $searchFolder
290
     *
291
     * @return bool
292
     */
293
    protected function _search($searchFolder)
294
    {
295
        $this->writeDebug('Search for Magento in folder <info>' . $searchFolder . '</info>');
296
297
        if (!is_dir($searchFolder . '/app')) {
298
            return false;
299
        }
300
301
        $finder = Finder::create();
302
        $finder
303
            ->ignoreUnreadableDirs(true)
304
            ->depth(0)
305
            ->followLinks()
306
            ->name('Mage.php')
307
            ->name('bootstrap.php')
308
            ->name('autoload.php')
309
            ->in($searchFolder . '/app');
310
311
        if ($finder->count() > 0) {
312
            $files = iterator_to_array($finder, false);
313
            /* @var $file \SplFileInfo */
314
315
            $hasMageFile = false;
316
            foreach ($files as $file) {
317
                if ($file->getFilename() == 'Mage.php') {
318
                    $hasMageFile = true;
319
                }
320
            }
321
322
            $this->_magentoRootFolder = $searchFolder;
323
324
            // Magento 2 does not have a god class and thus if this file is not there it is version 2
325
            if ($hasMageFile == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
326
                $this->_magentoMajorVersion = Application::MAGENTO_MAJOR_VERSION_2;
327
            } else {
328
                $this->_magentoMajorVersion = Application::MAGENTO_MAJOR_VERSION_1;
329
            }
330
331
            $this->writeDebug(
332
                sprintf(
333
                    'Found Magento <info> v%d </info> in folder <info>%s</info>',
334
                    $this->_magentoMajorVersion,
335
                    $this->_magentoRootFolder
336
                )
337
            );
338
339
            return true;
340
        }
341
342
        return false;
343
    }
344
345
    /**
346
     * @return array
347
     * @throws RuntimeException
348
     */
349
    public function getBaseConfig()
350
    {
351
        if (!$this->baseConfig) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->baseConfig 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...
352
            $this->initBaseConfig();
353
        }
354
355
        return $this->baseConfig;
356
    }
357
358
    private function initBaseConfig()
359
    {
360
        $this->baseConfig = [];
361
362
        $application = $this->getApplication();
363
364
        $configFiles = [
365
            'app/etc/config.php',
366
            'app/etc/env.php',
367
        ];
368
369
        foreach ($configFiles as $configFileName) {
370
            $this->addBaseConfig($application->getMagentoRootFolder(), $configFileName);
371
        }
372
    }
373
374
    /**
375
     * private getter for application that has magento detected
376
     *
377
     * @return Application
378
     */
379
    private function getApplication()
380
    {
381
        $command = $this->getHelperSet()->getCommand();
382
383
        $application = $command ? $command->getApplication() : new Application();
384
385
        // verify type because of detectMagento() call below
386
        if (!$application instanceof Application) {
387
            throw new UnexpectedValueException(
388
                sprintf('Expected magerun application got %s', get_class($application))
389
            );
390
        }
391
392
        $application->detectMagento();
393
394
        return $application;
395
    }
396
397
    /**
398
     * @param string $root
399
     * @param string $configFileName
400
     */
401
    private function addBaseConfig($root, $configFileName)
402
    {
403
        $configFile = $root . '/' . $configFileName;
404
        if (!(is_file($configFile) && is_readable($configFile))) {
405
            throw new RuntimeException(sprintf('%s is not readable', $configFileName));
406
        }
407
408
        $config = @include $configFile;
409
410
        if (!is_array($config)) {
411
            throw new RuntimeException(sprintf('%s is corrupted. Please check it.', $configFileName));
412
        }
413
414
        $this->baseConfig = array_merge($this->baseConfig, $config);
415
    }
416
417
    /**
418
     * @param string $message
419
     * @return void
420
     */
421
    private function writeDebug($message)
422
    {
423
        if (OutputInterface::VERBOSITY_DEBUG <= $this->output->getVerbosity()) {
424
            $this->output->writeln(
425
                '<debug>' . $message . '</debug>'
426
            );
427
        }
428
    }
429
}
430