Laravel ile En Pratik Uygulamalar

in #laravel5 years ago

Uzun zamandır PHP ve Laravel ile çalışan tanıdığım herkese, sıkca paylaştığım bir GitHub repositorysi vardı. Bunun Türkçe versiyonun da olması gerektiğini düşünerek, anlaşılır bir dilde çevirmek istedim. Orjinal metinden gelen yabancı terminolojiyi, lütfen "Plaza Türkçesi" olarak değil de teknik dil olarak görünüz :)

Orjinal repository (İngilizce): https://github.com/alexeymezenin/laravel-best-practices


Bu metin Laravel için SOLID prensipleri, patternler vb. şeylerin uygulaması değildir. Burada, Laravel projelerinde
geliştiriciler tarafından dikkate alınmayan iyi ve kötü pratiklerin karşılaştırmalarını bulacaksınız.

*Çevirmenin Notu #1: Wikipedia'da Türkçe başlığı olmaması nedeniyle SOLID prensipleri hakkında açıklayıcı bir Ekşi Sözlük entrysi

İçerik

  • Single responsibility principle (Tek sorumluluk prensibi)
  • Büyük modeller, çirkin controllerlar
  • Validation (Veri Doğrulama)
  • Business logic servis class'ında bulunmalıdır
  • Kendini tekrar etme (DRY: Don't repeat yourself)
  • Query Builder ve düz queryler kullanmak yerine Eloquent, array kullanmak yerine Collection kullanın
  • Mass assignment (Toplu atama)
  • Blade templatelerinde asla query çalıştırmayın, eager loading kullanın (N + 1 problemi)
  • Koda yorum yazın ancak öncelikli olarak anlamlı method ve değişken isimleri seçin
  • Blade içinde JS ve CSS kullanmayın ve PHP classlarına HTML yazmayın
  • Config ve language dosyalarını kullanın, kod içinde ise metin kullanmak yerine constant kullanın
  • Laravel topluluğu tarafından kabul edilen standart araçları kullanın
  • Laravel'de isimlendirme
  • Mümkün olduğunca daha kısa ve okunabilir syntax kullanın
  • new Class kullanımı yerine IoC container ya da facade kullanın
  • .env dosyasından doğrudan veri çekmeyin
  • Tarihleri standart formatta kaydedin. Tarihleri formatlamak için accessor ve mutator kullanın
  • Diğer iyi pratikler

Single responsibility principle (Tek sorumluluk prensibi)

Bir class ya da method'un tek bir görevi ve amacı olmalıdır.

Kötü:

public function getFullNameAttribute()
{
    if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
        return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
    } else {
        return $this->first_name[0] . '. ' . $this->last_name;
    }
}

İyi:

public function getFullNameAttribute()
{
    return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}

public function isVerifiedClient()
{
    return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}

public function getFullNameLong()
{
    return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}

public function getFullNameShort()
{
    return $this->first_name[0] . '. ' . $this->last_name;
}

Büyük modeller, çirkin controllerlar

Bütün database ile ilişkili Query Builder ve raw SQL işlemlerini Eloquent modelinde ya da Repository class'ında tanımlayarak kullanın.

Kötü:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}

İyi:

public function index()
{
    return view('index', ['clients' => $this->client->getWithNewOrders()]);
}

class Client extends Model
{
    public function getWithNewOrders()
    {
        return $this->verified()
            ->with(['orders' => function ($q) {
                $q->where('created_at', '>', Carbon::today()->subWeek());
            }])
            ->get();
    }
}

Veri Doğrulama, Validasyon

Validation işlemlerini controller içinde değil, Request classları içinde yapın.

Kötü:

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ....
}

İyi:

public function store(PostRequest $request)
{    
    ....
}

class PostRequest extends Request
{
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
            'publish_at' => 'nullable|date',
        ];
    }
}

Business logic servis class'ında bulunmalıdır

Bir controller sadece bir görevden sorumlu olmalıdır. Bu nedenle business logic, controllerlar yerine servis classında tanımlanmalıdır.

Kötü:

public function store(Request $request)
{
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images') . 'temp');
    }
    
    ....
}

İyi:

public function store(Request $request)
{
    $this->articleService->handleUploadedImage($request->file('image'));

    ....
}

class ArticleService
{
    public function handleUploadedImage($image)
    {
        if (!is_null($image)) {
            $image->move(public_path('images') . 'temp');
        }
    }
}

Kendini tekrar etme (DRY: Don't repeat yourself)

Kullanabildiğiniz sürece kodu tekrar kullanın. SRP (single responsibility principle - tek sorumluluk ilkesi) size kod
tekrarını azaltmanıza da katkı sunar. Blade templatelerini, Eloqunt scopelarını tekrar kullanın.

Kötü:

public function getActive()
{
    return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->where('verified', 1)->whereNotNull('deleted_at');
        })->get();
}

