SalimMammadov
Ön bilgi
Ben uygulamamda $user->isAdmin()
, $user->isTeacher()
ya da $user->hasRole('manager')
gibi ilerleyeceğim diyorsanız tüm kullanıcıları users tablosunda tutacaksınız fakat role göre ayıracaksınız. Genellikle istenen yapı bu oluyor ve o yüzden kullanımı kolay olduğu için bunu öneriyoruz. users tablosu kullanıcıya özel olan email, username, password gibi alanların tutulduğu bir tablo. O tabloda köpeğin cinsini, arabasının rengini vs tutmamanız gerekiyor çünkü kullanıcının rolüne göre o alanlara ihtiyacı olmayabilir. O tür alanlar genellikle ayrı tablolarda tutulur. O yüzden users tablosu aslında sizin öğretmenin, yöneticinin, müşterinin... bilgilerinin tutulduğu tablo değil, onları sistemde giriş yapmış kullanıcı olarak temsil eden kullanıcının bilgileri. O kullanıcının da temel olarak username ve password gibi bir alana ihtiyacı var. Ben o kullanıcının öğretmen mi öğrenci mi olduğunu bu aşamada bilmiyorum, sadece kullanıcı o. Onun ne olduğunu bilmemi sağlayan rolü.
Burada önemli olan nokta şu, üstteki yazdığım $user->isAdmin()
, $user->isTeacher()
ya da $user->hasRole('manager')
örneğinde gördüğünüz gibi hep bir, ortak, $user'dan yola çıkılmış. Yani tek bir kullanıcı modeli var, yani varsayılan olarak düşünürsek App\Models\User. Siz bu şekilde ilerlediğinizde ben sadece User modeli kullanacağım şeklinde taahhüt etmiş oluyorsunuz. Bunu sağlayan ise guard.
Öncelikle düz session auth kullandığınızı varsayalım. config/auth.php içerisinde bakarsanız defaults.guard değerini web olarak görürsünüz. Aşağıdaki guards dizisindeki web anahtarını kullan demek. guards dizisine bakarsanız guards.web.provider değerinin de users olduğunu görürsünüz. Bu da users provider'ını kullan demek. providers dizisine bakarsanız providers.users.model değerinin de App\Models\User::class olduğunu görürsünüz. Yani bu bağlama işini tablo olarak göstermek istersek:
+-------+---------+-----------------+
| guard | driver | model |
+-------+---------+-----------------+
| web | session | App\Models\User |
+-------+---------+-----------------+
Bunun açıklaması; web guard'ını kullandığımda authentication için session kullan ve Auth::user() ya da Request::user() çağırdığımda bana App\Models\User ver demek.
routes/web.php:
Route::get('/me', [UserController::class, 'me'])->middleware('auth');
app/Controllers/UserController.php:
public function me(Request $equest)
{
return $request->user();
// ya da
// return Auth::user();
}
Bu şekilde bir rota tanımlayıp, giriş yaptığınızda, bu rota ise giriş yapmış kullanıcıyı verir ama bu kullanıcıyı App\Models\User modelinden getirir, yani bir App\Models\User modeli alırsınız. Bunun sebebi middleware('auth') kısımında middleware('auth:api') gibi bir guard belirlememiş olmamız. Guard belirlemediğimiz için config/auth.php içerisindeki default guard'ı yani web'i aldı. web'in bize verdiği model de App\Models\User idi değil mi. Bu guard ve modelin eşleştiği yer ise \Illuminate\Auth\Middleware\Authenticate::authenticate() yöntemi:
protected function authenticate($request, array $guards)
{
if (empty($guards)) {
$guards = [null];
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
$this->unauthenticated($request, $guards);
}
return $this->auth->shouldUse($guard);
kısmında authentication'ın hangi guard'ı kullanacağı belirleniyor. Daha da derine inip shouldUse yöntemine bakarsanız \Illuminate\Auth\SessionGuard::user() içinde de kullanıcı getiriliyor.
---
Bu kadar ön bilgi yeter. Şimdi bu ön bilgiyi vermemin sebebine gelelim
Diyelim ki siz de kullanıcıları tabloya göre ayırma kararı aldınız. admins ve teachers şeklinde iki tablonuz var. Bunları da temsil eden Admin ve Teacher şeklinde iki modeliniz var (User diye bir modeliniz yok, onu sildiniz, sonuçta amaç böyle modellere ayırmak değil mi, öğretmenlerin modeli neden User olsun ki, mis gibi Teacher işte):
app/Models/Admin.php:
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
//
}
app/Models/Teacher.php:
use Illuminate\Foundation\Auth\User as Authenticatable;
class Teacher extends Authenticatable
{
//
}
Bunlar için config/auth.php dosyasını ayarladınız:
'guards' => [
'web-admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'web-teacher' => [
'driver' => 'session',
'provider' => 'teachers',
],
],
'providers' => [
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
'teachers' => [
'driver' => 'eloquent',
'model' => App\Models\Teacher::class,
],
],
Şimdi login olmaya geldi. Böyle ayırdığınız için kolay olsun diye admin ve teacher girişlerini de ayırdınız:
Route::get('admin/login', [AdminLoginController::class, 'showLogin']);
Route::post('admin/login', [AdminLoginController::class, 'login']);
Route::get('teacher/login', [TeacherLoginController::class, 'showLogin']);
Route::post('teacher/login', [TeacherLoginController::class, 'login']);
login yönteminde de artık Auth::attempt yerine Auth::guard()->attempt() kullanmanız gerekecek. Mesela:
AdminLoginController::login():
if (Auth::guard('web-admin')->attempt($credentials)) {
// ...
}
TeacherLoginController::login():
if (Auth::guard('web-teacher')->attempt($credentials)) {
// ...
}
Buraya kadar bir sıkıntı yok. Şimdi admin ve teacher rotalarımızı tanımlayalım:
// Sadece admin'in erişebileceği rotalar:
Route::middleware('auth:web-admin')->prefix('admin-dashboard')->group(function() {
// ...
});
// Sadece teacher'ın erişebileceği rotalar:
Route::middleware('auth:web-teacher')->prefix('teacher-dashboard')->group(function() {
// ...
});
Buraya kadar da çok güzel. auth:web-admin ile korunan bir rotada yer alan bir yapıda Auth::user() derseniz size Admin modelini verir; auth:web-teacher ile korunanda ise Teacher modelini alırsınız. Çok iyi, rol kontrol derdinden kurtuldunuz, kullanıcıları ayrı tabloda tutmayı başardınız, üstelik Admin ve Teacher şeklinde iki farklı modeliniz olduğu için artık bu modellere ayrı ayrı aynı isimde yöntemler de tanımlayabilirsiniz.
Dananın kuyruğunun koptuğu yer
Peki hem admin'in hem de teacher'ın girebildiği bir rota varsa (mesela students gibi), orada ne yapacaksınız?
Route::middleware('auth:web-admin,web-teacher')->group(function() {
Route::resource('students', StudentController::class);
});
Aynı rotadan hem admin hem de teacher için mi oluşturacaksınız? Bu genellikle istemediğimiz bir durum, aynı kodun tekrar etmemesi için güzel bir mimari kurmanız gerekiyor, servis bazlı çalışmanız gerekiyor yoksa bir çok yerde aynı koddan yer alacak.
Diyelim ki tek rota kullanmaya karar verdiniz, giriş yapan kullanıcı admin ise böyle, teacher ise şöyle şeklinde işlem yapacaksınız. Sistemde rol yok, kullanıcıyı nasıl ayıracaksınız?
public function index()
{
// Şimdi bu, middleware('auth:web-admin,web-teacher') ile korunan bir rotada
// Admin mi Teacher mı?
$user = Auth::user();
// Bu durumda mecburen şöyle yapmanız gerekiyor:
if($user instanceof Admin) {
// Kullanıcı admin...
}
if($user instanceof Teacher) {
// Kullanıcı teacher...
}
}
Uygulama geliştikçe bu tür yerler daha da karmaşık hale gelecek...
Sonuç
Bu ve bunun gibi durumlardan ötürü multi-guard kullanmak orta ve büyük ölçekli işlerde düzgün bir mimari ile ilerlemenizi gerektiriyor ve arkadaşların mimari bilgileri buna yetmediği için ileride büyük sıkıntılar yaşamamaları adına bu yolu ne yaptıklarını bilmiyorlarsa tavsiye etmiyorum. Bu olay sadece kullanıcı tiplerini ayrı tablolarda tutmak ile bitmiyor ne yazık ki.