14
14
15
15
use InvalidArgumentException ;
16
16
use TypeError ;
17
+ use function array_combine ;
18
+ use function array_fill_keys ;
19
+ use function array_map ;
20
+ use function array_reduce ;
17
21
use function filter_var ;
18
22
use function get_class ;
19
23
use function gettype ;
20
24
use function hash ;
21
25
use function intdiv ;
26
+ use function is_numeric ;
22
27
use function is_object ;
23
28
use function is_scalar ;
24
29
use function method_exists ;
27
32
use function preg_replace_callback ;
28
33
use function round ;
29
34
use function sprintf ;
35
+ use function str_split ;
30
36
use function strtoupper ;
31
37
use function substr ;
32
38
use function trim ;
@@ -40,6 +46,7 @@ class Multiavatar
40
46
private const SVG_ELEMENT_HEAD = '<path d="m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z" style="fill:#000;"/> ' ;
41
47
private const SVG_ELEMENT_BASE_STYLE_PROPERTIES = 'stroke-linecap:round;stroke-linejoin:round;stroke-width: ' ;
42
48
private const THEME_LIST = [0 => 'A ' , 1 => 'B ' , 2 => 'C ' ];
49
+ private const BODY_PARTS = ['env ' , 'clo ' , 'head ' , 'mouth ' , 'eyes ' , 'top ' ];
43
50
44
51
public const DEFAULT_OPTIONS = [
45
52
'ver ' => [
@@ -71,15 +78,17 @@ public function __invoke($avatarId, array $options = self::DEFAULT_OPTIONS): str
71
78
return '' ;
72
79
}
73
80
74
- $ svgElements = $ this ->partsToElements ($ this ->avatarToParts ($ avatarId ), $ options );
81
+ $ parts = $ this ->setParts ($ avatarId , $ options );
82
+ $ elements = $ this ->partsToElements ($ parts );
83
+ $ elements = $ this ->filterElements ($ elements , $ options );
75
84
76
85
return self ::SVG_ROOT_OPEN_TAG
77
- . $ svgElements ['env ' ]
78
- . $ svgElements ['head ' ]
79
- . $ svgElements ['clo ' ]
80
- . $ svgElements ['top ' ]
81
- . $ svgElements ['eyes ' ]
82
- . $ svgElements ['mouth ' ]
86
+ . $ elements ['env ' ]
87
+ . $ elements ['head ' ]
88
+ . $ elements ['clo ' ]
89
+ . $ elements ['top ' ]
90
+ . $ elements ['eyes ' ]
91
+ . $ elements ['mouth ' ]
83
92
. self ::SVG_ROOT_CLOSE_TAG ;
84
93
}
85
94
@@ -108,6 +117,10 @@ private function filterOptions(array $inputOptions): array
108
117
$ options ['sansEnv ' ] = filter_var ($ inputOptions ['sansEnv ' ] ?? false , FILTER_VALIDATE_BOOLEAN );
109
118
110
119
if (isset ($ inputOptions ['ver ' ]['part ' ])) {
120
+ if (!is_string ($ inputOptions ['ver ' ]['part ' ]) && !is_numeric ($ inputOptions ['ver ' ]['part ' ])) {
121
+ throw new InvalidArgumentException ('The submitted part is expected to be a scalar; ' .gettype ($ inputOptions ['ver ' ]['part ' ]).' was given . ' );
122
+ }
123
+
111
124
$ part = sprintf ("%'.02d " , $ inputOptions ['ver ' ]['part ' ]);
112
125
if (1 !== preg_match ('/^(0[0-9])|(1[0-5])$/ ' , $ part )) {
113
126
throw new InvalidArgumentException ('The submitted part does not exists; expecting a value between `00` and `15`. ' );
@@ -120,7 +133,11 @@ private function filterOptions(array $inputOptions): array
120
133
return $ options ;
121
134
}
122
135
123
- if (1 !== preg_match ('/^([a-c])$/i ' , $ inputOptions ['ver ' ]['theme ' ])) {
136
+ if (!is_string ($ inputOptions ['ver ' ]['theme ' ])) {
137
+ throw new InvalidArgumentException ('The submitted theme is expected to be a string; ' .gettype ($ inputOptions ['ver ' ]['part ' ]).' was given . ' );
138
+ }
139
+
140
+ if (1 !== preg_match ('/^[a-c]$/i ' , $ inputOptions ['ver ' ]['theme ' ])) {
124
141
throw new InvalidArgumentException ('The submitted theme does not exists; expecting a value between `A`, `B` and `C`. ' );
125
142
}
126
143
@@ -130,29 +147,54 @@ private function filterOptions(array $inputOptions): array
130
147
}
131
148
132
149
/**
133
- * @return array<string,array{part:string,theme:string}>
150
+ * @param array{ver:array{part:string|null, theme:string|null}, sansEnv:bool} $options
151
+ *
152
+ * @return array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}}
134
153
*/
135
- private function avatarToParts (string $ avatarId ): array
154
+ private function setParts (string $ avatarId, array $ options ): array
136
155
{
137
- /** @var string $str */
138
- $ str = preg_replace ("/\D/ " , "" , hash ('sha256 ' , $ avatarId ));
156
+ if (isset ($ options ['ver ' ]['theme ' ], $ options ['ver ' ]['part ' ])) {
157
+ return array_fill_keys (self ::BODY_PARTS , $ options ['ver ' ]);
158
+ }
139
159
160
+ /** @var string $str */
161
+ $ str = preg_replace ('/\D/ ' , '' , hash ('sha256 ' , $ avatarId ));
140
162
$ hash = substr ($ str , 0 , 12 );
141
163
142
- return array_map ([$ this , 'mapPart ' ], [
143
- 'env ' => $ hash [0 ] . $ hash [1 ],
144
- 'clo ' => $ hash [2 ] . $ hash [3 ],
145
- 'head ' => $ hash [4 ] . $ hash [5 ],
146
- 'mouth ' => $ hash [6 ] . $ hash [7 ],
147
- 'eyes ' => $ hash [8 ] . $ hash [9 ],
148
- 'top ' => $ hash [10 ] . $ hash [11 ],
149
- ]);
164
+ /** @var array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts */
165
+ $ parts = array_combine (self ::BODY_PARTS , array_map ([$ this , 'stringToVersion ' ], str_split ($ hash , 2 )));
166
+
167
+ if (isset ($ options ['ver ' ]['theme ' ])) {
168
+ $ theme = $ options ['ver ' ]['theme ' ];
169
+
170
+ /** @var array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts */
171
+ $ parts = array_map (function (array $ settings ) use ($ theme ): array {
172
+ $ settings ['theme ' ] = $ theme ;
173
+
174
+ return $ settings ;
175
+ }, $ parts );
176
+
177
+ return $ parts ;
178
+ }
179
+
180
+ if (isset ($ options ['ver ' ]['part ' ])) {
181
+ $ part = $ options ['ver ' ]['part ' ];
182
+
183
+ /** @var array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts */
184
+ $ parts = array_map (function (array $ settings ) use ($ part ): array {
185
+ $ settings ['part ' ] = $ part ;
186
+
187
+ return $ settings ;
188
+ }, $ parts );
189
+ }
190
+
191
+ return $ parts ;
150
192
}
151
193
152
194
/**
153
195
* @return array{part:string, theme:string}
154
196
*/
155
- private function mapPart (string $ part ): array
197
+ private function stringToVersion (string $ part ): array
156
198
{
157
199
$ part = (int ) round (47 / 100 * (int ) $ part );
158
200
@@ -163,33 +205,26 @@ private function mapPart(string $part): array
163
205
}
164
206
165
207
/**
166
- * @param array<string, array{part:string, theme:string}> $bodyParts
167
- * @param array{ver: array{part:string|null, theme:string|null}, sansEnv: bool} $options
208
+ * @param array{env:array{part:string, theme:string}, clo:array{part:string, theme:string}, head:array{part:string, theme:string}, mouth:array{part:string, theme:string}, eyes:array{part:string, theme:string}, top:array{part:string, theme:string}} $parts
168
209
*
169
- * @return array< string,string>
210
+ * @return array{env: string, clo: string, head:string, mouth:string, eyes:string, top:string}
170
211
*/
171
- private function partsToElements (array $ bodyParts , array $ options ): array
212
+ private function partsToElements (array $ parts ): array
172
213
{
173
- $ elements = [];
174
- foreach ($ bodyParts as $ index => $ settings ) {
175
- $ elements [$ index ] = $ this ->mapElement ($ index , $ settings , $ options );
176
- }
214
+ $ reducer = function (array $ elements , string $ name ) use ($ parts ): array {
215
+ $ elements [$ name ] = $ this ->createElement ($ name , $ parts [$ name ]['part ' ], $ parts [$ name ]['theme ' ]);
216
+
217
+ return $ elements ;
218
+ };
219
+
220
+ /** @var array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string} $elements */
221
+ $ elements = array_reduce (self ::BODY_PARTS , $ reducer , []);
177
222
178
223
return $ elements ;
179
224
}
180
225
181
- /**
182
- * @param array{part:string, theme:string} $settings
183
- * @param array{ver: array{part:string|null, theme:string|null}, sansEnv: bool} $options
184
- */
185
- private function mapElement (string $ name , array $ settings , array $ options ): string
226
+ private function createElement (string $ name , string $ part , string $ theme ): string
186
227
{
187
- if ('env ' === $ name && $ options ['sansEnv ' ]) {
188
- return '' ;
189
- }
190
-
191
- $ part = $ options ['ver ' ]['part ' ] ?? $ settings ['part ' ];
192
- $ theme = $ options ['ver ' ]['theme ' ] ?? $ settings ['theme ' ];
193
228
$ colors = self ::themes ()[$ part ][$ theme ][$ name ];
194
229
$ index = 0 ;
195
230
$ replace = function (array $ result ) use ($ colors , &$ index ): string {
@@ -202,6 +237,21 @@ private function mapElement(string $name, array $settings, array $options): stri
202
237
return $ element ;
203
238
}
204
239
240
+ /**
241
+ * @param array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string} $elements
242
+ * @param array{ver:array{part:string|null, theme:string|null}, sansEnv:bool} $options
243
+ *
244
+ * @return array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string}
245
+ */
246
+ private function filterElements (array $ elements , array $ options ): array
247
+ {
248
+ if ($ options ['sansEnv ' ]) {
249
+ $ elements ['env ' ] = '' ;
250
+ }
251
+
252
+ return $ elements ;
253
+ }
254
+
205
255
/**
206
256
* @return array<int|string, array<int|string, string>>
207
257
*/
0 commit comments