Skip to content

Commit 52b4dcb

Browse files
committed
feat: document storage
1 parent 4a8ac79 commit 52b4dcb

File tree

1 file changed

+190
-0
lines changed
  • src/Web/Documentation/content/main/2-tempest-in-depth

1 file changed

+190
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
title: File storage
3+
description: "Tempest's storage provides a way to access many different types of filesystems, such as the local filesystem, Amazon S3, Cloudflare R2 or even an FTP server."
4+
---
5+
6+
## Overview
7+
8+
Tempest provides the ability to interact with the local filesystem and many cloud storage solutions, such as Cloudflare R2 or Amazon S3, using the same interface.
9+
10+
This implementation is built on top of [Flysystem](https://github.com/thephpleague/flysystem)—a reliable, battle-tested abstraction layer for file systems.
11+
12+
## Getting started
13+
14+
To get started with file storage, you will first need to create a configuration file for your desired filesystem.
15+
16+
Tempest provides a different configuration object for each provider. For instance, if you wish to interact with an Amazon S3 bucket, you may create a `s3.config.php` file returning an instance of {b`Tempest\Storage\Config\S3StorageConfig`}:
17+
18+
```php src/s3.config.php
19+
return new S3StorageConfig(
20+
bucket: env('S3_BUCKET'),
21+
region: env('S3_REGION'),
22+
accessKeyId: env('S3_ACCESS_KEY_ID'),
23+
secretAccessKey: env('S3_SECRET_ACCESS_KEY'),
24+
);
25+
```
26+
27+
In this example, the S3 credentials are specified in the `.env`, so a different bucket and credentials can be configured depending on the environment.
28+
29+
Once your storage is configured, you may interact with it by using the {`Tempest\Storage\Storage`} interface. This is usually done through [dependency injection](../1-essentials/01-container.md#injecting-dependencies):
30+
31+
```php src/UserService.php
32+
final readonly class UserService
33+
{
34+
public function __construct(
35+
private Storage $storage,
36+
) {}
37+
38+
public function getProfilePictureUrl(User $user): string
39+
{
40+
return $this->storage->publicUrl($user->profile_picture_path);
41+
}
42+
43+
// …
44+
}
45+
```
46+
47+
## The storage interface
48+
49+
Once you have access to the the {b`Tempest\Storage\Storage`} interface, you gain access to a few useful methods for working with files, directory and streams. All methods are documented, so you are free to explore the source to get an understanding of what you can do with it.
50+
51+
Below are a few useful methods that you may need more often than the others:
52+
53+
```php
54+
/**
55+
* Gets a public URL to the file at the specified `$location`.
56+
*/
57+
$storage->publicUrl($location);
58+
59+
/**
60+
* Writes the given `$contents` to the specified `$location`.
61+
*/
62+
$storage->write($location, $contents);
63+
64+
/**
65+
* Reads the contents of the file at the specified `$location`.
66+
*/
67+
$storage->read($location);
68+
69+
/**
70+
* Deletes the contents of the file at the specified `$location`.
71+
*/
72+
$storage->delete($location);
73+
74+
/**
75+
* Determines whether a file exists at the specified `$location`.
76+
*/
77+
$storage->fileOrDirectoryExists($location);
78+
```
79+
80+
## Configuration
81+
82+
Tempest provides a different configuration object for each storage provider. Below are the
83+
84+
- {`Tempest\Storage\Config\R2StorageConfig`}
85+
- {`Tempest\Storage\Config\S3StorageConfig`}
86+
- {`Tempest\Storage\Config\AzureStorageConfig`}
87+
- {`Tempest\Storage\Config\FTPStorageConfig`}
88+
- {`Tempest\Storage\Config\GoogleCloudStorageConfig`}
89+
- {`Tempest\Storage\Config\InMemoryStorageConfig`}
90+
- {`Tempest\Storage\Config\LocalStorageConfig`}
91+
- {`Tempest\Storage\Config\SFTPStorageConfig`}
92+
- {`Tempest\Storage\Config\StorageConfig`}
93+
- {`Tempest\Storage\Config\ZipArchiveStorageConfig`}
94+
- {`Tempest\Storage\Config\CustomStorageConfig`}
95+
96+
### Multiple storages
97+
98+
If you need to work with multiple storage locations, you create multiple storage configurations using tags. This tag may then be used to resolve the {b`Tempest\Storage\Storage`} interface, which will use the corresponding configuration.
99+
100+
It's a good practice to use an enum for the tag:
101+
102+
```php src/userdata.storage.config.php
103+
return new S3StorageConfig(
104+
tag: StorageLocation::USER_DATA,
105+
bucket: env('USERDATA_S3_BUCKET'),
106+
region: env('USERDATA_S3_REGION'),
107+
accessKeyId: env('USERDATA_S3_ACCESS_KEY_ID'),
108+
secretAccessKey: env('USERDATA_S3_SECRET_ACCESS_KEY'),
109+
);
110+
```
111+
112+
```php src/backup.storage.config.php
113+
return new R2StorageConfig(
114+
tag: StorageLocation::BACKUPS,
115+
bucket: env('BACKUPS_R2_BUCKET'),
116+
endpoint: env('BACKUPS_R2_ENDPOINT'),
117+
accessKeyId: env('BACKUPS_R2_ACCESS_KEY_ID'),
118+
secretAccessKey: env('BACKUPS_R2_SECRET_ACCESS_KEY'),
119+
);
120+
```
121+
122+
Once you have configured your storages and your tags, you may inject the {b`Tempest\Storage\Storage`} interface using the corresponding tag:
123+
124+
```php src/BackupService.php
125+
final readonly class BackupService
126+
{
127+
public function __construct(
128+
#[Tag(StorageLocation::BACKUPS)]
129+
private Storage $storage,
130+
) {}
131+
132+
// …
133+
}
134+
```
135+
136+
### Read-only storage
137+
138+
A storage may be restricted to only allow read operations. Attempting to write to such a storage will result in a `League\Flysystem\UnableToWriteFile` exception being thrown.
139+
140+
First, the `league/flysystem-read-only` adapter needs to be installed:
141+
142+
```sh
143+
composer require league/flysystem-read-only
144+
```
145+
146+
Once this is done, you may pass the `readonly` parameter to the adapter configuration and set it to `true`.
147+
148+
```php src/data-snapshots.storage.confg.php
149+
return new S3StorageConfig(
150+
tag: StorageLocation::DATA_SNAPSHOTS,
151+
readonly: true,
152+
bucket: env('DATA_SNAPSHOTS_S3_BUCKET'),
153+
region: env('DATA_SNAPSHOTS_S3_REGION'),
154+
accessKeyId: env('DATA_SNAPSHOTS_S3_ACCESS_KEY_ID'),
155+
secretAccessKey: env('DATA_SNAPSHOTS_S3_SECRET_ACCESS_KEY'),
156+
);
157+
```
158+
159+
## Testing
160+
161+
By extending {`Tempest\Framework\Testing\IntegrationTest`} from your test case, you gain access to the storage testing utilities through the `storage` property.
162+
163+
These utilities include a way to replace the storage with a testing implementation, as well as a few assertion methods related to files and directories.
164+
165+
### Faking a storage
166+
167+
You may generate a fake, testing-only storage by calling the `fake()` method on the `storage` property. This will replace the storage implementation in the container, and provide useful assertion methods.
168+
169+
```php
170+
// Replace the storage with a fake implementation
171+
$storage = $this->storage->fake();
172+
173+
// Replace the specified storage with a fake implementation
174+
$storage = $this->storage->fake('user-profile-pictures');
175+
176+
// Asserts that the specified file exists
177+
$storage->assertFileExists('file.txt');
178+
```
179+
180+
These fake storages are located in `vendor/.tempest/tests/storage`. They get erased every time the `fake()` method is called. To prevent this, you may set the `persist` argument to `true`.
181+
182+
### Preventing storage access during tests
183+
184+
It may be useful to prevent code from using any of the registered storages during tests. This could happen when forgetting to fake a storage for a specific test, for instance, and could result in unexpected costs when relying on a cloud storage provider.
185+
186+
This may be achieved by calling the `preventUsageWithoutFake()` method on the `storage` property.
187+
188+
```php tests/MyServiceTest.php
189+
$this->storage->preventEventHandling();
190+
```

0 commit comments

Comments
 (0)