You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: "Database interactions are the heart of applications. Tempest provides a minimalistic query builder, but its main strength is in its integrations with the mapper."
4
-
keywords: "Experimental"
4
+
keywords: ["Experimental", "ORM"]
5
5
---
6
6
7
-
## Overview
8
-
9
7
:::warning
10
-
The ORM implementation of Tempest is currently experimental and is not covered by our backwards compatibility promise. We are also currently discussing about the approach to take. [We'd like to hear your opinion](https://github.com/tempestphp/tempest-framework/issues/1074)!
11
-
:::
12
-
13
-
In contrast to many popular ORMs, Tempest models aren't required to be tied to the database. A model's persisted data can be loaded from any kind of data source: an external API, JSON, Redis, XML, … In essence, a model is nothing more than a class with public typed properties and methods. Tempest will use a model class' type information to determine how data should be mapped between objects.
14
-
15
-
In other words, a model can be as simple as this class:
16
-
17
-
```php
18
-
// app/Book.php
19
-
20
-
use Tempest\Validation\Rules\Length;
21
-
use App\Author;
22
-
23
-
final class Book
24
-
{
25
-
#[Length(min: 1, max: 120)]
26
-
public string $title;
27
-
28
-
public Author $author;
29
-
30
-
/** @var \App\Chapter[] */
31
-
public array $chapters = [];
32
-
}
33
-
```
34
-
35
-
Retrieving and persisting a model from a data source is done via Tempest's `map()` function:
36
-
37
-
```php
38
-
use function Tempest\map;
8
+
The database layer of Tempest is currently experimental and is not covered by our backwards compatibility promise. Important features such as query builder relationships are not polished nor documented.
We are currently discussing about taking a different approach to the ORM. [We'd like to hear your opinion](https://github.com/tempestphp/tempest-framework/issues/1074)!
11
+
:::
44
12
45
-
## Database models
13
+
## Overview
46
14
47
-
Because database persistence is a pretty common use case, Tempest provides an implementation for models that should interact with the database specifically. Any model class can implement the `DatabaseModel` interface, and use the `IsDatabaseModel` trait like so:
15
+
Because database persistence is a pretty common use case, Tempest provides an implementation for models that should interact with the database specifically.
48
16
49
-
```php
50
-
// app/Book.php
17
+
Model classes can implement the {`Tempest\Database\DatabaseModel`} interface and include its default implementation with the {`Tempest\Database\IsDatabaseModel`} trait.
51
18
19
+
```php app/Book.php
52
20
use Tempest\Database\DatabaseModel;
53
21
use Tempest\Database\IsDatabaseModel;
54
22
use Tempest\Validation\Rules\Length;
@@ -68,29 +36,19 @@ final class Book implements DatabaseModel
68
36
}
69
37
```
70
38
71
-
## Database config
72
-
73
-
In order to connect to a database, you'll have to create a database config file:
39
+
## Changing databases
74
40
75
-
```php
76
-
// app/Config/database.config.php
77
-
78
-
use Tempest\Database\Config\SQLiteConfig;
79
-
80
-
return new SQLiteConfig(
81
-
path: __DIR__ . '/../database.sqlite',
82
-
);
83
-
```
41
+
By default, Tempest uses a SQLite database stored in the `vendor/.tempest` directory. Changing databases is done by providing a {`Tempest\Database\Config\DatabaseConfig`} [configuration object](./06-configuration) to the framework.
84
42
85
-
Tempest has three available database drivers: `SQLiteConfig`, `MysqlConfig`, and `PostgresConfig`. Note that you can use environment variables in config files like so:
43
+
Tempest ships with support for SQLite, PostgreSQL and MySQL. The corresponding configuration classes are `SQLiteConfig`, `PostgresConfig` and `MysqlConfig`, respectively.
86
44
87
-
```php
88
-
// app/Config/database.config.php
45
+
For instance, you may configure Tempest to connect to a PostreSQL database by creating the following `database.config.php` file:
89
46
90
-
use Tempest\Database\Config\MysqlConfig;
47
+
```php src/database.config.php
48
+
use Tempest\Database\Config\PostgresConfig;
91
49
use function Tempest\env;
92
50
93
-
return new MysqlConfig(
51
+
return new PostgresConfig(
94
52
host: env('DB_HOST'),
95
53
port: env('DB_PORT'),
96
54
username: env('DB_USERNAME'),
@@ -101,11 +59,13 @@ return new MysqlConfig(
101
59
102
60
## Migrations
103
61
104
-
Migrations are used to manage database tables that hold persisted model data. Migrations are discovered, so you can create them wherever you like, as long as they implement the `DatabaseMigration` interface:
62
+
A migration is a file instructing the framework how to apply a change to the database schema. Tempest uses migrations to create and update databases accross different environments.
105
63
106
-
```php
107
-
// app/CreateBookTable.php
64
+
### Writing migrations
65
+
66
+
Thanks to [discovery](../4-internals/02-discovery), `.sql` files and classes implementing the {`Tempest\Database\DatabaseMigration`} interface are automatically registered as migrations, which means they can be stored anywhere.
108
67
68
+
```php app/CreateBookTable.php
109
69
use Tempest\Database\DatabaseMigration;
110
70
use Tempest\Database\QueryStatement;
111
71
use Tempest\Database\QueryStatements\CreateTableStatement;
@@ -121,130 +81,73 @@ final readonly class CreateBookTable implements DatabaseMigration
As an alternative, you can write plain SQL files which will be discovered as migrations as well. The file name will be used as the migration's name. Note that you can have multiple queries in one sql file, each of them will be run as a separate migration:
-- You can add several queries into one sql file if you want to
152
-
```
153
-
154
-
Please take note of some naming conventions:
100
+
:::info
101
+
The file name of `{txt}.sql` migrations and the `{txt}{:hl-type:$name:}` property of `DatabaseMigration` classes are used to determine the order in which they are applied. A good practice is to use their creation date as a prefix.
102
+
:::
155
103
156
-
- Model tables use the **model's short classname** by default, use the `::forModel()` method for convenience. Also note that when you rename a model class, old migrations might break and need your attention.
157
-
- If you'd like to prevent name change problems, you can use a hardcoded table name: `new CreateTableStatement('books')`
158
-
- Fields map 1 to 1 to a **model's property names**. It's up to you to use camelCase or snake_case.
159
-
- Relation fields are always suffixed with `_id`. In this case, `author_id` will map to the `Book::$author` property.
104
+
### Applying migrations
160
105
161
-
You can run migrations via the Tempest console:
106
+
A few [console commands](../3-console/02-building-console-commands) are provided to work with migrations. They are used to apply, rollback, or erase and re-apply them. When deploying your application to production, you should use the `php tempest migrate:up` to apply the latest migrations.
162
107
163
-
```
108
+
```sh
109
+
{:hl-comment:# Applies migrations that have not been run in the current environment:}
164
110
./tempest migrate:up
165
-
./tempest migrate:down
166
-
./tempest migrate:fresh {:hl-comment:# Drop all tables and rerun migrate:up:}
167
-
```
168
-
169
-
## Database persistence
170
-
171
-
Any class implementing `DatabaseModel` provides a range of methods to make interaction between the model and the database easier. Let's take a look at this interface:
172
-
173
-
```php
174
-
interface Model
175
-
{
176
-
// The model's table name. By default, it's the model's short classname
177
-
public static function table(): TableName;
178
-
179
-
// Create a query builder for this model class
180
-
public static function query(): ModelQueryBuilder;
181
-
182
-
// Retrieve all instances from the database
183
-
public static function all(): array;
184
-
185
-
// Create a new model instance without saving it
186
-
public static function new(mixed ...$params): self;
187
-
188
-
// Create a new model instance and save it to the database
189
-
public static function create(mixed ...$params): self;
190
-
191
-
// Find a model based on some input data. If it doesn't exist, create it.
192
-
// Next, update this model with some update data.
193
-
public static function updateOrCreate(array $find, array $update): self;
194
-
195
-
// Find a specific model, optionally loading relations as well
196
-
public static function get(Id $id, array $relations = []): ?self;
197
111
198
-
// Create a model query with a number of field conditions
199
-
public static function find(mixed ...$conditions): ModelQueryBuilder;
112
+
{:hl-comment:# Rolls back every migration:}
113
+
./tempest migrate:down
200
114
201
-
// Save a model instance to the database
202
-
public function save(): self;
115
+
{:hl-comment:# Drops all tables and rerun migrate:up:}
116
+
./tempest migrate:fresh
117
+
```
203
118
204
-
// Get the model's id
205
-
public function getId(): Id;
119
+
## The query builder
206
120
207
-
// Set the model's id
208
-
public function setId(Id $id): self;
121
+
Classes implementing the {`Tempest\Database\DatabaseModel`} interface have access to a `query()` method, which returns a new instance of {`Tempest\Database\Builder\ModelQueryBuilder`}.
209
122
210
-
// Update a model instance and save it to the database
211
-
public function update(mixed ...$params): self;
123
+
This offers a way of creating simply queries using a fluent builder interface.
212
124
213
-
// Load one or more relations for this model instance
Important to note is the `DatbaseModel::query()` method, which allows you to create more complex queries for model classes.Tempest deliberately takes a simplistic approach to its model query builder. If you want to build truly complex queries, you should write them directly in SQL, and map them to model classes like so:
134
+
For queries that are more complex, you may use a raw query using the {`Tempest\Database\Query`} class. When used in conjunction with the [mapper](../2-tempest-in-depth/01-mapper), you may obtain hydrated instances of your model.
221
135
222
136
```php
223
137
use Tempest\Database\Query;
224
138
use function Tempest\map;
225
139
226
140
$books = map(new Query(<<<SQL
227
141
SELECT*
228
-
FROMBook
142
+
FROMbooks
229
143
LEFTJOIN…
230
144
HAVING…
231
145
SQL))->collection()->to(Book::class);
232
146
```
233
147
234
-
For simpler queries, you can use the query builder API.
By default, all public properties are considered to be part of the model's query fields. In order to exclude a field from the database mapper, you should mark it as virtual:
150
+
By default, all public properties are considered to be part of the model's query fields. In order to exclude a field from the database mapper, you may use the `Tempest\Database\Virtual` attribute.
248
151
249
152
```php
250
153
use Tempest\Database\Virtual;
@@ -265,34 +168,3 @@ class Book implements DatabaseModel
265
168
}
266
169
}
267
170
```
268
-
269
-
## Model relations
270
-
271
-
Relations between models are primarily determined by a model's property type information. Tempest tries to infer as much information as possible from plain PHP code, so that you don't have to provide unnecessary configuration.
272
-
273
-
```php
274
-
// app/Book.php
275
-
276
-
use Tempest\Database\DatabaseModel;
277
-
use Tempest\Database\IsDatabaseModel;
278
-
use Tempest\Validation\Rules\Length;
279
-
use App\Author;
280
-
281
-
final class Book implements DatabaseModel
282
-
{
283
-
use IsDatabaseModel;
284
-
285
-
public function __construct(
286
-
// This is a BelongsTo relation.
287
-
// Tempest will infer the relation based on the property's name and its type
288
-
public Author $author,
289
-
290
-
// This is a HasMany relation
291
-
// Tempest will infer the related model based on the doc block
292
-
/** @var \App\Chapter[] */
293
-
public array $chapters = [],
294
-
) {}
295
-
}
296
-
```
297
-
298
-
We're still finetuning the relation API, you can follow the progress in [this issue](https://github.com/tempestphp/tempest-framework/issues/756).
0 commit comments