[Tutorial] Como eu faço upload de imagens no laravel.

Olá galera, estou aqui para compartilhar e tambem para aprender, quero mostrar a forma que faço upload no laravel e queria opnião de vocês, eu trabalho com laravel 1 ano, então sou relativamente iniciante, e aprendi dessa forma.

aqui já vou assumir, que vocês sabem criar o projeto, então vou focar somente no codigo mesmo, mais caso queiram saber como eu faço tambem para a criação do projeto e ferramentas que eu uso, comentem ai.

Primeiramente , eu crio toda a parte do frontend, formularios e as rotas.

então vamos lá.

routes/web.php

<?php

use App\Http\Controllers\PerfilAssociadoController;
use Illuminate\Support\Facades\Route;


Route::get('/', [PerfilAssociadoController::class, 'index'])->name('perfil-associado.index');
Route::post('/', [PerfilAssociadoController::class, 'store'])->name('perfil-associado.store');


Então eu crio a index.blade.php

resources/view/index.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Upload</title>

    <!-- Fonts -->
    <link rel="preconnect" href="https://fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>

<body class="antialiased">
    <div class="container">
        @if (session()->has('success'))
        <div class="alert alert-success alert-dismissible fade show" role="alert">
            <strong>Sucesso!</strong> {{ session()->get('success') }}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>
        @endif
        @if (session()->has('error'))
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <strong>Erro!</strong> {{ session()->get('error') }}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>
        @endif
        <form action="{{ route('perfil-associado.store')}}" method="POST" enctype="multipart/form-data">
            @csrf
            <div class="mb-3">
                <label for="logo" class="form-label">{{ __('Logotipo') }}</label>
                <input id="logo" type="file" name="logo" class="form-control">
                @error('logo')
                <span style="color:red;padding-top:4px;">{{ $message }}</span>
                @enderror
            </div>
            <div class="mb-3">
                <label for="nome" class="form-label">{{ __('Nome') }}</label>
                <input id="nome" type="text" name="nome" value="{{ old('nome') }}" class="form-control" autofocus>
                @error('nome')
                <span style="color:red;padding-top:4px;">{{ $message }}</span>
                @enderror
            </div>
            <div class="mb-3">
                <label for="email" class="form-label">{{ __('E-mail') }}</label>
                <input id="email" type="text" name="email" value="{{ old('email') }}" class="form-control" autofocus>
                @error('email')
                <span style="color:red;padding-top:4px;">{{ $message }}</span>
                @enderror
            </div>
            <div class="mt-4">
                <button type="submit" class="btn btn-primary">{{ __('Cadastrar') }}</button>
            </div>
        </form>
    </div>

    <div class="container mt-4">
        <div class="row">
            @if($perfis)
            @foreach($perfis as $perfil)
            <div class="col-md-4">
                <div class="card" style="width: 18rem;">
                    <img src="{{ asset('photos/'. $perfil->logo) }}" class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title
                            ">{{ $perfil->nome }}</h5>
                        <p class="card-text">{{ $perfil->email }}</p>

                        @error('nome')
                    <span style="color:red;padding-top:4px;">{{ $message }}</span>
                @enderror

                    </div>
                </div>
            </div>
            @endforeach
            @endif
        </div>
    </div>
</body>

</html>



após isso, crio o model e a migration.

php artisan make:model PerfilAssociado -m

