]> git.mxchange.org Git - friendica.git/blob - src/Core/Config/Util/ConfigFileTransformer.php
Add support for toString/Serializable
[friendica.git] / src / Core / Config / Util / ConfigFileTransformer.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core\Config\Util;
23
24 /**
25  * Util to transform back the config array into a string
26  */
27 class ConfigFileTransformer
28 {
29         /**
30          * This method takes an array of config values and applies some standard rules for formatting on it
31          *
32          * Beware that the applied rules follow some basic formatting principles for node.config.php
33          * and doesn't support any custom formatting rules.
34          *
35          * f.e. associative array and list formatting are very complex with newlines and indentations, thus there are
36          * three hardcoded types of formatting for them.
37          *
38          * a negative example, what's NOT working:
39          * key => [ value1, [inner_value1, inner_value2], value2]
40          * Since this isn't necessary for config values, there's no further logic handling such complex-list-in-list scenarios
41          *
42          * @param array $data A full config array
43          *
44          * @return string The config stream, which can be saved
45          */
46         public static function encode(array $data): string
47         {
48                 // Add the typical header values
49                 $dataString = '<?php' . PHP_EOL . PHP_EOL;
50                 $dataString .= 'return ';
51
52                 $dataString .= static::extractArray($data);
53
54                 // the last array line, close it with a semicolon
55                 $dataString .= ";" . PHP_EOL;
56
57                 return $dataString;
58         }
59
60         /**
61          * Extracts an inner config array.
62          * Either as an associative array or as a list
63          *
64          * @param array $config The config array which should get extracted
65          * @param int   $level  The current level of recursion (necessary for tab-indentation calculation)
66          * @param bool  $inList If true, the current array resides inside another list. Different rules may be applicable
67          *
68          * @return string The config string
69          */
70         protected static function extractArray(array $config, int $level = 0, bool $inList = false): string
71         {
72                 if (array_values($config) === $config) {
73                         return self::extractList($config, $level, $inList);
74                 } else {
75                         return self::extractAssociativeArray($config, $level, $inList);
76                 }
77         }
78
79         /**
80          * Extracts a key-value array and save it into a string
81          * output:
82          * [
83          *    'key' => value,
84          *    'key' => value,
85          *    ...
86          * ]
87          *
88          * @param array $config The associative/key-value array
89          * @param int   $level  The current level of recursion (necessary for tab-indentation calculation)
90          * @param bool  $inList If true, the current array resides inside another list. Different rules may be applicable
91          *
92          * @return string The config string
93          */
94         protected static function extractAssociativeArray(array $config, int $level = 0, bool $inList = false): string
95         {
96                 $string = '';
97
98                 // Because we're in a list, we have to add a line-break first
99                 if ($inList) {
100                         $string .= PHP_EOL . str_repeat("\t", $level);
101                 }
102
103                 // Add a typical Line break for a taxative list of key-value pairs
104                 $string .= '[' . PHP_EOL;
105
106                 foreach ($config as $configKey => $configValue) {
107                         $string .= str_repeat("\t", $level + 1) .
108                                            "'$configKey' => " .
109                                            static::transformConfigValue($configValue, $level) .
110                                            ',' . PHP_EOL;
111                 }
112
113                 $string .= str_repeat("\t", $level) . ']';
114
115                 return $string;
116         }
117
118         /**
119          * Extracts a list and save it into a string
120          * output1 - simple:
121          * [ value, value, value ]
122          *
123          * output2 - complex:
124          * [
125          *    [ value, value, value ],
126          *    value,
127          *    [
128          *       key => value,
129          *       key => value,
130          *    ],
131          * ]
132          *
133          * @param array $config The list
134          * @param int   $level  The current level of recursion (necessary for tab-indentation calculation)
135          * @param bool  $inList If true, the current array resides inside another list. Different rules may be applicable
136          *
137          * @return string The config string
138          */
139         protected static function extractList(array $config, int $level = 0, bool $inList = false): string
140         {
141                 $string = '[';
142
143                 $countConfigValues = count($config);
144                 // multiline defines, if each entry uses a new line
145                 $multiline = false;
146
147                 // Search if any value is an array, because then other formatting rules are applicable
148                 foreach ($config as $item) {
149                         if (is_array($item)) {
150                                 $multiline = true;
151                                 break;
152                         }
153                 }
154
155                 for ($i = 0; $i < $countConfigValues; $i++) {
156                         $isArray = is_array($config[$i]);
157
158                         /**
159                          * In case this is an array in an array, directly extract this array again and continue
160                          * Skip any other logic since this isn't applicable for an array in an array
161                          */
162                         if ($isArray) {
163                                 $string .= PHP_EOL . str_repeat("\t", $level + 1);
164                                 $string .= static::extractArray($config[$i], $level + 1, $inList) . ',';
165                                 continue;
166                         }
167
168                         if ($multiline) {
169                                 $string .= PHP_EOL . str_repeat("\t", $level + 1);
170                         }
171
172                         $string .= static::transformConfigValue($config[$i], $level, true);
173
174                         // add trailing commas or whitespaces for certain config entries
175                         if (($i < ($countConfigValues - 1))) {
176                                 $string .= ',';
177                                 if (!$multiline) {
178                                         $string .= ' ';
179                                 }
180                         }
181                 }
182
183                 // Add a new line for the last bracket as well
184                 if ($multiline) {
185                         $string .= PHP_EOL . str_repeat("\t", $level);
186                 }
187
188                 $string .= ']';
189
190                 return $string;
191         }
192
193         /**
194          * Transforms one config value and returns the corresponding text-representation
195          *
196          * @param mixed $value  Any value to transform
197          * @param int   $level  The current level of recursion (necessary for tab-indentation calculation)
198          * @param bool  $inList If true, the current array resides inside another list. Different rules may be applicable
199          *
200          * @return string
201          */
202         protected static function transformConfigValue($value, int $level = 0, bool $inList = false): string
203         {
204                 switch (gettype($value)) {
205                         case "boolean":
206                                 return ($value ? 'true' : 'false');
207                         case "integer":
208                         case "double":
209                                 return $value;
210                         case "string":
211                                 return sprintf('\'%s\'', addcslashes($value, '\'\\'));
212                         case "array":
213                                 return static::extractArray($value, ++$level, $inList);
214                         case "NULL":
215                                 return "null";
216                         case "object":
217                                 if (method_exists($value, '__toString')) {
218                                         return sprintf('\'%s\'', $value);
219                                 } elseif ($value instanceof \Serializable) {
220                                         try {
221                                                 return $value->serialize();
222                                         } catch (\Exception $e) {
223                                                 throw new \InvalidArgumentException(sprintf('Cannot serialize %s.', gettype($value)), $e);
224                                         }
225                                 } else {
226                                         throw new \InvalidArgumentException(sprintf('%s is an object without stringify.', gettype($value)));
227                                 }
228                         case "resource":
229                         case "resource (closed)":
230                                 throw new \InvalidArgumentException(sprintf('%s in configs are not supported yet.', gettype($value)));
231                         case "unknown type":
232                                 throw new \InvalidArgumentException(sprintf('%s is an unknown value', $value));
233                         default:
234                                 throw new \InvalidArgumentException(sprintf('%s is currently unsupported', $value));
235                 }
236         }
237 }