TypeCommandHandler::handleDefine()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 4
Bugs 1 Features 2
Metric Value
c 4
b 1
f 2
dl 0
loc 17
ccs 10
cts 10
cp 1
rs 9.4285
cc 2
eloc 12
nc 2
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of the puli/cli package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Cli\Handler;
13
14
use Puli\Cli\Style\PuliTableStyle;
15
use Puli\Cli\Util\ArgsUtil;
16
use Puli\Cli\Util\StringUtil;
17
use Puli\Discovery\Api\Type\BindingParameter;
18
use Puli\Discovery\Api\Type\BindingType;
19
use Puli\Manager\Api\Discovery\BindingTypeDescriptor;
20
use Puli\Manager\Api\Discovery\BindingTypeState;
21
use Puli\Manager\Api\Discovery\DiscoveryManager;
22
use Puli\Manager\Api\Module\ModuleList;
23
use RuntimeException;
24
use Webmozart\Console\Api\Args\Args;
25
use Webmozart\Console\Api\IO\IO;
26
use Webmozart\Console\UI\Component\Table;
27
use Webmozart\Expression\Expr;
28
29
/**
30
 * Handles the "puli type" command.
31
 *
32
 * @since  1.0
33
 *
34
 * @author Bernhard Schussek <[email protected]>
35
 */
