hakanylmz
Controller Kullanımı
https://laravel.com/docs/8.x/controllers
CRUD (Create Read Update Delete) işlemi için hem klasik yaklaşım hem de api için Laravel bize iki tane yöntem sunuyor. Birisi resource controller diğeri ise api resource controller. Düz resource controller içerisinde kaydı oluşturma ve düzenleme ekranı gösterebilmeniz için iki adet fazladan yöntem bulunuyor (create ve edit yöntemleri). Bu yapı hem klasik uygulamalara hem de RESTFul api'lara uygun. O yüzden bu yapının dışına çıkmadan Laravel ile geleni kullanmak mantıklı olandır. Kullanımı şu şekilde:
Route::resource('users', UserController::class);
// ya da sadece api ise
Route::apiResource('users', UserController::class);
Bunların içindeki yöntemleri değiştirmeye gerek yok. Açıkçası kendi yöntemlerini yazan arkadaşlar sadece kendilerine ek iş çıkartıyorlar. Bazı durumlarda ekstra yöntemler de eklemeniz gerekebiliyor ama bunu ayrı bir controller içinde yapmanız daha doğru olur. Mesela siparişlerle ilgili işlem yapıyor olalım ve sipariş durumunu değiştirmek için yönteme ihtiyacımız var. Mevcut bir OrderController var ama bu tamamen Order ile işlem yapmak için. İçerisine durumu güncellemek için updateStatus() şeklinde ayrı bir yöntem eklemek yerine OrderStatusController şeklinde ayrı bir controller oluşturun:
Route::resource('orders', OrderController::class);
Route::post('order-status', [OrderStatusController::class, 'update']);
Eğer bir controller sadece bir iş yapıyorsa invokable dediğimiz controller kullanın:
Route::post('order-status', OrderStatusController::class);
app/Controllers/OrderStatusController.php:
public function __invoke(Request $request)
{
// Sipariş durumu güncelleme işlemleri...
}
Form Request Validation
https://laravel.com/docs/8.x/validation
Klasik satır-içi validasyon kullanmak yerine form request validation kullanın. Bu şekilde validasyon mantığını controller dışına alarak controller'ı temiz tutmuş olursunuz. Ayrıca kuralları yazarken çok kompleks validasyon kuralları yazmak yerine kendi kurallarınızı oluşturun.
Validasyon sadece HTTP katmanında kullanabileceğiniz bir yapı değil. Bunu internal işlerde de kullanabilirsiniz. Bu gibi yerlerde ise satır-içi validasyon kullanın ve başarılı olup olmadığını kendiniz kontrol ederek otomatik yönlendirme yapılmasına engel olun:
$data = [
'starts_at' => $someObject->starts_at,
'ends_at' => $someObject->ends_at,
'status' => $someArray['order_status'],
];
$validator = Validator::make($data, [
'starts_at' => 'required|date_format:Y-m-d H:i:s',
'ends_at' => 'required|date_format:Y-m-d H:i:s|after:starts_at',
'status' => ['required', new ValidOrderStatus]
]);
if ($validator->fails()) {
throw new InvalidFilterException($validator->errors());
}
$validated = $validator->validated();
Dependency Injection
https://laravel.com/docs/8.x/container
DI kullanmanın birden fazla amacı var. Birincisi objenin otomatik yüklenip otomatik çözümlenmesi. Controller içerisinde
public function getData()
{
$params = [...];
$someService = new SomeService($params);
$otherService = new OtherService($someService);
$data = $otherService->getData();
//...
}
yapmak yerine sadece şunu yapmanıza olanak sağlamış oluyor:
public function getData(OtherService $otherService)
{
$data = $someService->getData();
//...
}
Diğer bir avantajı ise Inversion of Control ilkesini sağlaması. Interface kullanarak DI yaparsanız sadece bir noktadan yazdığınız kodları ellemeden altyapı değiştirebilirsiniz ya da bunu contextual binding ile belli şartlara göre yapabilirsiniz:
$this->app->bind(OtherService::class, function ($app) {
$params = [...];
$someService = new SomeService($params);
return new OtherService($someService);
});
$this->app->bind(AnotherService::class, function ($app) {
$params = [...];
$someService = new SomeService($params);
return new AnotherService($someService);
});
$this->app->bind(OtherService::class, ServiceInterface::class);
public function getData(ServiceInterface $service)
{
$data = $service->getData();
//...
}
Bu şekilde yaptığınızda eğer
$this->app->bind(OtherService::class, ServiceInterface::class);
yerine
$this->app->bind(AnotherService::class, ServiceInterface::class);
yaparsanız aşağıda herhangi bir değişiklik yapmadan OtherService::getData() yerine AnotherService::getData() yönteminin kullanılmasını sağlarsınız (Buna genel olarak IoC yani Inversion of Control denir. Ameleliği framework'e yaptırmak gibi de düşünebilirsiniz.)
Repository Pattern
Bazı sorgu işlemleri kompleks olabilir ve bunlar uygulama içerisinde tekrar tekrar kullanmanız gerekebilir. Bu gibi durumlarda ilgili kaynak için bir repository oluşturabilir ve bunu her yerde bir servis gibi kullanabilirsiniz. Benim tavsiyem interface binding kullanmanız, böylece üstte anlattığım IoC ilkesini de sağlamış olursunuz:
interface UserRepositoryInterface
{
public function someComplexQuery(
User $user,
CarbonInterface $startsAt,
CarbonInterface $endsAt
): Collection
}
class UserRepository implements UserRepositoryInterface
{
public function someComplexQuery(
User $user,
CarbonInterface $startsAt,
CarbonInterface $endsAt
): Collection
{
$results = $user->...
return $results:
}
}
$this->app->bind(UserRepository::class, UserRepositoryInterface::class);
class SomeService
{
public function __construct(
public UserRepositoryInterface $userRepository
)
{}
public function getData(int $userId)
{
$user = User::findOrFail($userId);
$endsAt = CarbonImmutable::now();
$startsAt = $endsAt->subMonth();
$data = $userRepository->someComplexQuery($user, $startsAt, $endsAt);
//...
}
}
Repository pattern'in tek amacı bu değil elbette, kendiniz araştırabilirsiniz:
https://designpatternsphp.readthedocs.io/en/latest/README.html
Helper vs Service
Helper ile servis arasındaki farkı bir benzetme ile açıklamak gerekirse; helper, kırmızı ışıkta bekleyen 10 kişiden oluşan bir insan kalabalığı, servis ise 10 kişilik bir takımdır. Helper yöntemler birbirlerini bilmez, farklı amaçlara hizmet edebilirler, bağımsızdırlar, aralarında tek ortak nokta helper olmalarıdır. Amaçları sadece belli işlemleri daha kısa yazıp kompakt olarak kullanabilmek ve kod tekrarını engellemektir, amaçları kendileridir, kendi çıkarlarıdır. Servis ise ortak bir amaca hizmet eden hizmetler bütünüdür. İçindeki her bir yöntem ortak bir amaç için vardır, ortak bir bağlam ile yazılmışlardır, amaçları kendileri değil servistir. Sistematiktir ve bir bütündür. Bu durumda bir dizinin eleman sayısını veren yöntem bir helper olabilir, verilen cümlenin baş harflerini büyütüp veren bir yöntem helper olabilir ama sistemdeki adminleri veren bir yöntem, en çok satılan ürünleri veren bir yöntem helper olamaz çünkü bunların çok daha fazla gereksinimi vardır ve değişkendirler, ortak bir bilincin altındadırlar, bir bağlamları vardır. Bir servis içinde yöntemler birbirlerini çağırabilirler ama bir helper başka bir helper'ı çağırmamalıdır. Daha başka sebepler de sayılabilir ama genel olarak bu.