database/migration/2024_02_27_142713_create_perfil_associados_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{

    public function up(): void
    {
        Schema::create('perfil_associados', function (Blueprint $table) {
            $table->id();
            $table->string('nome');
            $table->string('email')->unique();
            $table->string('logo')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('perfil_associados');
    }
};


App/Models/PerfilAssociado.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class PerfilAssociado extends Model
{
    use HasFactory;

    protected $fillable = [
        'nome',
        'email',
        'logo',
    ];
}



pronto, agora podemos criar uma trait em

Traits/FileUploadTrait.php

<?php

namespace App\Traits;

use File;
use Illuminate\Http\Request;

trait FileUploadTrait
{
    public function uploadImage(Request $request, string $inputName, ?string $oldPath = null,  string $path = '/uploads'): ?string
    {

        if ($request->hasFile($inputName)) {

            $image = $request->file($inputName);
            $ext = $image->getClientOriginalExtension();
            $imageName = 'media_' . uniqid() . '.' . $ext;

            $destinationPath = 'photos' . $path;
            $image->move($destinationPath, $imageName);

            if ($oldPath && file_exists(public_path($oldPath))) {
                File::delete(public_path($oldPath));
            }

            return $path . '/' . $imageName;
        }

        return null;
    }
}



precisamos validar os dados, então vamos criar uma request. php artisan make:request CreateAssociadoRequest

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreateAssociadoRequest extends FormRequest
{

    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
            return [
                'nome' => 'required',
                'email' => 'required|unique:perfil_associados,email',
                'logo' => 'max:2048|mimes:jpeg,png,jpg,gif,svg|nullable',
            ];
        }

        public function messages(): array
        {
            return [
                'nome.required' => 'O campo Nome é obrigatório',
                'email.required' => 'O campo E-mail é obrigatório',
                'email.email' => 'O campo E-mail deve ser um endereço de e-mail válido',
                'email.unique' => 'O campo E-mail já está cadastrado',
                'logo.max' => 'O campo Logo deve ter no máximo 2MB',
                'logo.mimes' => 'O campo Logo deve ser um arquivo do tipo: jpeg, png, jpg, gif, svg',

            ];
        }
    }



então eu crio a controller nesse caso, estarei mostrando um exemplo de perfil de um associado.

php artisan make:controller PerfilAssociadoController

App/Http/Controllers/PerfilAssociadoController

<?php

namespace App\Http\Controllers;

use App\Http\Requests\CreateAssociadoRequest;
use App\Models\PerfilAssociado;
use App\Traits\FileUploadTrait;
use Exception;
use Illuminate\Support\Facades\DB;

class PerfilAssociadoController extends Controller
{
    use FileUploadTrait;

    public function index()
    {
        $perfis = PerfilAssociado::get();
        return view('index', compact('perfis'));
    }

    public function store(CreateAssociadoRequest $request)
    {
        try {

            DB::beginTransaction();

            $perfilAssociado = new PerfilAssociado();
            $perfilAssociado->fill($request->validated());
            $perfilAssociado->setAttribute('logo', $this->uploadImage($request, 'logo'));
            $perfilAssociado->save();

            DB::commit();

            return redirect()->back()->with('success', 'Perfil cadastrado com sucesso!');

        } catch (Exception $e) {
            DB::rollback();
            return redirect()->back()->with('fail', 'Falha ao cadastrar o perfil!');
        }
    }
}


agora não podemos esquecer, de ajustar o banco de dados, estou usando mysql. então eu crio um banco de dados e ajusto no .env na raiz.

.env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tabnews
DB_USERNAME=root
DB_PASSWORD=****

pronto, agora executamos um

php artisan migrate

e se tudo correu como imaginava, já é possivel de nos cadastrar um perfil.

Parabéns mano, o artigo ficou muito bom e bem explicado, eu não manjo muito do laravel, mas conheço bem o codeigniter e consegui entender super bem. Continue assim, quando explicamos algo ou algum conceito a alguém aprendemos 2x mais.

Obrigado, como falei ainda estou aprendendo, e acho que a melhor forma de aprender é explicando e tentando passar para os outros, então com certeza, estarei postando mais. logo logo, estarei postando sobre java tambem, estou estudando e gostei bastante.

belo artigo! não manjo muito essa parte de Traits do Laravel, parece ser como uma função externa, ficou bacana! vou estudar um pouco mais.

uma observação se me permite, não precisa instanciar o PerfilAssociado para depois dar um ::create, pode ir direto com PerfilAssociado::create([array com campos])

As traits são do PHP mesmo, não do Laravel. Pode utilizar para padronizar e reaproveitar métodos em que mais de uma classe utilizam em comum. Segue documentação, caso tenha interesse: https://www.php.net/manual/pt_BR/language.oop5.traits.php
Opa, a laravel é muito bom, realmente, é que eu acabei deixando passar, tinha outro codigo ali que eu precisava ter instanciando, mais tirei e acabei deixando, mais obrigado por ver , já ajustei ali, deixei comentado, para pessoal ver, a mudança. antes e depois.

Recomendaria você criar uma classe para salvar o conteúdo e deixar o controller realmente só controlando, mas para simplificar irei fazer conforme seu exemplo.

Sugestões de melhoria:

  1. Controle no DB: Caso ocorra algum problema durante a manipulação das imagens ou salvamento você pode precisar fazer o banco voltar ao estado inicial, uma transaction fará você manter a coesão do bancomutilizando o rollback para desfazer tudo. Utilize isso sempre que um registro pode afetar outro e a ação depende do ciclo completo. (Nesse seu caso não precisaria necessáriamente, mas para controlar melhor a mensageria eu preferi por)

  2. Preenchimento dos dados: Seu $request->validated() já contem um array com os dados, abuse dele. Note que eu utilizei o método $model->fill() para isso e o dado que não contem ali eu passo manualmente para realmente saber que sai do padrão da request, que é acionada por outra parte do fonte (método privado para salvar a imagem).

  3. Trate os erros: caso de erro seu front deve exibir uma mensagem amigável também. Não esqueça de usar o old() no blade para repopular os inputs já digitados para diminuir a frustração do usuário.

Segue fonte refatorado:

    public function store(CreateAssociadoRequest $request)
    {

        try{
            DB::begginTransaction();
            
            $perfilAssociado = new PerfilAssociado();
            $perfilAssociado->fill($request->validated());
            $perfilAssociado->setAttribute('logo', $this->uploadImage($request, 'logo'));
            $perfilAssociado->save();

            DB::commit();
        }catch(Exception $e){
            DB::rollback();
            return redirect()->back()->with('fail', 'Falha ao cadastrar o perfil!');
        }

        return redirect()->back()->with('success', 'Perfil cadastrado com sucesso!');
    }

Lembrando que é só minha opnião e uma sugestão. Espero que essas dicas deixei seu fonte com uma leitura mais fluída ou seja útil de alguma forma. Qualquer dúvida estou a disposição

Obrigado, testei lá e ficou bom, e entendi para que serve, então realmente é bom ir melhorando e refatorando a os poucos. só percebi que DB::begginTransaction(); estava escrito errado, mais já ajustei meu codigo lá e está 100% valeu mesmo.