···11+<?php
22+33+namespace SocialDept\Schema\Contracts;
44+55+interface Transformer
66+{
77+ /**
88+ * Transform raw data to model.
99+ */
1010+ public function fromArray(array $data): mixed;
1111+1212+ /**
1313+ * Transform model to raw data.
1414+ */
1515+ public function toArray(mixed $model): array;
1616+1717+ /**
1818+ * Check if this transformer supports the given type.
1919+ */
2020+ public function supports(string $type): bool;
2121+}
+192
src/Services/ModelMapper.php
···11+<?php
22+33+namespace SocialDept\Schema\Services;
44+55+use SocialDept\Schema\Contracts\Transformer;
66+use SocialDept\Schema\Exceptions\SchemaException;
77+88+class ModelMapper
99+{
1010+ /**
1111+ * Registered transformers.
1212+ *
1313+ * @var array<string, Transformer>
1414+ */
1515+ protected array $transformers = [];
1616+1717+ /**
1818+ * Register a transformer for a specific type.
1919+ */
2020+ public function register(string $type, Transformer $transformer): self
2121+ {
2222+ $this->transformers[$type] = $transformer;
2323+2424+ return $this;
2525+ }
2626+2727+ /**
2828+ * Register multiple transformers at once.
2929+ *
3030+ * @param array<string, Transformer> $transformers
3131+ */
3232+ public function registerMany(array $transformers): self
3333+ {
3434+ foreach ($transformers as $type => $transformer) {
3535+ $this->register($type, $transformer);
3636+ }
3737+3838+ return $this;
3939+ }
4040+4141+ /**
4242+ * Transform raw data to model.
4343+ */
4444+ public function fromArray(string $type, array $data): mixed
4545+ {
4646+ $transformer = $this->getTransformer($type);
4747+4848+ if ($transformer === null) {
4949+ throw SchemaException::withContext(
5050+ "No transformer registered for type '{$type}'",
5151+ ['type' => $type]
5252+ );
5353+ }
5454+5555+ return $transformer->fromArray($data);
5656+ }
5757+5858+ /**
5959+ * Transform model to raw data.
6060+ */
6161+ public function toArray(string $type, mixed $model): array
6262+ {
6363+ $transformer = $this->getTransformer($type);
6464+6565+ if ($transformer === null) {
6666+ throw SchemaException::withContext(
6767+ "No transformer registered for type '{$type}'",
6868+ ['type' => $type]
6969+ );
7070+ }
7171+7272+ return $transformer->toArray($model);
7373+ }
7474+7575+ /**
7676+ * Transform multiple items from arrays.
7777+ *
7878+ * @param array<array> $items
7979+ * @return array<mixed>
8080+ */
8181+ public function fromArrayMany(string $type, array $items): array
8282+ {
8383+ return array_map(
8484+ fn (array $item) => $this->fromArray($type, $item),
8585+ $items
8686+ );
8787+ }
8888+8989+ /**
9090+ * Transform multiple items to arrays.
9191+ *
9292+ * @param array<mixed> $items
9393+ * @return array<array>
9494+ */
9595+ public function toArrayMany(string $type, array $items): array
9696+ {
9797+ return array_map(
9898+ fn (mixed $item) => $this->toArray($type, $item),
9999+ $items
100100+ );
101101+ }
102102+103103+ /**
104104+ * Get transformer for a specific type.
105105+ */
106106+ public function getTransformer(string $type): ?Transformer
107107+ {
108108+ // Check exact match first
109109+ if (isset($this->transformers[$type])) {
110110+ return $this->transformers[$type];
111111+ }
112112+113113+ // Check if any transformer supports this type
114114+ foreach ($this->transformers as $transformer) {
115115+ if ($transformer->supports($type)) {
116116+ return $transformer;
117117+ }
118118+ }
119119+120120+ return null;
121121+ }
122122+123123+ /**
124124+ * Check if a transformer is registered for a type.
125125+ */
126126+ public function has(string $type): bool
127127+ {
128128+ return $this->getTransformer($type) !== null;
129129+ }
130130+131131+ /**
132132+ * Unregister a transformer.
133133+ */
134134+ public function unregister(string $type): self
135135+ {
136136+ unset($this->transformers[$type]);
137137+138138+ return $this;
139139+ }
140140+141141+ /**
142142+ * Get all registered transformers.
143143+ *
144144+ * @return array<string, Transformer>
145145+ */
146146+ public function all(): array
147147+ {
148148+ return $this->transformers;
149149+ }
150150+151151+ /**
152152+ * Clear all registered transformers.
153153+ */
154154+ public function clear(): self
155155+ {
156156+ $this->transformers = [];
157157+158158+ return $this;
159159+ }
160160+161161+ /**
162162+ * Try to transform from array, return null if transformer not found.
163163+ */
164164+ public function tryFromArray(string $type, array $data): mixed
165165+ {
166166+ if (! $this->has($type)) {
167167+ return null;
168168+ }
169169+170170+ return $this->fromArray($type, $data);
171171+ }
172172+173173+ /**
174174+ * Try to transform to array, return null if transformer not found.
175175+ */
176176+ public function tryToArray(string $type, mixed $model): ?array
177177+ {
178178+ if (! $this->has($type)) {
179179+ return null;
180180+ }
181181+182182+ return $this->toArray($type, $model);
183183+ }
184184+185185+ /**
186186+ * Get count of registered transformers.
187187+ */
188188+ public function count(): int
189189+ {
190190+ return count($this->transformers);
191191+ }
192192+}