Skip to content

Commit 9eaeb6c

Browse files
committed
Merge branch '2.x' into upgrade
2 parents 7d29da3 + b1459db commit 9eaeb6c

File tree

103 files changed

+1646
-1040
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+1646
-1040
lines changed

composer.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@
228228
"scripts": {
229229
"phpunit": "@php -d memory_limit=2G vendor/bin/phpunit --display-warnings --display-skipped --display-deprecations --display-errors --display-notices",
230230
"coverage": "vendor/bin/phpunit --coverage-html build/reports/html --coverage-clover build/reports/clover.xml",
231-
"mago:fmt": "vendor/bin/mago fmt && vendor/bin/mago lint --fix --potentially-unsafe --fmt",
232-
"mago:lint": "vendor/bin/mago lint --minimum-level=note",
231+
"fmt": "vendor/bin/mago fmt && vendor/bin/mago lint --fix --potentially-unsafe --fmt",
232+
"lint": "vendor/bin/mago lint --minimum-level=note",
233233
"phpstan": "vendor/bin/phpstan analyse src tests --memory-limit=1G",
234234
"rector": "vendor/bin/rector process --no-ansi",
235235
"merge": "php -d\"error_reporting = E_ALL & ~E_DEPRECATED\" vendor/bin/monorepo-builder merge",
@@ -239,13 +239,13 @@
239239
"./bin/release"
240240
],
241241
"qa": [
242-
"composer mago:fmt",
242+
"composer fmt",
243243
"composer merge",
244244
"./bin/validate-packages",
245245
"./tempest discovery:clear --no-interaction",
246246
"composer rector",
247247
"composer phpunit",
248-
"composer mago:lint",
248+
"composer lint",
249249
"composer phpstan"
250250
]
251251
}

docs/1-essentials/01-routing.md

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,10 @@ public function docsRedirect(string $path): Redirect
162162

163163
## Generating URIs
164164

165-
Tempest provides a `\Tempest\uri` function that can be used to generate an URI to a controller method. This function accepts the FQCN of the controller or a callable to a method as its first argument, and named parameters as [the rest of its arguments](https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list).
165+
Tempest provides a `\Tempest\uri` function that can be used to generate a URI to a controller method. This function accepts the FQCN of the controller or a callable to a method as its first argument, and named parameters as [the rest of its arguments](https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list).
166166

167167
```php
168-
use function Tempest\uri;
168+
use function Tempest\Router\uri;
169169

170170
// Invokable classes can be referenced directly:
171171
uri(HomeController::class);
@@ -181,15 +181,62 @@ uri([AircraftController::class, 'show'], id: $aircraft->id);
181181
```
182182

183183
:::info
184-
Note that Tempest does not have named routes, and currently doesn't plan on adding them. However, if you have an argument for them, feel free to hop on our [Discord server](/discord){:ssg-ignore="true"} to discuss it.
184+
URI-related methods are also available by injecting the {b`Tempest\Router\UriGenerator`} class into your controller.
185185
:::
186186

187-
## Matching the current URI
187+
### Signed URIs
188188

189-
To determine whether the current request matches a specific controller action, Tempest provides the `\Tempest\is_current_uri` function. This function accepts the same arguments as `uri`, and returns a boolean.
189+
A signed URI may be used to ensure that the URI was not modified after it was created. This is useful for implementing login links, or other endpoints that need protection against tampering.
190+
191+
To create a signed URI, you may use the `signed_uri` function. This function accepts the same arguments as `uri`, and returns the URI with a `signature` parameter:
192+
193+
```php
194+
use function Tempest\Router\signed_uri;
195+
196+
signed_uri(
197+
action: [MailingListController::class, 'unsubscribe'],
198+
email: $email
199+
);
200+
```
201+
202+
Alternatively, you may use `temporary_signed_uri` to provide a duration after which the signed URI will expire, providing an extra layer of security.
203+
204+
```php
205+
use function Tempest\Router\temporary_signed_uri;
206+
207+
temporary_signed_uri(
208+
action: PasswordlessAuthenticationController::class,
209+
duration: Duration::minutes(10),
210+
userId: $userId
211+
);
212+
```
213+
214+
To ensure the validity of a signed URL, you should call the `hasValidSignature` method on the {`Tempest\Router\UriGenerator`} class.
215+
216+
```php
217+
final class PasswordlessAuthenticationController
218+
{
219+
public function __construct(
220+
private readonly UriGenerator $uri,
221+
) {}
222+
223+
public function __invoke(Request $request): Response
224+
{
225+
if (! $this->uri->hasValidSignature($request)) {
226+
return new Invalid();
227+
}
228+
229+
// ...
230+
}
231+
}
232+
```
233+
234+
### Matching the current URI
235+
236+
To determine whether the current request matches a specific controller action, Tempest provides the `is_current_uri` function. This function accepts the same arguments as `uri`, and returns a boolean.
190237

