> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Bestandsopslag

> Leer bestanden lezen en schrijven, uploaden en integreren met cloudopslag via Laravels Flysystem-integratie.

## Wat is bestandsopslag

Laravel biedt een krachtige abstractielaag voor bestandssystemen, gebaseerd op de PHP-package [Flysystem](https://github.com/thephpleague/flysystem).
Omdat je verschillende opslagbackends zoals lokale schijven, SFTP en Amazon S3 met dezelfde API bedient, kun je per omgeving wisselen **zonder je code aan te passen**.

<Info>
  Ook als je van driver wisselt, blijft je code hetzelfde. Een opzet met lokale opslag in ontwikkeling en S3 in productie is zo eenvoudig te realiseren.
</Info>

## Configuratie

De configuratie van het bestandssysteem staat in `config/filesystems.php`.
Hier definieer je "disks". Een disk is een combinatie van een specifieke driver en een opslaglocatie.

De belangrijkste drivers zijn:

| Driver         | Gebruik                                          |
| -------------- | ------------------------------------------------ |
| `local`        | Het lokale bestandssysteem van de server         |
| `public`       | Lokale disk voor publiek toegankelijke bestanden |
| `s3`           | Amazon S3 (of S3-compatibele diensten)           |
| `sftp`         | SFTP-server                                      |
| `ftp`          | FTP-server                                       |
| `read-through` | Primaire en fallback-disk tijdens een migratie   |

### De local-driver

Met de `local`-driver werk je met bestanden relatief aan de `root`-directory uit de `filesystems`-configuratie.
Standaard is `storage/app/private` de `root`.

```php theme={null}
use Illuminate\Support\Facades\Storage;

Storage::disk('local')->put('example.txt', 'Contents');
// Wordt geschreven naar storage/app/private/example.txt
```

### De standaarddisk wijzigen

Met de omgevingsvariabele `FILESYSTEM_DISK` wissel je van standaarddisk.

```ini theme={null}
FILESYSTEM_DISK=s3
```

## De public-disk en symbolische links

De `public`-disk is bedoeld voor bestanden die via het web toegankelijk moeten zijn.
Standaard worden ze opgeslagen in de directory `storage/app/public`.

Om ze toegankelijk te maken vanaf de webserver, maak je een symbolische link van `public/storage` naar `storage/app/public`.

<Steps>
  <Step title="De symbolische link maken">
    ```shell theme={null}
    php artisan storage:link
    ```
  </Step>

  <Step title="De URL van een bestand opvragen">
    Na het maken van de symbolische link kun je met de `asset`-helper een URL genereren.

    ```php theme={null}
    echo asset('storage/file.txt');
    ```
  </Step>
</Steps>

<Tip>
  Heb je extra symbolische links nodig, dan kun je die instellen in de `links`-array van `config/filesystems.php`.

  ```php theme={null}
  'links' => [
      public_path('storage') => storage_path('app/public'),
      public_path('images') => storage_path('app/images'),
  ],
  ```
</Tip>

Symbolische links verwijder je met `storage:unlink`.

```shell theme={null}
php artisan storage:unlink
```

## Basisbewerkingen met de Storage-facade

### Bestanden lezen

```php theme={null}
use Illuminate\Support\Facades\Storage;

// De inhoud van een bestand als string ophalen
$contents = Storage::get('file.jpg');

// Een JSON-bestand ophalen en decoderen
$data = Storage::json('orders.json');

// Controleren of een bestand bestaat
if (Storage::exists('file.jpg')) {
    // Het bestand bestaat
}

// Controleren of een bestand niet bestaat
if (Storage::missing('file.jpg')) {
    // Het bestand bestaat niet
}
```

### Bestanden schrijven

```php theme={null}
// Naar een bestand schrijven
Storage::put('file.jpg', $contents);

// Een resource als stream wegschrijven
Storage::put('file.jpg', $resource);

// Aan het begin of einde toevoegen
Storage::prepend('file.log', 'Prepended Text');
Storage::append('file.log', 'Appended Text');

// Bestanden kopiëren en verplaatsen
Storage::copy('old/file.jpg', 'new/file.jpg');
Storage::move('old/file.jpg', 'new/file.jpg');
```

<Info>
  Als `put` mislukt, geeft de methode standaard `false` terug.
  Door in de diskconfiguratie `'throw' => true` in te stellen, kun je in plaats daarvan een exception laten gooien.
</Info>

### Bestanden verwijderen

```php theme={null}
// Eén bestand verwijderen
Storage::delete('file.jpg');

// Meerdere bestanden verwijderen
Storage::delete(['file.jpg', 'file2.jpg']);

// Een bestand op een specifieke disk verwijderen
Storage::disk('s3')->delete('path/file.jpg');
```

### Een downloadresponse voor bestanden

```php theme={null}
// Een response teruggeven die de browser tot downloaden aanzet
return Storage::download('file.jpg');

// Met opgegeven bestandsnaam en headers
return Storage::download('file.jpg', 'my-file.jpg', $headers);
```

## URL's genereren

### Reguliere URL's

```php theme={null}
use Illuminate\Support\Facades\Storage;

$url = Storage::url('file.jpg');
```

De `local`-driver geeft een relatieve URL terug, zoals `/storage/file.jpg`.
De `s3`-driver geeft een volledige remote URL terug.

### Tijdelijke URL's (temporary URL)

Wil je een URL met een vervaldatum genereren, gebruik dan de methode `temporaryUrl`.
Deze is beschikbaar voor de drivers `local` en `s3`.

```php theme={null}
use Illuminate\Support\Facades\Storage;

$url = Storage::temporaryUrl(
    'file.jpg',
    now()->plus(minutes: 5)
);
```

Bij S3 kun je ook extra requestparameters opgeven.

```php theme={null}
$url = Storage::temporaryUrl(
    'file.jpg',
    now()->plus(minutes: 5),
    [
        'ResponseContentType' => 'application/octet-stream',
        'ResponseContentDisposition' => 'attachment; filename=file.jpg',
    ]
);
```

<Tip>
  Heb je een tijdelijke upload-URL nodig, gebruik dan de methode `temporaryUploadUrl`.
  Handig in serverless-opstellingen waarbij de client rechtstreeks naar S3 uploadt.

  ```php theme={null}
  ['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl(
      'file.jpg', now()->plus(minutes: 5)
  );
  ```
</Tip>

## Bestanden uploaden

Een veelvoorkomend patroon voor het opslaan van bestanden die gebruikers via een formulier uploaden.

### De store-methode (bestandsnaam automatisch genereren)

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserAvatarController extends Controller
{
    public function update(Request $request): string
    {
        // De bestandsnaam wordt automatisch gegenereerd, met de extensie afgeleid van het MIME-type
        $path = $request->file('avatar')->store('avatars');

        return $path;
    }
}
```

### De storeAs-methode (bestandsnaam opgeven)

```php theme={null}
$path = $request->file('avatar')->storeAs(
    'avatars',
    $request->user()->id
);
```

### Uploaden naar een specifieke disk

```php theme={null}
// Opslaan op de s3-disk
$path = $request->file('avatar')->store(
    'avatars/' . $request->user()->id,
    's3'
);
```

### Uploaden via de Storage-facade

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Bestandsnaam automatisch genereren
$path = Storage::putFile('avatars', $request->file('avatar'));

// Bestandsnaam opgeven
$path = Storage::putFileAs(
    'avatars',
    $request->file('avatar'),
    $request->user()->id
);
```

<Warning>
  `getClientOriginalName()` en `getClientOriginalExtension()` zijn onveilig omdat gebruikers ze kunnen manipuleren.
  Gebruik `hashName()` voor de bestandsnaam en `extension()` voor de extensie.

  ```php theme={null}
  $file = $request->file('avatar');

  $name = $file->hashName();   // Genereert een unieke willekeurige naam
  $extension = $file->extension(); // Bepaalt de extensie op basis van het MIME-type
  ```
</Warning>

## Zichtbaarheid van bestanden (visibility)

In Flysystem beheer je met `visibility` of bestanden publiek of privé zijn.

```php theme={null}
use Illuminate\Support\Facades\Storage;

// De zichtbaarheid opgeven bij het schrijven
Storage::put('file.jpg', $contents, 'public');

// De zichtbaarheid opvragen en wijzigen
$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public');
```

Wil je een geüpload bestand publiek opslaan, gebruik dan `storePublicly`.

```php theme={null}
$path = $request->file('avatar')->storePublicly('avatars', 's3');
```

## Meerdere disks gebruiken

Met de `disk`-methode wissel je van disk.

```php theme={null}
// Opslaan op de standaarddisk
Storage::put('avatars/1', $content);

// Opslaan op de s3-disk
Storage::disk('s3')->put('avatars/1', $content);

// On demand een disk aanmaken
$disk = Storage::build([
    'driver' => 'local',
    'root' => '/path/to/root',
]);
$disk->put('image.jpg', $content);
```

## Read-through-disks

Met een `read-through`-disk kun je bestanden zonder downtime naar een andere disk migreren. Bij het lezen van een bestand wordt eerst de primaire disk gecontroleerd; bestaat het daar niet, dan wordt het van de fallback-disk gelezen en naar de primaire disk gekopieerd voor volgende requests.

```php theme={null}
'assets' => [
    'driver' => 'read-through',
    'primary' => 's3',
    'fallback' => 'legacy-s3',
],
```

Schrijfbewerkingen en het opvragen van directorylijsten gebeuren op de primaire disk. Bij het controleren van bestandsbestaan en het opvragen van metadata worden beide disks geraadpleegd, maar wordt het bestand niet naar de primaire disk gekopieerd.

Mislukt het kopiëren van de fallback-disk naar de primaire disk, dan slaagt de leesbewerking zelf standaard alsnog. Wil je bij een mislukte kopie een exception laten gooien, stel dan `throw_on_promotion_failure` in op `true`.

## Cloudopslag (S3) configureren

### De package installeren

```shell theme={null}
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
```

### Omgevingsvariabelen instellen

Zet de S3-inloggegevens in het `.env`-bestand.

```ini theme={null}
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_USE_PATH_STYLE_ENDPOINT=false
```

<Info>
  S3-compatibele diensten zoals DigitalOcean Spaces, Cloudflare R2 en Vultr Object Storage werken ook met de `s3`-driver.
  Geef de endpoint-URL van de dienst op in de optie `endpoint`.

  ```php theme={null}
  'endpoint' => env('AWS_ENDPOINT', 'https://your-endpoint.example.com'),
  ```
</Info>

## Bestandsmetadata opvragen

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Bestandsgrootte (bytes)
$size = Storage::size('file.jpg');

// Laatste wijzigingsdatum (UNIX-timestamp)
$time = Storage::lastModified('file.jpg');

// MIME-type
$mime = Storage::mimeType('file.jpg');

// Het bestandspad opvragen
$path = Storage::path('file.jpg');
```

<Info>
  Welke waarde de `path`-methode teruggeeft, verschilt per driver. Bij de `local`-driver krijg je het absolute pad van het bestand, bij de `s3`-driver het relatieve pad binnen de S3-bucket.
</Info>

## Directorybewerkingen

```php theme={null}
use Illuminate\Support\Facades\Storage;

// Lijst van bestanden in een directory
$files = Storage::files($directory);

// Lijst van bestanden inclusief subdirectories
$files = Storage::allFiles($directory);

// Lijst van directories
$directories = Storage::directories($directory);

// Een directory aanmaken
Storage::makeDirectory($directory);

// Een directory inclusief de bestanden erin verwijderen
Storage::deleteDirectory($directory);
```

## Testen

Met `Storage::fake()` schrijf je tests voor bestandsbewerkingen zonder een echte disk aan te raken.

```php theme={null}
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

test('een avatar kan worden geüpload', function () {
    Storage::fake('avatars');

    $file = UploadedFile::fake()->image('avatar.jpg');

    $this->post('/user/avatar', ['avatar' => $file]);

    // Controleren dat het bestand is opgeslagen
    Storage::disk('avatars')->assertExists('avatar.jpg');

    // Controleren dat het bestand niet is opgeslagen
    Storage::disk('avatars')->assertMissing('other.jpg');
});
```

<Info>
  Voor `UploadedFile::fake()->image()` heb je de [GD-extensie](https://www.php.net/manual/ja/book.image.php) van PHP nodig.
</Info>

## Praktische use case: een profielfoto uploaden

Een praktisch controllervoorbeeld dat validatie, opslag en het vastleggen van het pad in de database combineert.

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;

class ProfileController extends Controller
{
    public function updateAvatar(Request $request): RedirectResponse
    {
        $request->validate([
            'avatar' => ['required', 'image', 'max:2048'],
        ]);

        $user = $request->user();

        // De bestaande avatar verwijderen
        if ($user->avatar_path) {
            Storage::disk('public')->delete($user->avatar_path);
        }

        // De nieuwe avatar opslaan
        $path = $request->file('avatar')->store('avatars', 'public');

        // Het pad in de database opslaan
        $user->update(['avatar_path' => $path]);

        return back()->with('status', 'avatar-updated');
    }
}
```

In je template haal je de URL op met `Storage::url()`.

```blade theme={null}
<img src="{{ Storage::disk('public')->url($user->avatar_path) }}" alt="Avatar">
```


## Related topics

- [MongoDB](/nl/mongodb.md)
- [Logging](/nl/logging.md)
