Completed
Push — master ( 6c80b5...e40af7 )
by Nikola
03:03
created

GenerateBuilderCommand::getBuilderClass()   C

Complexity

Conditions 7
Paths 16

Size

Total Lines 30
Code Lines 15

Duplication

Lines 6
Ratio 20 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 6
loc 30
ccs 0
cts 22
cp 0
rs 6.7272
cc 7
eloc 15
nc 16
nop 1
crap 56
1
<?php
2
/*
3
 * This file is part of the Abstract builder package, an RunOpenCode project.
4
 *
5
 * (c) 2017 RunOpenCode
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace RunOpenCode\AbstractBuilder\Command;
11
12
use RunOpenCode\AbstractBuilder\Exception\RuntimeException;
13
use RunOpenCode\AbstractBuilder\Helper\Style;
14
use RunOpenCode\AbstractBuilder\Helper\Tokenizer;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Input\InputArgument;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Question\ChoiceQuestion;
20
use Symfony\Component\Console\Question\Question;
21
22
/**
23
 * Class GenerateBuilderCommand
24
 *
25
 * @package RunOpenCode\AbstractBuilder\Command
26
 */
27
class GenerateBuilderCommand extends Command
28
{
29
    /**
30
     * @var Style
31
     */
32
    private $style;
33
34
    /**
35
     * @var InputInterface
36
     */
37
    private $input;
38
39
    /**
40
     * @var OutputInterface
41
     */
42
    private $output;
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    protected function configure()
48
    {
49
        $this
50
            ->setName('runopencode:generate:builder')
51
            ->setDescription('Generates builder class skeleton for provided class.')
52
            ->addArgument('class', InputArgument::OPTIONAL, 'Full qualified class name of building object that can be autoloaded, or path to file with class definition.')
53
            ->addArgument('builder', InputArgument::OPTIONAL, 'Full qualified class name of builder class can be autoloaded, or it will be autoloaded, or path to file with class definition.')
54
            ->addArgument('location', InputArgument::OPTIONAL, 'Path to location of file where builder class will be saved.');
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function execute(InputInterface $input, OutputInterface $output)
61
    {
62
        $this->input = $input;
63
        $this->output = $output;
64
        $this->style = new Style($input, $output);
65
66
        $this->style->displayLogo();
67
68
        $this->style->title('Generate builder class');
69
70
        try {
71
            $buildingClass = $this->getBuildingClass();
72
            $this->style->info(sprintf('Builder class for class "%s" will be generated.', $buildingClass));
73
74
            $builderClass = $this->getBuilderClass(sprintf('%sBuilder', $buildingClass));
75
            $this->style->info(sprintf('Full qualified namespace for builder class is "%s".', $builderClass));
76
        } catch (\Exception $e) {
77
            $this->style->error($e->getMessage());
78
            return 0;
79
        }
80
81
    }
82
83
    /**
84
     * Get class name for which skeleton should be built.
85
     *
86
     * @return string|bool
87
     */
88
    private function getBuildingClass()
89
    {
90
        $class = $this->input->getArgument('class');
91
92 View Code Duplication
        if (null === $class) {
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...
93
            $helper = $this->getHelper('question');
94
            $question = new Question('Enter full qualified class name, or path to file with class, for which you want to generate builder class: ', null);
95
96
            $class = $helper->ask($this->input, $this->output, $question);
97
        }
98
99
        if (!class_exists($class, true)) {
100
            $class = Tokenizer::findClass($class);
101
        }
102
103
        if (!class_exists($class, true)) {
104
            throw new RuntimeException(sprintf('Unable to autoload class "%s". Does this class exists? Can it be autoloaded?', $class));
105
        }
106
107
        return ltrim(str_replace('\\\\', '\\', $class), '\\');
108
    }
109
110
    /**
111
     * Get class name for builder class.
112
     *
113
     * @param string $suggest
114
     * @return bool|string
115
     */
116
    private function getBuilderClass($suggest)
117
    {
118
        $class = $this->input->getArgument('builder');
119
120 View Code Duplication
        if (null === $class) {
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...
121
            $helper = $this->getHelper('question');
122
            $question = new Question(sprintf('Enter full qualified class name of your builder class (default: "%s"): ', $suggest), $suggest);
123
124
            $class = $helper->ask($this->input, $this->output, $question);
125
        }
126
127
        if (file_exists($class) && !class_exists($class, true)) {
128
            $class = Tokenizer::findClass($class);
129
        }
130
131
        $class = ltrim(str_replace('\\\\', '\\', $class), '\\');
132
133
        if ('' === $class) {
134
            throw new RuntimeException('Builder class name must be provided.');
135
        }
136
137
        foreach (explode('\\', $class) as $part) {
138
139
            if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $part)) {
140
                throw new RuntimeException(sprintf('Provided builder class name "%s" is not valid PHP class name.', $class));
141
            }
142
        }
143
144
        return $class;
145
    }
146
147
    /**
148
     * Get getters and setters that will be generated.
149
     *
150
     * @param string[] $parameters
151
     * @return string[]
152
     */
153
    private function getGettersAndSetters($parameters)
154
    {
155
        $helper = $this->getHelper('question');
156
157
        $question = new ChoiceQuestion(
158
            'Choose for which constructor arguments you want to generate getters and setters (separate choices with coma, enter none for all choices):',
159
            $parameters,
160
            implode(',', array_keys($parameters))
161
        );
162
163
        $question->setMultiselect(true);
164
165
        $selected = $helper->ask($this->input, $this->output, $question);
166
167
        if (0 === count($selected)) {
168
            $this->style->error('You have to choose at least one constructor argument.');
169
        }
170
171
        return
172
            array_merge(
173
                array_map(function($parameter) {
174
                    return sprintf('get%s()', ucfirst($parameter));
175
                }, $selected),
176
                array_map(function($parameter) {
177
                    return sprintf('set%s($%s)', ucfirst($parameter), $parameter);
178
                }, $selected)
179
            );
180
    }
181
182
183
    /**
184
     * Get builder class location
185
     *
186
     * @param string $suggest
187
     * @return bool|string
188
     */
189
    private function getBuilderClassLocation($suggest)
190
    {
191
        $helper = $this->getHelper('question');
192
        $question = new Question(sprintf('Where do you want to generate your builder class code (default: "%s"): ', $suggest), $suggest);
193
194
        $location = trim($helper->ask($this->input, $this->output, $question));
195
196
        if (file_exists($location) && !is_writable($location)) {
197
            $this->style->error(sprintf('File on location "%s" already exists, but it is not writeable.', $location));
198
            return false;
199
        }
200
201
        // TODO - check if it si possible to create file at all...?
202
203
        return $location;
204
    }
205
}
206