# Amazon S3

The S3 storage saves a copy of each file on Amazon S3. It works alongside the local files and the
database storage. It needs an S3 client plugin.

## Installing an S3 Client

Filebinder works with two S3 clients:

- [AsyncAws](https://async-aws.com), through
  [pieceofcake2/async-aws](https://github.com/pieceofcake2/async-aws)
- the AWS SDK for PHP, through [pieceofcake2/aws](https://github.com/pieceofcake2/aws)

Install and load one of them.

### AsyncAws

Install pieceofcake2/async-aws with async-aws/simple-s3:

```bash
composer require pieceofcake2/async-aws async-aws/simple-s3
```

Load the plugin in `config/bootstrap.php`:

```php
CakePlugin::load('AsyncAws');
```

### AWS SDK for PHP

Install pieceofcake2/aws:

```bash
composer require pieceofcake2/aws
```

Load the plugin in `config/bootstrap.php`:

```php
CakePlugin::load('Aws');
```

## Choosing the Client

Filebinder uses the client of the loaded plugin. When both plugins are loaded, it uses AsyncAws.

To choose the client yourself, set `Filebinder.S3.client` to `'AsyncAws'` or `'Aws'`:

```php
Configure::write('Filebinder.S3.client', 'AsyncAws');
```

The plugin of the client must be loaded. Installing the AWS SDK for PHP alone is not enough.
When Filebinder cannot use any client, saving a file throws a `FilebinderS3StorageException`.
Its message says which package to install and load.

## Saving Files on S3

Add `BindableBehavior::STORAGE_S3` to the `storage` setting, and set the bucket of the field:

```php
public $actsAs = [
    'Filebinder.Bindable' => [
        'storage' => [BindableBehavior::STORAGE_DB, BindableBehavior::STORAGE_S3],
    ],
];

public $bindFields = [
    [
        'field' => 'image',
        'filePath' => '/var/www/html/myapp/webroot/files/',
        'bucket' => 'my-bucket',
        'acl' => 'private',
    ],
];
```

The behavior saves each file with this key:

```text
<urlPrefix><model>/<model_id>/<field_name>/<file_name>
```

When a local file does not exist, `find()` downloads it from S3 (see
[Restoring Files](https://pieceofcake2.github.io/filebinder/storage.md#restoring-files)).

The `Content-Type` of a file on S3 comes from the contents of the file. Filebinder detects it with
the fileinfo extension, as it does for `file_content_type`. It does not use the type that the
browser sent or the extension of the file.

## S3 Settings

Each field can set its own `bucket`, `acl`, `urlPrefix` and `region` in `$bindFields` (see
[Bound Fields](https://pieceofcake2.github.io/filebinder/configuration.md#bound-fields)). For the fields without them, Filebinder uses the
`Filebinder.S3` settings:

```php
Configure::write('Filebinder.S3', [
    'bucket' => 'my-bucket',
    'acl' => 'private',
    'urlPrefix' => 'files/',
]);
```

| Setting | Description |
| --- | --- |
| `Filebinder.S3.client` | `'AsyncAws'` or `'Aws'`. Default: the client of the loaded plugin. |
| `Filebinder.S3.bucket` | Bucket, for the fields without `bucket`. |
| `Filebinder.S3.acl` | Canned ACL, for the fields without `acl`. Default: `public-read`. |
| `Filebinder.S3.urlPrefix` | Prefix of the keys, for the fields without `urlPrefix`. |
| `Filebinder.S3.region` | Deprecated. Set `region` in the plugin configuration instead. Region, for the fields without `region`. It overrides the plugin configuration. |
| `Filebinder.S3.key`, `Filebinder.S3.secret` | Deprecated. Set the credentials in the plugin configuration, or use the default credentials. Access key of AWS. It overrides the plugin configuration. |

## Client Configuration

Filebinder creates the S3 client from the configuration of the client plugin. Set the region
and the credentials there.

pieceofcake2/async-aws reads the `AsyncAws` configuration. When `AsyncAws` is not set, it reads
the `Aws` configuration. See
[AsyncAws Configuration](https://pieceofcake2.github.io/async-aws/#asyncaws-configuration).

```php
Configure::write('AsyncAws', [
    'region' => 'ap-northeast-1',
]);
```

pieceofcake2/aws reads the `Aws` configuration. See
[AWS Configuration](https://pieceofcake2.github.io/aws/#aws-configuration).

```php
Configure::write('Aws', [
    'region' => 'ap-northeast-1',
]);
```

The `region` of a field overrides the region of the configuration for all the S3 requests of that
field.

### Credentials

When the configuration has no credentials, the SDKs use their default credentials:

- environment variables
- the shared credentials file
- the IAM role of the EC2 instance, the ECS task or the Lambda function

`Filebinder.S3.key` and `Filebinder.S3.secret` still work and override the configuration, but
they are deprecated. Move the credentials to the plugin configuration, or use the default
credentials.

## Public and Private Files

The ACL of a file decides how the Label helper links it. The ACL is the `acl` of the field, or
else `Filebinder.S3.acl`. The default is `public-read`.

- With a public ACL (`public-read` or `public-read-write`), the helper outputs the URL of the
  object. Anyone can open it.
- With any other ACL, such as `private`, the helper outputs a presigned URL. The URL expires after
  `Filebinder.expire`, one minute by default.

To keep the files private, set the ACL to `private`:

```php
Configure::write('Filebinder.S3.acl', 'private');
```

S3 checks the signature of a presigned URL. The files do not go through your application. See
[URLs of the Files](https://pieceofcake2.github.io/filebinder/label.md#urls-of-the-files).

## S3 Storage API

`s3Storage()` returns the S3 storage of a field. The storage is a `FilebinderS3StorageInterface`,
configured as the behavior configures it for that field:

```php
$storage = $this->Post->s3Storage('image');
```

Call its methods with the bucket and the key of an object:

```php
$this->set('imageUrl', $storage->url('my-bucket', 'files/Post/1/image/photo.jpg'));
```

The storage has these methods:

| Method | Description |
| --- | --- |
| `put(string $bucket, string $key, string $file, array $options = []): void` | Uploads a local file. Options: `ACL`. |
| `get(string $bucket, string $key): string` | Returns the contents of an object. |
| `saveAs(string $bucket, string $key, string $file): void` | Downloads an object to a local file. |
| `delete(string $bucket, string $key): void` | Deletes an object. |
| `url(string $bucket, string $key): string` | Returns the URL of an object, as the client builds it. The URL is not signed. |
| `presignedUrl(string $bucket, string $key, DateTimeImmutable $expires): string` | Returns a presigned URL to download an object. The URL expires at `$expires`. |

### Storage Factory

`FilebinderS3StorageFactory` creates the storages. Load it first:

```php
App::uses('FilebinderS3StorageFactory', 'Filebinder.Storage');
```

`FilebinderS3StorageFactory::create()` creates the storage of the configured client. It takes
client options, such as `region` and `credentials`:

```php
$storage = FilebinderS3StorageFactory::create(['region' => 'ap-northeast-1']);
```

`FilebinderS3StorageFactory::client()` returns the name of that client, `'AsyncAws'` or `'Aws'`:

```php
$client = FilebinderS3StorageFactory::client();
```
