Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Grasmash\YamlExpander;
|
Chris@0
|
4
|
Chris@0
|
5 use Dflydev\DotAccessData\Data;
|
Chris@0
|
6 use Symfony\Component\Yaml\Yaml;
|
Chris@0
|
7
|
Chris@0
|
8 /**
|
Chris@0
|
9 * Class Expander
|
Chris@0
|
10 * @package Grasmash\YamlExpander
|
Chris@0
|
11 */
|
Chris@0
|
12 class Expander
|
Chris@0
|
13 {
|
Chris@0
|
14
|
Chris@0
|
15 /**
|
Chris@0
|
16 * Parses a YAML string and expands property placeholders.
|
Chris@0
|
17 *
|
Chris@0
|
18 * Placeholders should formatted as ${parent.child}.
|
Chris@0
|
19 *
|
Chris@0
|
20 * @param string $yaml_string
|
Chris@0
|
21 * A string of YAML.
|
Chris@0
|
22 * @param array $reference_array
|
Chris@0
|
23 * Optional. An array of reference values. This is not operated upon but is used as a
|
Chris@0
|
24 * reference to provide supplemental values for property expansion.
|
Chris@0
|
25 *
|
Chris@0
|
26 * @return array
|
Chris@0
|
27 * The modified array in which placeholders have been replaced with
|
Chris@0
|
28 * values.
|
Chris@0
|
29 */
|
Chris@0
|
30 public static function parse($yaml_string, $reference_array = [])
|
Chris@0
|
31 {
|
Chris@0
|
32 $array = Yaml::parse($yaml_string);
|
Chris@0
|
33 return self::expandArrayProperties($array, $reference_array);
|
Chris@0
|
34 }
|
Chris@0
|
35
|
Chris@0
|
36
|
Chris@0
|
37 /**
|
Chris@0
|
38 * Expands property placeholders in an array.
|
Chris@0
|
39 *
|
Chris@0
|
40 * Placeholders should formatted as ${parent.child}.
|
Chris@0
|
41 *
|
Chris@0
|
42 * @param array $array
|
Chris@0
|
43 * An array containing properties to expand.
|
Chris@0
|
44 *
|
Chris@0
|
45 * @return array
|
Chris@0
|
46 * The modified array in which placeholders have been replaced with
|
Chris@0
|
47 * values.
|
Chris@0
|
48 */
|
Chris@0
|
49 public static function expandArrayProperties($array, $reference_array = [])
|
Chris@0
|
50 {
|
Chris@0
|
51 $data = new Data($array);
|
Chris@0
|
52 if ($reference_array) {
|
Chris@0
|
53 $reference_data = new Data($reference_array);
|
Chris@0
|
54 self::doExpandArrayProperties($data, $array, '', $reference_data);
|
Chris@0
|
55 } else {
|
Chris@0
|
56 self::doExpandArrayProperties($data, $array);
|
Chris@0
|
57 }
|
Chris@0
|
58
|
Chris@0
|
59 return $data->export();
|
Chris@0
|
60 }
|
Chris@0
|
61
|
Chris@0
|
62 /**
|
Chris@0
|
63 * Performs the actual property expansion.
|
Chris@0
|
64 *
|
Chris@0
|
65 * @param Data $data
|
Chris@0
|
66 * A data object, containing the $array.
|
Chris@0
|
67 * @param array $array
|
Chris@0
|
68 * The original, unmodified array.
|
Chris@0
|
69 * @param string $parent_keys
|
Chris@0
|
70 * The parent keys of the current key in dot notation. This is used to
|
Chris@0
|
71 * track the absolute path to the current key in recursive cases.
|
Chris@0
|
72 * @param Data|null $reference_data
|
Chris@0
|
73 * A reference data object. This is not operated upon but is used as a
|
Chris@0
|
74 * reference to provide supplemental values for property expansion.
|
Chris@0
|
75 */
|
Chris@0
|
76 protected static function doExpandArrayProperties(
|
Chris@0
|
77 $data,
|
Chris@0
|
78 $array,
|
Chris@0
|
79 $parent_keys = '',
|
Chris@0
|
80 $reference_data = null
|
Chris@0
|
81 ) {
|
Chris@0
|
82 foreach ($array as $key => $value) {
|
Chris@0
|
83 // Boundary condition(s).
|
Chris@0
|
84 if (is_null($value) || is_bool($value)) {
|
Chris@0
|
85 continue;
|
Chris@0
|
86 }
|
Chris@0
|
87 // Recursive case.
|
Chris@0
|
88 if (is_array($value)) {
|
Chris@0
|
89 self::doExpandArrayProperties($data, $value, $parent_keys . "$key.", $reference_data);
|
Chris@0
|
90 } // Base case.
|
Chris@0
|
91 else {
|
Chris@0
|
92 self::expandStringProperties($data, $parent_keys, $reference_data, $value, $key);
|
Chris@0
|
93 }
|
Chris@0
|
94 }
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@0
|
97 /**
|
Chris@0
|
98 * Expand a single property.
|
Chris@0
|
99 *
|
Chris@0
|
100 * @param Data $data
|
Chris@0
|
101 * A data object, containing the $array.
|
Chris@0
|
102 * @param string $parent_keys
|
Chris@0
|
103 * The parent keys of the current key in dot notation. This is used to
|
Chris@0
|
104 * track the absolute path to the current key in recursive cases.
|
Chris@0
|
105 * @param Data|null $reference_data
|
Chris@0
|
106 * A reference data object. This is not operated upon but is used as a
|
Chris@0
|
107 * reference to provide supplemental values for property expansion.
|
Chris@0
|
108 * @param string $value
|
Chris@0
|
109 * The unexpanded property value.
|
Chris@0
|
110 * @param string $key
|
Chris@0
|
111 * The immediate key of the property.
|
Chris@0
|
112 *
|
Chris@0
|
113 * @return mixed
|
Chris@0
|
114 */
|
Chris@0
|
115 protected static function expandStringProperties(
|
Chris@0
|
116 $data,
|
Chris@0
|
117 $parent_keys,
|
Chris@0
|
118 $reference_data,
|
Chris@0
|
119 $value,
|
Chris@0
|
120 $key
|
Chris@0
|
121 ) {
|
Chris@0
|
122 // We loop through all placeholders in a given string.
|
Chris@0
|
123 // E.g., '${placeholder1} ${placeholder2}' requires two replacements.
|
Chris@0
|
124 while (strpos($value, '${') !== false) {
|
Chris@0
|
125 $original_value = $value;
|
Chris@0
|
126 $value = preg_replace_callback(
|
Chris@0
|
127 '/\$\{([^\$}]+)\}/',
|
Chris@0
|
128 function ($matches) use ($data, $reference_data) {
|
Chris@0
|
129 return self::expandStringPropertiesCallback(
|
Chris@0
|
130 $matches,
|
Chris@0
|
131 $data,
|
Chris@0
|
132 $reference_data
|
Chris@0
|
133 );
|
Chris@0
|
134 },
|
Chris@0
|
135 $value
|
Chris@0
|
136 );
|
Chris@0
|
137
|
Chris@0
|
138 // If no replacement occurred at all, break to prevent
|
Chris@0
|
139 // infinite loop.
|
Chris@0
|
140 if ($original_value == $value) {
|
Chris@0
|
141 break;
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@0
|
144 // Set value on $data object.
|
Chris@0
|
145 if ($parent_keys) {
|
Chris@0
|
146 $full_key = $parent_keys . "$key";
|
Chris@0
|
147 } else {
|
Chris@0
|
148 $full_key = $key;
|
Chris@0
|
149 }
|
Chris@0
|
150 $data->set($full_key, $value);
|
Chris@0
|
151 }
|
Chris@0
|
152 return $value;
|
Chris@0
|
153 }
|
Chris@0
|
154
|
Chris@0
|
155 /**
|
Chris@0
|
156 * Expansion callback used by preg_replace_callback() in expandProperty().
|
Chris@0
|
157 *
|
Chris@0
|
158 * @param array $matches
|
Chris@0
|
159 * An array of matches created by preg_replace_callback().
|
Chris@0
|
160 * @param Data $data
|
Chris@0
|
161 * A data object containing the complete array being operated upon.
|
Chris@0
|
162 * @param Data|null $reference_data
|
Chris@0
|
163 * A reference data object. This is not operated upon but is used as a
|
Chris@0
|
164 * reference to provide supplemental values for property expansion.
|
Chris@0
|
165 *
|
Chris@0
|
166 * @return mixed
|
Chris@0
|
167 */
|
Chris@0
|
168 public static function expandStringPropertiesCallback(
|
Chris@0
|
169 $matches,
|
Chris@0
|
170 $data,
|
Chris@0
|
171 $reference_data = null
|
Chris@0
|
172 ) {
|
Chris@0
|
173 $property_name = $matches[1];
|
Chris@0
|
174 $unexpanded_value = $matches[0];
|
Chris@0
|
175
|
Chris@0
|
176 // Use only values within the subject array's data.
|
Chris@0
|
177 if (!$reference_data) {
|
Chris@0
|
178 return self::expandProperty($property_name, $unexpanded_value, $data);
|
Chris@0
|
179 } // Search both the subject array's data and the reference data for a value.
|
Chris@0
|
180 else {
|
Chris@0
|
181 return self::expandPropertyWithReferenceData(
|
Chris@0
|
182 $property_name,
|
Chris@0
|
183 $unexpanded_value,
|
Chris@0
|
184 $data,
|
Chris@0
|
185 $reference_data
|
Chris@0
|
186 );
|
Chris@0
|
187 }
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 /**
|
Chris@0
|
191 * Searches both the subject data and the reference data for value.
|
Chris@0
|
192 *
|
Chris@0
|
193 * @param string $property_name
|
Chris@0
|
194 * The name of the value for which to search.
|
Chris@0
|
195 * @param string $unexpanded_value
|
Chris@0
|
196 * The original, unexpanded value, containing the placeholder.
|
Chris@0
|
197 * @param Data $data
|
Chris@0
|
198 * A data object containing the complete array being operated upon.
|
Chris@0
|
199 * @param Data|null $reference_data
|
Chris@0
|
200 * A reference data object. This is not operated upon but is used as a
|
Chris@0
|
201 * reference to provide supplemental values for property expansion.
|
Chris@0
|
202 *
|
Chris@0
|
203 * @return string
|
Chris@0
|
204 * The expanded string.
|
Chris@0
|
205 */
|
Chris@0
|
206 public static function expandPropertyWithReferenceData(
|
Chris@0
|
207 $property_name,
|
Chris@0
|
208 $unexpanded_value,
|
Chris@0
|
209 $data,
|
Chris@0
|
210 $reference_data
|
Chris@0
|
211 ) {
|
Chris@0
|
212 $expanded_value = self::expandProperty(
|
Chris@0
|
213 $property_name,
|
Chris@0
|
214 $unexpanded_value,
|
Chris@0
|
215 $data
|
Chris@0
|
216 );
|
Chris@0
|
217 // If the string was not changed using the subject data, try using
|
Chris@0
|
218 // the reference data.
|
Chris@0
|
219 if ($expanded_value == $unexpanded_value) {
|
Chris@0
|
220 $expanded_value = self::expandProperty(
|
Chris@0
|
221 $property_name,
|
Chris@0
|
222 $unexpanded_value,
|
Chris@0
|
223 $reference_data
|
Chris@0
|
224 );
|
Chris@0
|
225 }
|
Chris@0
|
226
|
Chris@0
|
227 return $expanded_value;
|
Chris@0
|
228 }
|
Chris@0
|
229
|
Chris@0
|
230 /**
|
Chris@0
|
231 * Searches a data object for a value.
|
Chris@0
|
232 *
|
Chris@0
|
233 * @param string $property_name
|
Chris@0
|
234 * The name of the value for which to search.
|
Chris@0
|
235 * @param string $unexpanded_value
|
Chris@0
|
236 * The original, unexpanded value, containing the placeholder.
|
Chris@0
|
237 * @param Data $data
|
Chris@0
|
238 * A data object containing possible replacement values.
|
Chris@0
|
239 *
|
Chris@0
|
240 * @return mixed
|
Chris@0
|
241 */
|
Chris@0
|
242 public static function expandProperty($property_name, $unexpanded_value, $data)
|
Chris@0
|
243 {
|
Chris@0
|
244 if (strpos($property_name, "env.") === 0 &&
|
Chris@0
|
245 !$data->has($property_name)) {
|
Chris@0
|
246 $env_key = substr($property_name, 4);
|
Chris@0
|
247 if (getenv($env_key)) {
|
Chris@0
|
248 $data->set($property_name, getenv($env_key));
|
Chris@0
|
249 }
|
Chris@0
|
250 }
|
Chris@0
|
251
|
Chris@0
|
252 if (!$data->has($property_name)) {
|
Chris@0
|
253 self::log("Property \${'$property_name'} could not be expanded.");
|
Chris@0
|
254 return $unexpanded_value;
|
Chris@0
|
255 } else {
|
Chris@0
|
256 $expanded_value = $data->get($property_name);
|
Chris@0
|
257 if (is_array($expanded_value)) {
|
Chris@0
|
258 $expanded_value = Yaml::dump($expanded_value, 0);
|
Chris@0
|
259 return $expanded_value;
|
Chris@0
|
260 }
|
Chris@0
|
261 self::log("Expanding property \${'$property_name'} => $expanded_value.");
|
Chris@0
|
262 return $expanded_value;
|
Chris@0
|
263 }
|
Chris@0
|
264 }
|
Chris@0
|
265
|
Chris@0
|
266 /**
|
Chris@0
|
267 * @param $message
|
Chris@0
|
268 */
|
Chris@0
|
269 public static function log($message)
|
Chris@0
|
270 {
|
Chris@0
|
271 // print "$message\n";
|
Chris@0
|
272 }
|
Chris@0
|
273 }
|