191238
```php
192-
use function Tempest\is_current_uri;
239+
use function Tempest\Router\is_current_uri;
193240

194241
// Current URI is: /aircraft/1
195242

@@ -243,7 +290,7 @@ Once you have created a request class, you may simply inject it into a controlle
243290
use Tempest\Router\Post;
244291
use Tempest\Http\Responses\Redirect;
245292
use function Tempest\map;
246-
use function Tempest\uri;
293+
use function Tempest\Router\uri;
247294

248295
final readonly class AirportController
249296
{

docs/1-essentials/03-database.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,40 @@ final class Book
270270
}
271271
```
272272

273+
### Hashed properties
274+
275+
The {`#[Tempest\Database\Hashed]`} attribute will hash the model's property during serialization. If the property was already hashed, Tempest will detect that and avoid re-hashing it.
276+
277+
```php
278+
final class User
279+
{
280+
public PrimaryKey $id;
281+
282+
public string $email;
283+
284+
#[Hashed]
285+
public ?string $password;
286+
}
287+
```
288+
289+
Hashing requires the `SIGNING_KEY` environment variable to be set, as it's used as the hashing key.
290+
291+
### Encrypted properties
292+
293+
The {`#[Tempest\Database\Encrypted]`} attribute will encrypt the model's property during serialization and decrypt it during deserialization. If the property was already encrypted, Tempest will detect that and avoid re-encrypting it.
294+
295+
```php
296+
final class User
297+
{
298+
// ...
299+
300+
#[Encrypted]
301+
public ?string $accessToken;
302+
}
303+
```
304+
305+
The encryption key is taken from the `SIGNING_KEY` environment variable.
306+
273307
### DTO properties
274308