36
class TypeCommandHandler
37
{
38
    /**
39
     * @var DiscoveryManager
40
     */
41
    private $discoveryManager;
42
43
    /**
44
     * @var ModuleList
45
     */
46
    private $modules;
47
48
    /**
49
     * Creates the handler.
50
     *
51
     * @param DiscoveryManager $discoveryManager The discovery manager
52
     * @param ModuleList       $modules          The loaded modules
53
     */
54 25
    public function __construct(DiscoveryManager $discoveryManager, ModuleList $modules)
55
    {
56 25
        $this->discoveryManager = $discoveryManager;
57 25
        $this->modules = $modules;
58 25
    }
59
60
    /**
61
     * Handles the "puli type --list" command.
62
     *
63
     * @param Args $args The console arguments
64
     * @param IO   $io   The I/O
65
     *
66
     * @return int The status code
67
     */
68 11
    public function handleList(Args $args, IO $io)
69
    {
70 11
        $moduleNames = ArgsUtil::getModuleNames($args, $this->modules);
71 11
        $states = $this->getBindingTypeStates($args);
72
73 11
        $printStates = count($states) > 1;
74 11
        $printModuleName = count($moduleNames) > 1;
75 11
        $printHeaders = $printStates || $printModuleName;
76 11
        $printTypeAdvice = true;
77 11
        $printBindAdvice = false;
78 11
        $indentation = $printStates && $printModuleName ? 8
79 11
            : ($printStates || $printModuleName ? 4 : 0);
80
81 11
        foreach ($states as $state) {
82 11
            $statePrinted = !$printStates;
83
84 11
            foreach ($moduleNames as $moduleName) {
85 11
                $expr = Expr::method('getContainingModule', Expr::method('getName', Expr::same($moduleName)))
86 11
                    ->andMethod('getState', Expr::same($state));
87
88 11
                $bindingTypes = $this->discoveryManager->findTypeDescriptors($expr);
89
90 11
                if (!$bindingTypes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bindingTypes of type Puli\Manager\Api\Discovery\BindingTypeDescriptor[] 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...
91 1
                    continue;
92
                }
93
94 10
                $printTypeAdvice = false;
95
96 10
                if (!$statePrinted) {
97 6
                    $this->printBindingTypeState($io, $state);
98 6
                    $statePrinted = true;
99
100
                    // Only print the advice if at least one type was printed
101 6
                    $printBindAdvice = true;
102
                }
103
104 10 View Code Duplication
                if ($printModuleName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105 6
                    $prefix = $printStates ? '    ' : '';
106 6
                    $io->writeLine("{$prefix}Module: $moduleName");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $prefix instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $moduleName instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
107 6
                    $io->writeLine('');
108
                }
109
110 10
                $styleTag = BindingTypeState::ENABLED === $state ? null : 'bad';
111
112 10
                $this->printTypeTable($io, $bindingTypes, $styleTag, $indentation);
113
114 10
                if ($printHeaders) {
115 11
                    $io->writeLine('');
116
                }
117
            }
118
        }
119
120 11
        if ($printTypeAdvice) {
121 1
            $io->writeLine('No types defined. Use "puli type --define <name>" to define a type.');
122
        }
123 11
        if ($printBindAdvice) {
124 6
            $io->writeLine('Use "puli bind <resource> <type>" to bind a resource to a type.');
125
        }
126
127 11
        return 0;
128
    }
129
130
    /**
131
     * Handles the "puli type --define" command.
132
     *
133
     * @param Args $args The console arguments
134
     *
135
     * @return int The status code
136
     */
137 6
    public function handleDefine(Args $args)
138
    {
139 6
        $flags = $args->isOptionSet('force') ? DiscoveryManager::OVERRIDE : 0;
140 6
        $bindingParams = array();
141 6
        $paramDescriptions = array();
142
143 6
        $this->parseParamDescriptions($args, $paramDescriptions);
144 6
        $this->parseParams($args, $bindingParams);
145
146 6
        $this->discoveryManager->addRootTypeDescriptor(new BindingTypeDescriptor(
147 6
            new BindingType($args->getArgument('name'), $bindingParams),
148 6
            $args->getOption('description'),
149
            $paramDescriptions
150
        ), $flags);
151
152 6
        return 0;
153
    }
154
155
    /**
156
     * Handles the "puli type --update" command.
157
     *
158
     * @param Args $args The console arguments
159
     *
160
     * @return int The status code
161
     */
162 6
    public function handleUpdate(Args $args)
163
    {
164 6
        $name = $args->getArgument('name');
165 6
        $descriptorToUpdate = $this->discoveryManager->getRootTypeDescriptor($name);
166 6
        $bindingParams = $descriptorToUpdate->getType()->getParameters();
167 6
        $description = $descriptorToUpdate->getDescription();
168 6
        $paramDescriptions = $descriptorToUpdate->getParameterDescriptions();
169
170 6
        $this->parseParamDescriptions($args, $paramDescriptions);
171 6
        $this->parseParams($args, $bindingParams);
172 6
        $this->parseUnsetParams($args, $bindingParams, $paramDescriptions);
173
174 6
        if ($args->isOptionSet('description')) {
175 1
            $description = $args->getOption('description');
176
        }
177
178 6
        $updatedDescriptor = new BindingTypeDescriptor(
179 6
            new BindingType($name, $bindingParams),
180
            $description,
181
            $paramDescriptions
182
        );
183
184 6
        if ($this->typesEqual($descriptorToUpdate, $updatedDescriptor)) {
185 1
            throw new RuntimeException('Nothing to update.');
186
        }
187
188 5
        $this->discoveryManager->addRootTypeDescriptor($updatedDescriptor, DiscoveryManager::OVERRIDE);
189
190 5
        return 0;
191
    }
192
193
    /**
194
     * Handles the "puli type --delete" command.
195
     *
196
     * @param Args $args The console arguments
197
     *
198
     * @return int The status code
199
     */
200 2
    public function handleDelete(Args $args)
201
    {
202 2
        $typeName = $args->getArgument('name');
203
204 2
        if (!$this->discoveryManager->hasRootTypeDescriptor($typeName)) {
205 1
            throw new RuntimeException(sprintf(
206 1
                'The type "%s" does not exist in the module "%s".',
207
                $typeName,
208 1
                $this->modules->getRootModuleName()
209
            ));
210
        }
211
212 1
        $this->discoveryManager->removeRootTypeDescriptor($typeName);
213
214 1
        return 0;
215
    }
216
217
    /**
218
     * Returns the binding type states selected in the console arguments.
219
     *
220
     * @param Args $args The console arguments
221
     *
222
     * @return int[] A list of {@link BindingTypeState} constants
223
     */
224 11
    private function getBindingTypeStates(Args $args)
225
    {
226 11
        $states = array();
227
228 11
        if ($args->isOptionSet('enabled')) {
229 4
            $states[] = BindingTypeState::ENABLED;
230
        }
231
232 11
        if ($args->isOptionSet('duplicate')) {
233 2
            $states[] = BindingTypeState::DUPLICATE;
234
        }
235
236 11
        return $states ?: BindingTypeState::all();
237
    }
238
239
    /**
240
     * Prints the binding types in a table.
241
     *
242
     * @param IO                      $io          The I/O
243
     * @param BindingTypeDescriptor[] $descriptors The type descriptors to print
244
     * @param string                  $styleTag    The tag used to style the output
0 ignored issues
show
Documentation introduced by
Should the type for parameter $styleTag not be string|null?

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...
245
     * @param int                     $indentation The number of spaces to indent
246
     */
247 10
    private function printTypeTable(IO $io, array $descriptors, $styleTag = null, $indentation = 0)
248
    {
249 10
        $table = new Table(PuliTableStyle::borderless());
250
251 10
        $table->setHeaderRow(array('Type', 'Description', 'Parameters'));
252
253 10
        $paramTag = $styleTag ?: 'c1';
254 10
        $typeTag = $styleTag ?: 'u';
255
256 10
        foreach ($descriptors as $descriptor) {
257 10
            $type = $descriptor->getType();
258 10
            $parameters = array();
259
260 10
            foreach ($type->getParameters() as $parameter) {
261 6
                $paramString = $parameter->isRequired()
262 6
                    ? $parameter->getName()
263 6
                    : $parameter->getName().'='.StringUtil::formatValue($parameter->getDefaultValue());
264
265 6
                $parameters[$parameter->getName()] = "<$paramTag>$paramString</$paramTag>";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $paramTag instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $paramString instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
266
            }
267
268 10
            $description = $descriptor->getDescription();
269
270 10
            if ($styleTag) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $styleTag of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
271 7
                $description = "<$styleTag>$description</$styleTag>";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $styleTag instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $description instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
272
            }
273
274 10
            ksort($parameters);
275
276 10
            $table->addRow(array(
277 10
                "<$typeTag>".$descriptor->getTypeName()."</$typeTag>",
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $typeTag instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
278 10
                $description,
279 10
                implode("\n", $parameters),
280
            ));
281
        }
282
283 10
        $table->render($io, $indentation);
284 10
    }
