diff --git a/guide/id/concept/configuration.md b/guide/id/concept/configuration.md new file mode 100644 index 00000000..1420996c --- /dev/null +++ b/guide/id/concept/configuration.md @@ -0,0 +1,344 @@ +# Konfigurasi + +Ada banyak cara untuk mengonfigurasi aplikasi Anda. Kita akan fokus pada konsep yang digunakan dalam +[template proyek default](https://github.com/yiisoft/app). + +Konfigurasi Yii3 adalah bagian dari aplikasi. Anda dapat mengubah banyak aspek cara kerja aplikasi dengan mengedit +konfigurasi di dalam `config/`. + +## Plugin konfigurasi + +Dalam template aplikasi digunakan [yiisoft/config](https://github.com/yiisoft/config). Karena menulis seluruh konfigurasi +aplikasi dari awal adalah proses yang melelahkan, banyak paket menawarkan konfigurasi default, dan plugin membantu +menyalin konfigurasi-konfigurasi tersebut ke dalam aplikasi. + +Untuk menawarkan konfigurasi default, `composer.json` dari paket harus memiliki bagian `config-plugin`. +Saat memasang atau memperbarui paket dengan Composer, plugin membaca bagian `config-plugin` untuk setiap dependensi, +menyalin berkas-berkas tersebut ke `config/packages/` aplikasi jika belum ada dan menulis rencana penggabungan ke +`config/packages/merge_plan.php`. Rencana penggabungan mendefinisikan bagaimana menggabungkan konfigurasi-konfigurasi menjadi +satu array besar yang siap diteruskan ke [DI container](di-container.md). + +Lihat apa yang ada di `composer.json` "yiisoft/app" secara default: + +```json +"config-plugin-options": { + "output-directory": "config/packages" +}, +"config-plugin": { + "common": "config/common/*.php", + "params": [ + "config/params.php", + "?config/params-local.php" + ], + "web": [ + "$common", + "config/web/*.php" + ], + "console": [ + "$common", + "config/console/*.php" + ], + "events": "config/events.php", + "events-web": [ + "$events", + "config/events-web.php" + ], + "events-console": [ + "$events", + "config/events-console.php" + ], + "providers": "config/providers.php", + "providers-web": [ + "$providers", + "config/providers-web.php" + ], + "providers-console": [ + "$providers", + "config/providers-console.php" + ], + "routes": "config/routes.php" +}, +``` + +Terdapat banyak konfigurasi bernama. Untuk setiap nama ada sebuah konfigurasi. + +String berarti plugin mengambil konfigurasi apa adanya dan menggabungkannya dengan konfigurasi bernama sama dari paket yang Anda butuhkan. +Hal ini terjadi jika paket-paket tersebut memiliki `config-plugin` di `composer.json` mereka. + +Array berarti plugin akan menggabungkan banyak berkas sesuai urutan yang ditentukan. + +`?` di awal jalur berkas menunjukkan bahwa berkas tersebut mungkin tidak ada. Dalam kasus ini, berkas dilewati. + +`$` di awal nama berarti referensi ke konfigurasi bernama lain. + +`params` agak istimewa karena dicadangkan untuk parameter aplikasi. Ini otomatis tersedia sebagai `$params` di semua berkas konfigurasi lainnya. + +Anda dapat mempelajari lebih lanjut tentang fitur plugin konfigurasi [dari dokumentasinya](https://github.com/yiisoft/config/blob/master/README.md). + +## Berkas konfigurasi + +Sekarang, setelah Anda tahu bagaimana plugin merakit konfigurasi, lihat direktori `config`: + +``` +common/ + application-parameters.php + i18n.php + router.php +console/ +packages/ + yiisoft/ + dist.lock + merge_plan.php +web/ + application.php + psr17.php +events.php +events-console.php +events-web.php +params.php +providers.php +providers-console.php +providers-web.php +routes.php +``` + +### Konfigurasi container + +Aplikasi terdiri dari sekumpulan layanan yang terdaftar di dalam [dependency container](di-container.md). Berkas-berkas konfigurasi +yang bertanggung jawab untuk konfigurasi langsung dependency container berada di bawah direktori `common/`, `console/` dan `web/`. +Kita menggunakan `web/` untuk konfigurasi spesifik aplikasi web dan `console/` untuk konfigurasi spesifik perintah konsol. Baik web maupun +console berbagi konfigurasi di bawah `common/`. + +```php + [ + 'class' => ApplicationParameters::class, + 'charset()' => [$params['app']['charset']], + 'name()' => [$params['app']['name']], + ], +]; +``` + +Plugin konfigurasi meneruskan variabel khusus `$params` ke semua berkas konfigurasi. +Kode di atas meneruskan nilainya ke layanan. + +Panduan ["Dependency injection and container"](di-container.md) menjelaskan +format konfigurasi dan gagasan dependency injection secara rinci. + +Untuk kenyamanan, ada konvensi penamaan untuk kunci string kustom: + +1. Awali dengan nama paket seperti `yiisoft/cache-file/custom-definition`. +2. Jika konfigurasi untuk aplikasi itu sendiri, lewati prefix paket, misalnya `custom-definition`. + +### Service provider + +Sebagai alternatif mendaftarkan dependensi secara langsung, Anda dapat menggunakan service provider. Pada dasarnya, ini adalah kelas-kelas yang +mengonfigurasi dan mendaftarkan layanan di dalam container berdasarkan parameter yang diberikan. Mirip dengan tiga berkas konfigurasi +dependensi yang dijelaskan sebelumnya, ada tiga konfigurasi untuk menentukan service provider: `providers-console.php` untuk perintah konsol, +`providers-web.php` untuk aplikasi web dan `providers.php` untuk keduanya: + +```php +/* @var array $params */ + +// ... +use App\Provider\CacheProvider; +use App\Provider\MiddlewareProvider; +// ... + +return [ + // ... + 'yiisoft/yii-web/middleware' => MiddlewareProvider::class, + 'yiisoft/cache/cache' => [ + 'class' => CacheProvider::class, + '__construct()' => [ + $params['yiisoft/cache-file']['file-cache']['path'], + ], + ], + // ... +``` + +Dalam konfigurasi ini kunci adalah nama provider. Menurut konvensi, ini berupa `vendor/package-name/provider-name`. Nilai adalah nama kelas provider. +Kelas-kelas ini bisa dibuat dalam proyek itu sendiri atau disediakan oleh paket. + +Jika Anda perlu mengonfigurasi beberapa opsi untuk sebuah layanan, mirip dengan konfigurasi container langsung, ambil nilai +dari `$params` dan teruskan ke provider. + +Provider harus mengimplementasikan satu metode, `public function register(Container $container): void`. Dalam metode ini Anda +perlu menambahkan layanan ke container menggunakan metode `set()`. Di bawah ini adalah contoh provider untuk layanan cache: + +```php +use Psr\Container\ContainerInterface; +use Psr\SimpleCache\CacheInterface; +use Yiisoft\Aliases\Aliases; +use Yiisoft\Cache\Cache; +use Yiisoft\Cache\CacheInterface as YiiCacheInterface; +use Yiisoft\Cache\File\FileCache; +use Yiisoft\Di\Container; +use Yiisoft\Di\Support\ServiceProvider; + +final readonly class CacheProvider extends ServiceProvider +{ + public function __construct( + private string $cachePath = '@runtime/cache' + ) + { + $this->cachePath = $cachePath; + } + + public function register(Container $container): void + { + $container->set(CacheInterface::class, function (ContainerInterface $container) { + $aliases = $container->get(Aliases::class); + + return new FileCache($aliases->get($this->cachePath)); + }); + + $container->set(YiiCacheInterface::class, Cache::class); + } +} +``` + +### Routes + +Anda dapat mengonfigurasi bagaimana aplikasi web merespons URL tertentu di `config/routes.php`: + +```php +use App\Controller\SiteController; +use Yiisoft\Router\Route; + +return [ + Route::get('/')->action([SiteController::class, 'index'])->name('site/index') +]; +``` + +Baca lebih lanjut tentang ini di ["Routes"](../runtime/routing.md). + +### Events + +Banyak layanan memancarkan event tertentu yang bisa Anda lampirkan (attach). +Anda dapat melakukannya melalui tiga berkas konfigurasi: `events-web.php` untuk event aplikasi web, +`events-console.php` untuk event konsol dan `events.php` untuk keduanya. +Konfigurasinya adalah sebuah array di mana kunci adalah nama event dan nilainya adalah array handler: + +```php +return [ + EventName::class => [ + // Just a regular closure, it will be called from the Dispatcher "as is". + static fn (EventName $event) => someStuff($event), + + // A regular closure with an extra dependency. All the parameters after the first one (the event itself) + // will be resolved from your DI container within `yiisoft/injector`. + static fn (EventName $event, DependencyClass $dependency) => someStuff($event), + + // An example with a regular callable. If the `staticMethodName` method has some dependencies, + // they will be resolved the same way as in the earlier example. + [SomeClass::class, 'staticMethodName'], + + // Non-static methods are allowed too. In this case, `SomeClass` will be instantiated by your DI container. + [SomeClass::class, 'methodName'], + + // An object of a class with the `__invoke` method implemented + new InvokableClass(), + + // In this case, the `InvokableClass` with the `__invoke` method will be instantiated by your DI container + InvokableClass::class, + + // Any definition of an invokable class may be here while your `$container->has('the definition)` + 'di-alias' + ], +]; +``` + +Read more about it in ["Events"](events.md). + + +Baca lebih lanjut mengenai ini di ["Events"](events.md). + +### Parameter + +Parameter pada `config/params.php` menyimpan nilai konfigurasi yang digunakan di berkas konfigurasi lain untuk mengonfigurasi layanan +dan service provider. + +> [!TIP] +> Jangan menggunakan parameter, konstanta, atau variabel environment secara langsung di aplikasi Anda; konfigurasikan +> layanan sebagai gantinya. + +Params `params.php` aplikasi default terlihat seperti berikut: + +```php + [ + 'charset' => 'UTF-8', + 'locale' => 'en', + 'name' => 'My Project', + ], + + 'yiisoft/aliases' => [ + 'aliases' => [ + '@root' => dirname(__DIR__), + '@assets' => '@root/public/assets', + '@assetsUrl' => '/assets', + '@baseUrl' => '/', + '@message' => '@root/resources/message', + '@npm' => '@root/node_modules', + '@public' => '@root/public', + '@resources' => '@root/resources', + '@runtime' => '@root/runtime', + '@vendor' => '@root/vendor', + '@layout' => '@resources/views/layout', + '@views' => '@resources/views', + ], + ], + + 'yiisoft/yii-view' => [ + 'injections' => [ + Reference::to(ContentViewInjection::class), + Reference::to(CsrfViewInjection::class), + Reference::to(LayoutViewInjection::class), + ], + ], + + 'yiisoft/yii-console' => [ + 'commands' => [ + 'hello' => Hello::class, + ], + ], +]; +``` + +Untuk kenyamanan, terdapat konvensi penamaan mengenai parameter: + +1. Kelompokkan parameter berdasarkan nama paket seperti `yiisoft/cache-file`. +2. Jika parameter untuk aplikasi itu sendiri, seperti `app`, lewati prefix paket. +3. Jika terdapat banyak layanan dalam paket, seperti `file-target` dan `file-rotator` di paket `yiisoft/log-target-file`, + kelompokkan parameter berdasarkan nama layanan. +4. Gunakan `enabled` sebagai nama parameter agar dapat menonaktifkan atau mengaktifkan sebuah layanan, misalnya `yiisoft/yii-debug`. + +### Konfigurasi paket + +Plugin konfigurasi yang dijelaskan menyalin konfigurasi paket default ke direktori `packages/`. Setelah disalin Anda +menjadi pemilik konfigurasi tersebut, sehingga Anda dapat menyesuaikannya sesuka hati. `yiisoft/` dalam template default menunjukkan vendor paket. Karena +hanya paket `yiisoft` yang ada di template, terdapat satu direktori. `merge_plan.php` digunakan saat runtime untuk mendapatkan urutan +bagaimana konfigurasi-konfigurasi digabungkan. +Perlu dicatat bahwa untuk kunci konfigurasi seharusnya ada satu sumber kebenaran (single source of truth). +Satu konfigurasi tidak bisa menimpa nilai dari konfigurasi lain. + +`dist.lock` digunakan oleh plugin untuk melacak perubahan dan menampilkan diff antara konfigurasi saat ini dan contoh. diff --git a/guide/id/concept/di-container.md b/guide/id/concept/di-container.md new file mode 100644 index 00000000..5388cf2b --- /dev/null +++ b/guide/id/concept/di-container.md @@ -0,0 +1,211 @@ +# Dependency injection dan container + +## Dependency injection + +Ada dua cara untuk menggunakan kembali (re-use) sesuatu dalam OOP: pewarisan (inheritance) dan komposisi (composition). + +Pewarisan itu sederhana: + +```php +class Cache +{ + public function getCachedValue($key) + { + // .. + } +} + +final readonly class CachedWidget extends Cache +{ + public function render(): string + { + $output = $this->getCachedValue('cachedWidget'); + if ($output !== null) { + return $output; + } + // ... + } +} +``` + +Masalahnya, kedua kelas ini menjadi saling terkait secara berlebihan (coupled) atau saling bergantung sehingga menjadi lebih rapuh. + +Cara lain untuk menangani ini adalah komposisi: + + +```php +interface CacheInterface +{ + public function getCachedValue($key); +} + +final readonly class Cache implements CacheInterface +{ + public function getCachedValue($key) + { + // .. + } +} + +final readonly class CachedWidget +{ + public function __construct( + private CacheInterface $cache + ) + { + } + + public function render(): string + { + $output = $this->cache->getCachedValue('cachedWidget'); + if ($output !== null) { + return $output; + } + // ... + } +} +``` + +Kita menghindari pewarisan yang tidak perlu dan menggunakan interface untuk mengurangi keterikatan (coupling). Anda dapat mengganti implementasi cache tanpa mengubah `CachedWidget`, sehingga komponennya menjadi lebih stabil. + +`CacheInterface` di sini adalah sebuah dependency: sebuah objek yang menjadi ketergantungan objek lain. +Proses memasukkan instance dependency ke dalam objek (`CachedWidget`) disebut dependency injection. +Ada banyak cara untuk melakukannya: + +- Injeksi konstruktor (Constructor injection). Terbaik untuk dependency yang wajib. +- Injeksi metode (Method injection). Terbaik untuk dependency yang opsional. +- Injeksi properti (Property injection). Sebaiknya dihindari di PHP kecuali mungkin untuk data transfer object. + + +## DI container + +Menginjeksikan dependency dasar itu sederhana dan mudah. Anda memilih tempat di mana Anda tidak peduli tentang dependency, +biasanya sebuah action handler (penangan aksi), yang biasanya tidak akan Anda unit-test, membuat instance dependency yang dibutuhkan +dan meneruskannya ke kelas-kelas yang bergantung. + +Cara ini bekerja baik saat jumlah dependency sedikit dan tidak ada dependency bersarang. Ketika jumlahnya banyak dan setiap dependency +memiliki dependency sendiri, membuat keseluruhan hirarki menjadi proses yang melelahkan, membutuhkan banyak kode, dan dapat menyebabkan +kesalahan yang sulit dideteksi. + +Selain itu, banyak dependency, seperti pembungkus (wrapper) API pihak ketiga tertentu, sama untuk setiap kelas yang menggunakannya. +Jadi masuk akal untuk: + +- Menentukan cara membuat instance pembungkus API tersebut sekali saja. +- Membuatnya saat diperlukan dan hanya sekali per permintaan. + +Itulah fungsi dependency container. + +Dependency injection (DI) container adalah sebuah objek yang tahu bagaimana membuat dan mengonfigurasi objek serta +semua objek dependensinya. [Artikel Martin Fowler](https://martinfowler.com/articles/injection.html) menjelaskan dengan baik +mengapa DI container berguna. Di sini kita akan menjelaskan penggunaan DI container yang disediakan oleh Yii. + +Yii menyediakan fitur DI container melalui paket [yiisoft/di](https://github.com/yiisoft/di) dan +paket [yiisoft/injector](https://github.com/yiisoft/injector). + +### Mengonfigurasi container + +Karena untuk membuat objek baru Anda membutuhkan dependency-nya, Anda harus mendaftarkannya sedini mungkin. +Anda dapat melakukannya di konfigurasi aplikasi, mis. `config/web.php`. Untuk layanan berikut: + +```php +final class MyService implements MyServiceInterface +{ + public function __construct(int $amount) + { + } + + public function setDiscount(int $discount): void + { + + } +} +``` + +konfigurasinya bisa seperti: + +```php +return [ + MyServiceInterface::class => [ + 'class' => MyService::class, + '__construct()' => [42], + 'setDiscount()' => [10], + ], +]; +``` + +Itu setara dengan: + +```php +$myService = new MyService(42); +$myService->setDiscount(10); +``` + +Ada metode tambahan untuk mendeklarasikan dependency: + +```php +return [ + // declare a class for an interface, resolve dependencies automatically + EngineInterface::class => EngineMarkOne::class, + + // array definition (same as above) + 'full_definition' => [ + 'class' => EngineMarkOne::class, + '__construct()' => [42], + '$propertyName' => 'value', + 'setX()' => [42], + ], + + // closure + 'closure' => static function(ContainerInterface $container) { + return new MyClass($container->get('db')); + }, + + // static call + 'static_call' => [MyFactory::class, 'create'], + + // instance of an object + 'object' => new MyClass(), +]; +``` + +### Menginjeksikan dependency + +Mereferensikan container secara langsung dalam sebuah kelas adalah ide yang buruk karena kode menjadi tidak generik, terikat pada antarmuka container +dan, yang lebih buruk, dependency menjadi tersembunyi. Karena itu, Yii membalik kontrol dengan secara otomatis menginjeksikan +objek dari container ke beberapa konstruktor dan metode berdasarkan tipe argumen metode. + +Ini terutama dilakukan pada konstruktor dan metode penangan aksi: + +```php +use \Yiisoft\Cache\CacheInterface; + +final readonly class MyController +{ + public function __construct( + private CacheInterface $cache + ) + { + $this->cache = $cache; + } + + public function actionDashboard(RevenueReport $report) + { + $reportData = $this->cache->getOrSet('revenue_report', function() use ($report) { + return $report->getData(); + }); + + return $this->render('dashboard', [ + 'reportData' => $reportData, + ]); + } +} +``` + +Karena [yiisoft/injector](https://github.com/yiisoft/injector) yang membuat dan memanggil action handler, ia +memeriksa tipe argumen konstruktor dan metode, mengambil dependency dengan tipe tersebut dari container dan meneruskannya sebagai argumen. Hal ini biasanya disebut auto-wiring. Ini juga berlaku untuk sub-dependency: jika Anda tidak memberikan dependency secara eksplisit, container akan memeriksa apakah ia memiliki dependency tersebut terlebih dahulu. +Cukup deklarasikan dependency yang Anda butuhkan, dan dependency itu akan diambil dari container secara otomatis. + + +## Referensi + +- [Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler](https://martinfowler.com/articles/injection.html) diff --git a/guide/id/concept/events.md b/guide/id/concept/events.md new file mode 100644 index 00000000..36b4371b --- /dev/null +++ b/guide/id/concept/events.md @@ -0,0 +1,142 @@ +# Events + +Events memungkinkan Anda menjalankan kode kustom pada titik-titik eksekusi tertentu tanpa memodifikasi kode yang sudah ada. +Anda dapat melampirkan kode kustom yang disebut "handler" ke sebuah event sehingga ketika event tersebut dipicu, handler +akan dijalankan secara otomatis. + +Contohnya, ketika seorang pengguna mendaftar, Anda perlu mengirim email sambutan. Anda bisa melakukannya langsung di +`SignupService` tetapi kemudian, ketika Anda juga perlu mengubah ukuran (resize) gambar avatar pengguna, Anda harus +mengubah kode `SignupService` lagi. Dengan kata lain, `SignupService` akan terikat (coupled) pada kode pengirim email dan +kode pengubah ukuran avatar. + +Untuk menghindari hal itu, alih-alih menentukan apa yang harus dilakukan setelah pendaftaran secara eksplisit, Anda dapat +mengangkat (raise) event `UserSignedUp` lalu menyelesaikan proses pendaftaran. Kode pengirim email dan kode pengubah ukuran +avatar akan melampirkan handler ke event tersebut dan, oleh karena itu, akan dijalankan. Jika sewaktu-waktu Anda perlu melakukan +tindakan tambahan saat pendaftaran, Anda bisa menambahkan handler event tanpa memodifikasi `SignupService`. + +Untuk menaikkan event dan melampirkan handler ke event-event tersebut, Yii menyediakan layanan khusus bernama event dispatcher. +Layanan ini tersedia dari paket [yiisoft/event-dispatcher](https://github.com/yiisoft/event-dispatcher). + +## Event Handlers + +Event handler adalah sebuah [PHP callable](https://www.php.net/manual/en/language.types.callable.php) yang dijalankan +ketika event yang dilampirkannya dipicu. + +Tanda tangan (signature) dari sebuah event handler adalah: + +```php +function (EventClass $event) { + // handle it +} +``` + +## Melampirkan event handlers + +Anda dapat melampirkan handler ke sebuah event seperti berikut: + +```php +use Yiisoft\EventDispatcher\Provider\Provider; + +final readonly class WelcomeEmailSender +{ + public function __construct(Provider $provider) + { + $provider->attach([$this, 'handleUserSignup']); + } + + public function handleUserSignup(UserSignedUp $event) + { + // handle it + } +} +``` + +Metode `attach()` menerima sebuah callback. Berdasarkan tipe argumen callback tersebut, jenis event akan ditentukan. + +## Urutan event handlers + +Anda dapat melampirkan satu atau lebih handler ke sebuah event. Ketika sebuah event dipicu, handler yang terlampir +akan dipanggil sesuai urutan saat mereka dilampirkan pada event. Jika sebuah event mengimplementasikan +`Psr\EventDispatcher\StoppableEventInterface`, handler event dapat menghentikan eksekusi handler-handler berikutnya +jika `isPropagationStopped()` mengembalikan `true`. + +Secara umum, lebih baik tidak bergantung pada urutan eksekusi handler event. + +## Menaikkan event + +Event dinaikkan (dispatched) seperti berikut: + +```php +use Psr\EventDispatcher\EventDispatcherInterface; + +final readonly class SignupService +{ + public function __construct( + private EventDispatcherInterface $eventDispatcher + ) + { + } + + public function signup(SignupForm $form) + { + // handle signup + + $event = new UserSignedUp($form); + $this->eventDispatcher->dispatch($event); + } +} +``` + +Pertama, Anda membuat sebuah event dengan menyertakan data yang mungkin berguna bagi handler. Lalu Anda mendispatch event itu. + +Kelas event itu sendiri dapat terlihat seperti berikut: + +```php +final readonly class UserSignedUp +{ + public function __construct( + public SignupForm $form + ) + { + } +} +``` + +## Hirarki Events + +Events tidak memiliki nama atau pencocokan wildcard atas tujuan tertentu. Nama kelas event dan hirarki kelas/interface +serta komposisi dapat digunakan untuk mencapai fleksibilitas yang baik: + +```php +interface DocumentEvent +{ +} + +final readonly class BeforeDocumentProcessed implements DocumentEvent +{ +} + +final readonly class AfterDocumentProcessed implements DocumentEvent +{ +} +``` + +Dengan menggunakan interface, Anda dapat mendengarkan semua event yang berkaitan dengan dokumen: + +```php +$provider->attach(function (DocumentEvent $event) { + // log events here +}); +``` + +## Melepas (detaching) event handlers + +Untuk melepas handler dari sebuah event Anda dapat memanggil metode `detach()`: + +```php +$provider->detach(DocmentEvent::class); +``` + +## Mengonfigurasi event aplikasi + +Biasanya Anda menugaskan (assign) event handler melalui konfigurasi aplikasi. Lihat ["Configuration"](configuration.md) untuk detail. diff --git a/guide/id/concept/immutability.md b/guide/id/concept/immutability.md new file mode 100644 index 00000000..5d7cff80 --- /dev/null +++ b/guide/id/concept/immutability.md @@ -0,0 +1,149 @@ +# Immutability + +Immutability berarti status (state) sebuah objek tidak dapat diubah setelah objek tersebut dibuat. +Alih-alih memodifikasi sebuah instance, Anda membuat instance baru dengan perubahan yang diinginkan. +Pendekatan ini umum untuk objek bernilai (value objects) seperti Money, ID, dan DTO. Ini membantu menghindari efek samping yang tidak disengaja: +metode tidak dapat diam-diam mengubah state yang dibagi, sehingga kode menjadi lebih mudah untuk dipahami. + +## Perangkap mutable (yang kita hindari) + +```php +// A shared base query built once and reused: +$base = Post::find()->where(['status' => Post::STATUS_PUBLISHED]); + +// Somewhere deep in the code we only need one post: +$one = $base->limit(1)->one(); // mutates the underlying builder (sticky limit!) + +// Later we reuse the same $base expecting a full list: +$list = $base->orderBy(['created_at' => SORT_DESC])->all(); +// Oops: still limited to 1 because the previous limit(1) modified $base. +``` + +## Membuat objek immutable di PHP + +Tidak ada cara langsung untuk memodifikasi sebuah instance, tetapi Anda dapat menggunakan clone untuk membuat instance baru dengan perubahan yang diinginkan. +Itulah yang dilakukan metode `with*`. + +```php +final class Money +{ + public function __construct( + private int $amount, + private string $currency, + ) { + $this->validateAmount($amount); + $this->validateCurrency($currency); + } + + private function validateAmount(string $amount) + { + if ($amount < 0) { + throw new InvalidArgumentException('Amount must be positive.'); + } + } + + private function validateCurrency(string $currency) + { + if (!in_array($currency, ['USD', 'EUR'])) { + throw new InvalidArgumentException('Invalid currency. Only USD and EUR are supported.'); + } + } + + public function withAmount(int $amount): self + { + $this->validateAmount($amount); + + if ($amount === $this->amount) { + return $this; + } + + $clone = clone $this; + $clone->amount = $amount; + return $clone; + } + + public function withCurrency(string $currency): self + { + $this->validateCurrency($currency); + + if ($currency === $this->currency) { + return $this; + } + + $clone = clone $this; + $clone->currency = $currency; + return $clone; + } + + public function amount(): int + { + return $this->amount; + } + + public function currency(): string + { + return $this->currency; + } + + public function add(self $money): self + { + if ($money->currency !== $this->currency) { + throw new InvalidArgumentException('Currency mismatch. Cannot add money of different currency.'); + } + return $this->withAmount($this->amount + $money->amount); + } +} + +$price = new Money(1000, 'USD'); +$discounted = $price->withAmount(800); +// $price is still 1000 USD, $discounted is 800 USD +``` + +- Kita menandai kelas sebagai `final` untuk mencegah perubahan oleh subclass; sebagai alternatif, rancang ekstensi dengan hati-hati. +- Lakukan validasi di konstruktor dan metode `with*` sehingga setiap instance selalu valid. + +> [!TIP] +> Jika Anda mendefinisikan DTO sederhana, Anda dapat menggunakan kata kunci `readonly` di PHP modern dan membiarkan properti `public`. Kata kunci `readonly` memastikan properti tidak dapat diubah setelah objek dibuat. + +## Menggunakan clone (dan mengapa ini murah) + +Clone PHP melakukan salinan dangkal (shallow copy) dari objek. Untuk objek nilai immutable yang hanya berisi skalar +atau objek immutable lainnya, cloning dangkal sudah cukup dan cepat. Di PHP modern, cloning objek nilai kecil +murah baik dari segi waktu maupun memori. + +Jika objek Anda memegang sub-objek yang mutable dan juga harus disalin, implementasikan `__clone` untuk melakukan deep-clone pada sub-objek tersebut: + +```php +final class Order +{ + public function __construct( + private Money $total + ) {} + + public function total(): Money + { + return $this->total; + } + + public function __clone(): void + { + // Money is immutable in our example, so a deep clone is not required. + // If it were mutable, you could do: $this->total = clone $this->total; + } + + public function withTotal(Money $total): self + { + $clone = clone $this; + $clone->total = $total; + return $clone; + } +} +``` + +## Gaya penggunaan + +- Bangun sebuah value object sekali lalu teruskan. Jika Anda perlu mengubahnya, gunakan metode `with*` yang mengembalikan instance baru. +- Utamakan field skalar/immutable di dalam objek immutable; jika sebuah field dapat berubah, isolasi dan lakukan deep-clone di `__clone` bila diperlukan. + +Immutability sejalan dengan preferensi Yii untuk kode yang dapat diprediksi dan bebas efek samping, serta membuat layanan, caching, +dan konfigurasi menjadi lebih tangguh. diff --git a/guide/id/databases/db-migrations.md b/guide/id/databases/db-migrations.md new file mode 100644 index 00000000..0251a2aa --- /dev/null +++ b/guide/id/databases/db-migrations.md @@ -0,0 +1,100 @@ +# Migrasi + +Untuk menggunakan migrasi, pasang paket [yiisoft/db-migration](https://github.com/yiisoft/db-migration/): + +```shell +composer require yiisoft/db-migration +``` + +### Contoh penggunaan + +Pertama, konfigurasikan DI container. Buat `config/common/db.php` dengan isi berikut: + +```php + [ + 'class' => SqliteConnection::class, + '__construct()' => [ + 'dsn' => 'sqlite:' . __DIR__ . '/Data/yiitest.sq3' + ] + ] +]; +``` + +Tambahkan hal berikut ke `config/params.php`: + +```php +... +'yiisoft/db-migration' => [ + 'newMigrationNamespace' => 'App\\Migration', + 'sourceNamespaces' => ['App\\Migration'], +], +... +``` + +Sekarang tes apakah sudah bekerja: + +```shell +./yii list migrate +``` + +### Membuat migrasi + +Untuk bekerja dengan migrasi, Anda dapat menggunakan [view](https://github.com/yiisoft/db-migration/tree/master/resources/views) yang disediakan. + +```shell +./yii migrate:create my_first_table --command=table --fields=name,example --table-comment=my_first_table +``` + +Perintah tersebut akan menghasilkan file seperti berikut: + +```php +createTable('my_first_table', [ + 'id' => $b->primaryKey(), + 'name', + 'example', + ]); + + $b->addCommentOnTable('my_first_table', 'dest'); + } + + public function down(MigrationBuilder $b): void + { + $b->dropTable('my_first_table'); + } +} +``` + +Untuk informasi lebih lanjut lihat dokumentasi paket [di sini](https://github.com/yiisoft/db-migration/tree/master/docs/guide/en). + +### Migrasi dari Yii2 + +Migrasi di Yii2 dan paket [yiisoft/db-migration](https://github.com/yiisoft/db-migration/) tidak kompatibel, +dan tabel `migration` juga tidak kompatibel. +Solusi yang mungkin adalah menggunakan dump struktur dan mengganti nama tabel `migration` lama. Pada eksekusi awal +migrasi, tabel `migration` baru dengan field yang baru akan dibuat. Semua perubahan selanjutnya pada skema basis data +akan diterapkan menggunakan komponen migrasi yang baru dan dicatat di tabel migrasi yang baru. diff --git a/guide/id/glossary.md b/guide/id/glossary.md new file mode 100644 index 00000000..be99ef00 --- /dev/null +++ b/guide/id/glossary.md @@ -0,0 +1,76 @@ +# A + +## alias + +Alias adalah sebuah string yang digunakan oleh Yii untuk merujuk ke kelas atau direktori, misalnya `@app/vendor`. +Baca lebih lanjut di ["Aliases"](concept/aliases.md). + +## asset + +Aset merujuk pada berkas sumber daya. Biasanya berisi kode JavaScript atau CSS, tetapi bisa berupa konten statis apa pun yang diakses melalui HTTP. + +# C + +## configuration + +Konfigurasi dapat merujuk pada proses mengatur properti suatu objek atau pada berkas konfigurasi yang menyimpan +pengaturan untuk sebuah objek atau kelas objek. Baca lebih lanjut di ["Configuration"](concept/configuration.md). + +# D + +## DI + +Dependency Injection (Injeksi Dependensi) adalah teknik pemrograman di mana dependensi sebuah objek disediakan (disuntikkan) dari luar. ["DI"](concept/di-container.md) + +# I + +## installation + +Instalasi adalah proses menyiapkan sesuatu agar dapat bekerja, baik dengan mengikuti berkas readme maupun menjalankan skrip +yang disiapkan khusus. Dalam konteks Yii, ini mencakup pengaturan izin dan pemenuhan persyaratan perangkat lunak. + +# M + +## middleware + +Middleware adalah pemroses dalam tumpukan pemrosesan permintaan (request). Diberikan sebuah request, ia dapat menghasilkan response +atau melakukan suatu aksi dan meneruskan pemrosesan ke middleware berikutnya. Baca lebih lanjut di ["Middleware"](structure/middleware.md). + +## module + +Modul adalah sub-aplikasi yang mengelompokkan sejumlah kode berdasarkan suatu kasus penggunaan. Biasanya digunakan di dalam aplikasi utama +dan dapat berisi handler URL atau perintah konsol. + +# N + +## namespace + +Namespace merujuk pada [fitur bahasa PHP](https://www.php.net/manual/en/language.namespaces.php). + +# P + +## package + +Paket biasanya merujuk pada [paket Composer](https://getcomposer.org/doc/). Ini adalah kode yang siap digunakan ulang dan +didistribusikan, yang dapat diinstal secara otomatis melalui manajer paket. + +# R + +## rule + +Aturan biasanya merujuk pada aturan validasi dari paket [yiisoft/validator](https://github.com/yiisoft/validator). +Aturan menyimpan serangkaian parameter untuk memeriksa apakah sebuah himpunan data valid. +"Rule handler" melakukan pemrosesan sebenarnya. + +# Q + +## queue + +Antrian mirip dengan tumpukan (stack), tetapi mengikuti metodologi First-In-First-Out. + +# V + +## vendor + +Vendor adalah organisasi atau pengembang individu yang menyediakan kode dalam bentuk paket. Istilah ini juga dapat merujuk pada +direktori `vendor` milik [Composer](https://getcomposer.org/doc/).