· Admin

Laravel'de Excel ve CSV Import/Export İşlemleri

Müşteri diyor ki: "Ürünleri Excel'den toplu yükleyebilmemiz lazım." Veya "Kullanıcı listesini Excel olarak indirmek istiyorum." Hemen hemen her yönetim panelinde bu talep gelir. Maatwebsite/Excel paketi bu işi Laravel'de çok kolay yapıyor.

Kurulum

composer require maatwebsite/excel

Laravel 12'de auto-discovery çalışır, ekstra config gerekmez. Ama config dosyasını publish etmek isterseniz:

php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider" --tag=config

Export — Veriyi Excel'e Aktarmak

Basit Export

php artisan make:export UsersExport --model=User
namespace App\Exports;

use App\Models\User;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;

class UsersExport implements FromQuery, WithHeadings, WithMapping
{
    use Exportable;

    public function query()
    {
        return User::query()->where('is_active', true);
    }

    public function headings(): array
    {
        return ['ID', 'Ad Soyad', 'E-posta', 'Kayıt Tarihi'];
    }

    public function map($user): array
    {
        return [
            $user->id,
            $user->name,
            $user->email,
            $user->created_at->format('d.m.Y'),
        ];
    }
}

Neden FromQuery kullanıyorum, FromCollection değil? Çünkü FromQuery chunk'lara bölerek çalışır — 100.000 kullanıcınız varsa hepsini belleğe almaz. FromCollection küçük veri setleri için tamam ama büyük tablolarda memory limit'e çarpar.

Controller'da Kullanım

use App\Exports\UsersExport;
use Maatwebsite\Excel\Facades\Excel;

public function export()
{
    return Excel::download(new UsersExport, 'kullanicilar.xlsx');
}

// CSV olarak indirmek için
public function exportCsv()
{
    return Excel::download(new UsersExport, 'kullanicilar.csv');
}

Dosya uzantısını .csv yapınca otomatik CSV formatında indirir. Başka bir şey değiştirmenize gerek yok.

Stil Eklemek

use Maatwebsite\Excel\Concerns\WithStyles;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class UsersExport implements FromQuery, WithHeadings, WithMapping, WithStyles
{
    // ... diğer metodlar

    public function styles(Worksheet $sheet): array
    {
        return [
            1 => [
                'font' => ['bold' => true, 'size' => 12],
                'fill' => [
                    'fillType' => 'solid',
                    'startColor' => ['rgb' => '4472C4'],
                ],
                'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
            ],
        ];
    }
}

İlk satır (başlıklar) mavi arka plan, beyaz kalın yazı. Müşteriler bu tür detayları çok sever.

Import — Excel'den Veri Yüklemek

Basit Import

php artisan make:import UsersImport --model=User
namespace App\Imports;

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;

class UsersImport implements ToModel, WithHeadingRow, WithValidation, SkipsEmptyRows
{
    public function model(array $row): User
    {
        return new User([
            'name' => $row['ad_soyad'],
            'email' => $row['eposta'],
            'password' => Hash::make('gecici-sifre-123'),
            'is_active' => true,
        ]);
    }

    public function rules(): array
    {
        return [
            'ad_soyad' => ['required', 'string', 'max:255'],
            'eposta' => ['required', 'email', 'unique:users,email'],
        ];
    }

    public function customValidationMessages(): array
    {
        return [
            'ad_soyad.required' => ':attribute alanı zorunludur.',
            'eposta.required' => ':attribute alanı zorunludur.',
            'eposta.unique' => 'Bu e-posta adresi zaten kayıtlı.',
        ];
    }
}

WithHeadingRow — Excel'in ilk satırını sütun adı olarak kullanır. $row['ad_soyad'] şeklinde erişirsiniz. Bu olmazsa $row[0], $row[1] gibi index ile erişirsiniz ki okunabilirlik sıfır.

WithValidation — Her satırı Laravel validation kurallarıyla kontrol eder. Geçersiz satır varsa hata fırlatır.

