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

tempestphp / tempest-framework / 14104197200

27 Mar 2025 10:20AM UTC coverage: 79.581% (+0.2%) from 79.334%
14104197200

Pull #1076

github

web-flow
Merge 7b37f2d9f into 6af05d563
Pull Request #1076: refactor(database): remove `DatabaseModel` interface

471 of 480 new or added lines in 34 files covered. (98.13%)

1 existing line in 1 file now uncovered.

10679 of 13419 relevant lines covered (79.58%)

92.77 hits per line

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

97.37
/src/Tempest/Database/src/Builder/QueryBuilders/InsertQueryBuilder.php
1
<?php
2

3
namespace Tempest\Database\Builder\QueryBuilders;
4

5
use Tempest\Database\Builder\ModelDefinition;
6
use Tempest\Database\Builder\TableDefinition;
7
use Tempest\Database\Id;
8
use Tempest\Database\Query;
9
use Tempest\Mapper\SerializerFactory;
10
use Tempest\Reflection\ClassReflector;
11

12
use function Tempest\Support\arr;
13

14
final class InsertQueryBuilder
15
{
16
    public function __construct(
63✔
17
        private string|object $model,
18
        private array $rows,
19
        private SerializerFactory $serializerFactory,
20
    ) {}
63✔
21

NEW
22
    public function execute(...$bindings): Id
×
23
    {
NEW
24
        return $this->build()->execute(...$bindings);
×
25
    }
26

27
    public function build(): Query
63✔
28
    {
29
        $table = $this->resolveTableDefinition();
63✔
30

31
        $columns = $this->resolveColumns();
63✔
32

33
        $values = $this->resolveValues($columns);
63✔
34

35
        $valuesPlaceholders = arr($values)
63✔
36
            ->map(function (array $row) {
63✔
37
                return sprintf(
63✔
38
                    '(%s)',
63✔
39
                    arr($row)->map(fn (mixed $value) => '?')->implode(', '),
63✔
40
                );
63✔
41
            })
63✔
42
            ->implode(', ');
63✔
43

44
        return new Query(
63✔
45
            sprintf(
63✔
46
                <<<SQL
63✔
47
                INSERT INTO %s (%s)
48
                VALUES %s
49
                SQL,
63✔
50
                $table,
63✔
51
                arr($columns)->map(fn (string $column) => "`{$column}`")->implode(', '),
63✔
52
                $valuesPlaceholders,
63✔
53
            ),
63✔
54
            arr($values)->flatten(1)->toArray(),
63✔
55
        );
63✔
56
    }
57

58
    private function resolveColumns(): array
63✔
59
    {
60
        $firstEntry = $this->rows[array_key_first($this->rows)];
63✔
61

62
        if (is_array($firstEntry)) {
63✔
63
            return array_keys($firstEntry);
2✔
64
        }
65

66
        if (! is_object($firstEntry)) {
61✔
67
            // TODO: Shouldn't be allowed
68
        }
69

70
        $modelClass = new ClassReflector($firstEntry);
61✔
71

72
        $columns = [];
61✔
73

74
        foreach ($modelClass->getPublicProperties() as $property) {
61✔
75
            if (! $property->isInitialized($firstEntry)) {
61✔
76
                continue;
61✔
77
            }
78

79
            // 1:n relations
80
            if ($property->getIterableType()?->isRelation()) {
61✔
81
                continue;
33✔
82
            }
83

84
            if ($property->getType()->isRelation()) {
61✔
85
                $columns[] = $property->getName() . '_id';
31✔
86
            } else {
87
                $columns[] = $property->getName();
61✔
88
            }
89
        }
90

91
        return $columns;
61✔
92
    }
93

94
    private function resolveValues(array $columns): array
63✔
95
    {
96
        $values = [];
63✔
97

98
        foreach ($this->rows as $model) {
63✔
99
            if (is_array($model)) {
63✔
100
                $values[] = $model;
3✔
101

102
                continue;
3✔
103
            }
104

105
            if (! is_object($model)) {
61✔
106
                // TODO: this should now be allowed
107
            }
108

109
            $modelClass = new ClassReflector($model);
61✔
110

111
            $values[] = arr($columns)
61✔
112
                ->map(function (string $column) use ($modelClass, $model) {
61✔
113
                    // TODO: improve
114
                    $column = str($column)->replaceEnd('_id', '');
61✔
115

116
                    $property = $modelClass->getProperty($column);
61✔
117

118
                    $value = $model->{$column};
61✔
119

120
                    if ($value === null) {
61✔
121
                        return $value;
8✔
122
                    }
123

124
                    if ($property->getType()->isRelation()) {
61✔
125
                        if (isset($value->id)) {
24✔
126
                            $value = $value->id->id;
12✔
127
                        } else {
128
                            $value = new InsertQueryBuilder(
12✔
129
                                $value::class,
12✔
130
                                [$value],
12✔
131
                                $this->serializerFactory,
12✔
132
                            )->build();
12✔
133
                        }
134
                    }
135

136
                    // Check if serializer is available for value serialization
137
                    if (($serializer = $this->serializerFactory->forProperty($property)) !== null) {
61✔
138
                        return $serializer->serialize($value);
61✔
139
                    }
140

141
                    return $value;
24✔
142
                })
61✔
143
                ->toArray();
61✔
144
        }
145

146
        return $values;
63✔
147
    }
148

149
    private function resolveTableDefinition(): TableDefinition
63✔
150
    {
151
        $modelDefinition = ModelDefinition::tryFrom($this->model);
63✔
152

153
        if ($modelDefinition === null) {
63✔
154
            return new TableDefinition($this->model);
2✔
155
        }
156

157
        return $modelDefinition->getTableDefinition();
61✔
158
    }
159
}
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