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 $deprecationMessage = null;
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 The builder of the parent node
99     */
100    public function end()
101    {
102        return $this->parent;
103    }
104
105    /**
106     * Creates the node.
107     *
108     * @param bool $forceRootNode Whether to force this node as the root node
109     *
110     * @return NodeInterface
111     */
112    public function getNode(bool $forceRootNode = false)
113    {
114        if ($forceRootNode) {
115            $this->parent = null;
116        }
117
118        if (null !== $this->normalization) {
119            $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
120        }
121
122        if (null !== $this->validation) {
123            $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
124        }
125
126        $node = $this->createNode();
127        $node->setAttributes($this->attributes);
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     * You can use %node% and %path% placeholders in your message to display,
163     * respectively, the node name and its complete path.
164     *
165     * @return $this
166     */
167    public function setDeprecated(string $message = 'The child node "%node%" at path "%path%" is deprecated.')
168    {
169        $this->deprecationMessage = $message;
170
171        return $this;
172    }
173
174    /**
175     * Sets the equivalent value used when the node contains null.
176     *
177     * @param mixed $value
178     *
179     * @return $this
180     */
181    public function treatNullLike($value)
182    {
183        $this->nullEquivalent = $value;
184
185        return $this;
186    }
187
188    /**
189     * Sets the equivalent value used when the node contains true.
190     *
191     * @param mixed $value
192     *
193     * @return $this
194     */
195    public function treatTrueLike($value)
196    {
197        $this->trueEquivalent = $value;
198
199        return $this;
200    }
201
202    /**
203     * Sets the equivalent value used when the node contains false.
204     *
205     * @param mixed $value
206     *
207     * @return $this
208     */
209    public function treatFalseLike($value)
210    {
211        $this->falseEquivalent = $value;
212
213        return $this;
214    }
215
216    /**
217     * Sets null as the default value.
218     *
219     * @return $this
220     */
221    public function defaultNull()
222    {
223        return $this->defaultValue(null);
224    }
225
226    /**
227     * Sets true as the default value.
228     *
229     * @return $this
230     */
231    public function defaultTrue()
232    {
233        return $this->defaultValue(true);
234    }
235
236    /**
237     * Sets false as the default value.
238     *
239     * @return $this
240     */
241    public function defaultFalse()
242    {
243        return $this->defaultValue(false);
244    }
245
246    /**
247     * Sets an expression to run before the normalization.
248     *
249     * @return ExprBuilder
250     */
251    public function beforeNormalization()
252    {
253        return $this->normalization()->before();
254    }
255
256    /**
257     * Denies the node value being empty.
258     *
259     * @return $this
260     */
261    public function cannotBeEmpty()
262    {
263        $this->allowEmptyValue = false;
264
265        return $this;
266    }
267
268    /**
269     * Sets an expression to run for the validation.
270     *
271     * The expression receives the value of the node and must return it. It can
272     * modify it.
273     * An exception should be thrown when the node is not valid.
274     *
275     * @return ExprBuilder
276     */
277    public function validate()
278    {
279        return $this->validation()->rule();
280    }
281
282    /**
283     * Sets whether the node can be overwritten.
284     *
285     * @return $this
286     */
287    public function cannotBeOverwritten(bool $deny = true)
288    {
289        $this->merge()->denyOverwrite($deny);
290
291        return $this;
292    }
293
294    /**
295     * Gets the builder for validation rules.
296     *
297     * @return ValidationBuilder
298     */
299    protected function validation()
300    {
301        if (null === $this->validation) {
302            $this->validation = new ValidationBuilder($this);
303        }
304
305        return $this->validation;
306    }
307
308    /**
309     * Gets the builder for merging rules.
310     *
311     * @return MergeBuilder
312     */
313    protected function merge()
314    {
315        if (null === $this->merge) {
316            $this->merge = new MergeBuilder($this);
317        }
318
319        return $this->merge;
320    }
321
322    /**
323     * Gets the builder for normalization rules.
324     *
325     * @return NormalizationBuilder
326     */
327    protected function normalization()
328    {
329        if (null === $this->normalization) {
330            $this->normalization = new NormalizationBuilder($this);
331        }
332
333        return $this->normalization;
334    }
335
336    /**
337     * Instantiate and configure the node according to this definition.
338     *
339     * @return NodeInterface The node instance
340     *
341     * @throws InvalidDefinitionException When the definition is invalid
342     */
343    abstract protected function createNode();
344
345    /**
346     * Set PathSeparator to use.
347     *
348     * @return $this
349     */
350    public function setPathSeparator(string $separator)
351    {
352        if ($this instanceof ParentNodeDefinitionInterface) {
353            foreach ($this->getChildNodeDefinitions() as $child) {
354                $child->setPathSeparator($separator);
355            }
356        }
357
358        $this->pathSeparator = $separator;
359
360        return $this;
361    }
362}
363