# Modül Yaşam Döngüsü

https://dev.wisecp.com/tr/modul-yasam-dongusu

Bir modülün diske gelişiyle silinişi arasında olan biten. Ne zaman yüklenir, ne zaman kurulur, etkinlik işareti nerede durur ve hangi metodunuz çağrılır.

## Genel Bakış

Bir kurulum yordamı yoktur. Dizini yerine kopyalamak kurulumun kendisidir; modül bir sonraki istekte listelenir. Sonrasındaki her şey, ayrı ayrı başlatılan küçük adımlardan oluşur. Bu adımlardaki metotların neredeyse hepsi isteğe bağlıdır: platform sınıfınızda var mı diye bakar, yoksa devam eder.

İki kavram kolayca karışır. **Mevcut**, dizinin var olması demektir; modülün listelenmesi, kanca dosyasının çalıştırılması ve isteyen herkes tarafından kurulabilmesi için bu yeter. **Etkin** ise bir operatörün onu seçmiş olması demektir ve bu kararın nerede saklandığı tamamen tipe bağlıdır. Kapalı olmak, kodunuzun yüklenmesini hiçbir şekilde engellemez.

## Yapı

### Aşamalar

| Aşama | Neyle olur | Modülünüzde ne çalışır | Nasıl geri alınır |
| --- | --- | --- | --- |
| Diskte | Dizin kopyalanır, bir arşivden çıkarılır ya da panel tarafından indirilir | Hiçbir şey. Gelmiş olmak hiçbir kodu çalıştırmaz | Dizini silmek |
| Kancalar kaydedilir | Her istekte, her modül dizini için, hiçbir duruma bakılmadan | `hooks.php`, baştan sona | Yalnız gövdeyi kendiniz kapılayarak ya da dosyayı kaldırarak |
| Yüklenir | Bir yer kayıt defterinden bu modülü ya da tipinin tamamını ister | Hiçbir şey. `config.php` ve dil dosyası statik bir önbelleğe okunur | Geri alınacak bir şey yok; önbellek bir istek boyu yaşar |
| Kurulur | Fabrikadan bir nesne istenir | Taban yapıcı, sonra yazdıysanız sizinki | Geri alınacak bir şey yok; nesne bir istek boyu önbelleklenir |
| Bağlanır | Çağıran taraf bir hizmet, sipariş ya da ürün bağlar | Varsa sizin `set_service` geçersiz kılmanız | Aynı nesneye başka bir şey bağlamak |
| Etkinleşir | Operatör açar ya da bir içe aktarıma hemen etkinleştirmesi söylenir | `activate()`, ardından `enable()`; ikisi de isteğe bağlı, ikisi de reddedebilir | Kapatmak |
| Kapanır | Operatör kapatır | `deactivate()`, ardından `disable()`; ikisi de isteğe bağlı | Yeniden açmak; bu da etkinleştirme metotlarını tekrar koşturur |
| Silinir | Modül listesindeki silme eylemi | `uninstall()`, hiçbir dosyaya dokunulmadan önce | Dosyaları geri koymak. Sildiğiniz veriyi hiçbir şey geri getirmez |

## Referans

### Etkinlik İşareti Nerede Durur

Dört tip kendi yapılandırma dosyasında bir `status` anahtarı tutar. Kalanı başka bir yerde seçilir ve ikisinde böyle bir işaret hiç yoktur.

| Tip | Karar nerede saklanır | Kim yazar |
| --- | --- | --- |
| Addons | Modülün kendi `config.php` dosyasındaki `status` | Eklenti listesindeki anahtar, taban durum metodu üzerinden |
| Product | Modülün kendi `config.php` dosyasındaki `status` | O modül grubunun ayar ekranı; her modülün dosyasını tek geçişte yazar |
| Fraud | Modülün kendi `config.php` dosyasındaki `status` | Dolandırıcılık ayarları ekranı |
| SocialAuth | Modülün kendi `config.php` dosyasındaki `status` | Sosyal giriş ayarları |
| Mail, SMS, IP, Currency | Platformun modül yapılandırmasındaki tek bir modül adı | O grubun ayar ekranı. Aynı anda yalnız biri etkin olabilir |
| Payment | Platformun modül yapılandırmasındaki modül adı listesi | Ödeme ayarları ekranı; ayrıca kart saklama geçidini adlandıran ayrı bir girdi |
| Authentication | Platformun modül yapılandırmasındaki modül adı listesi | Güvenlik ayarları |
| Captcha | Seçenek yapılandırmasındaki seçili tip, yanında bir açık kapalı işaretiyle | Güvenlik ayarları |
| Servers | Hiçbir yerde işaret yok | Modülü adlandıran bir sunucu kaydı onu kullanıma sokar |
| Registrars | Hiçbir yerde işaret yok | Modülü gösteren bir üst düzey alan adı uzantısı onu kullanıma sokar |
| Storage, Pipe, Imports | Kullanıldığı noktada seçilir | Sırasıyla yedek hedefi, destek posta kutusu ve içe aktarma koşusu |

