Completed
Push — master ( 690242...271466 )
by Neomerx
01:49
created

CommandsCommand::executeCreate()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 45
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.1951

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 19
cts 28
cp 0.6786
rs 8.439
c 0
b 0
f 0
cc 6
eloc 28
nc 4
nop 2
crap 7.1951
1
<?php namespace Limoncello\Commands;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Composer\Command\BaseCommand;
20
use Limoncello\Commands\Traits\CacheFilePathTrait;
21
use Limoncello\Commands\Traits\CommandSerializationTrait;
22
use Limoncello\Commands\Traits\CommandTrait;
23
use Limoncello\Contracts\Application\ApplicationSettingsInterface as S;
24
use Limoncello\Contracts\Commands\CommandInterface;
25
use Limoncello\Contracts\Commands\CommandStorageInterface;
26
use Limoncello\Contracts\Commands\IoInterface;
27
use Limoncello\Contracts\FileSystem\FileSystemInterface;
28
use Limoncello\Contracts\Settings\SettingsProviderInterface;
29
use Psr\Container\ContainerInterface;
30
use Symfony\Component\Console\Input\InputArgument;
31
use Symfony\Component\Console\Input\InputInterface;
32
use Symfony\Component\Console\Output\OutputInterface;
33
34
/**
35
 * This is a special command which is immediately available from composer. The main purpose of it is to
36
 * load command list from user application and generate a special cache file with the list. On the next
37
 * composer run the list would be loaded into composer and all the commands would be available.
38
 *
39
 * Also it provides such a nice feature as generation of an empty/template command for the developer.
40
 *
41
 * @package Limoncello\Commands
42
 */
