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

tempestphp / tempest-framework / 14024978163

23 Mar 2025 05:55PM UTC coverage: 79.391% (-0.05%) from 79.441%
14024978163

push

github

web-flow
feat(view): cache Blade and Twig templates in internal storage (#1061)

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

912 existing lines in 110 files now uncovered.

10478 of 13198 relevant lines covered (79.39%)

91.09 hits per line

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

99.13
/src/Tempest/Database/src/Mappers/QueryToModelMapper.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Database\Mappers;
6

7
use Tempest\Database\DatabaseModel;
8
use Tempest\Database\Query;
9
use Tempest\Mapper\CasterFactory;
10
use Tempest\Mapper\Mapper;
11
use Tempest\Reflection\ClassReflector;
12
use Tempest\Reflection\PropertyReflector;
13

14
final readonly class QueryToModelMapper implements Mapper
15
{
16
    public function __construct(
123✔
17
        private CasterFactory $casterFactory,
18
    ) {
19
    }
123✔
20

21
    public function canMap(mixed $from, mixed $to): bool
123✔
22
    {
23
        return $from instanceof Query;
123✔
24
    }
25

26
    public function map(mixed $from, mixed $to): array
49✔
27
    {
28
        /** @var \Tempest\Database\Query $from */
29
        /** @var class-string<DatabaseModel> $to */
30
        $class = new ClassReflector($to);
49✔
31
        $table = $class->callStatic('table');
49✔
32

33
        $models = [];
49✔
34

35
        foreach ($from->fetch() as $row) {
49✔
36
            $idField = $table->tableName . '.id';
46✔
37

38
            $id = $row[$idField];
46✔
39

40
            $model = $models[$id] ?? $class->newInstanceWithoutConstructor();
46✔
41

42
            $models[$id] = $this->parse($class, $model, $row);
46✔
43
        }
44

45
        return $this->makeLazyCollection($models);
48✔
46
    }
47

48
    private function parse(ClassReflector $class, DatabaseModel $model, array $row): DatabaseModel
46✔
49
    {
50
        foreach ($row as $key => $value) {
46✔
51
            $keyParts = explode('.', $key);
46✔
52

53
            $propertyName = $keyParts[1];
46✔
54

55
            $count = count($keyParts);
46✔
56

57
            // TODO: clean up and document
58
            if ($count > 3) {
46✔
59
                $property = $class->getProperty(rtrim($propertyName, '[]'));
15✔
60

61
                if ($property->getIterableType()?->matches(DatabaseModel::class)) {
15✔
62
                    $collection = $property->get($model, []);
10✔
63
                    $childId = $row[$keyParts[0] . '.' . $keyParts[1] . '.id'];
10✔
64

65
                    if ($childId) {
10✔
66
                        $iterableType = $property->getIterableType();
8✔
67

68
                        $childModel = $collection[$childId] ?? $iterableType->asClass()->newInstanceWithoutConstructor();
8✔
69

70
                        unset($keyParts[0]);
8✔
71

72
                        $collection[$childId] = $this->parse(
8✔
73
                            $iterableType->asClass(),
8✔
74
                            $childModel,
8✔
75
                            [implode('.', $keyParts) => $value],
8✔
76
                        );
8✔
77
                    }
78

79
                    $property->set($model, $collection);
10✔
80
                } else {
81
                    $childModelType = $property->getType();
5✔
82

83
                    $childModel = $property->get($model, $childModelType->asClass()->newInstanceWithoutConstructor());
5✔
84

85
                    unset($keyParts[0]);
5✔
86

87
                    $property->set($model, $this->parse(
5✔
88
                        $childModelType->asClass(),
5✔
89
                        $childModel,
5✔
90
                        [implode('.', $keyParts) => $value],
5✔
91
                    ));
5✔
92
                }
93
            } elseif ($count === 3) {
46✔
94
                $childId = $row[$keyParts[0] . '.' . $keyParts[1] . '.id'] ?? null;
20✔
95

96
                if (str_contains($keyParts[1], '[]')) {
20✔
97
                    $property = $class->getProperty(rtrim($propertyName, '[]'));
11✔
98

99
                    $model = $this->parseHasMany(
11✔
100
                        $property,
11✔
101
                        $model,
11✔
102
                        (string) $childId,
11✔
103
                        $keyParts[2],
11✔
104
                        $value,
11✔
105
                    );
11✔
106
                } else {
107
                    $property = $class->getProperty($propertyName);
17✔
108

109
                    $model = $this->parseBelongsTo(
17✔
110
                        $property,
17✔
111
                        $model,
17✔
112
                        $keyParts[2],
17✔
113
                        $value,
17✔
114
                    );
17✔
115
                }
116
            } else {
117
                $property = $class->getProperty($propertyName);
46✔
118

119
                $model = $this->parseProperty($property, $model, $value);
46✔
120
            }
121
        }
122

123
        return $model;
46✔
124
    }
125

126
    private function parseProperty(PropertyReflector $property, DatabaseModel $model, mixed $value): DatabaseModel
46✔
127
    {
128
        $caster = $this->casterFactory->forProperty($property);
46✔
129

130
        if ($value && $caster !== null) {
46✔
131
            $value = $caster->cast($value);
46✔
132
        }
133

134
        if ($value === null && ! $property->isNullable()) {
46✔
UNCOV
135
            return $model;
×
136
        }
137

138
        $property->set($model, $value);
46✔
139

140
        return $model;
46✔
141
    }
142

143
    private function parseBelongsTo(
17✔
144
        PropertyReflector $property,
145
        DatabaseModel $model,
146
        string $childProperty,
147
        mixed $value,
148
    ): DatabaseModel {
149
        $childModel = $property->get(
17✔
150
            $model,
17✔
151
            $property->getType()->asClass()->newInstanceWithoutConstructor(),
17✔
152
        );
17✔
153

154
        $childProperty = new ClassReflector($childModel)->getProperty($childProperty);
17✔
155

156
        // TODO: must pass through the mapper
157
        $this->parseProperty(
17✔
158
            $childProperty,
17✔
159
            $childModel,
17✔
160
            $value,
17✔
161
        );
17✔
162

163
        $property->set($model, $childModel);
17✔
164

165
        return $model;
17✔
166
    }
167

168
    private function parseHasMany(
11✔
169
        PropertyReflector $property,
170
        DatabaseModel $model,
171
        ?string $childId,
172
        string $childProperty,
173
        mixed $value,
174
    ): DatabaseModel {
175
        $collection = $property->get($model, []);
11✔
176

177
        if (! $childId) {
11✔
178
            $property->set($model, $collection);
2✔
179

180
            return $model;
2✔
181
        }
182

183
        $childModel = $collection[$childId] ?? $property->getIterableType()->asClass()->newInstanceWithoutConstructor();
9✔
184

185
        $childProperty = new ClassReflector($childModel)->getProperty($childProperty);
9✔
186

187
        // TODO: must pass through the mapper
188
        $this->parseProperty(
9✔
189
            $childProperty,
9✔
190
            $childModel,
9✔
191
            $value,
9✔
192
        );
9✔
193

194
        $collection[$childId] = $childModel;
9✔
195

196
        $property->set($model, $collection);
9✔
197

198
        return $model;
9✔
199
    }
200

201
    private function makeLazyCollection(array $models): array
48✔
202
    {
203
        $lazy = [];
48✔
204

205
        foreach ($models as $model) {
48✔
206
            $lazy[] = $this->makeLazyModel($model);
46✔
207
        }
208

209
        return $lazy;
48✔
210
    }
211

212
    private function makeLazyModel(DatabaseModel $model): DatabaseModel
46✔
213
    {
214
        $classReflector = new ClassReflector($model);
46✔
215

216
        foreach ($classReflector->getPublicProperties() as $property) {
46✔
217
            if ($property->isUninitialized($model)) {
46✔
218
                $property->unset($model);
16✔
219

220
                continue;
16✔
221
            }
222

223
            if ($property->getIterableType()?->matches(DatabaseModel::class)) {
46✔
224
                foreach ($property->get($model) as $childModel) {
23✔
225
                    $this->makeLazyModel($childModel);
9✔
226
                }
227

228
                break;
23✔
229
            }
230
        }
231

232
        return $model;
46✔
233
    }
234
}
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