Skip to content

Commit 5783e41

Browse files
committed
Refactor the implementation to easily add/expand functionnality
1 parent 84c2b90 commit 5783e41

File tree

1 file changed

+90
-40
lines changed

1 file changed

+90
-40
lines changed

src/Multiavatar.php

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@
1414

1515
use InvalidArgumentException;
1616
use TypeError;
17+
use function array_combine;
18+
use function array_fill_keys;
19+
use function array_map;
20+
use function array_reduce;
1721
use function filter_var;
1822
use function get_class;
1923
use function gettype;
2024
use function hash;
2125
use function intdiv;
26+
use function is_numeric;
2227
use function is_object;
2328
use function is_scalar;
2429
use function method_exists;
@@ -27,6 +32,7 @@
2732
use function preg_replace_callback;
2833
use function round;
2934
use function sprintf;
35+
use function str_split;
3036
use function strtoupper;
3137
use function substr;
3238
use function trim;
@@ -40,6 +46,7 @@ class Multiavatar
4046
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;"/>';
4147
private const SVG_ELEMENT_BASE_STYLE_PROPERTIES = 'stroke-linecap:round;stroke-linejoin:round;stroke-width:';
4248
private const THEME_LIST = [0 => 'A', 1 => 'B', 2 => 'C'];
49+
private const BODY_PARTS = ['env', 'clo', 'head', 'mouth', 'eyes', 'top'];
4350

4451
public const DEFAULT_OPTIONS = [
4552
'ver' => [
@@ -71,15 +78,17 @@ public function __invoke($avatarId, array $options = self::DEFAULT_OPTIONS): str
7178
return '';
7279
}
7380

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);
7584

7685
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']
8392
. self::SVG_ROOT_CLOSE_TAG;
8493
}
8594

@@ -108,6 +117,10 @@ private function filterOptions(array $inputOptions): array
108117
$options['sansEnv'] = filter_var($inputOptions['sansEnv'] ?? false, FILTER_VALIDATE_BOOLEAN);
109118

110119
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+
111124
$part = sprintf("%'.02d", $inputOptions['ver']['part']);
112125
if (1 !== preg_match('/^(0[0-9])|(1[0-5])$/', $part)) {
113126
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
120133
return $options;
121134
}
122135

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'])) {
124141
throw new InvalidArgumentException('The submitted theme does not exists; expecting a value between `A`, `B` and `C`.');
125142
}
126143

@@ -130,29 +147,54 @@ private function filterOptions(array $inputOptions): array
130147
}
131148

132149
/**
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}}
134153
*/
135-
private function avatarToParts(string $avatarId): array
154+
private function setParts(string $avatarId, array $options): array
136155
{
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+
}
139159

160+
/** @var string $str */
161+
$str = preg_replace('/\D/', '', hash('sha256', $avatarId));
140162
$hash = substr($str, 0, 12);
141163

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;
150192
}
151193

152194
/**
153195
* @return array{part:string, theme:string}
154196
*/
155-
private function mapPart(string $part): array
197+
private function stringToVersion(string $part): array
156198
{
157199
$part = (int) round(47 / 100 * (int) $part);
158200

@@ -163,33 +205,26 @@ private function mapPart(string $part): array
163205
}
164206

165207
/**
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
168209
*
169-
* @return array<string,string>
210+
* @return array{env:string, clo:string, head:string, mouth:string, eyes:string, top:string}
170211
*/
171-
private function partsToElements(array $bodyParts, array $options): array
212+
private function partsToElements(array $parts): array
172213
{
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, []);
177222

178223
return $elements;
179224
}
180225

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
186227
{
187-
if ('env' === $name && $options['sansEnv']) {
188-
return '';
189-
}
190-
191-
$part = $options['ver']['part'] ?? $settings['part'];
192-
$theme = $options['ver']['theme'] ?? $settings['theme'];
193228
$colors = self::themes()[$part][$theme][$name];
194229
$index = 0;
195230
$replace = function (array $result) use ($colors, &$index): string {
@@ -202,6 +237,21 @@ private function mapElement(string $name, array $settings, array $options): stri
202237
return $element;
203238
}
204239

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+
205255
/**
206256
* @return array<int|string, array<int|string, string>>
207257
*/

0 commit comments

Comments
 (0)