### Bildirebileceğiniz Yaşam Döngüsü Metotları

İlk beşi salt konvansiyondur. Hiçbir taban sınıf onları bildirmez; her biri `method_exists` ile aranır ve yoksa atlanır. Yanlış bir dönüş, ait olduğu adımı durdurur. Son ikisi farklıdır ve bu fark önemlidir. `change_addon_status` eklenti tabanında zaten uygulanmıştır. `testConnection` ise sosyal giriş tabanında abstract bildirilmiştir; yani orada isteğe bağlı değil zorunludur.

```php
// Etkinleştirme yolu, bu sırayla. Önce activate(), sonra enable() koşar.
// false döndürmek modülü kapalı bırakır ve hiçbir şey yazılmaz.
public function activate(): bool;
public function enable(): bool;

// Kapatma yolu, bu sırayla.
public function deactivate(): bool;
public function disable(): bool;

// Modül dizini kaldırılmadan önce koşar. false döndürmek silmeyi iptal eder.
// Bu argümansız boolean biçim EKLENTİ biçimidir.
public function uninstall(): bool;

// Authentication tipi aynı adı farklı bir biçimle yeniden kullanır: saklanan
// kayıt verisi geçilir ve bir dizi döndürülür. Yalnız 'error' iptal eder.
public function uninstall(array $data = []): array;   // ['status' => 'successful']

// Test butonu. İki biçim var ve hangisini alacağınıza çağıran taraf karar verir:
// sosyal giriş sağlayıcıları ARGÜMANSIZ çağrılır (tabanda abstract),
// servis sağlayıcılar ise tek argüman olarak birleştirilmiş yapılandırmayla çağrılır.
public function testConnection(): bool;                  // SocialAuth
public function testConnection($config = []): bool;      // Registrars

// Eklenti tabanında zaten uygulanmıştır: yukarıdaki dört metodu koşturur, sonra
// yeni durumu config.php'ye yazar. Yalnız o davranışı değiştirmek için geçersiz kılın.
public function change_addon_status($arg = '');
```

> **Bu adlardan ikisi farklı imzayla yeniden kullanılır**
> 
> Bir servis sağlayıcıda argümansız `testConnection()` bildirmek tuzağın ta kendisidir. Taban, birleştirilmiş yapılandırma dizisini ilk argüman olarak geçer. Operatörün girdiği kimlik bilgilerini sınamak için gereken değeri atmış olursunuz. Kurulu her servis sağlayıcı `$config = []` bildirir. Aynısı `uninstall` için de geçerlidir: eklenti biçimi argüman almaz, kimlik doğrulama biçimi saklanan kayıt dizisini alır.

### Eklenti Durum Zinciri

Sıranın tamamı budur. Düşen bir `enable()` çağrısının modülü neden olduğu gibi bıraktığını görmenin en hızlı yolu bunu okumaktır.

```php
public function change_addon_status($arg = '')
{
    $status = $arg == "enable";
    $apply  = true;

    if ($status && method_exists($this, 'activate'))    $apply = $this->activate();
    if ($status && method_exists($this, 'enable'))      $apply = $this->enable();
    if (!$status && method_exists($this, 'deactivate')) $apply = $this->deactivate();
    if (!$status && method_exists($this, 'disable'))    $apply = $this->disable();

    // Bayrak EN SON yazılır, hem de yalnız modül onay verdiyse.
    if ($apply) {
        $config           = $this->config;
        $config["status"] = $status;
        $this->save_config($config);
    }

    return $apply;
}
```

Onu çağıran operation; bir reddin operatöre nasıl ulaştığını ve iki argümanın nereden geldiğini görün:

