Laravel Türkiye Discord Kanalı Forumda kod paylaşılırken dikkat edilmesi gerekenler!Birlikte proje geliştirmek ister misiniz?

Merhabalar arkadaşlar;

Laravel ile birçok proje geliştiriyoruz ve birçok projeyi inceleme şansımız oluyor.

1-) Crud işlemlerine baktığımızda çesitli şekillerde yapıldığını görüyoruz.

Örneğin;
Kimi direk Request $request kullanırken kimi BookStoreRequest $request şeklinde kullanıyor.

Kimi direk store metodu içinde işlemlerini yapıyor kimi ayrı bir save methodu yazıp store methodu içine çağırarak kullanıyor.

Kimi controllerda bütün işlemleri hallediyor kimi de Service kullanıp işlemleri service tarafinda hallediyor. Veya Dependncy Injection kullanıp Controller a ekstra yük bindirmiyor.

Bu örnekleri çoğaltabiliriz.

Profesyonel bi iş yaparken mesela kurumunuz için CRM yazarken kullandığınız veya olması gereken CRUD yapısı nasıl olmalı ki iş amatör olmasın(görünmesin).

Tabiki bi standardı yok ama bu şekilde olsa çok daha profesyonel olur dediğiniz yapı nasıl olmalı.

2-) Helper ile Service arasındaki fark nedir? Bir fonksiyonu helper veya service olarak ayıran mantık nedir? Helper hangi amaçla kullanılmalı? Service hangi amaçla kullanılmalı?

Örnekli bi açıklama olursa en azından yeni başlayanlarada bi kaynak olmuş olur.

Şimdiden herkese çok teşekkür ederim.
Saygılarımla.

  • mgsmus bunu yanıtladı.
  • 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.

    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.

    hakanylmz Ayrıca tek seferde bu kadar soruyu yanıtlamak insanı yoruyor. Ayrı ayrı konular açarak bölmek daha uygun olur. Hem yanıtlaması hem de takibi açısından. Şimdi bu konu altında bir tartışma olsa konu çorbaya döner çünkü her bir soru başlı başına ayrı bir bölüm. O yüzden forumda bu şekilde toplu değil de ayrı ayrı konular açılırsa hepimizin yararına olur.

      mgsmus Mustafa bence soruya hiç gerek bırakmamışsın. Eline sağlık.