43
class CommandsCommand extends BaseCommand
44
{
45
    use CommandTrait, CommandSerializationTrait, CacheFilePathTrait;
46
47
    /**
48
     * Command name.
49
     */
50
    const NAME = 'l:commands';
51
52
    /** Argument name */
53
    const ARG_ACTION = 'action';
54
55
    /** Command action */
56
    const ACTION_CONNECT = 'connect';
57
58
    /** Command action */
59
    const ACTION_CREATE = 'create';
60
61
    /** Argument name */
62
    const ARG_CLASS = 'class';
63
64
    /**
65
     * Constructor.
66
     */
67 3
    public function __construct()
68
    {
69 3
        parent::__construct(static::NAME);
70
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75 3
    public function configure()
76
    {
77 3
        parent::configure();
78
79 3
        $connect    = static::ACTION_CONNECT;
80 3
        $create     = static::ACTION_CREATE;
81 3
        $actionDesc = "Required action such as `$connect` to find and connect commands from application and plugins " .
82 3
            "or `$create` to create an empty command template.";
83
84 3
        $classDesc = "Required valid class name in commands' namespace for action `$create`.";
85
86
        $this
87 3
            ->setDescription('Manages commands executed from composer.')
88 3
            ->setHelp('This command connects plugin and user-defined commands and creates new commands.')
89 3
            ->setDefinition([
90 3
                new InputArgument(static::ARG_ACTION, InputArgument::REQUIRED, $actionDesc),
91 3
                new InputArgument(static::ARG_CLASS, InputArgument::OPTIONAL, $classDesc),
92
            ]);
93
    }
94
95
96
    /** @noinspection PhpMissingParentCallCommonInspection
97
     * @inheritdoc
98
     */
99 2
    public function execute(InputInterface $input, OutputInterface $output)
100
    {
101 2
        $inOut     = $this->wrapIo($input, $output);
102 2
        $container = $this->createContainer($this->getComposer(), static::NAME);
0 ignored issues
show
Bug introduced by
It seems like $this->getComposer() can be null; however, createContainer() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
103
104 2
        $argAction = static::ARG_ACTION;
105 2
        $action    = $inOut->getArgument($argAction);
106
        switch ($action) {
107 2
            case static::ACTION_CONNECT:
108 1
                $this->executeConnect($container, $inOut);
109 1
                break;
110 1
            case static::ACTION_CREATE:
111 1
                $this->executeCreate($container, $inOut);
112 1
                break;
113
            default:
114
                $inOut->writeError("Unknown value `$action` for argument `$argAction`." . PHP_EOL);
115
                break;
116
        }
117
    }
118
119
    /**
120
     * @param ContainerInterface $container
121
     * @param IoInterface        $inOut
122
     *
123
     * @return void
124
     */
125 1
    private function executeConnect(ContainerInterface $container, IoInterface $inOut): void
126
    {
127 1
        assert($container->has(CommandStorageInterface::class));
128
        /** @var CommandStorageInterface $commandStorage */
129 1
        $commandStorage = $container->get(CommandStorageInterface::class);
130
131 1
        $commandClasses = [];
132 1
        foreach ($commandStorage->getAll() as $commandClass) {
133 1
            if (class_exists($commandClass) === false ||
134 1
                array_key_exists(CommandInterface::class, class_implements($commandClass)) === false
135
            ) {
136
                $inOut->writeWarning("Class `$commandClass` either do not exist or not a command class." . PHP_EOL);
137
                continue;
138
            }
139
140 1
            $commandClasses[] = $this->commandClassToArray($commandClass);
141
        }
142
143 1
        if (empty($commandClasses) === false) {
144 1
            $now     = date(DATE_RFC2822);
145 1
            $data    = var_export($commandClasses, true);
146
            $content = <<<EOT
147
<?php
148
149
// THIS FILE IS AUTO GENERATED. DO NOT EDIT IT MANUALLY.
150 1
// Generated at: $now
151
152 1
    return $data;
153
154
EOT;
155 1
            $cacheFilePath = $this->getCommandsCacheFilePath($this->getComposer());
0 ignored issues
show
Bug introduced by
It seems like $this->getComposer() can be null; however, getCommandsCacheFilePath() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
156 1
            if (empty($cacheFilePath) === true) {
157
                $inOut->writeError("Commands cache file path is not set. Check your `Application` settings." . PHP_EOL);
158
                return;
159
            }
160
161 1
            $this->getFileSystem($container)->write($cacheFilePath, $content);
162
        }
163
    }
164
165
    /**
166
     * @param ContainerInterface $container
167
     * @param IoInterface        $inOut
168
     *
169
     * @return void
170
     */
171 1
    private function executeCreate(ContainerInterface $container, IoInterface $inOut): void
172
    {
173 1
        $argClass = static::ARG_CLASS;
174 1
        if ($inOut->hasArgument($argClass) === false) {
175
            $inOut->writeError("Argument `$argClass` is not provided." . PHP_EOL);
176
177
            return;
178
        }
179 1
        $class = $inOut->getArgument($argClass);
180
181 1
        $fileSystem     = $this->getFileSystem($container);
182 1
        $settings       = $this->getAppSettings($container);
183 1
        $commandsFolder = $settings[S::KEY_COMMANDS_FOLDER] ?? null;
184 1
        if (empty($commandsFolder) === true || $fileSystem->isFolder($commandsFolder) === false) {
185
            $inOut->writeError(
186
                "Commands folder `$commandsFolder` is not valid. Check your `Application` settings." . PHP_EOL
187
            );
188
189
            return;
190
        }
191
192 1
        $classPath = $commandsFolder . DIRECTORY_SEPARATOR . $class . '.php';
193 1
        if (ctype_alpha($class) === false ||
194 1
            $fileSystem->exists($classPath) === true
195
        ) {
196
            $inOut->writeError(
197
                "Class name `$class` does not look valid for a command. " .
198
                'Can you please choose another one?' . PHP_EOL
199
            );
200
201
            return;
202
        }
203
204 1
        $commandName = strtolower($class);
205
206 1
        $templateContent = $fileSystem->read(__DIR__ . DIRECTORY_SEPARATOR . 'SampleCommand.txt');
207
208 1
        $tmpContent = $templateContent;
209 1
        $tmpContent = str_replace('{CLASS_NAME}', $class, $tmpContent);
210 1
        $tmpContent = str_replace('{COMMAND_NAME}', $commandName, $tmpContent);
211 1
        $tmpContent = str_replace('{TO_DO}', 'TODO', $tmpContent);
212 1
        $content    = $tmpContent;
213
214 1
        $fileSystem->write($classPath, $content);
215
    }
216
217
    /**
218
     * @param ContainerInterface $container
219
     *
220
     * @return array
221
     */
222 1
    private function getAppSettings(ContainerInterface $container): array
223
    {
224 1
        assert($container->has(SettingsProviderInterface::class));
225
226
        /** @var SettingsProviderInterface $provider */
227 1
        $provider = $container->get(SettingsProviderInterface::class);
228 1
        $settings = $provider->get(S::class);
229
230 1
        return $settings;
231
    }
232
233
    /**
234
     * @param ContainerInterface $container
235
     *
236
     * @return FileSystemInterface
237
     */
238 2
    private function getFileSystem(ContainerInterface $container): FileSystemInterface
239
    {
240 2
        assert($container->has(FileSystemInterface::class));
241
242
        /** @var FileSystemInterface $fileSystem */
243 2
        $fileSystem = $container->get(FileSystemInterface::class);
244
245 2
        return $fileSystem;
246
    }
247
}
248