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     * @param string $info The info text
63     *
64     * @return $this
65     */
66    public function info($info)
67    {
68        return $this->attribute('info', $info);
69    }
70
71    /**
72     * Sets example configuration.
73     *
74     * @param string|array $example
75     *
76     * @return $this
77     */
78    public function example($example)
79    {
80        return $this->attribute('example', $example);
81    }
82
83    /**
84     * Sets an attribute on the node.
85     *
86     * @param string $key
87     * @param mixed  $value
88     *
89     * @return $this
90     */
91    public function attribute($key, $value)
92    {
93        $this->attributes[$key] = $value;
94
95        return $this;
96    }
97
98    /**
99     * Returns the parent node.
100     *
101     * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null The builder of the parent node
102     */
103    public function end()
104    {
105        return $this->parent;
106    }
107
108    /**
109     * Creates the node.
110     *
111     * @param bool $forceRootNode Whether to force this node as the root node
112     *
113     * @return NodeInterface
114     */
115    public function getNode($forceRootNode = false)
116    {
117        if ($forceRootNode) {
118            $this->parent = null;
119        }
120
121        if (null !== $this->normalization) {
122            $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
123        }
124
125        if (null !== $this->validation) {
126            $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
127        }
128
129        $node = $this->createNode();
130        if ($node instanceof BaseNode) {
131            $node->setAttributes($this->attributes);
132        }
133
134        return $node;
135    }
136
137    /**
138     * Sets the default value.
139     *
140     * @param mixed $value The default value
141     *
142     * @return $this
143     */
144    public function defaultValue($value)
145    {
146        $this->default = true;
147        $this->defaultValue = $value;
148
149        return $this;
150    }
151
152    /**
153     * Sets the node as required.
154     *
155     * @return $this
156     */
157    public function isRequired()
158    {
159        $this->required = true;
160
161        return $this;
162    }
163
164    /**
165     * Sets the node as deprecated.
166     *
167     * You can use %node% and %path% placeholders in your message to display,
168     * respectively, the node name and its complete path.
169     *
170     * @param string $message Deprecation message
171     *
172     * @return $this
173     */
174    public function setDeprecated($message = 'The child node "%node%" at path "%path%" is deprecated.')
175    {
176        $this->deprecationMessage = $message;
177
178        return $this;
179    }
180
181    /**
182     * Sets the equivalent value used when the node contains null.
183     *
184     * @param mixed $value
185     *
186     * @return $this
187     */
188    public function treatNullLike($value)
189    {
190        $this->nullEquivalent = $value;
191
192        return $this;
193    }
194
195    /**
196     * Sets the equivalent value used when the node contains true.
197     *
198     * @param mixed $value
199     *
200     * @return $this
201     */
202    public function treatTrueLike($value)
203    {
204        $this->trueEquivalent = $value;
205
206        return $this;
207    }
208
209    /**
210     * Sets the equivalent value used when the node contains false.
211     *
212     * @param mixed $value
213     *
214     * @return $this
215     */
216    public function treatFalseLike($value)
217    {
218        $this->falseEquivalent = $value;
219
220        return $this;
221    }
222
223    /**
224     * Sets null as the default value.
225     *
226     * @return $this
227     */
228    public function defaultNull()
229    {
230        return $this->defaultValue(null);
231    }
232
233    /**
234     * Sets true as the default value.
235     *
236     * @return $this
237     */
238    public function defaultTrue()
239    {
240        return $this->defaultValue(true);
241    }
242
243    /**
244     * Sets false as the default value.
245     *
246     * @return $this
247     */
248    public function defaultFalse()
249    {
250        return $this->defaultValue(false);
251    }
252
253    /**
254     * Sets an expression to run before the normalization.
255     *
256     * @return ExprBuilder
257     */
258    public function beforeNormalization()
259    {
260        return $this->normalization()->before();
261    }
262
263    /**
264     * Denies the node value being empty.
265     *
266     * @return $this
267     */
268    public function cannotBeEmpty()
269    {
270        $this->allowEmptyValue = false;
271
272        return $this;
273    }
274
275    /**
276     * Sets an expression to run for the validation.
277     *
278     * The expression receives the value of the node and must return it. It can
279     * modify it.
280     * An exception should be thrown when the node is not valid.
281     *
282     * @return ExprBuilder
283     */
284    public function validate()
285    {
286        return $this->validation()->rule();
287    }
288
289    /**
290     * Sets whether the node can be overwritten.
291     *
292     * @param bool $deny Whether the overwriting is forbidden or not
293     *
294     * @return $this
295     */
296    public function cannotBeOverwritten($deny = true)
297    {
298        $this->merge()->denyOverwrite($deny);
299
300        return $this;
301    }
302
303    /**
304     * Gets the builder for validation rules.
305     *
306     * @return ValidationBuilder
307     */
308    protected function validation()
309    {
310        if (null === $this->validation) {
311            $this->validation = new ValidationBuilder($this);
312        }
313
314        return $this->validation;
315    }
316
317    /**
318     * Gets the builder for merging rules.
319     *
320     * @return MergeBuilder
321     */
322    protected function merge()
323    {
324        if (null === $this->merge) {
325            $this->merge = new MergeBuilder($this);
326        }
327
328        return $this->merge;
329    }
330
331    /**
332     * Gets the builder for normalization rules.
333     *
334     * @return NormalizationBuilder
335     */
336    protected function normalization()
337    {
338        if (null === $this->normalization) {
339            $this->normalization = new NormalizationBuilder($this);
340        }
341
342        return $this->normalization;
343    }
344
345    /**
346     * Instantiate and configure the node according to this definition.
347     *
348     * @return NodeInterface The node instance
349     *
350     * @throws InvalidDefinitionException When the definition is invalid
351     */
352    abstract protected function createNode();
353
354    /**
355     * Set PathSeparator to use.
356     *
357     * @return $this
358     */
359    public function setPathSeparator(string $separator)
360    {
361        if ($this instanceof ParentNodeDefinitionInterface) {
362            if (method_exists($this, 'getChildNodeDefinitions')) {
363                foreach ($this->getChildNodeDefinitions() as $child) {
364                    $child->setPathSeparator($separator);
365                }
366            } else {
367                @trigger_error(sprintf('Not implementing the "%s::getChildNodeDefinitions()" method in "%s" is deprecated since Symfony 4.1.', ParentNodeDefinitionInterface::class, static::class), \E_USER_DEPRECATED);
368            }
369        }
370
371        $this->pathSeparator = $separator;
372
373        return $this;
374    }
375}
376