285
286
    /**
287
     * Prints the heading for a binding type state.
288
     *
289
     * @param IO  $io        The I/O
290
     * @param int $typeState The {@link BindingTypeState} constant
291
     */
292 6
    private function printBindingTypeState(IO $io, $typeState)
293
    {
294
        switch ($typeState) {
295 6
            case BindingTypeState::ENABLED:
296 6
                $io->writeLine('The following binding types are currently enabled:');
297 6
                $io->writeLine('');
298
299 6
                return;
300 6
            case BindingTypeState::DUPLICATE:
301 6
                $io->writeLine('The following types have duplicate definitions and are disabled:');
302 6
                $io->writeLine('');
303
304 6
                return;
305
        }
306
    }
307
308 12 View Code Duplication
    private function parseParamDescriptions(Args $args, array &$paramDescriptions)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
309
    {
310 12
        foreach ($args->getOption('param-description') as $paramDescription) {
311 2
            $pos = strpos($paramDescription, '=');
312
313 2
            if (false === $pos) {
314
                throw new RuntimeException(sprintf(
315
                    'The "--param-description" option expects a parameter in '.
316
                    'the form "key=value". Got: "%s"',
317
                    $paramDescription
318
                ));
319
            }
320
321 2
            $key = substr($paramDescription, 0, $pos);
322 2
            $paramDescriptions[$key] = StringUtil::parseValue(substr($paramDescription, $pos + 1));
323
        }
324 12
    }
325
326 12
    private function parseParams(Args $args, array &$bindingParams)
327
    {
328 12 View Code Duplication
        foreach ($args->getOption('param') as $parameter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
329
            // Optional parameter with default value
330 5
            if (false !== ($pos = strpos($parameter, '='))) {
331 2
                $key = substr($parameter, 0, $pos);
332
333 2
                $bindingParams[$key] = new BindingParameter(
334
                    $key,
335 2
                    BindingParameter::OPTIONAL,
336 2
                    StringUtil::parseValue(substr($parameter, $pos + 1))
337
                );
338
339 2
                continue;
340
            }
341
342
            // Required parameter
343 3
            $bindingParams[$parameter] = new BindingParameter(
344
                $parameter,
345 3
                BindingParameter::REQUIRED,
346 3
                null
347
            );
348
        }
349 12
    }
350
351 6
    private function parseUnsetParams(Args $args, array &$bindingParams, array &$paramDescriptions)
352
    {
353 6
        foreach ($args->getOption('unset-param') as $parameterName) {
354 1
            unset($bindingParams[$parameterName]);
355 1
            unset($paramDescriptions[$parameterName]);
356
        }
357 6
    }
358
359 6
    private function typesEqual(BindingTypeDescriptor $descriptor1, BindingTypeDescriptor $descriptor2)
360
    {
361 6
        return $descriptor1->getTypeName() === $descriptor2->getTypeName() &&
362 6
            $descriptor1->getDescription() === $descriptor2->getDescription() &&
363 6
            $descriptor1->getParameterDescriptions() === $descriptor2->getParameterDescriptions() &&
364 6
            $descriptor1->getType()->getParameters() === $descriptor2->getType()->getParameters();
365
    }
366
}
367