Completed
Push — master ( b6a56e...67d7d0 )
by Sergi Tur
03:06
created

LlumCommand::sqlite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
namespace Acacha\Llum\Console;
4
5
use Illuminate\Config\Repository;
6
use Symfony\Component\Console\Command\Command;
7
use Symfony\Component\Console\Input\InputArgument;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use Symfony\Component\Process\Process;
11
12
/**
13
 * Class LlumCommand.
14
 */
15
abstract class LlumCommand extends Command
16
{
17
    /**
18
     * The output interface.
19
     *
20
     * @var OutputInterface
21
     */
22
    protected $output;
23
24
    /**
25
     * Command name.
26
     *
27
     * @var string
28
     */
29
    protected $commandName;
30
31
    /**
32
     * Command description.
33
     *
34
     * @var string
35
     */
36
    protected $commandDescription;
37
38
    /**
39
     * Command argument.
40
     *
41
     * @var string
42
     */
43
    protected $argument;
44
45
    /**
46
     * Argument type.
47
     *
48
     * @var int
49
     */
50
    protected $argumentType = InputArgument::REQUIRED;
51
52
    /**
53
     * Command argument description.
54
     *
55
     * @var string
56
     */
57
    protected $argumentDescription;
58
59
    /**
60
     * Method to execute.
61
     *
62
     * @var string
63
     */
64
    protected $method;
65
66
    /**
67
     * Laravel config file (config/app.php).
68
     *
69
     * @var string
70
     */
71
    protected $laravel_config_file;
72
73
    /**
74
     * Path to config folder.
75
     *
76
     * @var string
77
     */
78
    protected $configPath;
79
80
    /**
81
     * Avoids using bash using stubs instead to modify config/app.php file.
82
     *
83
     * @var bool
84
     */
85
    protected $noBash = false;
86
87
    /**
88
     * Config repository.
89
     *
90
     * @var Repository
91
     */
92
    protected $config;
93
94
    /**
95
     * Executes boot command.
96
     */
97
    protected function boot()
98
    {
99
        $this->devtools();
100
        $this->touchSqliteFile();
101
        $this->configEnv();
102
        $this->migrate();
103
        $this->serve();
104
    }
105
106
    /**
107
     * LlumCommand constructor.
108
     */
109
    public function __construct()
110
    {
111
        parent::__construct();
112
        $this->configPath = __DIR__.'/../config/';
113
        $this->laravel_config_file = getcwd().'/config/app.php';
114
        $this->config = $this->obtainConfig();
115
    }
116
117
    /**
118
     * Touch sqlite database file.
119
     *
120
     * @param string $file
121
     */
122
    protected function touchSqliteFile($file = 'database/database.sqlite')
123
    {
124
        $this->touchFile($file);
125
    }
126
127
    /**
128
     * Touch a file.
129
     * @param string $file
130
     */
131
    protected function touchFile($file)
132
    {
133
        passthru('touch '.$file, $error);
134
        if ($error !== 0) {
135
            $this->output->writeln('<error>Error creating file'.$file.'</error>');
136
        } else {
137
            $this->output->writeln('<info>File '.$file.' created successfully</info>');
138
        }
139
    }
140
141
    /**
142
     * Config .env file.
143
     */
144
    protected function configEnv()
145
    {
146
        passthru('sed -i \'s/^DB_/#DB_/g\' .env ', $error);
147
        if ($error !== 0) {
148
            $this->output->writeln('<error>Error commenting DB_ entries in .env file </error>');
149
        }
150
        passthru('sed -i \'s/.*DB_HOST.*/DB_CONNECTION=sqlite\n&/\' .env', $error);
151
        if ($error !== 0) {
152
            $this->output->writeln('<error>Error adding DB_CONNECTION=sqlite to .env file </error>');
153
        } else {
154
            $this->output->writeln('.env file updated successfully');
155
        }
156
    }
157
158
    /**
159
     * sqlite command.
160
     */
161
    public function sqlite()
162
    {
163
        $this->touchSqliteFile();
164
        $this->configEnv();
165
    }
166
167
    /**
168
     * Serve command.
169
     *
170
     * @param int $port
171
     */
172
    protected function serve($port = 8000)
173
    {
174
        $continue = true;
175
        do {
176
            if ($this->check_port($port)) {
177
                $this->output->writeln('<info>Running php artisan serve --port='.$port.'</info>');
178
                exec('php artisan serve --port='.$port.' > /dev/null 2>&1 &');
179
                sleep(1);
180
                if (file_exists('/usr/bin/sensible-browser')) {
181
                    $this->output->writeln('<info>Opening http://localhost:'.$port.' with default browser</info>');
182
                    passthru('/usr/bin/sensible-browser http://localhost:'.$port);
183
                }
184
                $continue = false;
185
            }
186
            ++$port;
187
        } while ($continue);
188
    }
189
190
    /**
191
     * Check if port is in use.
192
     *
193
     * @param int    $port
194
     * @param string $host
195
     * @param int    $timeout
196
     *
197
     * @return bool
198
     */
199
    protected function check_port($port = 8000, $host = '127.0.0.1', $timeout = 3)
200
    {
201
        $fp = @fsockopen($host, $port, $errno, $errstr, $timeout);
202
        if (! $fp) {
203
            return true;
204
        } else {
205
            fclose($fp);
206
207
            return false;
208
        }
209
    }
210
211
    /**
212
     * Install /config/app.php file using bash script.
213
     */
214
    protected function installConfigAppFileWithBash()
215
    {
216
        passthru(__DIR__.'/../bash_scripts/iluminar.sh '.$this->laravel_config_file);
217
    }
218
219
    /**
220
     * Install /stubs/app.php into /config/app.php.
221
     */
222
    protected function installConfigAppFileWithStubs()
223
    {
224
        copy(__DIR__.'/stubs/app.php', $this->laravel_config_file);
225
    }
226
227
    /**
228
     * Check if Laravel config file exists.
229
     *
230
     * @return bool
231
     */
232
    protected function checkIfLaravelConfigFileExists()
233
    {
234
        return file_exists($this->laravel_config_file);
235
    }
236
237
    /**
238
     * Install llum custom config/app.php file.
239
     *
240
     * @return int
241
     */
242
    protected function installConfigAppFile()
243
    {
244
        if (! $this->checkIfLaravelConfigFileExists()) {
245
            $this->output->writeln('<error>File '.$this->laravel_config_file.' doesn\'t exists');
246
247
            return -1;
248
        }
249
250
        if ($this->configAppFileAlreadyInstalled()) {
251
            $this->output->writeln('<info>File '.$this->laravel_config_file.' already supports llum.</info>');
252
253
            return 0;
254
        }
255
256
        if ($this->isNoBashActive()) {
257
            $this->installConfigAppFileWithStubs();
258
            $this->output->writeln('<info>File '.$this->laravel_config_file.' overwrited correctly with and stub.</info>');
259
        } else {
260
            $this->installConfigAppFileWithBash();
261
        }
262
263
        return 0;
264
    }
265
266
    /**
267
     * Check if config/app.php stub file is already installed.
268
     *
269
     * @return bool
270
     */
271
    protected function configAppFileAlreadyInstalled()
272
    {
273
        if (strpos(file_get_contents($this->laravel_config_file), '#llum_providers') !== false) {
274
            return true;
275
        }
276
277
        return false;
278
    }
279
280
    /**
281
     *  Install Laravel ide helper package.
282
     */
283
    protected function idehelper()
284
    {
285
        $this->package('barryvdh/laravel-ide-helper');
286
    }
287
288
    /**
289
     * Install Laravel debugbar package.
290
     */
291
    protected function debugbar()
292
    {
293
        $this->package('barryvdh/laravel-debugbar');
294
    }
295
296
    /**
297
     * Execute devtools command.
298
     */
299
    protected function devtools()
300
    {
301
        $this->idehelper();
302
        $this->debugbar();
303
    }
304
305
    /**
306
     * Add Laravel IDE Helper provider to config/app.php file.
307
     *
308
     * @return int|null
309
     */
310
    protected function addLaravelIdeHelperProvider()
311
    {
312
        return $this->addProvider('Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class');
313
    }
314
315
    /**
316
     *  Add provider to config/app.php file.
317
     *
318
     * @param $provider
319
     *
320
     * @return int|null
321
     */
322
    private function addProvider($provider)
323
    {
324
        return $this->addTextIntoMountPoint('#llum_providers', $provider);
325
    }
326
327
    /**
328
     * Add alias to config/app.php file.
329
     *
330
     * @param string $alias
331
     *
332
     * @return int|null
333
     */
334
    private function addAlias($alias)
335
    {
336
        return $this->addTextIntoMountPoint('#llum_aliases', $alias);
337
    }
338
339
    /**
340
     * Insert text into file using mountpoint. Mountpoint is maintained at file.
341
     *
342
     * @param string $mountpoint
343
     * @param $textToAdd
344
     *
345
     * @return int|null
346
     */
347
    private function addTextIntoMountPoint($mountpoint, $textToAdd)
348
    {
349
        passthru(
350
            'sed -i \'s/.*'.$mountpoint.'.*/ \ \ \ \ \ \ \ '.$this->scapeSingleQuotes(preg_quote($textToAdd)).',\n \ \ \ \ \ \ \ '.$mountpoint.'/\' '.$this->laravel_config_file, $error);
351
352
        return $error;
353
    }
354
355
    /**
356
     * scape single quotes for sed using \x27.
357
     *
358
     * @param string $str
359
     *
360
     * @return mixed
361
     */
362
    private function scapeSingleQuotes($str)
363
    {
364
        return str_replace("'", '\\x27', $str);
365
    }
366
367
    /**
368
     * Require composer package.
369
     *
370
     * @param $package
371
     */
372
    private function requireComposerPackage($package)
373
    {
374
        $composer = $this->findComposer();
375
376
        $process = new Process($composer.' require '.$package.'', null, null, null, null);
377
        $this->output->writeln('<info>Running composer require '.$package.'</info>');
378
        $process->run(function ($type, $line) {
379
            $this->output->write($line);
380
        });
381
    }
382
383
    /**
384
     * Get the composer command for the environment.
385
     *
386
     * @return string
387
     */
388
    private function findComposer()
389
    {
390
        if (file_exists(getcwd().'/composer.phar')) {
391
            return '"'.PHP_BINARY.'" composer.phar"';
392
        }
393
394
        return 'composer';
395
    }
396
397
    /**
398
     * Migrate database with php artisan migrate.
399
     */
400
    protected function migrate()
401
    {
402
        $this->output->writeln('<info>Running php artisan migrate...</info>');
403
        passthru('php artisan migrate');
404
    }
405
406
    /**
407
     * Installs provider in laravel config/app.php file.
408
     *
409
     * @param $provider
410
     */
411
    protected function provider($provider)
412
    {
413
        if ($this->installConfigAppFile() == -1) {
414
            return;
415
        }
416
        $this->addProvider($provider);
417
    }
418
419
    /**
420
     * Installs alias/facade in laravel config/app.php file.
421
     *
422
     * @param $aliasName
423
     * @param $aliasClass
424
     */
425
    protected function alias($aliasName, $aliasClass)
426
    {
427
        if ($this->installConfigAppFile() == -1) {
428
            return;
429
        }
430
        $this->addAlias("'".$aliasName."' => ".$aliasClass);
431
    }
432
433
    /**
434
     * Shows list of supported packages.
435
     */
436
    protected function packageList()
437
    {
438
        $packages = $this->config->all();
439
        foreach ($packages as $name => $package) {
440
            $this->output->writeln('<info>'.$name.'</info> | '.$this->parsePackageInfo($package));
441
        }
442
    }
443
444
    /**
445
     * Parse package info.
446
     *
447
     * @param $package
448
     *
449
     * @return string
450
     */
451
    private function parsePackageInfo($package)
452
    {
453
        return 'Composer name: '.$package[ 'name' ];
454
    }
455
456
    /**
457
     * get package from config.
458
     *
459
     * @param $name
460
     *
461
     * @return array
462
     */
463
    private function getPackageFromConfig($name)
464
    {
465
        //Check if package name is a composer package name
466
        if (str_contains($name, '/')) {
467
            return $this->config->get($this->getPackageNameByComposerName($name));
468
        }
469
470
        return $this->config->get($name);
471
    }
472
473
    /**
474
     * Add providers to Laravel config file.
475
     *
476
     * @param $providers
477
     */
478
    protected function addProviders($providers)
479
    {
480
        foreach ($providers as $provider) {
481
            $this->output->writeln('<info>Adding '.$provider.' to Laravel config app.php file</info>');
482
            $this->addProvider($provider);
483
        }
484
    }
485
486
    /**
487
     * Add aliases to Laravel config file.
488
     *
489
     * @param $aliases
490
     */
491
    protected function addAliases($aliases)
492
    {
493
        foreach ($aliases as $alias => $aliasClass) {
494
            $this->output->writeln('<info>Adding '.$aliasClass.' to Laravel config app.php file</info>');
495
            $this->addAlias("'$alias' => ".$aliasClass);
496
        }
497
    }
498
499
    /**
500
     * Installs laravel package form config/packages.php file.
501
     *
502
     * @param $name
503
     */
504
    protected function package($name)
505
    {
506
        $package = $this->getPackageFromConfig($name);
507
508
        if ($package == null) {
509
            $this->showPackageNotFoundError($name);
510
511
            return;
512
        }
513
514
        list($name, $providers, $aliases, $after) = array_fill(0, 4, null);
515
        extract($package, EXTR_IF_EXISTS);
516
517
        $this->requireComposerPackage($name);
518
519
        if ($this->installConfigAppFile() == -1) {
520
            return;
521
        }
522
523
        $this->addProviders($providers);
524
525
        $this->addAliases($aliases);
526
527
        if ($after != null) {
528
            passthru($after);
529
        }
530
    }
531
532
    /**
533
     * Get config repository.
534
     *
535
     * @return Repository
536
     */
537
    protected function obtainConfig()
538
    {
539
        return new Repository(require $this->configPath.'packages.php');
540
    }
541
542
    /**
543
     * @param InputInterface  $input
544
     * @param OutputInterface $output
545
     */
546
    protected function initialize(InputInterface $input, OutputInterface $output)
547
    {
548
        parent::initialize($input, $output);
549
        if ($input->hasOption('no-bash')) {
550
            $this->noBash = $input->getOption('no-bash');
551
        }
552
    }
553
554
    /**
555
     * Check is --no-bash option is active.
556
     *
557
     * @return bool
558
     */
559
    private function isNoBashActive()
560
    {
561
        return $this->noBash;
562
    }
563
564
    /**
565
     * Get package name by composer package name.
566
     * 
567
     * @param $composerPackageName
568
     *
569
     * @return string
570
     */
571
    private function getPackageNameByComposerName($composerPackageName)
572
    {
573
        foreach ($this->config->all() as $key => $configItem) {
574
            if ($configItem[ 'name' ] == $composerPackageName) {
575
                return $key;
576
            }
577
        }
578
579
        return;
580
    }
581
582
    /**
583
     * Show package not found error.
584
     *
585
     * @param $name
586
     */
587
    protected function showPackageNotFoundError($name)
588
    {
589
        $this->output->writeln('<error>Package '.$name.' not found in file '.$this->configPath.'packages.php</error>');
590
591
        return;
592
    }
593
594
    /**
595
     * Configure the command options.
596
     *
597
     * @param ConsoleCommand $command
598
     */
599
    protected function configureCommand(ConsoleCommand $command)
600
    {
601
        $this->ignoreValidationErrors();
602
603
        $this->setName($command->name())
0 ignored issues
show
Bug introduced by
It seems like $command->name() targeting Acacha\Llum\Console\ConsoleCommand::name() can also be of type object<Acacha\Llum\Traits\GetSetable>; however, Symfony\Component\Consol...mand\Command::setName() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
604
            ->setDescription($command->description());
0 ignored issues
show
Bug introduced by
It seems like $command->description() targeting Acacha\Llum\Console\ConsoleCommand::description() can also be of type object<Acacha\Llum\Traits\GetSetable>; however, Symfony\Component\Consol...mmand::setDescription() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
605
        if ($command->argument() != null) {
606
            $this->addArgument($command->argument()[ 'name' ],
607
                $command->argument()[ 'type' ],
608
                $command->argument()[ 'description' ]
609
            );
610
        }
611
    }
612
613
    /**
614
     * Execute the command.
615
     *
616
     * @param InputInterface  $input
617
     * @param OutputInterface $output
618
     *
619
     * @return int|null|void
620
     */
621
    protected function execute(InputInterface $input, OutputInterface $output)
622
    {
623
        $this->output = $output;
624
        $method = $this->method;
625
        if ($this->argument != null) {
626
            $argument = $input->getArgument($this->argument);
627
            $this->$method($argument);
628
629
            return;
630
        }
631
632
        $this->$method();
633
    }
634
635
    /**
636
     * Configure the command options.
637
     */
638
    protected function configure()
639
    {
640
        $command = new ConsoleCommand();
641
642
        $command->name($this->commandName)
1 ignored issue
show
Bug introduced by
It seems like description() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
643
                ->description($this->commandDescription);
644
645
        if ($this->argument != null) {
646
            $command->argument([
647
                'name' => $this->argument,
648
                'description' => $this->argumentDescription,
649
                'type' => $this->argumentType,
650
            ]);
651
        }
652
        $this->configureCommand($command);
653
    }
654
}
655