SkipsEmptyRows — Boş satırları atlar. Excel dosyalarında her zaman beklenmedik boş satırlar olur.

Controller

use App\Imports\UsersImport;
use Maatwebsite\Excel\Facades\Excel;

public function import(Request $request)
{
    $request->validate([
        'file' => ['required', 'file', 'mimes:xlsx,csv,xls', 'max:10240'],
    ]);

    try {
        Excel::import(new UsersImport, $request->file('file'));

        return back()->with('success', 'Kullanıcılar başarıyla yüklendi.');
    } catch (\Maatwebsite\Excel\Validators\ValidationException $e) {
        $failures = $e->failures();

        return back()->with('import_errors', $failures);
    }
}

Hata Gösterimi (Blade)

@if (session('import_errors'))
    <div class="alert alert-danger">
        <strong>İçe aktarma hataları:</strong>
        <ul class="mb-0 mt-2">
            @foreach (session('import_errors') as $failure)
                <li>Satır {{ $failure->row() }}: {{ implode(', ', $failure->errors()) }}</li>
            @endforeach
        </ul>
    </div>
@endif

Kullanıcıya "3. satırda e-posta geçersiz" gibi detaylı hata gösteriyoruz — "bir hata oluştu" demekten çok daha faydalı.

Büyük Dosyalar İçin Queue

10.000+ satırlık Excel dosyalarını senkron işlemek HTTP timeout'a neden olur. Queue'ya atın:

// ShouldQueue interface'i ekleyin
use Maatwebsite\Excel\Concerns\WithChunkReading;
use Illuminate\Contracts\Queue\ShouldQueue;

class UsersImport implements ToModel, WithHeadingRow, WithValidation, WithChunkReading, ShouldQueue
{
    public function chunkSize(): int
    {
        return 500;
    }

    // ... diğer metodlar
}
// Controller'da
Excel::queuedImport(new UsersImport, $request->file('file'));

return back()->with('success', 'Dosya alındı, arka planda işleniyor.');

WithChunkReading dosyayı 500'er satırlık parçalara böler. Her chunk ayrı bir job olarak queue'ya atılır. 10.000 satırlık dosya = 20 job. Bellek kullanımı sabit kalır.

Şablon İndirme

Import için kullanıcılara boş şablon sunmak iyi pratik:

public function template()
{
    return Excel::download(new class implements \Maatwebsite\Excel\Concerns\FromArray, \Maatwebsite\Excel\Concerns\WithHeadings {
        public function array(): array
        {
            return [
                ['ProjeMan', '[email protected]'],
            ];
        }

        public function headings(): array
        {
            return ['Ad Soyad', 'E-posta'];
        }
    }, 'kullanici-sablonu.xlsx');
}

Anonim class ile tek seferlik export — ayrı dosya oluşturmaya gerek yok. Bir örnek satır koymak kullanıcının formatı anlamasını kolaylaştırır.

Route'lar

Route::get('users/export', [UserController::class, 'export'])->name('users.export');
Route::get('users/export-csv', [UserController::class, 'exportCsv'])->name('users.export-csv');
Route::get('users/template', [UserController::class, 'template'])->name('users.template');
Route::post('users/import', [UserController::class, 'import'])->name('users.import');

Export ve template GET, import POST. Export işlemleri dosya indirme olduğu için GET uygundur — browser'da direkt açabilirsiniz.

Performans İpuçları

  • Export: FromQuery kullanın, FromCollection değil. 10K+ kayıtta fark belirgin.
  • Import: WithChunkReading + ShouldQueue büyük dosyalar için şart.
  • CSV vs XLSX: CSV import/export çok daha hızlı. Stil gerekmiyorsa CSV tercih edin.
  • Geçici dosyalar: Import sonrası upload edilen dosyayı silmeyi unutmayın. Storage::delete() veya queue job'ında WithEvents ile AfterImport event'inde temizleyin.

Import/export özelliği müşterilerin çok sevdiği ve sürekli istediği bir özellik. Bir kez doğru kurduğunuzda her projede aynı pattern'i kullanırsınız.