Filistin'e Özgürlük

#IStandWithPalestine

Free Palestine

Laravel Jetstream Kullanıcı Kayıt Formunu Özelleştirmek

Laravel Jetstream ile kullanıcı kayıt formunda ilave form öğeleri eklemek için nasıl bir yöntem izlediğimi burada anlatıyorum.

Laravel ile çok hızlı uygulama geliştirmek mümkün. Bunu sonradan ekleyebileceğimiz ek araçlarla başarıyoruz. Jetstream de bunlardan biri. Jetstream, uygulamaların giriş, kayıt, e-posta doğrulama, iki faktörlü kimlik doğrulama, oturum yönetimi, Laravel Sanctum aracılığıyla API ve isteğe bağlı ekip yönetimi özellikleri gibi pek çok kolaylıklar sunuyor. Ancak bunların da nasıl kullanılacağını öğrenmek gerekiyor. Çünkü herkesin ihtiyacı farklı.

Bir uygulama için Laravel ve Jetstream’i kurduktan sonra kullanıcı kayıt formuna iki alan daha eklemek istedim. Kendi uygulamamdan farklı olarak bir kullanıcı kayıt formuna, doğum tarihi için ilave bir metin kutusu ve bulunulan il için bir açılır liste kutusunun nasıl ekleneceğini adım adım anlatacağım. 

Pek çok yerde aradım. Bir sürü video izledim. Bir sürü blog taradım. Dokümantasyon’a baktım. Stackoverflow‘a sordum. Orada da birşey bulamadım. En sonunda deneme yanılma yoluyla bir sonuca ulaştım. Şimdi başlayabiliriz.

<x-guest-layout>
    <x-authentication-card>
        <x-slot name="logo">
            <x-authentication-card-logo />
        </x-slot>

        <x-validation-errors class="mb-4" />

        <form method="POST" action="{{ route('register') }}">
            @csrf

            <div>
                <x-label for="name" value="{{ __('Name') }}" />
                <x-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required
                    autofocus autocomplete="name" />
            </div>

            <div class="mt-4">
                <x-label for="email" value="{{ __('Email') }}" />
                <x-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')"
                    required autocomplete="username" />
            </div>

            <div class="mt-4">
                <x-label for="password" value="{{ __('Password') }}" />
                <x-input id="password" class="block mt-1 w-full" type="password" name="password" required
                    autocomplete="new-password" />
            </div>

            <div class="mt-4">
                <x-label for="password_confirmation" value="{{ __('Confirm Password') }}" />
                <x-input id="password_confirmation" class="block mt-1 w-full" type="password"
                    name="password_confirmation" required autocomplete="new-password" />
            </div>

            @if (Laravel\Jetstream\Jetstream::hasTermsAndPrivacyPolicyFeature())
                <div class="mt-4">
                    <x-label for="terms">
                        <div class="flex items-center">
                            <x-checkbox name="terms" id="terms" required />

                            <div class="ms-2">
                                {!! __('I agree to the :terms_of_service and :privacy_policy', [
                                    'terms_of_service' =>
                                        '<a target="_blank" href="' .
                                        route('terms.show') .
                                        '" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">' .
                                        __('Terms of Service') .
                                        '</a>',
                                    'privacy_policy' =>
                                        '<a target="_blank" href="' .
                                        route('policy.show') .
                                        '" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">' .
                                        __('Privacy Policy') .
                                        '</a>',
                                ]) !!}
                            </div>
                        </div>
                    </x-label>
                </div>
            @endif

            <div class="flex items-center justify-end mt-4">
                <a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
                    href="{{ route('login') }}">
                    {{ __('Already registered?') }}
                </a>

                <x-button class="ms-4">
                    {{ __('Register') }}
                </x-button>
            </div>
        </form>
    </x-authentication-card>
</x-guest-layout>
 

Yukarıda resources/views/auth/register.blade.php dosyasının el değmemiş halini görüyoruz. 

Buraya, biri doğum tarihi, diğeri bulunulan şehir olmak üzere, iki alanı ekleyeceğiz. Birinci adımda kolaydan başlayarak doğum tarihini ekleyelim. 

