Skip to content

Commit 51cb3c3

Browse files
committed
feat: update database documentation
1 parent 361633e commit 51cb3c3

File tree

1 file changed

+50
-178
lines changed

1 file changed

+50
-178
lines changed
Lines changed: 50 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,22 @@
11
---
22
title: Database
33
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"]
55
---
66

7-
## Overview
8-
97
:::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.
399

40-
map('path/to/books.json')->collection->to(Book::class);
41-
42-
map($book)->toJson();
43-
```
10+
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+
:::
4412

45-
## Database models
13+
## Overview
4614

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.
4816

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.
5118

19+
```php app/Book.php
5220
use Tempest\Database\DatabaseModel;
5321
use Tempest\Database\IsDatabaseModel;
5422
use Tempest\Validation\Rules\Length;
@@ -68,29 +36,19 @@ final class Book implements DatabaseModel
6836
}
6937
```
7038

71-
## Database config
72-
73-
In order to connect to a database, you'll have to create a database config file:
39+
## Changing databases
7440

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.
8442

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.
8644

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:
8946

90-
use Tempest\Database\Config\MysqlConfig;
47+
```php src/database.config.php
48+
use Tempest\Database\Config\PostgresConfig;
9149
use function Tempest\env;
9250

93-
return new MysqlConfig(
51+
return new PostgresConfig(
9452
host: env('DB_HOST'),
9553
port: env('DB_PORT'),
9654
username: env('DB_USERNAME'),
@@ -101,11 +59,13 @@ return new MysqlConfig(
10159

10260
## Migrations
10361

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.
10563

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.
10867

68+
```php app/CreateBookTable.php
10969
use Tempest\Database\DatabaseMigration;
11070
use Tempest\Database\QueryStatement;
11171
use Tempest\Database\QueryStatements\CreateTableStatement;
@@ -121,130 +81,73 @@ final readonly class CreateBookTable implements DatabaseMigration
12181

12282
public function up(): QueryStatement|null
12383
{
124-
return CreateTableStatement::forModel(Book::class)
84+
return new CreateTableStatement('books')
12585
->primary()
12686
->text('title')
127-
->datetime('createdAt')
128-
->datetime('publishedAt', nullable: true)
87+
->datetime('created_at')
88+
->datetime('published_at', nullable: true)
12989
->integer('author_id', unsigned: true)
130-
->belongsTo('Book.author_id', 'Author.id');
90+
->belongsTo('books.author_id', 'authors.id');
13191
}
13292

13393
public function down(): QueryStatement|null
13494
{
135-
return DropTableStatement::forModel(Book::class);
95+
return new DropTableStatement('books');
13696
}
13797
}
13898
```
13999

140-
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:
141-
142-
```sql
143-
-- app/Migrations/2024-08-16_create_publisher_table.sql
144-
145-
CREATE TABLE Publisher
146-
(
147-
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
148-
`name` TEXT NOT NULL
149-
);
150-
151-
-- 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+
:::
155103

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
160105

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.
162107

163-
```
108+
```sh
109+
{:hl-comment:# Applies migrations that have not been run in the current environment:}
164110
./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;
197111

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
200114

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+
```
203118

204-
// Get the model's id
205-
public function getId(): Id;
119+
## The query builder
206120

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`}.
209122

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.
212124

213-
// Load one or more relations for this model instance
214-
public function load(string ...$relations): self;
215-
}
125+
```php
126+
$books = Book::query()
127+
->with('author.publisher')
128+
->where('createdat < :olderThan', olderThan: $olderThan)
129+
->orderBy('created_at DESC')
130+
->limit(5)
131+
->all();
216132
```
217133

218-
## Model query builder
219-
220-
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.
221135

222136
```php
223137
use Tempest\Database\Query;
224138
use function Tempest\map;
225139

226140
$books = map(new Query(<<<SQL
227141
SELECT *
228-
FROM Book
142+
FROM books
229143
LEFT JOIN
230144
HAVING
231145
SQL))->collection()->to(Book::class);
232146
```
233147

234-
For simpler queries, you can use the query builder API.
235-
236-
```php
237-
$books = Book::query()
238-
->with('author.publisher')
239-
->where('createdAt < :olderThan', olderThan: $olderThan)
240-
->orderBy('createdAt DESC')
241-
->limit(5)
242-
->all();
243-
```
244-
245148
## Virtual properties
246149

247-
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.
248151

249152
```php
250153
use Tempest\Database\Virtual;
@@ -265,34 +168,3 @@ class Book implements DatabaseModel
265168
}
266169
}
267170
```
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

Comments
 (0)