55namespace Tempest \Mapper ;
66
77use Closure ;
8- use ReflectionException ;
98use Tempest \Container \Container ;
109use Tempest \Mapper \Exceptions \CannotMapDataException ;
10+ use Tempest \Mapper \Exceptions \MissingMapperException ;
1111use Tempest \Mapper \Mappers \ArrayToJsonMapper ;
1212use Tempest \Mapper \Mappers \JsonToArrayMapper ;
1313use Tempest \Mapper \Mappers \ObjectToArrayMapper ;
@@ -21,13 +21,14 @@ final class ObjectFactory
2121
2222 private mixed $ to ;
2323
24+ private array $ with = [];
25+
2426 private bool $ isCollection = false ;
2527
2628 public function __construct (
2729 private readonly MapperConfig $ config ,
2830 private readonly Container $ container ,
29- ) {
30- }
31+ ) {}
3132
3233 /**
3334 * @template T of object
@@ -70,6 +71,18 @@ public function from(mixed $data): mixed
7071 );
7172 }
7273
74+ /**
75+ * @template MapperType of \Tempest\Mapper\Mapper
76+ * @param Closure(MapperType $mapper, mixed $from): mixed|class-string<\Tempest\Mapper\Mapper> ...$mappers
77+ * @return self<ClassType>
78+ */
79+ public function with (Closure |string ...$ mappers ): self
80+ {
81+ $ this ->with = [...$ this ->with , ...$ mappers ];
82+
83+ return $ this ;
84+ }
85+
7386 /**
7487 * @template T of object
7588 * @param T|class-string<T>|string $to
@@ -84,33 +97,53 @@ public function to(mixed $to): mixed
8497 );
8598 }
8699
100+ public function do (): mixed
101+ {
102+ if ($ this ->with === []) {
103+ throw new MissingMapperException ();
104+ }
105+
106+ $ result = $ this ->from ;
107+
108+ foreach ($ this ->with as $ mapper ) {
109+ $ result = $ this ->mapWith (
110+ mapper: $ mapper ,
111+ from: $ result ,
112+ to: null ,
113+ );
114+ }
115+
116+ return $ result ;
117+ }
118+
87119 public function toArray (): array
88120 {
89121 if (is_object ($ this ->from )) {
90- return $ this ->with (ObjectToArrayMapper::class);
122+ return $ this ->with (ObjectToArrayMapper::class)-> do () ;
91123 }
124+
92125 if (is_array ($ this ->from )) {
93126 return $ this ->from ;
94127 }
128+
95129 if (is_string ($ this ->from ) && json_validate ($ this ->from )) {
96- return $ this ->with (JsonToArrayMapper::class);
97- }
98- else {
99- throw new CannotMapDataException ($ this ->from , 'array ' );
130+ return $ this ->with (JsonToArrayMapper::class)->do ();
100131 }
132+
133+ throw new CannotMapDataException ($ this ->from , 'array ' );
101134 }
102135
103136 public function toJson (): string
104137 {
105138 if (is_object ($ this ->from )) {
106- return $ this ->with (ObjectToJsonMapper::class);
139+ return $ this ->with (ObjectToJsonMapper::class)-> do () ;
107140 }
141+
108142 if (is_array ($ this ->from )) {
109- return $ this ->with (ArrayToJsonMapper::class);
110- }
111- else {
112- throw new CannotMapDataException ($ this ->from , 'json ' );
143+ return $ this ->with (ArrayToJsonMapper::class)->do ();
113144 }
145+
146+ throw new CannotMapDataException ($ this ->from , 'json ' );
114147 }
115148
116149 /**
@@ -127,44 +160,13 @@ public function map(mixed $from, mixed $to): mixed
127160 );
128161 }
129162
130- /**
131- * @template MapperType of \Tempest\Mapper\Mapper
132- * @param Closure(MapperType $mapper, mixed $from): mixed|class-string<\Tempest\Mapper\Mapper> ...$mappers
133- * @throws ReflectionException
134- */
135- public function with (Closure |string ...$ mappers ): mixed
136- {
137- $ result = $ this ->from ;
138-
139- foreach ($ mappers as $ mapper ) {
140- if ($ mapper instanceof Closure) {
141- $ function = new FunctionReflector ($ mapper );
142-
143- $ data = [
144- 'from ' => $ result ,
145- ];
146-
147- foreach ($ function ->getParameters () as $ parameter ) {
148- $ data [$ parameter ->getName ()] ??= $ this ->container ->get ($ parameter ->getType ()->getName ());
149- }
150-
151- $ result = $ mapper (...$ data );
152- } else {
153- $ mapper = $ this ->container ->get ($ mapper );
154-
155- /** @var Mapper $mapper */
156- $ result = $ mapper ->map ($ result , null );
157- }
158- }
159-
160- return $ result ;
161- }
162-
163163 private function mapObject (
164164 mixed $ from ,
165165 mixed $ to ,
166166 bool $ isCollection ,
167- ): mixed {
167+ ): mixed
168+ {
169+ // Map collections
168170 if ($ isCollection && is_array ($ from )) {
169171 return array_map (
170172 fn (mixed $ item ) => $ this ->mapObject (
@@ -176,6 +178,22 @@ private function mapObject(
176178 );
177179 }
178180
181+ // Map using explicitly defined mappers
182+ if ($ this ->with ) {
183+ $ result = $ from ;
184+
185+ foreach ($ this ->with as $ mapper ) {
186+ $ result = $ this ->mapWith (
187+ mapper: $ mapper ,
188+ from: $ result ,
189+ to: $ to ,
190+ );
191+ }
192+
193+ return $ result ;
194+ }
195+
196+ // Map using an inferred mapper
179197 $ mappers = $ this ->config ->mappers ;
180198
181199 foreach ($ mappers as $ mapperClass ) {
@@ -189,4 +207,34 @@ private function mapObject(
189207
190208 throw new CannotMapDataException ($ from , $ to );
191209 }
210+
211+ /**
212+ * @template MapperType of \Tempest\Mapper\Mapper
213+ * @param Closure(MapperType $mapper, mixed $from): mixed|class-string<\Tempest\Mapper\Mapper> $mapper
214+ */
215+ private function mapWith (
216+ mixed $ mapper ,
217+ mixed $ from ,
218+ mixed $ to ,
219+ ): mixed
220+ {
221+ if ($ mapper instanceof Closure) {
222+ $ function = new FunctionReflector ($ mapper );
223+
224+ $ data = [
225+ 'from ' => $ from ,
226+ ];
227+
228+ foreach ($ function ->getParameters () as $ parameter ) {
229+ $ data [$ parameter ->getName ()] ??= $ this ->container ->get ($ parameter ->getType ()->getName ());
230+ }
231+
232+ return $ mapper (...$ data );
233+ }
234+
235+ $ mapper = $ this ->container ->get ($ mapper );
236+
237+ /** @var Mapper $mapper */
238+ return $ mapper ->map ($ from , $ to );
239+ }
192240}
0 commit comments