<x-guest-layout>
    <x-authentication-card>
        <x-slot name="logo">
            <x-authentication-card-logo />
        </x-slot>

        <x-validation-errors class="mb-4" />

        <form method="POST" action="{{ route('register') }}">
            @csrf

            <div>
                <x-label for="name" value="{{ __('Name') }}" />
                <x-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required
                    autofocus autocomplete="name" />
            </div>

            <div class="mt-4">
                <x-label for="email" value="{{ __('Email') }}" />
                <x-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')"
                    required autocomplete="username" />
            </div>

            <div class="mt-4">
                <x-label for="date_of_birth" value="{{ __('Date of Birth') }}" />
                <x-input id="date_of_birth" class="block mt-1 w-full" type="date" name="date_of_birth"
                    :value="old('date_of_birth')" required />
            </div>
            

            <div class="mt-4">
                <x-label for="password" value="{{ __('Password') }}" />
                <x-input id="password" class="block mt-1 w-full" type="password" name="password" required
                    autocomplete="new-password" />
            </div>

            <div class="mt-4">
                <x-label for="password_confirmation" value="{{ __('Confirm Password') }}" />
                <x-input id="password_confirmation" class="block mt-1 w-full" type="password"
                    name="password_confirmation" required autocomplete="new-password" />
            </div>

            @if (Laravel\Jetstream\Jetstream::hasTermsAndPrivacyPolicyFeature())
                <div class="mt-4">
                    <x-label for="terms">
                        <div class="flex items-center">
                            <x-checkbox name="terms" id="terms" required />

                            <div class="ms-2">
                                {!! __('I agree to the :terms_of_service and :privacy_policy', [
                                    'terms_of_service' =>
                                        '<a target="_blank" href="' .
                                        route('terms.show') .
                                        '" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">' .
                                        __('Terms of Service') .
                                        '</a>',
                                    'privacy_policy' =>
                                        '<a target="_blank" href="' .
                                        route('policy.show') .
                                        '" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">' .
                                        __('Privacy Policy') .
                                        '</a>',
                                ]) !!}
                            </div>
                        </div>
                    </x-label>
                </div>
            @endif

            <div class="flex items-center justify-end mt-4">
                <a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
                    href="{{ route('login') }}">
                    {{ __('Already registered?') }}
                </a>

                <x-button class="ms-4">
                    {{ __('Register') }}
                </x-button>
            </div>
        </form>
    </x-authentication-card>
</x-guest-layout>
 

İlk koddan farklı olarak şu kısmı ekledik:

 

<div class="mt-4">
    <x-label for="date_of_birth" value="{{ __('Date of Birth') }}" />
    <x-input id="date_of_birth" class="block mt-1 w-full" type="date" name="date_of_birth"
                    :value="old('date_of_birth')" required />
</div> 

Form elemanları için Jetstream ile birlikte gelen form öğelerinden faydalanıyoruz. Normalde düz html input öğeleri de kullanabiliriz. Ama kodun insicamını bozmamak için aynı esasla devam ediyoruz.

Kullanıcının kaydı esnasında girdiği doğum tarihi ve bulunulan şehir bilgileri için veritabanında gerekli alanları açmamız gerekiyor. Bunun için migration dosyasında gerekli eklemeleri yapacağız.

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            //doğum tarihi için bu satırı ekliyoruz
            $table->date('date_of_birth');
            //Şehir bilgisi için de ilgili şehirin ID numarasını tutan alanı ekliyoruz.
            $table->tinyInteger('cityid');
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->foreignId('current_team_id')->nullable();
            $table->string('profile_photo_path', 2048)->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
    }
};
 

Birkaç benzer değişikliği app/models/User.php dosyasında gerekli değişiklikleri yapacağız.

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        //bu iki satırı da ekliyoruz ki veritabanına ekleyebilelim. Sırası önemli değil.
        'cityid',
        'date_of_birth',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array<int, string>
     */
    protected $appends = [
        'profile_photo_url',
    ];
}
 

Kullanıcı kaydı sırasında girilen verilerin doğru ve geçerli olup olmadığını kontrol etmemiz gerekiyor. Bunun kontrolü de Jetstream ile birlikte gelen app/Actions/Fortify/CreateNewUser.php dosyası içindeki geçerlilik kurallarına ekleyeceğiz.

<?php

namespace App\Actions\Fortify;

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use Laravel\Jetstream\Jetstream;

class CreateNewUser implements CreatesNewUsers
{
    use PasswordValidationRules;

    /**
     * Validate and create a newly registered user.
     *
     * @param  array<string, string>  $input
     */
    public function create(array $input): User
    {
        Validator::make($input, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            //Şehir için id numarası
            'cityid' => ['required', 'integer'],
            //Kullanıcının doğum tarihi
            'date_of_birth' => ['required', 'date'],
            'password' => $this->passwordRules(),
            'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
        ])->validate();

        return User::create([
            'name' => $input['name'],
            'email' => $input['email'],
            'date_of_birth' => $input['date_of_birth'],
            'cityid' => $input['cityid'],
            'password' => Hash::make($input['password']),
        ]);
    }
}
 

Bu kadar mı? Tabii ki hayır. Eksik olan birşeyler var. Şehirlerin kayıt formunda listelenmesi lazım. Ama ondan önce şehirler için bir migration ve model oluşturmak gerekiyor. Oluşturalım.

