8000 Add configuration builder for writing PHP config · symfony/symfony@c2f9a74 · GitHub
[go: up one dir, main page]

Skip to content

Commit c2f9a74

Browse files
committed
Add configuration builder for writing PHP config
1 parent 9abeb24 commit c2f9a74

File tree

6 files changed

+662
-3
lines changed

6 files changed

+662
-3
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 Symfony\Component\Config\Builder;
13+
14+
/**
15+
* Build PHP classes to generate config.
16+
*
17+
* @internal
18+
*
19+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
20+
*/
21+
class ClassBuilder
22+
{
23+
private const INDENTATION = ' ';
24+
25+
/** @var string */
26+
private $namespace;
27+
28+
/** @var string */
29+
private $name;
30+
31+
/** @var Property[] */
32+
private $properties = [];
33+
34+
/** @var Method[] */
35+
private $methods = [];
36+
private $require = [];
37+
38+
public function __construct(string $namespace, string $name)
39+
{
40+
$this->namespace = $namespace;
41+
$this->name = ucfirst($this->camelCase($name)).'Config';
42+
}
43+
44+
public function getFilePath(): string
45+
{
46+
return str_replace('\\', \DIRECTORY_SEPARATOR, $this->namespace);
47+
}
48+
49+
public function getFilename(): string
50+
{
51+
return $this->name.'.php';
52+
}
53+
54+
public function build(): string
55+
{
56+
$this->buildToArray();
57+
$this->buildConstructor();
58+
59+
$rootPath = explode(\DIRECTORY_SEPARATOR, $this->getFilePath());
60+
$require = '';
61+
foreach ($this->require as $class) {
62+
// figure out relative path.
63+
$path = explode(\DIRECTORY_SEPARATOR, $class->getFilePath());
64+
$path[] = $class->getFilename();
65+
foreach ($rootPath as $key => $value) {
66+
if ($path[$key] === $value) {
67+
unset($path[$key]);
68+
} else {
69+
break;
70+
}
71+
}
72+
$require .= strtr('require_once __DIR__.\'FILE\';', ['FILE' => \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $path)]).\PHP_EOL;
73+
}
74+
75+
$body = '';
76+
foreach ($this->properties as $property) {
77+
$body .= self::INDENTATION.$property->getContent().\PHP_EOL;
78+
}
79+
foreach ($this->methods as $method) {
80+
$lines = explode(\PHP_EOL, $method->getContent());
81+
foreach ($lines as $i => $line) {
82+
$body .= self::INDENTATION.$line.\PHP_EOL;
83+
}
84+
}
85+
86+
$content = strtr('<?php
87+
88+
namespace NAMESPACE;
89+
90+
REQUIRE
91+
92+
/**
93+
* This class is automatically generated to help creating config.
94+
*
95+
* @experimental in 5.3
96+
*/
97+
class CLASS
98+
{
99+
BODY
100+
}
101+
', ['NAMESPACE' => $this->namespace, 'REQUIRE' => $require, 'CLASS' => $this->getName(), 'BODY' => $body]);
102+
103+
return $content;
104+
}
105+
106+
private function buildToArray()
107+
{
108+
/*
109+
* Add toArray()
110+
*/
111+
$body = '$output = [];';
112+
foreach ($this->properties as $p) {
113+
$code = '$this->PROPERTY;';
114+
if (null !== $p->getType()) {
115+
if ($p->isArray()) {
116+
$code = 'array_map(function($v) { return $v->toArray(); }, $this->PROPERTY);';
117+
} else {
118+
$code = '$this->PROPERTY->toArray();';
119+
}
120+
}
121+
122+
$body .= strtr('
123+
if (null !== $this->PROPERTY) {
124+
$output["ORG_NAME"] = '.$code.'
125+
}', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
126+
}
127+
128+
$this->addMethod('toArray', '
129+
public function NAME(): array
130+
{
131+
'.$body.'
132+
133+
return $output;
134+
}
135+
');
136+
}
137+
138+
private function buildConstructor()
139+
{
140+
$body = '';
141+
foreach ($this->properties as $p) {
142+
$code = '$value["PROPERTY"]';
143+
if (null !== $p->getType()) {
144+
if ($p->isArray()) {
145+
$code = 'array_map(function($v) { return new '.$p->getType().'($v); }, $value["PROPERTY"]);';
146+
} else {
147+
$code = 'new '.$p->getType().'($value["PROPERTY"])';
148+
}
149+
}
150+
151+
$body .= strtr('
152+
if (isset($value["PROPERTY"])) {
153+
$this->PROPERTY = '.$code.';
154+
unset($value["PROPERTY"]);
155+
}
156+
', ['PROPERTY' => $p->getName()]);
157+
}
158+
159+
$body .= '
160+
if ($value !== []) {
161+
throw new \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__) . implode(\', \', array_keys($value)));
162+
}';
163+
164+
$this->addMethod('__construct', '
165+
public function __construct(array $value = [])
166+
{
167+
'.$body.'
168+
}
169+
');
170+
}
171+
172+
public function addRequire(self $class)
173+
{
174+
$this->require[] = $class;
175+
}
176+
177+
public function addMethod(string $name, string $body, array $params = []): void
178+
{
179+
$this->methods[] = new Method(strtr($body, ['NAME' => $this->camelCase($name)] + $params));
180+
}
181+
182+
public function addProperty(string $name, string $classType = null): Property
183+
{
184+
$property = new Property($name, $this->camelCase($name));
185+
if (null !== $classType) {
186+
$property->setType($classType);
187+
}
188+
$this->properties[] = $property;
189+
$property->setContent(strtr('private $NAME;', ['NAME' => $property->getName()]));
190+
191+
return $property;
192+
}
193+
194+
private function camelCase(string $input): string
195+
{
196+
$output = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $input))));
197+
198+
return preg_replace('#\W#', '', $output);
199+
}
200+
201+
public function getName(): string
202+
{
203+
return $this->name;
204+
}
205+
206+
public function getFqcn()
207+
{
208+
return '\\'.$this->namespace.'\\'.$this->name;
209+
}
210+
}

0 commit comments

Comments
 (0)
0