• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 14049246919

24 Mar 2025 09:42PM UTC coverage: 79.353% (-0.04%) from 79.391%
14049246919

push

github

web-flow
feat(support): support array parameters in string manipulations (#1073)

48 of 48 new or added lines in 2 files covered. (100.0%)

735 existing lines in 126 files now uncovered.

10492 of 13222 relevant lines covered (79.35%)

90.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.17
/src/Tempest/Mapper/src/ObjectFactory.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Mapper;
6

7
use Closure;
8
use Tempest\Container\Container;
9
use Tempest\Mapper\Exceptions\CannotMapDataException;
10
use Tempest\Mapper\Exceptions\MissingMapperException;
11
use Tempest\Mapper\Mappers\ArrayToJsonMapper;
12
use Tempest\Mapper\Mappers\JsonToArrayMapper;
13
use Tempest\Mapper\Mappers\ObjectToArrayMapper;
14
use Tempest\Mapper\Mappers\ObjectToJsonMapper;
15
use Tempest\Reflection\FunctionReflector;
16

17
/** @template ClassType */
18
final class ObjectFactory
19
{
20
    private mixed $from;
21

22
    private mixed $to;
23

24
    private array $with = [];
25

26
    private bool $isCollection = false;
27

28
    public function __construct(
143✔
29
        private readonly MapperConfig $config,
30
        private readonly Container $container,
31
    ) {}
143✔
32

33
    /**
34
     * @template T of object
35
     * @param T|class-string<T> $objectOrClass
36
     * @return self<T>
37
     */
38
    public function forClass(mixed $objectOrClass): self
71✔
39
    {
40
        $this->to = $objectOrClass;
71✔
41

42
        return $this;
71✔
43
    }
44

45
    public function withData(mixed $data): self
122✔
46
    {
47
        $this->from = $data;
122✔
48

49
        return $this;
122✔
50
    }
51

52
    /**
53
     * @return self<ClassType[]>
54
     */
55
    public function collection(): self
54✔
56
    {
57
        $this->isCollection = true;
54✔
58

59
        return $this;
54✔
60
    }
61

62
    /**
63
     * @return ClassType
64
     */
65
    public function from(mixed $data): mixed
71✔
66
    {
67
        return $this->mapObject(
71✔
68
            from: $data,
71✔
69
            to: $this->to,
71✔
70
            isCollection: $this->isCollection,
71✔
71
        );
71✔
72
    }
73

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
59✔
80
    {
81
        $this->with = [...$this->with, ...$mappers];
59✔
82

83
        return $this;
59✔
84
    }
85

86
    /**
87
     * @template T of object
88
     * @param T|class-string<T>|string $to
89
     * @return T|T[]|mixed
90
     */
91
    public function to(mixed $to): mixed
105✔
92
    {
93
        return $this->mapObject(
105✔
94
            from: $this->from,
105✔
95
            to: $to,
105✔
96
            isCollection: $this->isCollection,
105✔
97
        );
105✔
98
    }
99

100
    public function do(): mixed
53✔
101
    {
102
        if ($this->with === []) {
53✔
103
            throw new MissingMapperException();
1✔
104
        }
105

106
        $result = $this->from;
52✔
107

108
        foreach ($this->with as $mapper) {
52✔
109
            $result = $this->mapWith(
52✔
110
                mapper: $mapper,
52✔
111
                from: $result,
52✔
112
                to: null,
52✔
113
            );
52✔
114
        }
115

116
        return $result;
52✔
117
    }
118

119
    public function toArray(): array
4✔
120
    {
121
        if (is_object($this->from)) {
4✔
122
            return $this->with(ObjectToArrayMapper::class)->do();
4✔
123
        }
124

UNCOV
125
        if (is_array($this->from)) {
×
126
            return $this->from;
×
127
        }
128

UNCOV
129
        if (is_string($this->from) && json_validate($this->from)) {
×
130
            return $this->with(JsonToArrayMapper::class)->do();
×
131
        }
132

UNCOV
133
        throw new CannotMapDataException($this->from, 'array');
×
134
    }
135

UNCOV
136
    public function toJson(): string
×
137
    {
UNCOV
138
        if (is_object($this->from)) {
×
139
            return $this->with(ObjectToJsonMapper::class)->do();
×
140
        }
141

UNCOV
142
        if (is_array($this->from)) {
×
143
            return $this->with(ArrayToJsonMapper::class)->do();
×
144
        }
145

UNCOV
146
        throw new CannotMapDataException($this->from, 'json');
×
147
    }
148

149
    /**
150
     * @template T of object
151
     * @param T|class-string<T>|string $to
152
     * @return T|mixed
153
     */
154
    public function map(mixed $from, mixed $to): mixed
2✔
155
    {
156
        return $this->mapObject(
2✔
157
            from: $from,
2✔
158
            to: $to,
2✔
159
            isCollection: $this->isCollection,
2✔
160
        );
2✔
161
    }
162

163
    private function mapObject(
126✔
164
        mixed $from,
165
        mixed $to,
166
        bool $isCollection,
167
    ): mixed {
168
        // Map collections
169
        if ($isCollection && is_array($from)) {
126✔
170
            return array_map(
5✔
171
                fn (mixed $item) => $this->mapObject(
5✔
172
                    from: $item,
5✔
173
                    to: $to,
5✔
174
                    isCollection: false,
5✔
175
                ),
5✔
176
                $from,
5✔
177
            );
5✔
178
        }
179

180
        // Map using explicitly defined mappers
181
        if ($this->with) {
126✔
182
            $result = $from;
13✔
183

184
            foreach ($this->with as $mapper) {
13✔
185
                $result = $this->mapWith(
13✔
186
                    mapper: $mapper,
13✔
187
                    from: $result,
13✔
188
                    to: $to,
13✔
189
                );
13✔
190
            }
191

192
            return $result;
11✔
193
        }
194

195
        // Map using an inferred mapper
196
        $mappers = $this->config->mappers;
123✔
197

198
        foreach ($mappers as $mapperClass) {
123✔
199
            /** @var Mapper $mapper */
200
            $mapper = $this->container->get($mapperClass);
123✔
201

202
            if ($mapper->canMap(from: $from, to: $to)) {
123✔
203
                return $mapper->map(from: $from, to: $to);
122✔
204
            }
205
        }
206

207
        throw new CannotMapDataException($from, $to);
1✔
208
    }
209

210
    /**
211
     * @template MapperType of \Tempest\Mapper\Mapper
212
     * @param Closure(MapperType $mapper, mixed $from): mixed|class-string<\Tempest\Mapper\Mapper> $mapper
213
     */
214
    private function mapWith(
59✔
215
        mixed $mapper,
216
        mixed $from,
217
        mixed $to,
218
    ): mixed {
219
        if ($mapper instanceof Closure) {
59✔
220
            $function = new FunctionReflector($mapper);
1✔
221

222
            $data = [
1✔
223
                'from' => $from,
1✔
224
            ];
1✔
225

226
            foreach ($function->getParameters() as $parameter) {
1✔
227
                $data[$parameter->getName()] ??= $this->container->get($parameter->getType()->getName());
1✔
228
            }
229

230
            return $mapper(...$data);
1✔
231
        }
232

233
        $mapper = $this->container->get($mapper);
59✔
234

235
        /** @var Mapper $mapper */
236
        return $mapper->map($from, $to);
59✔
237
    }
238
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc