File modules/util/text/variables/VariableResolver.class.php

Last commit: Thu Nov 19 21:42:39 2020 +0100	Jan Dankert	Fix: Variables with key '0' are now resolved.
1 <?php 2 3 4 namespace util\text\variables; 5 6 /** 7 * VariableResolver for resolving variables in strings and arrays. 8 */ 9 10 class VariableResolver 11 { 12 public $marker = '$'; 13 public $open = '{'; 14 public $close = '}'; 15 public $namespaceSeparator = '.'; 16 public $defaultSeparator = ':'; 17 public $renderOnlyVariables = false; 18 19 /** 20 * @var callable 21 */ 22 private $filterValue = null; 23 24 private $resolvers = []; 25 26 private $value = null; 27 28 29 /** 30 * Adding a filter function for values. 31 * @param $filter callable 32 */ 33 public function addFilter( $filter ) { 34 $this->filterValue = $filter; 35 } 36 37 /** 38 * Adding a default variable resolver. 39 * @param $resolver callable|array 40 */ 41 public function addDefaultResolver( $resolver ) { 42 $this->resolvers[''] = $resolver; 43 } 44 45 /** 46 * Adding a variable resolver for a key. 47 * 48 * @param $key string key 49 * @param $resolver callable|array 50 */ 51 public function addResolver( $key, $resolver ) { 52 $this->resolvers[$key] = $resolver; 53 } 54 55 56 /** 57 * Resolving a variable in a text like 'name is ${env:username:default}.' 58 * 59 * @param $value string 60 * @return string 61 */ 62 public function resolveVariables($value) { 63 64 $this->parseString( $value ); 65 return $this->renderToString(); 66 } 67 68 /** 69 * Resolving a variable in a text like 'name is ${env.username:default}.' 70 * 71 * @param $value array 72 * @return array 73 */ 74 public function resolveVariablesInArray($value) { 75 76 // pass-by-reference 77 array_walk_recursive($value, function (&$item, $keyI) { 78 79 $item = $this->resolveVariables($item); 80 return $item; 81 }); 82 83 return $value; 84 85 } 86 87 /** 88 * Resolving a variable in a text like 'name is ${env:username:default}'. 89 * 90 * @param $value 91 * @param $key 92 * @param $resolver 93 * @return string 94 */ 95 public function parseString($value) 96 { 97 $this->value = $this->parseToValue($value); 98 } 99 100 public function renderToString() 101 { 102 return $this->render( $this->value ); 103 } 104 105 106 107 protected function render( $value ) 108 { 109 $text = ''; 110 111 foreach( $value->expressions as $expression ) 112 { 113 if ( $expression instanceof ValueExpression ) { 114 $key = $this->render($expression->prefix); 115 $resolver = @$this->resolvers[ $key ]; 116 $v = ''; 117 if ( is_callable($resolver) ) { 118 $v = $resolver( $this->render($expression->name) ); 119 } 120 elseif ( is_array($resolver) ) { 121 $v = @$resolver[ $this->render($expression->name ) ]; 122 } 123 if ( strlen($v)==0 ) 124 $v = $this->render($expression->default); 125 126 if ( $this->filterValue ) { 127 $filter = $this->filterValue; 128 $v = $filter( $v ); 129 } 130 131 $text .= $v; 132 }else { 133 $text .= strval( $expression ); 134 } 135 } 136 137 return $text; 138 } 139 140 141 private function findNextPosOfChar( $haystack, $needle ) { 142 143 $chars = str_split($haystack); 144 $depth = 0; 145 $pos = 0; 146 foreach( $chars as $char) { 147 148 if ( $char == $this->open ) 149 $depth++; 150 elseif ( $char == $needle && $depth == 0 ) 151 return $pos; 152 elseif ( $char == $this->close ) 153 $depth--; 154 155 $pos++; 156 } 157 158 return false; 159 160 } 161 162 /** 163 * @param $inputText 164 * @return Value 165 */ 166 public function parseToValue($inputText) 167 { 168 $v = new Value(); 169 170 while (true) { 171 172 if ( strlen($inputText)==0 ) // Do not compare to "false" here, as '0' is false ;) 173 break; 174 175 // Search the next variable marker '$' 176 $nextVariableMarkerPos = strpos($inputText, $this->marker.$this->open); 177 178 if ($nextVariableMarkerPos === false ) 179 { 180 // no variable found. 181 $v->expressions[] = $inputText; 182 break; 183 } 184 else 185 { 186 $v->expressions[] = substr($inputText,0,$nextVariableMarkerPos); 187 188 $inputText = substr($inputText,$nextVariableMarkerPos+strlen($this->marker)+strlen($this->open)); 189 190 $pos = $this->findNextPosOfChar($inputText,$this->close); 191 192 if ( $pos === false) 193 throw new \RuntimeException('non-closed variable: '.$inputText); 194 195 $vv = substr($inputText,0,$pos); 196 $namespace = ''; 197 $default = ''; 198 199 $prefixSepPos = $this->findNextPosOfChar($vv,$this->namespaceSeparator); 200 if ( $prefixSepPos!==false) { 201 $namespace = substr($vv,0,$prefixSepPos); 202 $vv = substr($vv,$prefixSepPos+strlen($this->namespaceSeparator)); 203 } 204 205 $defaultSepPos = $this->findNextPosOfChar($vv,$this->defaultSeparator); 206 if ( $defaultSepPos!==false) { 207 $default = substr($vv,$defaultSepPos+strlen($this->defaultSeparator)); 208 $vv = substr($vv, 0,$defaultSepPos); 209 } 210 211 $v->expressions[] = new ValueExpression($this->parseToValue($namespace),$this->parseToValue($vv),$this->parseToValue($default)); 212 213 $inputText = substr($inputText,$pos+1); 214 } 215 216 } 217 218 return $v; 219 } 220 221 222 public function createExpression($namespace, $name, $default='') 223 { 224 return $this->marker.$this->open.($namespace?$namespace.$this->namespaceSeparator:'').$name.($default?$this->defaultSeparator.$default:'').$this->close; 225 } 226 } 227 228 /** Usage: * 229 230 $resolver = new VariableResolver(); 231 print_r( $resolver->resolveVariables('Born in the ${bruce:birthcountry}.','bruce',function($name){return 'USA';} ) ); 232 print_r( $resolver->resolveVariables('Born in ${bruce:birthyear:19??}.','bruce',function($name){return '';} ) ); 233 print_r( $resolver->resolveVariables('Born in the ${bruce:birthcountry}.${x:y}${x}','bruce',function($name){return 'USA';} ) ); 234 exit; 235 */
Download modules/util/text/variables/VariableResolver.class.php
History Thu, 19 Nov 2020 21:42:39 +0100 Jan Dankert Fix: Variables with key '0' are now resolved. Sat, 26 Sep 2020 04:26:55 +0200 Jan Dankert Refactoring: read configuration values with a class. Sat, 16 May 2020 01:08:40 +0200 Jan Dankert Refactoring: Switching the ValueExpressions in the templates to the new VariableResolver for supporting nested variables like ${message:prefix_${key}}. Wed, 13 May 2020 23:29:44 +0200 Jan Dankert Refactoring: New Variable Resolver with support for namespaces, default values and nested value expressions.