İyi:

public function scopeActive($q)
{
    return $q->where('verified', 1)->whereNotNull('deleted_at');
}

public function getActive()
{
    return $this->active()->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->active();
        })->get();
}

Query Builder ve düz queryler kullanmak yerine Eloquent, array kullanmak yerine Collection kullanın

Eloquent okunabilir ve sürdürülebilir kod yazmanıza izin verir. Ayrıca, Eloquent güzel dahili araçlara sahiptir. Örnek olarak;
soft delete, event ve scope özellikleri verilebilir.

Kötü:

SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
              FROM `users`
              WHERE `articles`.`user_id` = `users`.`id`
              AND EXISTS (SELECT *
                          FROM `profiles`
                          WHERE `profiles`.`user_id` = `users`.`id`) 
              AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC

İyi:

Article::has('user.profile')->verified()->latest()->get();

Mass assignment (Toplu atama)

Kötü:

$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Add category to article
$article->category_id = $category->id;
$article->save();

İyi:

$category->article()->create($request->validated());

Blade templatelerinde asla query çalıştırmayın, eager loading kullanın (N + 1 problemi)

Kötü (100 kullanıcı için, 101 DB tane query çalıştırılacak):

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

İyi (100 kullanıcı için, 2 DB tane query çalıştırılacak):

$users = User::with('profile')->get();

...

@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach

Koda yorum yazın ancak öncelikli olarak anlamlı method ve değişken isimleri seçin

Kötü:

if (count((array) $builder->getQuery()->joins) > 0)

Görece İyi:

// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)

İyi:

if ($this->hasJoins())

Blade içinde JS ve CSS kullanmayın ve PHP classlarına HTML yazmayın

Kötü:

let article = `{{ json_encode($article) }}`;

Daha İyi:

<input id="article" type="hidden" value="@json($article)">

Ya da

<button class="js-fav-article" data-article="@json($article)">{{ $article->name }}<button>

Javascript dosyasında:

let article = $('#article').val();

Data transferi için en iyi yol amaca özel programlanmış PHP'den JS'ye veri aktaran paketleri kullanmaktır.

Config ve language dosyalarını kullanın, kod içinde ise metin kullanmak yerine constant kullanın

Kötü:

public function isNormal()
{
    return $article->type === 'normal';
}

return back()->with('message', 'Makaleniz eklendi!');

İyi:

public function isNormal()
{
    return $article->type === Article::TYPE_NORMAL;
}

return back()->with('message', __('app.article_added'));

Laravel topluluğu tarafından kabul edilen standart araçları kullanın

Geniş topluluk desteği olmayan paketler yerine Laravel'e dahili gelen ya da topluluk tarafından kabul edilmiş araç ve paketleri kullanın.
Bu şekilde koda dahil olan herkesin rahatlıkla projeye katkı sunmasını sağlayabilirsiniz. Ayrıca topluluk desteği düşük olan
paketleri ya da araçları kullandığınızda yardım alabilme ihtimaliniz önemli derecede azalmaktadır. Bu paketleri tekrar
hazırlamak yerine, tercih edilen paketleri kullanın ve bu maliyetlerden kaçının.

Çevirmen Notu #2: Tercihen en çok forklanmış, en çok yıldızlanmış, en çok takip edilen repositorylerden faydalanabilirsiniz. Koda yapılan son commit tarihi ve issue kısmında yanıtlanan soru ve cevap dökümleri paketin güncelliği ve ne kadar güncel kalacağı ile ilgili size bilgi sunar.

YapılacakStandart araç3rd party araçlar
Authorization (Yetkilendirme)PoliciesEntrust, Sentinel vb.
Compiling assets (CSS ve JS Derleme)Laravel MixGrunt, Gulp, 3rd party paketler
Geliştirme OrtamıHomesteadDocker
DeploymentLaravel ForgeDeployer and other solutions
Unit testingPHPUnit, MockeryPhpspec
Browser testingLaravel DuskCodeception
DBEloquentSQL, Doctrine
TemplateBladeTwig
Veri işlemeLaravel collectionsArrays
Form doğrulamaRequest classları3rd party paketler, controllerda doğrulama
Authentication (Doğrulama)Dahili3rd party paketler ya da kendi çözümünüz
API authentication (Doğrulama)Laravel Passport3rd party JWT and OAuth packetleri
API OluşturmaDahiliDingo API vb.
DB YapısıMigrationsDoğrudan DB yönetimi
Lokalizasyon (Yerelleştirme)Dahili3rd party paketler
Gerçek zamanlı kullanıcı etkileşimiLaravel Echo, PusherDoğrudan WebSocket kullanan 3rd party paketler
Test verisi oluşturmakSeeder classları, Model Factoryleri, FakerOluşturup manuel test etmek
Görev ZamanlamaLaravel Task SchedulerScriptler ve 3rd party paketler
DBMySQL, PostgreSQL, SQLite, SQL ServerMongoDB

