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

tempestphp / tempest-framework / 14370002535

09 Apr 2025 12:32PM UTC coverage: 81.363% (+0.04%) from 81.323%
14370002535

push

github

web-flow
feat(database): model validation before update, create, and save (#1131)

36 of 38 new or added lines in 4 files covered. (94.74%)

3 existing lines in 1 file now uncovered.

11525 of 14165 relevant lines covered (81.36%)

106.7 hits per line

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

97.47
/src/Tempest/Database/src/IsDatabaseModel.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Database;
6

7
use Tempest\Database\Builder\QueryBuilders\SelectQueryBuilder;
8
use Tempest\Database\Exceptions\MissingRelation;
9
use Tempest\Database\Exceptions\MissingValue;
10
use Tempest\Reflection\ClassReflector;
11
use Tempest\Reflection\PropertyReflector;
12
use Tempest\Validation\SkipValidation;
13

14
use function Tempest\make;
15

16
trait IsDatabaseModel
17
{
18
    #[SkipValidation]
19
    public Id $id;
20

21
    public static function new(mixed ...$params): self
77✔
22
    {
23
        return make(self::class)->from($params);
77✔
24
    }
25

26
    public static function resolve(string $input): static
1✔
27
    {
28
        return self::get(new Id($input));
1✔
29
    }
30

31
    /**
32
     * @return \Tempest\Database\Builder\QueryBuilders\SelectQueryBuilder<self>
33
     */
34
    public static function select(): SelectQueryBuilder
58✔
35
    {
36
        return query(self::class)->select();
58✔
37
    }
38

39
    /** @return self[] */
40
    public static function all(array $relations = []): array
23✔
41
    {
42
        return self::select()
23✔
43
            ->with(...$relations)
23✔
44
            ->all();
23✔
45
    }
46

47
    public static function get(Id $id, array $relations = []): ?self
20✔
48
    {
49
        return self::select()
20✔
50
            ->with(...$relations)
20✔
51
            ->get($id);
20✔
52
    }
53

54
    public static function find(mixed ...$conditions): SelectQueryBuilder
1✔
55
    {
56
        $query = self::select();
1✔
57

58
        array_walk($conditions, fn ($value, $column) => $query->whereField($column, $value));
1✔
59

60
        return $query;
1✔
61
    }
62

63
    public static function create(mixed ...$params): self
66✔
64
    {
65
        model(self::class)->validate(...$params);
66✔
66

67
        $model = self::new(...$params);
65✔
68

69
        $model->id = query(self::class)
65✔
70
            ->insert($model)
65✔
71
            ->build()
65✔
72
            ->execute();
65✔
73

74
        return $model;
65✔
75
    }
76

77
    public static function findOrNew(array $find, array $update): self
1✔
78
    {
79
        $existing = self::select()->bind(...$find);
1✔
80

81
        foreach ($find as $key => $value) {
1✔
82
            $existing = $existing->where("{$key} = :{$key}");
1✔
83
        }
84

85
        $model = $existing->first() ?? self::new(...$find);
1✔
86

87
        foreach ($update as $key => $value) {
1✔
88
            $model->{$key} = $value;
1✔
89
        }
90

91
        return $model;
1✔
92
    }
93

94
    public static function updateOrCreate(array $find, array $update): self
1✔
95
    {
96
        $model = self::findOrNew($find, $update);
1✔
97

98
        if (! isset($model->id)) {
1✔
NEW
99
            return self::create(...$update);
×
100
        }
101

102
        return $model->save();
1✔
103
    }
104

105
    public function __get(string $name): mixed
3✔
106
    {
107
        $property = PropertyReflector::fromParts($this, $name);
3✔
108

109
        if ($property->hasAttribute(Lazy::class)) {
3✔
110
            $this->load($name);
1✔
111

112
            return $property->getValue($this);
1✔
113
        }
114

115
        $type = $property->getType();
2✔
116

117
        if ($type->isIterable()) {
2✔
118
            throw new MissingRelation($this, $name);
×
119
        }
120

121
        if ($type->isBuiltIn()) {
2✔
122
            throw new MissingValue($this, $name);
1✔
123
        }
124

125
        throw new MissingRelation($this, $name);
1✔
126
    }
127

128
    public function load(string ...$relations): self
9✔
129
    {
130
        $new = self::get($this->id, $relations);
9✔
131

132
        foreach (new ClassReflector($new)->getPublicProperties() as $property) {
9✔
133
            $property->setValue($this, $property->getValue($new));
9✔
134
        }
135

136
        return $this;
9✔
137
    }
138

139
    public function save(): self
45✔
140
    {
141
        $model = model($this);
45✔
142

143
        $model->validate(...model($this)->getPropertyValues());
45✔
144

145
        if (! isset($this->id)) {
44✔
146
            $query = query($this::class)->insert($this);
37✔
147

148
            $this->id = $query->execute();
37✔
149
        } else {
150
            query($this)->update(
9✔
151
                ...model($this)->getPropertyValues(),
9✔
152
            )->execute();
9✔
153
        }
154

155
        return $this;
44✔
156
    }
157

158
    public function update(mixed ...$params): self
3✔
159
    {
160
        model(self::class)->validate(...$params);
3✔
161

162
        foreach ($params as $key => $value) {
2✔
163
            $this->{$key} = $value;
2✔
164
        }
165

166
        query($this)
2✔
167
            ->update(...$params)
2✔
168
            ->build()
2✔
169
            ->execute();
2✔
170

171
        return $this;
2✔
172
    }
173

174
    public function delete(): void
2✔
175
    {
176
        query($this)
2✔
177
            ->delete()
2✔
178
            ->build()
2✔
179
            ->execute();
2✔
180
    }
181
}
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

© 2025 Coveralls, Inc