```php
$key    = (string) Filter::init("POST/module", "route");
$status = (int) Filter::init("POST/status", "rnumbers");

$instance = Modules::getInstance("Addons", $key);

if (!method_exists($instance, "change_addon_status"))
    throw new Exception("Module class does not have a method named change_addon_status.");

$status = $status ? "enable" : "disable";

// Fırlatılan bir istisna doğrudan hata mesajı olarak dışarı çıkar. false dönüşü ise
// eski yoldur: çağıran taraf o zaman gerekçe için eski hata özelliğini okur.
$result = $instance->change_addon_status($status);
if (!$result) throw new Exception($instance->error ?: "Unknown error");

User::addAction($adata["id"], "alteration", "change-addon-status-" . $status, ['module' => $key]);

Hook::run('action:addon.status_changed', $key, $status);
```

### Yaşam Döngüsü Kancaları

Kapılar veto edebilir, eylemler yalnız izler. Boş olmayan bir dize döndüren kapı, o dizeyi operatörün gördüğü hataya çevirir.

- **gate:module.activate**: Herhangi bir modül grubu etkinleştirmesi yazılmadan önce, grup ve yeni etkinleşen adlar listesiyle çalışır. Reddetmek için boş olmayan bir dize ya da mesaj taşıyan bir dizi döndürün.
- **action:module.activated**: Grup ayarları kaydedildikten sonra, kapalıdan açığa geçen adlarla. Yalnız fark bildirilir, seçimin tamamı değil. Dönüş yoksayılır.
- **action:module.deactivated**: Bir öncekinin aynası; açıktan kapalıya geçen adlarla. Dönüş yoksayılır.
- **action:addon.status_changed**: Bir eklenti açıldıktan ya da kapandıktan sonra, modül anahtarı ve uygulanan kelimenin kendisiyle. Dönüş yoksayılır.
- **gate:module.addon_install**: Yüklenen arşiv açılmadan önce, yükleme kaydı ve hemen etkinleştirilip etkinleştirilmeyeceği bilgisiyle.
- **action:addon.installed**: Çıkarma sonrası, modül anahtarı ve etkinleştirme işaretiyle. Mağaza kaydı ya da güncelleme künyesi buraya yazılır.
- **gate:module.addon_delete**: Bir eklenti silinmeden önce, anahtarıyla. Modül hâlâ canlı veri taşırken reddetmek için kullanın.
- **action:addon.deleted**: Dizin gittikten sonra. Modülün kendi sınıfı bu noktada artık yoktur; dinleyiciyi başka bir yere koyun.
- **gate:module.delete**: Eklenti olmayan bir modül için aynı veto; tip ve anahtarla.
- **action:module.config_saved**: Bir modülün yapılandırma dosyası yeniden yazıldıktan sonra, tip ve anahtarla. Modülünüzün tuttuğu bir önbelleği temizlemek için işe yarar.

## Örnek

Kendi şemasını kuran bir etkinleştirme, veriyi bilerek koruyan bir kapatma ve nihayet onu silen bir kaldırma. Üçünün de amacı şudur: yalnız sonuncusu yıkıcıdır.

```php
public function enable(): bool
{
    // Bilerek idempotent: enable() her yeniden etkinleştirmede ve güncellemeden sonra tekrar koşar.
    $this->check_database();

    return true;
}

/** Yalnız ekler. Asla düşürmez, var olan bir kolonu asla yeniden yazmaz. */
private function check_database(): void
{
    if (!\WDB::hasTable("Acme_events"))
        \WDB::exec('CREATE TABLE `Acme_events` ('
            . '`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,'
            . '`service_id` INT(11) UNSIGNED NOT NULL DEFAULT 0,'
            . '`payload` TEXT NULL DEFAULT NULL,'
            . 'PRIMARY KEY (`id`)'
            . ') ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;');

    // Tablo ilk gönderildikten sonra eklenen kolonlar tek tek kontrol edilir.
    $col = \WDB::query("SHOW COLUMNS FROM `Acme_events` LIKE 'created_at'");
    if (!($col ? \WDB::getAssoc($col) : false))
        \WDB::exec("ALTER TABLE `Acme_events` ADD `created_at` INT(11) UNSIGNED NOT NULL DEFAULT '0'");
}

public function disable(): bool
{
    // Burada hiçbir şey düşürülmez. Kapatmak geri alınabilir bir işlemdir; modülü bir
    // öğleden sonra kapatan operatör bir yıllık satırı kaybetmemelidir.
    return true;
}