Laravel'de isimlendirme

PSR standards takip edin.

Ayrıca, topluluk tarafından kabul gören isimlendirmeler:

NeNasılİyiKötü
ControllertekilArticleControllerArticlesController
Routeçoğularticles/1article/1
Named routesnake_case ve dot notation (nokta kullanımı)users.show_activeusers.show-active, show-active-users
ModeltekilUserUsers
hasOne or belongsTo relationshiptekilarticleCommentarticleComments, article_comment
All other relationshipsçoğularticleCommentsarticleComment, article_comments
Tableçoğularticle_commentsarticle_comment, articleComments
Pivot tabletekil model isimleri alfabetik sıradaarticle_useruser_article, articles_users
Table columnsnake_case ve model adı olmadanmeta_titleMetaTitle; article_meta_title
Model propertysnake_case$model->created_at$model->createdAt
Foreign keytekil model adı ve _id suffix'i (soneki)article_idArticleId, id_article, articles_id
Primary key-idcustom_id
Migration-2017_01_01_000000_create_articles_table2017_01_01_000000_articles
MethodcamelCasegetAllget_all
Method in resource controllertablestoresaveArticle
Method in test classcamelCasetestGuestCannotSeeArticletest_guest_cannot_see_article
VariablecamelCase$articlesWithAuthor$articles_with_author
Collectiontanımlayıcı, çoğul$activeUsers = User::active()->get()$active, $data
Objecttanımlayıcı, tekil$activeUser = User::active()->first()$users, $obj
Config and language files indexsnake_casearticles_enabledArticlesEnabled; articles-enabled
Viewsnake_caseshow_filtered.blade.phpshowFiltered.blade.php, show-filtered.blade.php
Configsnake_casegoogle_calendar.phpgoogleCalendar.php, google-calendar.php
Contract (interface)sıfat ya da isimAuthenticatableAuthenticationInterface, IAuthentication
TraitsıfatNotifiableNotificationTrait

Mümkün olduğunca daha kısa ve okunabilir syntax kullanın

Kötü:

$request->session()->get('cart');
$request->input('name');

İyi:

session('cart');
$request->name;

Daha çok örnek:

Ortak syntaxKısa ve daha okunabilir syntax
Session::get('cart')session('cart')
$request->session()->get('cart')session('cart')
Session::put('cart', $data)session(['cart' => $data])
$request->input('name'), Request::get('name')$request->name, request('name')
return Redirect::back()return back()
is_null($object->relation) ? null : $object->relation->idoptional($object->relation)->id
return view('index')->with('title', $title)->with('client', $client)return view('index', compact('title', 'client'))
$request->has('value') ? $request->value : 'default';$request->get('value', 'default')
Carbon::now(), Carbon::today()now(), today()
App::make('Class')app('Class')
->where('column', '=', 1)->where('column', 1)
->orderBy('created_at', 'desc')->latest()
->orderBy('age', 'desc')->latest('age')
->orderBy('created_at', 'asc')->oldest()
->select('id', 'name')->get()->get(['id', 'name'])
->first()->name->value('name')

new Class kullanımı yerine IoC container ya da facade kullanın

new Class kullanımı classlar arası bağlantıları doğrudan kurar ve test sürecini karmaşıklaştırır. IoC container ya da facade kullanın.

Kötü:

$user = new User;
$user->create($request->validated());

İyi:

public function __construct(User $user)
{
    $this->user = $user;
}

....

$this->user->create($request->validated());

.env dosyasından doğrudan veri çekmeyin

Veriyi config dosyasında çağırın ve config() helper fonksiyonunu kullanarak uygulama içinde erişin.

Kötü:

$apiKey = env('API_KEY');

İyi:

// config/api.php
'key' => env('API_KEY'),

// Use the data
$apiKey = config('api.key');

Tarihleri standart formatta kaydedin. Tarihleri formatlamak için accessor ve mutator kullanın

Kötü:

{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}

İyi:

// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at'];
public function getSomeDateAttribute($date)
{
    return $date->format('m-d');
}

// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}

Diğer iyi pratikler

Route dosyalarına asla logic yazmayın.

Blade template dosyalarında vanilya PHP (düz PHP) kullanmayın.

Sort:  

Congratulations @ikidnapmyself! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Do not miss the last post from @steemitboard:

SteemFest Meet The Stemians Contest - The mysterious rule revealed
Vote for @Steemitboard as a witness to get one more award and increased upvotes!