You might also want to check out the [real-world Laravel example application](https://github.com/alexeymezenin/laravel-realworld-example-app)

## Konten

[Prinsip _single responsibility_](#prinsip-single-responsibility)

[Model tebal, _controller_ tipis](#model-tebal-controller-tipis)

[Validasi](#validasi)

[_Business logic_ harus di dalam kelas _services_](#business-logic-harus-di-dalam-kelas-services)

[_Don't repeat yourself_ (DRY)](#dont-repeat-yourself-dry)

[Lebih memilih menggunakan _Eloquent_ daripada menggunakan _Query Builder_ dan query SQL mentah. Lebih memilih _collections_ daripada _array_](#lebih-memilih-menggunakan-eloquent-daripada-menggunakan-query-builder-dan-query-sql-mentah-lebih-memilih-collections-daripada-array)

[_Mass assignment_](#mass-assignment)

[Jangan mengeksekusi kueri dalam _template blade_ dan gunakan _eager loading_ (masalah N + 1)](#jangan-mengeksekusi-kueri-dalam-template-blade-dan-gunakan-eager-loading-masalah-n-1)

[Komentari kode anda, tetapi lebih baik _method_ dan nama variabel yang deskriptif daripada komentar](#komentari-kode-anda-tetapi-lebih-baik-method-dan-nama-variabel-yang-deskriptif-daripada-komentar)

[Jangan letakkan JS dan CSS di _template blade_ dan jangan letakkan HTML apa pun di kelas PHP](#jangan-letakkan-js-dan-css-di-template-blade-dan-jangan-letakkan-html-apa-pun-di-kelas-php)

[Gunakan file _config_, _language_, dan konstanta daripada teks dalam kode](#gunakan-file-config-language-dan-konstanta-daripada-teks-dalam-kode)

[Gunakan _tools_ standar Laravel yang diterima oleh komunitas](#gunakan-tools-standar-laravel-yang-diterima-oleh-komunitas)

[Ikuti konvensi penamaan Laravel](#ikuti-konvensi-penamaan-laravel)

[Gunakan sintaks yang lebih pendek dan lebih mudah dibaca jika memungkinkan](#gunakan-sintaks-yang-lebih-pendek-dan-lebih-mudah-dibaca-jika-memungkinkan)

[Gunakan _IoC Container_ atau _facades_ daripada kelas baru](#gunakan-ioc-container-atau-facades-daripada-kelas-baru)

[Jangan mendapatkan data dari file `.env` secara langsung](#jangan-mendapatkan-data-dari-file-env-secara-langsung)

[Simpan tanggal dalam format standar. Gunakan _accessors_ dan _mutators_ untuk mengubah format tanggal](#simpan-tanggal-dalam-format-standar-gunakan-accessors-dan-mutators-untuk-mengubah-format-tanggal)

[Praktik bagus lainnya](#praktik-bagus-lainnya)

### **Prinsip _single responsibility_**

Kelas dan metode seharusnya hanya memiliki satu tanggung jawab.

Contoh buruk:

```php
use Illuminate\Database\Eloquent\Casts\Attribute;

public function fullName(): Attribute
{
    return Attribute::make(
        get: function () {
            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;
            }
        }
    );
}
```

Contoh terbaik:

```php
use Illuminate\Database\Eloquent\Casts\Attribute;

public function fullName(): Attribute
{
    return Attribute::make(
        get: fn () => $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort()
    );
}

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

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

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

[🔝 Kembali ke konten](#konten)

### **Model tebal, _controller_ tipis**

Masukkan semua logika terkait DB ke model _eloquent_ atau ke dalam kelas repositori jika anda menggunakan _Query Builder_ atau kueri SQL mentah.

Contoh buruk:

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

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

Contoh terbaik:

```php
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();
    }
}
```

[🔝 Kembali ke konten](#konten)

### **Validasi**

Pindahkan validasi dari _controller_ ke kelas _request_.

Contoh buruk:

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

    ...
}
```

Contoh terbaik:

```php
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',
        ];
    }
}
```

[🔝 Kembali ke konten](#konten)

### **_Business logic_ harus di dalam kelas _services_**

_Controller_ harus hanya memiliki satu tanggung jawab, jadi pindahkan _business logic_ dari _controller_ ke kelas _service_.

Contoh buruk:

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

    ...
}
```

Contoh terbaik:

```php
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');
        }
    }
}
```

[🔝 Kembali ke konten](#konten)

### **_Don't repeat yourself_ (DRY)**

Gunakan kembali kode ketika anda bisa. [PSR](#prinsip-single-responsibility) membantu anda menghindari duplikasi. Juga, gunakan kembali _template blade_, _scope eloquent_, dll.

Contoh buruk:

```php
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();
}
```

Contoh terbaik:

```php
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();
}
```

[🔝 Kembali ke konten](#konten)

### **Lebih memilih menggunakan _Eloquent_ daripada menggunakan _Query Builder_ dan query SQL mentah. Lebih memilih _collections_ daripada _array_**

_Eloquent_ memungkinkan anda menulis kode yang dapat dibaca dan _maintainable_. Dan, _Eloquent_ memiliki _built-in tools_ yang bagus seperti _soft deletes_, _events_, _scopes_, dll.

Contoh buruk:

```sql
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
```

Contoh terbaik:

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

[🔝 Kembali ke konten](#konten)

### **_Mass assignment_**

Contoh buruk:

```php
$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();
```

Contoh terbaik:

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

[🔝 Kembali ke konten](#konten)

### **Jangan mengeksekusi kueri dalam _template blade_ dan gunakan _eager loading_ (masalah N + 1)**

Contoh buruk (untuk 100 _user_, 101 kueri DB akan dieksekusi):

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

Contoh terbaik (untuk 100 _user_, 2 kueri DB akan dieksekusi):

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

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

[🔝 Kembali ke konten](#konten)

### **Komentari kode anda, tetapi lebih baik _method_ dan nama variabel yang deskriptif daripada komentar**

Contoh buruk:

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

Contoh lebih baik:

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

Contoh terbaik:

```php
if ($this->hasJoins())
```

[🔝 Kembali ke konten](#konten)

### **Jangan letakkan JS dan CSS di _template blade_ dan jangan letakkan HTML apa pun di kelas PHP**

Contoh buruk:

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

Contoh lebih baik:

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

Atau

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

Dalam file javascript:

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

Cara terbaik adalah dengan menggunakan _package_ `PHP to JS` khusus untuk mentransfer data.

[🔝 Kembali ke konten](#konten)

### **Gunakan file _config_, _language_, dan konstanta daripada teks dalam kode**

Contoh buruk:

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

return back()->with('message', 'Your article has been added!');
```

Contoh terbaik:

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

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

[🔝 Kembali ke konten](#konten)

### **Gunakan _tools_ standar Laravel yang diterima oleh komunitas**

Selalu gunakan fungsi _built-in_ bawaan laravel dan _packages_ komunitas daripada menggunakan _packages_ dan _tools_ pihak ke-3. _Developer_ manapun yang akan bekerja dengan aplikasi anda di masa mendatang perlu mempelajari _tools_ baru. Dan juga, peluang untuk mendapatkan bantuan dari komunitas Laravel jauh lebih rendah saat anda menggunakan _packages_ atau _tools_ pihak ke-3. Jangan membuat klien Anda membayar untuk itu.

| Task                      | Tools _standar_                        | _Tools_ pihak ke-3                                      |
| ------------------------- | -------------------------------------- | ------------------------------------------------------- |
| Authorization             | Policies                               | Entrust, Sentinel and other packages                    |
| Compiling assets          | Laravel Mix, Vite                      | Grunt, Gulp, 3rd party packages                         |
| Development Environment   | Laravel Sail, Homestead                | Docker                                                  |
| Deployment                | Laravel Forge                          | Deployer and other solutions                            |
| Unit testing              | PHPUnit, Mockery                       | Phpspec, Pest                                           |
| Browser testing           | Laravel Dusk                           | Codeception                                             |
| DB                        | Eloquent                               | SQL, Doctrine                                           |
| Templates                 | Blade                                  | Twig                                                    |
| Working with data         | Laravel collections                    | Arrays                                                  |
| Form validation           | Request classes                        | 3rd party packages, validation in controller            |
| Authentication            | Built-in                               | 3rd party packages, your own solution                   |
| API authentication        | Laravel Passport, Laravel Sanctum      | 3rd party JWT and OAuth packages                        |
| Creating API              | Built-in                               | Dingo API and similar packages                          |
| Working with DB structure | Migrations                             | Working with DB structure directly                      |
| Localization              | Built-in                               | 3rd party packages                                      |
| Realtime user interfaces  | Laravel Echo, Pusher                   | 3rd party packages and working with WebSockets directly |
| Generating testing data   | Seeder classes, Model Factories, Faker | Creating testing data manually                          |
| Task scheduling           | Laravel Task Scheduler                 | Scripts and 3rd party packages                          |
| DB                        | MySQL, PostgreSQL, SQLite, SQL Server  | MongoDB                                                 |

[🔝 Kembali ke konten](#konten)

### **Ikuti konvensi penamaan Laravel**

Ikuti [PSR standards](https://www.php-fig.org/psr/psr-12/).

Dan juga, ikuti konvensi penamaan yang diterima oleh komunitas Laravel:

| What                                                                  | How                                                                       | Good                                    | Bad                                                             |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------- |
| Controller                                                            | singular                                                                  | ArticleController                       | ~~ArticlesController~~                                          |
| Route                                                                 | plural                                                                    | articles/1                              | ~~article/1~~                                                   |
| Route name                                                            | snake_case with dot notation                                              | users.show_active                       | ~~users.show-active, show-active-users~~                        |
| Model                                                                 | singular                                                                  | User                                    | ~~Users~~                                                       |
| hasOne or belongsTo relationship                                      | singular                                                                  | articleComment                          | ~~articleComments, article_comment~~                            |
| All other relationships                                               | plural                                                                    | articleComments                         | ~~articleComment, article_comments~~                            |
| Table                                                                 | plural                                                                    | article_comments                        | ~~article_comment, articleComments~~                            |
| Pivot table                                                           | singular model names in alphabetical order                                | article_user                            | ~~user_article, articles_users~~                                |
| Table column                                                          | snake_case without model name                                             | meta_title                              | ~~MetaTitle; article_meta_title~~                               |
| Model property                                                        | snake_case                                                                | $model->created_at                      | ~~$model->createdAt~~                                           |
| Foreign key                                                           | singular model name with \_id suffix                                      | article_id                              | ~~ArticleId, id_article, articles_id~~                          |
| Primary key                                                           | -                                                                         | id                                      | ~~custom_id~~                                                   |
| Migration                                                             | -                                                                         | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~                                  |
| Method                                                                | camelCase                                                                 | getAll                                  | ~~get_all~~                                                     |
| Method in resource controller                                         | [table](https://laravel.com/docs/master/controllers#resource-controllers) | store                                   | ~~saveArticle~~                                                 |
| Method in test class                                                  | camelCase                                                                 | testGuestCannotSeeArticle               | ~~test_guest_cannot_see_article~~                               |
| Variable                                                              | camelCase                                                                 | $articlesWithAuthor                     | ~~$articles_with_author~~                                       |
| Collection                                                            | descriptive, plural                                                       | $activeUsers = User::active()->get()    | ~~$active, $data~~                                              |
| Object                                                                | descriptive, singular                                                     | $activeUser = User::active()->first()   | ~~$users, $obj~~                                                |
| Config and language files index                                       | snake_case                                                                | articles_enabled                        | ~~ArticlesEnabled; articles-enabled~~                           |
| View                                                                  | kebab-case                                                                | show-filtered.blade.php                 | ~~showFiltered.blade.php, show_filtered.blade.php~~             |
| Config                                                                | snake_case                                                                | google_calendar.php                     | ~~googleCalendar.php, google-calendar.php~~                     |
| Contract (interface)                                                  | adjective or noun                                                         | AuthenticationInterface                 | ~~Authenticatable, IAuthentication~~                            |
| Trait                                                                 | adjective                                                                 | Notifiable                              | ~~NotificationTrait~~                                           |
| Trait [(PSR)](https://www.php-fig.org/bylaws/psr-naming-conventions/) | adjective                                                                 | NotifiableTrait                         | ~~Notification~~                                                |
| Enum                                                                  | singular                                                                  | UserType                                | ~~UserTypes~~, ~~UserTypeEnum~~                                 |
| FormRequest                                                           | singular                                                                  | UpdateUserRequest                       | ~~UpdateUserFormRequest~~, ~~UserFormRequest~~, ~~UserRequest~~ |
| Seeder                                                                | singular                                                                  | UserSeeder                              | ~~UsersSeeder~~                                                 |

[🔝 Kembali ke konten](#konten)

### **Gunakan sintaks yang lebih pendek dan lebih mudah dibaca jika memungkinkan**

Contoh buruk:

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

Contoh terbaik:

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

Contoh:

| Sintaks umum                                                           | Sintaks pendek dan mudah dibaca                    |
| ---------------------------------------------------------------------- | -------------------------------------------------- |
| `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->id`            | `optional($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')`                                  |

[🔝 Kembali ke konten](#konten)

### **Gunakan _IoC Container_ atau _facades_ daripada kelas baru**

Sintaks Kelas baru membuat penggabungan yang sempit antar kelas dan memperumit proses pengujian. Gunakan _facades_ atau _IoC container_ sebagai gantinya.

Contoh buruk:

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

Contoh terbaik:

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

...

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

[🔝 Kembali ke konten](#konten)

### **Jangan mendapatkan data dari file `.env` secara langsung**

Alihkan data ke file konfigurasi sebagai gantinya dan kemudian gunakan fungsi `config ()` untuk menggunakan data dalam aplikasi.

Contoh buruk:

```php
$apiKey = env('API_KEY');
```

Contoh terbaik:

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

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

[🔝 Kembali ke konten](#konten)

### **Simpan tanggal dalam format standar. Gunakan _accessors_ dan _mutators_ untuk mengubah format tanggal**

Contoh buruk:

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

Contoh terbaik:

```php
// Model
protected $casts = [
    'ordered_at' => 'datetime',
];

public function getSomeDateAttribute($date)
{
    return $date->format('m-d');
}

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

[🔝 Kembali ke konten](#konten)

### **Praktik bagus lainnya**

Jangan pernah menaruh logika apa pun di file `route`.

Minimalkan penggunaan _vanilla_ PHP di _template blade_.

[🔝 Kembali ke konten](#konten)
