Makaleler Kod Parçaları Projeler

Laravel’de `multipart/form-data` İsteklerini doğru JSON Veri Tipleri ile Doğrulama

4 Ocak '22 7 ay önce

2 dk 264 kelime

Bu makale ben ve Oğuzhan Karacabay tarafından yazılmıştır, İngilizce dilinde de okunabilir, Medium üzerinde de okunabilir.

Bir Frontend SPA’dan Laravel Backend API’nize hem dosya yüklemek hem de JSON veri göndermek istediğinizde multipart/form-data şeklinde gönderirsiniz.

Frontend Framework’lardan bağımsız olarak bunu yapmanın tipik bir örneği şöyle olabilir:

postMultipartFormData(form) {
	let formData = new FormData()

	formData.append('file', form.file)
	formData.append('id', form.id) // 1234
	formData.append('reason_type', form.reason_type) // 3
	formData.append('rate', form.rate) // 10.15
	formData.append('fee', form.fee * 100) // 1000 * 100
	formData.append('tax', form.tax * 100) // 190 * 100
	formData.append('description', form.description)  // some description
	formData.append('action_date', form.action_date) // 2022-01-07 17:52:06

	return apiClient
	  .post('/post/multipart-formdata', formData, {
	    headers: {'Content-Type': 'multipart/form-data'}
	  })
	  .then(response => { return response })
	  .catch(err => { throw err })
}

Frontend’ten gelen bu isteği Backend API’de, iyi bir Laravel geliştiricisi olarak, Controller'a uğramadan önce, bir Request ile doğrulamak istersiniz.

Laravel Controller’u da aşağı yukarı şuna benzer.

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\MultipartFormRequest;
use App\Http\Resources\MultipartFormResource;

class MultipartFormController extends Controller
{
    public function store(MultipartFormRequest $request): MultipartFormResource
    {
        $file = $request->file('file');
        $path = '/your/path';
        $filename = 'filename.xlsx';

        // Save file to a local or remote file bucket
        $file->storeAs($path, $filename, ['disk' => 's3-public']);

        // Create a model with file url
        $yourModel = YourModel::create(array_merge($request->validated(), [
            'file_url' => $path . $filename,
        ]));

        // Return a resource with your newly generated model
        return new MultipartFormResource($yourModel);
    }
}

Veriler ve yüklenecek dosya Controller’a gelmeden önce şuna benzer bir Request ile doğrulanır:

<?php

namespace App\Http\Requests;

use App\Enums\PromissoryNoteReasonType;
use BenSampo\Enum\Rules\EnumValue;
use Illuminate\Foundation\Http\FormRequest;

class MultipartFormRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'bank_id'                => ['bail', 'required', 'numeric', 'exists:banks,id'],
            'promissory_note_reason' => ['bail', 'required', new EnumValue(PromissoryNoteReasonType::class)],
            'interest_rate'          => ['bail', 'required', 'between:0,99.9999'],
            'fixed_fee'              => ['bail', 'required', 'integer'],
            'tax'                    => ['bail', 'required', 'integer'],
            'description'            => ['bail', 'required', 'string'],
            'action_date'            => ['bail', 'required', 'date'],
            'file'                   => ['bail', 'required', 'file', 'mimes:xls,xlsx',
            ],
        ];
    }
}

Buraya kadar her şey mükemmel ilerledi fakat bu kodu çalıştırdığınızda, gönderdiğiniz verilerin doğrulamadan geçemediğini ve derinlemesine incelediğinizde Laravel API’nize gelen verilerin beklediğimizden çok farklı olduğunu farkedeceksiniz:

array:8 [
  "file" => "file-content"
  "id" => "123"
  "reason_type" => "3"
  "rate" => "10.15"
  "fee" => "10000"
  "tax" => "1900"
  "description" => "request description"
  "action_date" => "2020-10-07 17:52:06"
]

Böylece multipart/formdata’nın binary dosya gönderebilme yeteneğinin yanında, bütün veri tiplerini string’lere çevirdiğini farkettiniz.

Bu aşamadan sonra gelen verileri API tarafında doğrulama kurallarınıza gelen verilerin string olacağını varsayarak değiştirdikten sonra, string tipindeki değerleri tam olarak doğrulamak için kendi parse jimnastiğinizi yapabilirsiniz.

Validation with proper JSON Data Types

Eğer FormData nesnesi sadece string türünde veri gönderebiliyor ve dosya yüklemek için mutlaka FormData nesnesi kullanmamız gerekiyorsa biz de öyle yaparız. Tabii ki öncesinde gönderilecek verilerin hepsini bir JSON string'e çevirerek.

Frontend tarafında yüklemek istediğimiz dosyayı FormData’ya ekledikten sonra (I.) tüm diğer tüm verileri stringify() ile bir JSON string’e çeviriyoruz. (II.)

Böylece Backend API’sine göndermek üzere, elimizde sadece file ve payload verilerini içeren bir FormData nesnesi olmuş oldu. (III.)

stringify() fonksiyonu float tipindeki verileri JSON string’e doğru veri tipiyle aktaramadığı için parseFloat() fonksiyonunu kullandık. (IV.)

postMultipartFormData(form) {
  let formData = new FormData()

  formData.append('file', form.file) // I.

  let payload = JSON.stringify({
    id: form.id,
    reason_type: form.reason_type,
    rate: parseFloat(form.rate), // IV.
    fee: form.fee * 100,
    tax: form.tax * 100,
    description: form.description,
    action_date: form.action_date
  }); // II.

  formData.append('payload', payload) // III.

  return apiClient
      .post('/post/multipart-formdata', formData, {
        headers: { 'Content-Type': 'multipart/form-data' }
      })
      .then(response => { return response })
      .catch(err => { throw err })
}

Backend API tarafına gelen JSON string'i doğrulama kurallarından geçirmeden hemen önce bir JSON Payload'a çevirmemiz gerekiyor. Laravel’in Request sınıflarındaki prepareForValidation() metodu da tam da bu iş için var.

  /**
   * Prepare the data for validation.
   *
   * @return void
   *             
   * @throws \JsonException
   */
  protected function prepareForValidation(): void
  {
      $this->merge(json_decode($this->payload, true, 512, JSON_THROW_ON_ERROR));
  }

prepareForValidation() metodu içinde JSON String’i json_decode() fonksiyonunu kullanarak JSON Payload’a çevirdikten sonra yine Laravel Request sınıflarının diğer bir metodu olan merge() ile diğer Request verileriyle birleştiriyoruz.

array:7 [
  "id" => 123
  "reason_type" => 3
  "rate" => 10.15
  "fee" => 10000
  "tax" => 1900
  "description" => "request description"
  "action_date" => "2020-10-07 17:52:06"
]

Böylece verilerimiz uygun veri tiplerine cast edilerek doğrulama kurallarından geçebilmiş oldu. Ne doğrulama kurallarını değiştirmeye ne de ayrıca doğrulama jimnastiği yapmaya gerek kaldı.