public function uninstall(): bool
{
    // Kaybedilecek bir şey varken sessizce yok etmek yerine reddedin.
    // select() OLUŞTURUCUYU döndürür, yani build() ve getAssoc() onun üzerinde çağrılır.
    // Oluşturucuyu WDB::getAssoc() metoduna argüman olarak vermek ölümcüldür: o
    // parametre bir PDOStatement bekler, onu da WDB::query() döndürür.
    $stmt = \WDB::select('COUNT(id) AS total')->from('Acme_events');
    $rows = $stmt->build() ? (int) (($stmt->getAssoc() ?: [])['total'] ?? 0) : 0;

    if ($rows > 0 && !(int) \Filter::init("POST/purge", "rnumbers"))
        throw new \Exception($this->lang['error-uninstall-has-data'] ?? 'The module still holds records.');

    \WDB::exec("DROP TABLE IF EXISTS `Acme_events`");

    return true;
}
```

Diğer taraf, modülde değil bir dinleyicide; çünkü silme dinleyicisinin tepki verdiği sınıftan daha uzun yaşaması gerekir:

```php
// Modül etkin olsun olmasın, HER istekte koşar. Burayı yalnız kayıtlara ayırın.
Hook::add('gate:module.addon_delete', 1, function ($key) {
    if ($key !== 'Acme') return '';

    $stmt = WDB::select('COUNT(id) AS cnt')->from('Acme_events');
    $stmt->where('processed', '=', 0);

    $open = $stmt->build() ? (int) (($stmt->getAssoc() ?: [])['cnt'] ?? 0) : 0;

    // Boş olmayan dize reddin kendisidir ve operatörün okuduğu şey odur.
    return $open > 0 ? 'Acme still has ' . $open . ' unprocessed events.' : '';
});

Hook::add('action:addon.status_changed', 1, function ($key, $status) {
    if ($key !== 'Acme') return;

    Cache::getInstance()->clear(['acme']);
});
```

## Tuzaklar

> **Etkinleştirme birden çok kez çalışır, yani idempotent olmalıdır**
> 
> İlk etkinleştirmede ve her yeniden etkinleştirmede çalışır; bir güncellemenin şemayı yeniden kurduğu olağan yer de burasıdır. Oluşturmadan önce kontrol edin, kolonları tek tek ekleyin ve operatörün düzenlediği bir satırı asla ezmesine izin vermeyin. Tohumlama yalnız boş tabloya sayım kapısıyla yapıldığında güvenlidir.

> **Kapatmak kaldırmak değildir, silmek de değildir**
> 
> Kapatma her tabloyu ve her satırı olduğu gibi bırakmalıdır; o bir anahtardır, temizlik değil. Silme yalnız dizini kaldırır, yani modülünüzün veritabanına yazdığı her şey hayatta kalır. Veri de gidecekse, dosyalar yok olmadan önce çalışan tek adım olan kaldırma metodunda düşürün.

> **Kapalı bir modül de kancalarını kaydeder**
> 
> Kanca dosyaları modüller dizini gezilerek toplanır, hiçbir duruma bakılmadan. Gövde modülün kendi etkinlik işaretini önce kontrol etmiyorsa dinleyicileriniz modül kapalıyken de çalışır. Kanca dosyasını bir kayıt listesi olarak görün ve kararı her dinleyicinin içine koyun.

> **Reddi fırlatarak, kişinin yapabileceği bir cümleyle bildirin**
> 
> Fırlatılan bir istisna ekrandaki mesaj olur. Mesajsız bir yanlış dönüş ise "Unknown error" kelimelerini üretir. Çağıran taraf bu durumda yeni kodun doldurmadığı eski hata özelliğine düşer ve operatör hiçbir şey öğrenmez.

> **Sınıfınıza önce yalnız eklenti silme eylemi sorar**
> 
> Kaldırma metodunu eklenti silme eylemi çağırır; bir de kimlik doğrulama tipi, kayıt kaldırılırken çağırır. Başka herhangi bir tipteki modülü silmek, kapı kancası reddetme şansını kullandıktan sonra dosyaları kaldırır. Sınıfınızdaki hiçbir metot çağrılmaz. Kaldırma anında çalışması gereken ne varsa, kimsenin çağırmadığı bir metoda değil kapının arkasına koyun.

## İlgili Makaleler

- [Modül Sistemi](https://dev.wisecp.com/tr/modul-sistemi)
- [Modül Anatomisi](https://dev.wisecp.com/tr/modul-anatomisi)
- [Modül Yapılandırması](https://dev.wisecp.com/tr/modul-yapilandirmasi)
- [Modülden Kanca Kaydetme](https://dev.wisecp.com/tr/modulden-kanca-kaydetme)
- [Eklenti Modülü Yazma](https://dev.wisecp.com/tr/eklenti-modulu-yazma)
- [Veritabanı Şemasını Değiştirme](https://dev.wisecp.com/tr/veritabani-semasini-degistirme)
