Completed
Push — master ( 271466...84e0b1 )
by Neomerx
01:17
created

CommandsCommand::getCommandsFolder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 1
crap 1
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\ApplicationConfigurationInterface as S;
24
use Limoncello\Contracts\Application\CacheSettingsProviderInterface;
25
use Limoncello\Contracts\Commands\CommandInterface;
26
use Limoncello\Contracts\Commands\CommandStorageInterface;
27
use Limoncello\Contracts\Commands\IoInterface;
28
use Limoncello\Contracts\FileSystem\FileSystemInterface;
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 9
    public function __construct()
68
    {
69 9
        parent::__construct(static::NAME);
70
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75 9
    public function configure()
76
    {
77 9
        parent::configure();
78
79 9
        $connect    = static::ACTION_CONNECT;
80 9
        $create     = static::ACTION_CREATE;
81 9
        $actionDesc = "Required action such as `$connect` to find and connect commands from application and plugins " .
82 9
            "or `$create` to create an empty command template.";
83
84 9
        $classDesc = "Required valid class name in commands' namespace for action `$create`.";
85
86
        $this
87 9
            ->setDescription('Manages commands executed from composer.')
88 9
            ->setHelp('This command connects plugin and user-defined commands and creates new commands.')
89 9
            ->setDefinition([
90 9
                new InputArgument(static::ARG_ACTION, InputArgument::REQUIRED, $actionDesc),
91 9
                new InputArgument(static::ARG_CLASS, InputArgument::OPTIONAL, $classDesc),
92
            ]);
93
    }
94
95
96
    /** @noinspection PhpMissingParentCallCommonInspection
97
     * @inheritdoc
98
     */
99 8
    public function execute(InputInterface $input, OutputInterface $output)
100
    {
101 8
        $inOut     = $this->wrapIo($input, $output);
102 8
        $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 8
        $argAction = static::ARG_ACTION;
105 8
        $action    = $inOut->getArgument($argAction);
106
        switch ($action) {
107 8
            case static::ACTION_CONNECT:
108 3
                $this->executeConnect($container, $inOut);
109 3
                break;
110 5
            case static::ACTION_CREATE:
111 4
                $this->executeCreate($container, $inOut);
112 4
                break;
113
            default:
114 1
                $inOut->writeError("Unknown value `$action` for argument `$argAction`." . PHP_EOL);
115 1
                break;
116
        }
117
    }
118
119
    /**
120
     * @param ContainerInterface $container
121
     * @param IoInterface        $inOut
122
     *
123
     * @return void
124
     */
125 3
    private function executeConnect(ContainerInterface $container, IoInterface $inOut): void
126
    {
127 3
        assert($container->has(CommandStorageInterface::class));
128
        /** @var CommandStorageInterface $commandStorage */
129 3
        $commandStorage = $container->get(CommandStorageInterface::class);
130
131 3
        $commandClasses = [];
132 3
        foreach ($commandStorage->getAll() as $commandClass) {
133 3
            if (class_exists($commandClass) === false ||
134 3
                array_key_exists(CommandInterface::class, class_implements($commandClass)) === false
135
            ) {
136 1
                $inOut->writeWarning("Class `$commandClass` either do not exist or not a command class." . PHP_EOL);
137 1
                continue;
138
            }
139
140 2
            $inOut->writeInfo("Found command class `$commandClass`." . PHP_EOL, IoInterface::VERBOSITY_VERBOSE);
141
142 2
            $commandClasses[] = $this->commandClassToArray($commandClass);
143
        }
144
145 3
        if (empty($commandClasses) === false) {
146 2
            $now           = date(DATE_RFC2822);
147 2
            $data          = var_export($commandClasses, true);
148
            $content       = <<<EOT
149
<?php
150
151
// THIS FILE IS AUTO GENERATED. DO NOT EDIT IT MANUALLY.
152 2
// Generated at: $now
153
154 2
    return $data;
155
156
EOT;
157 2
            $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...
158 2
            if (empty($cacheFilePath) === true) {
159 1
                $inOut->writeError("Commands cache file path is not set. Check your `Application` settings." . PHP_EOL);
160
161 1
                return;
162
            }
163
164 1
            $this->getFileSystem($container)->write($cacheFilePath, $content);
165
166 1
            $inOut->writeInfo('Commands connected.' . PHP_EOL);
167
168 1
            return;
169
        }
170
171 1
        $inOut->writeWarning('No commands found.' . PHP_EOL);
172
    }
173
174
    /**
175
     * @param ContainerInterface $container
176
     * @param IoInterface        $inOut
177
     *
178
     * @return void
179
     */
180 4
    private function executeCreate(ContainerInterface $container, IoInterface $inOut): void
181
    {
182 4
        $argClass = static::ARG_CLASS;
183 4
        if ($inOut->hasArgument($argClass) === false) {
184 1
            $inOut->writeError("Argument `$argClass` is not provided." . PHP_EOL);
185
186 1
            return;
187
        }
188 3
        $class = $inOut->getArgument($argClass);
189
190 3
        $fileSystem     = $this->getFileSystem($container);
191 3
        $commandsFolder = $this->getCommandsFolder($container);
192 3
        if (empty($commandsFolder) === true || $fileSystem->isFolder($commandsFolder) === false) {
193 1
            $inOut->writeError(
194 1
                "Commands folder `$commandsFolder` is not valid. Check your `Application` settings." . PHP_EOL
195
            );
196
197 1
            return;
198
        }
199
200 2
        $classPath = $commandsFolder . DIRECTORY_SEPARATOR . $class . '.php';
201 2
        if (ctype_alpha($class) === false ||
202 2
            $fileSystem->exists($classPath) === true
203
        ) {
204 1
            $inOut->writeError(
205 1
                "Class name `$class` does not look valid for a command. " .
206 1
                'Can you please choose another one?' . PHP_EOL
207
            );
208
209 1
            return;
210
        }
211
212 1
        $commandName = strtolower($class);
213
214 1
        $templateContent = $fileSystem->read(__DIR__ . DIRECTORY_SEPARATOR . 'SampleCommand.txt');
215
216 1
        $tmpContent = $templateContent;
217 1
        $tmpContent = str_replace('{CLASS_NAME}', $class, $tmpContent);
218 1
        $tmpContent = str_replace('{COMMAND_NAME}', $commandName, $tmpContent);
219 1
        $tmpContent = str_replace('{TO_DO}', 'TODO', $tmpContent);
220 1
        $content    = $tmpContent;
221
222 1
        $fileSystem->write($classPath, $content);
223
    }
224
225
    /**
226
     * @param ContainerInterface $container
227
     *
228
     * @return string
229
     */
230 3
    private function getCommandsFolder(ContainerInterface $container): string
231
    {
232 3
        assert($container->has(CacheSettingsProviderInterface::class));
233
234
        /** @var CacheSettingsProviderInterface $provider */
235 3
        $provider  = $container->get(CacheSettingsProviderInterface::class);
236 3
        $appConfig = $provider->getApplicationConfiguration();
237 3
        $folder    = $appConfig[S::KEY_COMMANDS_FOLDER];
238
239 3
        return $folder;
240
    }
241
242
    /**
243
     * @param ContainerInterface $container
244
     *
245
     * @return FileSystemInterface
246
     */
247 4
    private function getFileSystem(ContainerInterface $container): FileSystemInterface
248
    {
249 4
        assert($container->has(FileSystemInterface::class));
250
251
        /** @var FileSystemInterface $fileSystem */
252 4
        $fileSystem = $container->get(FileSystemInterface::class);
253
254 4
        return $fileSystem;
255
    }
256
}
257