Contexto

Recientemente fue necesario separar una aplicación web de los archivos que genera, por lo que se buscó y se encontró MinIO para solventar el almacenamiento de archivos. (Instalación en docker) En éste post mostraré cómo integrar MinIO con un proyecto Laravel

1.- Crear proyecto Laravel

Crear un proyecto de laravel con el siguiente comando:

composer create-project laravel/laravel project-minio

2.- Instalar el paquete league/flysystem-aws-s3-v3:

composer require league/flysystem-aws-s3-v3

3.- Ahora configurar los accesos en el archivo .env

Los datos de MINIO_SECRET_ACCESS_KEY y MINIO_ACCESS_KEY_ID se obtienen cuando se creó el Service Account del usuario que se registró en el post: instalación de MinIO si no se anotó o guardó, se puede crear un nuevo service account.

MINIO_ACCESS_KEY_ID=pzwnKFgyD3pYD3DQ
MINIO_SECRET_ACCESS_KEY=J7bhE7cPRaMkc0nQoNTiakyHKRy1Aa33
MINIO_DEFAULT_REGION=us-east-1
MINIO_BUCKET=files
FILESYSTEM_CLOUD=minio
MINIO_URL=http://127.0.0.1:9000/${MINIO_BUCKET}
MINIO_ENDPOINT=http://127.0.0.1:9000
MINIO_USE_PATH_STYLE_ENDPOINT=true

Y buscar la configuración que de nombre FILESYSTEM_DISK y cambiar a la siguiente forma:

FILESYSTEM_DISK=minio

4.- Editar el archivo config/filesystems.php

Acceder y modificar el archivo config/filesystems.php añadiendo la siguiente línea:

'cloud' => env('FILESYSTEM_CLOUD', 's3'),

Y en la sección de disks crear un nuevo array:

'minio' => [
    'driver' => 's3',
    'key' => env('MINIO_ACCESS_KEY_ID'),
    'secret' => env('MINIO_SECRET_ACCESS_KEY'),
    'region' => env('MINIO_DEFAULT_REGION'),
    'bucket' => env('MINIO_BUCKET'),
    'url' => env('MINIO_URL'),
    'endpoint' => env('MINIO_ENDPOINT'),
    'throw' => true,
],

5.- Crear controlador FileController

php artisan make:controller FileController

Y añadir el siguiente contenido:

<?php

namespace App\Http\Controllers;

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

class FileController extends Controller
{

    public function index(Request $request)
    {
        $data['archivos'] = [];
        $data['total'] = 0;
        return view('main', $data);
    }

    public function store(Request $request)
    {
        $data = [];
        $archivos = [];
        $data['total'] = 0;
        if ($request->hasfile('files')) {
            foreach ($request->file('files') as $key => $file) {
                $data['total'] ++;
                $nombreArchivo = Str::random(4).'-'.Str::random(4).'.'.$file->getClientOriginalExtension();
                $contenido = file_get_contents($file);
                $path = Storage::cloud()->put($nombreArchivo, $contenido);
                $archivos[] = $nombreArchivo;
            }
        }
        $data['archivos'] = $archivos;
        return view('main', $data);
    }
}

6.- Editar el archivo routes/api.php

Abrir el archivo routes/api.php y añadir las siguientes rutas:

Route::post('file','App\Http\Controllers\FileController@store');
Route::get('file','App\Http\Controllers\FileController@show');

7.- Editar el archivo routes/web.php

Route::get('/', 'App\Http\Controllers\FileController@index')->name('/');
Route::get('/', 'App\Http\Controllers\FileController@index')->name('home');
Route::post('store', 'App\Http\Controllers\FileController@store')->name('store');

8.- Crear la vista views/main.blade.php

Ésta vista contendrá un formulario simple para subir archivos, y en automático se suben a MinIO:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Storage MinIO Laravel</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
        integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ=="
        crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

<body>
    <div class="container">
        <header
            class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">
            <a href="/" class="d-flex align-items-center col-md-3 mb-2 mb-md-0 text-dark text-decoration-none">
                <svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap">
                    <use xlink:href="#bootstrap"></use>
                </svg>
            </a>

            <ul class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">
                <li><a href="#" class="nav-link px-2 link-secondary">Home</a></li>
                <li><a href="#" class="nav-link px-2 link-dark">About</a></li>
            </ul>

            <div class="col-md-3 text-end">
                <button type="button" class="btn btn-outline-primary me-2">Login</button>
                <button type="button" class="btn btn-primary">Sign-up</button>
            </div>
        </header>
    </div>
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <form action="{{route('store')}}" method="post" accept-charset="utf-8" enctype="multipart/form-data">
                    @csrf
                    <div class="row">
                        <div class="col-md-4">
                            <div class="input-group mb-3">
                                <label class="input-group-text" for="file1">Archivo uno</label>
                                <input type="file" class="form-control" name="files[]" id="file1" required="" multiple="">
                            </div>
                        </div>
                        <div class="col-md-4">
                            <button type="submit" class="btn btn-success float-end">Enviar</button>
                        </div>
                    </div>
                </form>
            </div>
            <div class="col-md-12">
                <span>Total archivos: {{$total}}</span>
                <table class="table">
                    <thead>
                        <tr>
                            <th scope="col">#</th>
                            <th scope="col">Archivo</th>
                        </tr>
                    </thead>
                    <tbody>
                        @forelse ($archivos as $archivo)
                            <tr>
                                <td>{{$loop->iteration}}</td>
                                <td>
                                    <a href="{{ asset(env('MINIO_URL')).'/'.$archivo}}" target="_blank">{{$archivo}}</a>
                                </td>
                            </tr>
                        @empty
                            <tr>
                                <td colspan="2" class="text-center">Sin archivos recientes</td>
                            </tr>
                        @endforelse                      
                    </tbody>
                </table>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
        crossorigin="anonymous"></script>
</body>
</html>

Al iniciar en MinIO y navegar al Bucket/Files se podrán visualizar los archivos subidos

9.- Probar el proyecto

php artisan serve

Y ahora navegar al enlace: http://127.0.0.1:8000/ para comenzar a subir archivos.

10.- Conclusión

En éste ejemplo básico, se suben los archivos a MinIO, sin embargo no se guardan los enlaces a una base de datos por parte del proyecto de laravel, sin embargo, éste proyecto puede servir como base e implementar las funcionalidades específicas que se requieran para cada caso.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *