Passed
Push — master ( 65ca3f...66b084 )
by Radosław
02:04
created

CreateBundleCommand::getParametersSource()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 1
nop 1
dl 0
loc 21
rs 9.8666
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Phln\Build\Command;
5
6
use Illuminate\Console\Command;
7
use Illuminate\View\Factory;
8
use Symfony\Component\Process\ProcessBuilder;
9
use const phln\collection\last;
10
use const phln\fn\T;
11
use const phln\object\keys;
12
use const phln\relation\𝑓equals;
13
use function phln\collection\{
14
    filter, join, map, reject
15
};
16
use function phln\fn\{
17
    always, compose, partial, pipe
18
};
19
use function phln\logic\cond;
20
use function phln\object\prop;
21
use function phln\relation\equals;
22
use function phln\string\{
23
    match, replace, split
24
};
25
26
class CreateBundleCommand extends Command
27
{
28
    protected $signature = 'create:bundle';
29
30
    private $filters = [];
31
32
    /**
33
     * @var Factory
34
     */
35
    private $view;
36
37
    private $destFile = __DIR__.'/../../src/Phln.php';
38
39
    public function __construct(Factory $view)
40
    {
41
        parent::__construct();
42
43
        $this->view = $view;
44
        $this->filters = [
45
            'phln_all' => filter(match('#^phln\\\\#u')),
46
            'phln_noncurried' => reject(match('#phln\\\\\w+\\\\𝑓\w+#u')),
47
        ];
48
    }
49
50
    public function handle()
51
    {
52
        $this->output->writeln('Building bundle');
53
54
        $this->output->writeln('Collecting functions and constants');
55
        $functions = $this->getFunctions();
56
        $constants = $this->getConstants();
57
58
        $this->output->writeln('Rendering..');
59
60
        try {
61
            $this->saveFile($functions, $constants);
62
            $this->output->writeln('Done');
63
        } catch (\Exception $e) {
64
            $this->output->writeln('<error>Failed to create bundle</error>');
65
            $this->output->writeln($e->getMessage());
66
        }
67
    }
68
69
    private function getReturnTypeSource(\ReflectionType $reflectionType = null): string
70
    {
71
        if (true === is_null($reflectionType)) {
72
            return '';
73
        }
74
75
        return sprintf(
76
            ': %s%s',
77
            $reflectionType->isBuiltin() ? '' : '\\',
78
            $reflectionType
79
        );
80
    }
81
82
    private function saveFile(array $functions, array $constants)
83
    {
84
        $tmpFile = $this->destFile.'.tmp';
85
86
        file_put_contents($tmpFile, $this->renderClass($functions, $constants));
87
88
        try {
89
            $this->validateGeneratedSources($tmpFile);
90
            rename($tmpFile, $this->destFile);
91
        } finally {
92
            if (file_exists($tmpFile)) {
93
                unlink($tmpFile);
94
            }
95
        }
96
    }
97
98
    private function validateGeneratedSources(string $filePath)
99
    {
100
        ProcessBuilder::create(['php', '-l', realpath($filePath)])
101
            ->getProcess()
102
            ->mustRun();
103
    }
104
105
    private function renderClass(array $functions, array $constants)
106
    {
107
        return $this->view->make('phln-class')
108
            ->with(compact('functions', 'constants'))
109
            ->render();
110
    }
111
112
    private function getFunctions(): array
113
    {
114
        $getName = function (callable $fn) {
115
            $reflection = new \ReflectionFunction($fn);
116
            $parameters = $reflection->getParameters();
117
118
            return [
119
                'name' => compose([last, split('\\')])($reflection->getName()),
120
                'fqn' => $reflection->getName(),
121
                'parameters' => $this->getParametersSource($parameters),
122
                'returnType' => $this->getReturnTypeSource($reflection->getReturnType()),
123
                'doc' => $this->getFunctionDocumentation($reflection),
124
            ];
125
        };
126
127
        $f = pipe([
128
            compose([prop('user'), '\\get_defined_functions']),
129
            $this->filters['phln_all'],
130
            $this->filters['phln_noncurried'],
131
            map($getName),
132
            reject(compose([
133
                match('/@internal/'),
134
                prop('doc'),
135
            ]))
136
        ]);
137
138
        return $f();
139
    }
140
141
    private function getConstants(): array
142
    {
143
        $f = pipe([
144
            compose([keys, prop('user'), '\\get_defined_constants', T]),
145
            $this->filters['phln_all'],
146
            $this->filters['phln_noncurried'],
147
            map(function ($constName) {
148
                return [
149
                    'fqn' => $constName,
150
                    'name' => compose([last, split('\\')])($constName),
151
                ];
152
            }),
153
        ]);
154
155
        return $f();
156
    }
157
158
    /**
159
     * @param \ReflectionParameter[] $parameters
160
     * @return array
161
     */
162
    private function getParametersSource(array $parameters): array
163
    {
164
        $mapParameters = function (\ReflectionParameter $parameter) {
165
            $data = [
166
                'name' => $parameter->getName(),
167
                'variadic' => $parameter->isVariadic(),
168
                'type' => $parameter->getType(),
169
            ];
170
171
            try {
172
                $data['defaultValue'] = $parameter->getDefaultValue();
173
            } catch (\ReflectionException $e) {
174
            }
175
176
            return $data;
177
        };
178
179
        $mappedParams = map($mapParameters, $parameters);
180
        $definition = $this->getParametersDefinition($mappedParams);
181
182
        return compact('definition');
183
    }
184
185
    private function getParametersDefinition(array $parameters)
186
    {
187
        $exportDefaultValue = cond([
188
            [equals([]), always('[]')],
189
            [T, function ($value) {
190
                return var_export($value, true);
191
            }],
192
        ]);
193
194
        $toSrc = function ($param) use ($exportDefaultValue) {
195
            $base = sprintf(
196
                '%s%s$%s',
197
                empty($param['type']) ? '' : "{$param['type']} ",
198
                $param['variadic'] ? '...' : '',
199
                $param['name']
200
            );
201
202
            if (true === array_key_exists('defaultValue', $param)) {
203
                $defaultValue = $param['defaultValue'];
204
                $base .= sprintf(' = %s', $exportDefaultValue($defaultValue));
205
            }
206
207
            return $base;
208
        };
209
210
        return pipe([
211
            map($toSrc),
212
            join(', '),
213
        ])($parameters);
214
    }
215
216
    private function getFunctionDocumentation(\ReflectionFunction $function): string
217
    {
218
        $getDoc = pipe([
219
            [$function, 'getDocComment'],
220
            replace('/^/gm', '    '),
221
            replace('/\\\\phln\\\\\w+\\\\(\w+)(\()?/g', 'P::$1$2'),
222
            replace('/phln\\\\\w+\\\\(\w+)/g', 'P::$1'),
223
        ]);
224
225
        return $getDoc();
226
    }
227
}
228