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
12namespace Symfony\Component\Config\Definition\Builder;
13
14use Symfony\Component\Config\Definition\BaseNode;
15use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
16use Symfony\Component\Config\Definition\NodeInterface;
17
18/**
19 * This class provides a fluent interface for defining a node.
20 *
21 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
22 */
23abstract class NodeDefinition implements NodeParentInterface
24{
25    protected $name;
26    protected $normalization;
27    protected $validation;
28    protected $defaultValue;
29    protected $default = false;
30    protected $required = false;
31    protected $deprecation = [];
32    protected $merge;
33    protected $allowEmptyValue = true;
34    protected $nullEquivalent;
35    protected $trueEquivalent = true;
36    protected $falseEquivalent = false;
37    protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR;
38    protected $parent;
39    protected $attributes = [];
40
41    public function __construct(?string $name, NodeParentInterface $parent = null)
42    {
43        $this->parent = $parent;
44        $this->name = $name;
45    }
46
47    /**
48     * Sets the parent node.
49     *
50     * @return $this
51     */
52    public function setParent(NodeParentInterface $parent)
53    {
54        $this->parent = $parent;
55
56        return $this;
57    }
58
59    /**
60     * Sets info message.
61     *
62     * @return $this
63     */
64    public function info(string $info)
65    {
66        return $this->attribute('info', $info);
67    }
68
69    /**
70     * Sets example configuration.
71     *
72     * @param string|array $example
73     *
74     * @return $this
75     */
76    public function example($example)
77    {
78        return $this->attribute('example', $example);
79    }
80
81    /**
82     * Sets an attribute on the node.
83     *
84     * @param mixed $value
85     *
86     * @return $this
87     */
88    public function attribute(string $key, $value)
89    {
90        $this->attributes[$key] = $value;
91
92        return $this;
93    }
94
95    /**
96     * Returns the parent node.
97     *
98     * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null
99     */
100    public function end()
101    {
102        return $this->parent;
103    }
104
105    /**
106     * Creates the node.
107     *
108     * @return NodeInterface
109     */
110    public function getNode(bool $forceRootNode = false)
111    {
112        if ($forceRootNode) {
113            $this->parent = null;
114        }
115
116        if (null !== $this->normalization) {
117            $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
118        }
119
120        if (null !== $this->validation) {
121            $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
122        }
123
124        $node = $this->createNode();
125        if ($node instanceof BaseNode) {
126            $node->setAttributes($this->attributes);
127        }
128
129        return $node;
130    }
131
132    /**
133     * Sets the default value.
134     *
135     * @param mixed $value The default value
136     *
137     * @return $this
138     */
139    public function defaultValue($value)
140    {
141        $this->default = true;
142        $this->defaultValue = $value;
143
144        return $this;
145    }
146
147    /**
148     * Sets the node as required.
149     *
150     * @return $this
151     */
152    public function isRequired()
153    {
154        $this->required = true;
155
156        return $this;
157    }
158
159    /**
160     * Sets the node as deprecated.
161     *
162     * @param string $package The name of the composer package that is triggering the deprecation
163     * @param string $version The version of the package that introduced the deprecation
164     * @param string $message the deprecation message to use
165     *
166     * You can use %node% and %path% placeholders in your message to display,
167     * respectively, the node name and its complete path
168     *
169     * @return $this
170     */
171    public function setDeprecated(/* string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */)
172    {
173        $args = \func_get_args();
174
175        if (\func_num_args() < 2) {
176            trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
177
178            $message = $args[0] ?? 'The child node "%node%" at path "%path%" is deprecated.';
179            $package = $version = '';
180        } else {
181            $package = (string) $args[0];
182            $version = (string) $args[1];
183            $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.');
184        }
185
186        $this->deprecation = [
187            'package' => $package,
188            'version' => $version,
189            'message' => $message,
190        ];
191
192        return $this;
193    }
194
195    /**
196     * Sets the equivalent value used when the node contains null.
197     *
198     * @param mixed $value
199     *
200     * @return $this
201     */
202    public function treatNullLike($value)
203    {
204        $this->nullEquivalent = $value;
205
206        return $this;
207    }
208
209    /**
210     * Sets the equivalent value used when the node contains true.
211     *
212     * @param mixed $value
213     *
214     * @return $this
215     */
216    public function treatTrueLike($value)
217    {
218        $this->trueEquivalent = $value;
219
220        return $this;
221    }
222
223    /**
224     * Sets the equivalent value used when the node contains false.
225     *
226     * @param mixed $value
227     *
228     * @return $this
229     */
230    public function treatFalseLike($value)
231    {
232        $this->falseEquivalent = $value;
233
234        return $this;
235    }
236
237    /**
238     * Sets null as the default value.
239     *
240     * @return $this
241     */
242    public function defaultNull()
243    {
244        return $this->defaultValue(null);
245    }
246
247    /**
248     * Sets true as the default value.
249     *
250     * @return $this
251     */
252    public function defaultTrue()
253    {
254        return $this->defaultValue(true);
255    }
256
257    /**
258     * Sets false as the default value.
259     *
260     * @return $this
261     */
262    public function defaultFalse()
263    {
264        return $this->defaultValue(false);
265    }
266
267    /**
268     * Sets an expression to run before the normalization.
269     *
270     * @return ExprBuilder
271     */
272    public function beforeNormalization()
273    {
274        return $this->normalization()->before();
275    }
276
277    /**
278     * Denies the node value being empty.
279     *
280     * @return $this
281     */
282    public function cannotBeEmpty()
283    {
284        $this->allowEmptyValue = false;
285
286        return $this;
287    }
288
289    /**
290     * Sets an expression to run for the validation.
291     *
292     * The expression receives the value of the node and must return it. It can
293     * modify it.
294     * An exception should be thrown when the node is not valid.
295     *
296     * @return ExprBuilder
297     */
298    public function validate()
299    {
300        return $this->validation()->rule();
301    }
302
303    /**
304     * Sets whether the node can be overwritten.
305     *
306     * @return $this
307     */
308    public function cannotBeOverwritten(bool $deny = true)
309    {
310        $this->merge()->denyOverwrite($deny);
311
312        return $this;
313    }
314
315    /**
316     * Gets the builder for validation rules.
317     *
318     * @return ValidationBuilder
319     */
320    protected function validation()
321    {
322        if (null === $this->validation) {
323            $this->validation = new ValidationBuilder($this);
324        }
325
326        return $this->validation;
327    }
328
329    /**
330     * Gets the builder for merging rules.
331     *
332     * @return MergeBuilder
333     */
334    protected function merge()
335    {
336        if (null === $this->merge) {
337            $this->merge = new MergeBuilder($this);
338        }
339
340        return $this->merge;
341    }
342
343    /**
344     * Gets the builder for normalization rules.
345     *
346     * @return NormalizationBuilder
347     */
348    protected function normalization()
349    {
350        if (null === $this->normalization) {
351            $this->normalization = new NormalizationBuilder($this);
352        }
353
354        return $this->normalization;
355    }
356
357    /**
358     * Instantiate and configure the node according to this definition.
359     *
360     * @return NodeInterface
361     *
362     * @throws InvalidDefinitionException When the definition is invalid
363     */
364    abstract protected function createNode();
365
366    /**
367     * Set PathSeparator to use.
368     *
369     * @return $this
370     */
371    public function setPathSeparator(string $separator)
372    {
373        if ($this instanceof ParentNodeDefinitionInterface) {
374            foreach ($this->getChildNodeDefinitions() as $child) {
375                $child->setPathSeparator($separator);
376            }
377        }
378
379        $this->pathSeparator = $separator;
380
381        return $this;
382    }
383}
384