php artisan make:migration create_cities_table 
php artisan make:model City 

Burada yeni tablomuzu oluşturmadan önce migration dosyası içine bir bakalım.

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('cities', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('cities');
    }
};
 

Hemen şehirin adının tutulacağı isim alanını ekleyelim.

 

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('cities', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            //bu satırı biz ekliyoruz.
            $table->string('name');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('cities');
    }
};
 

Migration hazır oldu. Onu bir veritabanına işleyelim.

php artisan migrate 

Gelelim City.php isimli modelin içine gözatmaya. Burada da şehirin adının doldurulabilir olmasını bildirmemiz gerekiyor.

<?php

namespace App\Models;

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

class City extends Model
{
    use HasFactory;
    
    
    //bu satırı biz ekliyoruz.
    protected $fillable = ['name'];
}
 

Bundan sonra elle veya Factory kullanarak tabloyu doldurabilirsiniz. Orası size kalmış. Burada onları anlatmaya gerek görmüyorum.

Asıl mesele: Açılır Liste Kutusu

Form içinde doğum tarihi var. Ancak henüz şehir seçimi için bir açılır liste kutusunu eklemedik. Kısa yoldan şu şekilde anlatalım.

Basit bir şekilde select input öğesi kullanarak bu listeyi oluşturalım. Doğum Tarihi alanının hemen altına aşağıdaki kodu ekleyelim.

<div class="mt-4">
    <x-label for="city" value="City" />
    <select name="city" id="city" class="block mt-1 w-full flex justify-between items-center px-4 py-2 border border-gray-300 rounded-md ">
        <option value="">Please select the city you live</option>
       
    </select>
</div> 

Hala birşeyler eksik. Veritabanında şehir isimleri duruyor. Ancak bunları buraya aktarmamız gerekiyor. Onu nasıl yapacağız. İşte şimdi size çok değerli ve internette bulunmayan bir bilgi vereceğim. Hazırsanız başlayalım. 

Öncelikle Jetstream bizim kullandığımız Controller classlarını kullanmıyor. Kendi başına bir ekosistemi var. app/Providers içinde birkaç dosyaya rastladım. Bunlardan bir tanesi app/Providers/AuthServiceProvider.php dosyası. Öncelikle bu dosyanın varsayılan içeriğini göstereyim.

<?php

namespace App\Providers;

// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Fortify\Fortify;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The model to policy mappings for the application.
     *
     * @var array<class-string, class-string>
     */
    protected $policies = [
        //
    ];

    /**
     * Register any authentication / authorization services.
     */
    public function boot(): void
    {
        //
    }
}
 

Bu dosyanın içinde City modelinden faydalanarak şehirleri veritabanından okuyacağız ve kayıt formu içine aktaracağız. Bu provider içindeki boot() fonksiyonuna aşağıdaki kodu ekleyeceğiz.

<?php

namespace App\Providers;

// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Fortify\Fortify;
//bu City modelini referans ediniyoruz.
use App\Models\City;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The model to policy mappings for the application.
     *
     * @var array<class-string, class-string>
     */
    protected $policies = [
        //
    ];

    /**
     * Register any authentication / authorization services.
     */
    public function boot(): void
    {
        //
        //Burada kayıt formu çağrılırken içini dolu gönderiyoruz. 
        //Benzer olarak başka tablolardan da faydalanabilirdik.
        Fortify::registerView(function () {
            $cities = City::all();
            return view('auth.register', ['cities' => $cities]);
        });
    }
}
 
Ekran görüntüsü 2024-04-27 223428

İşte şimdi bu şekilde şehirleri ve doğum tarihini kullanıcı kayıt formuna eklemiş olduk. 

Saatlerce aradım, taradım bir çözüm bulamamıştım. Seyrettiğim Youtube videolarında (bu, bu, bu ya da bu) da bir çözüm önerilmemiş ve yorumlarda aynı sorular sorulmuş. Kimse de cevaplamamış. 

register.blade.php view içinde şu değişiklikleri yapıyoruz.

<div class="mt-4">
    <x-label for="city" value="City" />
    <select name="city" id="city" class="block mt-1 w-full flex justify-between items-center px-4 py-2 border border-gray-300 rounded-md ">
        <option value="">Please select the city you live</option>
        @foreach ($cities as $city)
            <option class="block mt-1 w-full flex justify-between items-center px-4 py-2 rounded-md"
                        value="{{ $city->id }}">{{ $city->name }}
            </option>
        @endforeach
    </select>
</div> 
Ekran görüntüsü 2024-04-27 223443
Okunma Sayısı: 16