66
77use Closure ;
88use ReflectionProperty ;
9+ use SlackPhp \BlockKit \Blocks \Block ;
910use SlackPhp \BlockKit \Collections \ComponentCollection ;
1011use SlackPhp \BlockKit \Component ;
1112use SlackPhp \BlockKit \FauxProperty ;
1213use SlackPhp \BlockKit \Property ;
1314use SlackPhp \BlockKit \Tools \Reflector ;
1415use SlackPhp \BlockKit \Type ;
16+ use Throwable ;
1517
1618class Hydrator
1719{
18- /** @var array<string, mixed> */
19- private array $ data ;
20-
2120 /** @var array<string, bool> */
2221 private array $ consumed = [];
2322
24- public readonly Component $ component ;
23+ public static function forJson (string $ json ): self
24+ {
25+ try {
26+ return new self (json_decode ($ json , true , 512 , JSON_THROW_ON_ERROR ));
27+ } catch (Throwable $ err ) {
28+ throw new HydrationException ('JSON error hydrating component ' , [], $ err );
29+ }
30+ }
2531
2632 /**
2733 * @param array<string, mixed> $data
2834 */
29- public function __construct (array $ data , ?string $ initiatingClass = null )
35+ public function __construct (private array $ data )
36+ {}
37+
38+ /**
39+ * @param class-string $targetClass
40+ */
41+ public function getComponent (string $ targetClass ): Component
3042 {
31- $ this ->data = $ data ;
32- $ this ->component = $ this ->initComponent ($ initiatingClass );
43+ try {
44+ $ component = $ this ->initComponent ($ targetClass );
45+ $ this ->fillComponent ($ component );
46+ return $ component ;
47+ } catch (HydrationException $ ex ) {
48+ throw $ ex ;
49+ } catch (Throwable $ ex ) {
50+ throw new HydrationException ($ ex ->getMessage (), [], $ ex );
51+ }
3352 }
3453
35- private function initComponent (?string $ initiatingClass ): Component
54+ /**
55+ * @param class-string $targetClass
56+ */
57+ private function initComponent (string $ targetClass ): Component
3658 {
3759 $ type = Type::fromValue ($ this ->useValue ('type ' ));
3860
39- $ class = $ type ?->toClass() ?? $ initiatingClass ;
40- if ($ class === null ) {
41- throw new HydrationException ('The class to hydrate could not be determine. ' );
61+ // Since the "image" type is used in Slack as both a Block and Element type, we have to map an image component
62+ // in a surface context to a different type in order to resolve it to the BlockImage class. The reverse logic
63+ // for dehydration is handled by the BlockImage's `AliasType` attribute.
64+ if ($ type === Type::IMAGE && $ targetClass === Block::class) {
65+ $ type = Type::BLOCK_IMAGE ;
66+ }
67+
68+ $ typeClass = $ type ?->toClass() ?? $ targetClass ;
69+
70+ if (!is_a ($ typeClass , Component::class, true )) {
71+ throw new HydrationException ('Class %s not a Component class ' , [$ typeClass ]);
4272 }
4373
44- $ reflection = Reflector::component ($ class );
45- if ($ reflection ->isAbstract () || !$ reflection ->isSubclassOf (Component::class)) {
46- throw new HydrationException ('Class %s is abstract or is not a Component ' , [$ reflection ->getShortName ()]);
74+ if (!is_a ($ typeClass , $ targetClass , true )) {
75+ throw new HydrationException ('Class %s is not a subclass of %s ' , [$ typeClass , $ targetClass ]);
76+ }
77+
78+ $ reflection = Reflector::component ($ typeClass );
79+
80+ if ($ reflection ->isAbstract ()) {
81+ throw new HydrationException ('Class %s is abstract and cannot be instantiated ' , [$ reflection ->getName ()]);
4782 }
4883
4984 /** @var Component $component */
@@ -52,31 +87,25 @@ private function initComponent(?string $initiatingClass): Component
5287 return $ component ;
5388 }
5489
55- public function getComponent ( ): Component
90+ private function fillComponent ( Component $ component ): void
5691 {
57- try {
58- $ class = Reflector::component ($ this ->component );
59-
60- foreach ($ class ->getProperties () as $ classProperty ) {
61- $ property = Reflector::propertyAttribute ($ classProperty );
62- $ fauxProperty = Reflector::fauxPropertyAttribute ($ classProperty );
63-
64- if ($ property || $ fauxProperty ) {
65- $ setter = $ class ->getMethod ($ classProperty ->getName ())->getClosure ($ this ->component );
66- if ($ property ) {
67- $ this ->setProperty ($ classProperty , $ property , $ setter );
68- } else {
69- $ this ->setFauxProperty ($ fauxProperty , $ setter );
70- }
92+ $ class = Reflector::component ($ component );
93+
94+ foreach ($ class ->getProperties () as $ classProperty ) {
95+ $ property = Reflector::propertyAttribute ($ classProperty );
96+ $ fauxProperty = Reflector::fauxPropertyAttribute ($ classProperty );
97+
98+ if ($ property || $ fauxProperty ) {
99+ $ setter = $ class ->getMethod ($ classProperty ->getName ())->getClosure ($ component );
100+ if ($ property ) {
101+ $ this ->setProperty ($ classProperty , $ property , $ setter );
102+ } else {
103+ $ this ->setFauxProperty ($ fauxProperty , $ setter );
71104 }
72105 }
73-
74- $ this ->component ->extra (array_diff_key ($ this ->data , $ this ->consumed ));
75-
76- return $ this ->component ;
77- } catch (\Throwable $ ex ) {
78- throw new HydrationException ($ ex ->getMessage (), [], $ ex );
79106 }
107+
108+ $ component ->extra (array_diff_key ($ this ->data , $ this ->consumed ));
80109 }
81110
82111 private function setProperty (ReflectionProperty $ reflection , Property $ property , Closure $ setValue ): void
@@ -100,7 +129,7 @@ private function setProperty(ReflectionProperty $reflection, Property $property,
100129 private function setFauxProperty (FauxProperty $ property , Closure $ setValue ): void
101130 {
102131 if ($ property ->fields === ['* ' ]) {
103- $ setValue (array_map (fn (array $ value ) => Component::fromArray ($ value ), $ this ->useComponents ( null )));
132+ $ setValue (array_map (fn (array $ value ) => Component::fromArray ($ value ), $ this ->useAllAsArray ( )));
104133 } else {
105134 $ setValue ($ this ->useValues (...$ property ->fields ));
106135 }
@@ -130,14 +159,8 @@ private function useValues(string ...$keys): array
130159 /**
131160 * @return array<int, mixed>
132161 */
133- private function useArray (? string $ key ): array
162+ private function useArray (string $ key ): array
134163 {
135- if ($ key === null ) {
136- $ this ->consumed += array_fill_keys (array_keys ($ this ->data ), true );
137-
138- return array_values ($ this ->data );
139- }
140-
141164 $ this ->consumed [$ key ] = true ;
142165
143166 return $ this ->data [$ key ] ?? [];
@@ -146,9 +169,11 @@ private function useArray(?string $key): array
146169 /**
147170 * @return array<int, array<string, mixed>>
148171 */
149- private function useComponents (? string $ key ): array
172+ private function useAllAsArray ( ): array
150173 {
151- return $ this ->useArray ($ key );
174+ $ this ->consumed += array_fill_keys (array_keys ($ this ->data ), true );
175+
176+ return array_values ($ this ->data );
152177 }
153178
154179 /**
0 commit comments