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