275309
Sometimes, you might want to store data objects as-is in a table, without there needing to be a relation to another table. To do so, it's enough to add a serializer and caster to the data object's class, and Tempest will know that these objects aren't meant to be treated as database models. Next, you can store the object's data as a json field on the table (see [migrations](#migrations) for more info).

docs/1-essentials/07-testing.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ composer phpunit
2222

2323
## Test-specific discovery locations
2424

25-
Tempest will only discover non-dev namespaces defined in composer.json automatically. That means that `{:hl-keyword:require-dev:}` namespaces aren't discovered automatically. Whenever you need Tempest to discover test-specific locations, you may specify them within the `discoverTestLocations()` method of the provided `IntegrationTest` class.
25+
Tempest will only discover non-dev namespaces defined in composer.json automatically. That means that `{:hl-keyword:require-dev:}` namespaces aren't discovered automatically. Whenever you need Tempest to discover test-specific locations, you may specify them within the `discoverTestLocations()` method of the provided `IntegrationTest` class.
2626

2727
On top of that, Tempest _will_ look for files in the `tests/Fixtures` directory and discover them by default. You can override this behavior by providing your own implementation of `discoverTestLocations()`, where you can return an array of `DiscoveryLocation` objects (or nothing).
2828

@@ -46,7 +46,7 @@ final class HomeControllerTest extends IntegrationTest
4646
If you want to test code that interacts with the database, your test class can call the `setupDatabase()` method. This method will create and migrate a clean database for you on the fly.
4747

4848
```php
49-
class TodoControllerTest extends IntegrationTest
49+
final class TodoControllerTest extends IntegrationTest
5050
{
5151
protected function setUp(): void
5252
{
@@ -60,8 +60,6 @@ class TodoControllerTest extends IntegrationTest
6060
Most likely, you'll want to use a test-specific database connection. You can create a `database.config.php` file anywhere within test-specific discovery locations, and Tempest will use that connection instead of the project's default. For example, you can create a file `tests/Fixtures/database.config.php` like so:
6161

6262
```php tests/Fixtures/database.config.php
63-
<?php
64-
6563
use Tempest\Database\Config\SQLiteConfig;
6664

6765
return new SQLiteConfig(
@@ -72,7 +70,7 @@ return new SQLiteConfig(
7270
By default, no tables will be migrated. You can choose to provide a list of migrations that will be run for every test that calls `setupDatabase()`, or you can run specific migrations on a per-test basis.
7371

7472
```php
75-
class TodoControllerTest extends IntegrationTest
73+
final class TodoControllerTest extends IntegrationTest
7674
{
7775
protected function migrateDatabase(): void
7876
{
@@ -85,7 +83,7 @@ class TodoControllerTest extends IntegrationTest
8583
```
8684

8785
```php
88-
class TodoControllerTest extends IntegrationTest
86+
final class TodoControllerTest extends IntegrationTest
8987
{
9088
public function test_create_todo(): void
9189
{

docs/4-internals/03-view-spec.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Tempest will merge all imports at the top of the compiled view, meaning that eac
103103
```html
104104
<?php
105105
use App\PostController;
106-
use function Tempest\uri;
106+
use function Tempest\Router\uri;
107107
?>
108108

109109
{{ uri([PostController::class, 'show'], post: $post->id) }}
@@ -608,7 +608,7 @@ Referencing a symbol within a view will automatically import it at the top of th
608608
```html
609609
<?php
610610
use App\PostController;
611-
use function Tempest\uri;
611+
use function Tempest\Router\uri;
612612
?>
613613

614614
{{ uri([PostController::class, 'show'], post: $post->id) }}

packages/auth/src/Install/CreatePermissionsTable.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Tempest\Auth\Install;
66

7-
use Tempest\Database\DatabaseMigration;
7+
use Tempest\Database\MigratesDown;
8+
use Tempest\Database\MigratesUp;
89
use Tempest\Database\QueryStatements\CreateTableStatement;
910
use Tempest\Database\QueryStatements\DropTableStatement;
1011
use Tempest\Discovery\SkipDiscovery;
1112

1213
#[SkipDiscovery]
13-
final class CreatePermissionsTable implements DatabaseMigration
14+
final class CreatePermissionsTable implements MigratesUp, MigratesDown
1415
{
1516
private(set) string $name = '0000-00-01_create_permissions_table';
1617

@@ -23,6 +24,6 @@ public function up(): CreateTableStatement
2324

2425
public function down(): DropTableStatement
2526
{
26-
return DropTableStatement::forModel(Permission::class);
27+
return new DropTableStatement('permissions');
2728
}
2829
}

packages/auth/src/Install/CreateUserPermissionsTable.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Tempest\Auth\Install;
66

7-
use Tempest\Database\DatabaseMigration;
7+
use Tempest\Database\MigratesDown;
8+
use Tempest\Database\MigratesUp;
89
use Tempest\Database\QueryStatements\CreateTableStatement;
910
use Tempest\Database\QueryStatements\DropTableStatement;
1011
use Tempest\Discovery\SkipDiscovery;
1112

1213
#[SkipDiscovery]
13-
final class CreateUserPermissionsTable implements DatabaseMigration
14+
final class CreateUserPermissionsTable implements MigratesUp, MigratesDown
1415
{
1516
private(set) string $name = '0000-00-02_create_user_permissions_table';
1617

@@ -24,6 +25,6 @@ public function up(): CreateTableStatement
2425

2526
public function down(): DropTableStatement
2627
{
27-
return DropTableStatement::forModel(Permission::class);
28+
return new DropTableStatement('user_permissions');
2829
}
2930
}

packages/auth/src/Install/CreateUsersTable.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Tempest\Auth\Install;
66

7-
use Tempest\Database\DatabaseMigration;
7+
use Tempest\Database\MigratesDown;
8+
use Tempest\Database\MigratesUp;
89
use Tempest\Database\QueryStatements\CreateTableStatement;
910
use Tempest\Database\QueryStatements\DropTableStatement;
1011
use Tempest\Discovery\SkipDiscovery;
1112

1213
#[SkipDiscovery]
13-
final class CreateUsersTable implements DatabaseMigration
14+
final class CreateUsersTable implements MigratesUp, MigratesDown
1415
{
1516
private(set) string $name = '0000-00-00_create_users_table';
1617

@@ -26,6 +27,6 @@ public function up(): CreateTableStatement
2627

2728
public function down(): DropTableStatement
2829
{
29-
return DropTableStatement::forModel(User::class);
30+
return new DropTableStatement('users');
3031
}
3132
}

packages/container/src/GenericContainer.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,13 +419,15 @@ private function autowire(string $className, mixed ...$params): object
419419
}
420420

421421
foreach ($classReflector->getProperties() as $property) {
422-
if ($property->hasAttribute(Inject::class) && ! $property->isInitialized($instance)) {
422+
$inject = $property->getAttribute(Inject::class);
423+
424+
if ($inject && ! $property->isInitialized($instance)) {
423425
if ($property->hasAttribute(Proxy::class)) {
424426
$property->set($instance, $property->getType()->asClass()->getReflection()->newLazyProxy(
425-
fn () => $this->get($property->getType()->getName()),
427+
fn () => $this->get($property->getType()->getName(), $inject->tag),
426428
));
427429
} else {
428-
$property->set($instance, $this->get($property->getType()->getName()));
430+
$property->set($instance, $this->get($property->getType()->getName(), $inject->tag));
429431
}
430432
}
431433
}

packages/container/src/Inject.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@
99
#[Attribute(Attribute::TARGET_PROPERTY)]
1010
final readonly class Inject
1111
{
12+
public function __construct(
13+
public ?string $tag = null,
14+
) {}
1215
}

0 commit comments

Comments
 (0)