# Modülden Kanca Kaydetme

https://dev.wisecp.com/tr/modulden-kanca-kaydetme

Modül dizininize bir `hooks.php` koyarsınız; bir modülün çekirdeğe dokunmadan uzanma biçimi budur.

## Genel Bakış

Genişletmenin tamamı kancalar üzerinden yapılır. Bir modül, sahip olduğu tek dosyadan olayı yakalar, bir değeri değiştirir, işaretleme enjekte eder, yetenek kaydeder ya da bir işlemi reddeder. `coremio/` içindeki hiçbir şey modülünüzün varlığını bilmez.

Katalog beş kategoride **979** kanca noktası taşıyor: `ui` 343, `action` 298, `filter` 196, `gate` 131, `register` 11. Ad tahmin etmek yerine kataloğa bakın.

## Ön Koşullar

- `coremio/modules/{Tip}/{Ad}/` altında bir modül dizini; on altı tipin hepsi olur.
- Kanca adı, `hooks/INDEX.md` dosyasından kopyalanmış olarak. Var olmayan bir ad sessizce başarısız olur.
- O kancanın `hooks/{domain}/` altındaki sayfası: parametreler, hangisinin referans olduğu ve dönüş sözleşmesi.

## Yapı

- **classes/Hook.php**: Motor: kayıt, öncelik sıralaması, argüman eşleme ve dosyanızı bulan yükleyici.
- **{modül}/hooks.php**: Dinleyicileriniz. `coremio/modules/*/*/hooks.php` deseniyle bulunur: ad ve konum sabittir.
- **coremio/hooks/**: Çekirdeğin kendi dinleyici dosyaları; aynı geçişte, modül dosyalarından önce yüklenir.
- **hooks/INDEX.md**: Üretilmiş katalog: her kanca noktası, domain'i ve onu tetikleyen dosya ile satır.
- **{modül}/router.php**: İkinci ve daha erken giriş noktası: router kurulurken, kanca dosyalarından önce yüklenir.

## Adım Adım

### Dosyayı Oluşturun

1. Modül dizininizin köküne, `{Ad}.php` ve `config.php` dosyalarının yanına `hooks.php` ekleyin.
2. Dosya include edilir; en üst seviyede düz ifadeler yazın.
3. Kendi `src/` dizininizdeki sınıflar otomatik yüklenmez; başvurmadan önce `include_once` ile yükleyin.

### Kendi Durumunuzla Kapılayın

1. Modülü kurmadan yapılandırmanızı yükleyin: yükleyiciye üçüncü argüman olarak `true` geçin.
2. Her dinleyiciyi kendi etkinlik işaretinizin, lisanslı bir modülse lisansınızın kontrolüne sarın.
3. Dosyayı ucuz tutun; bir kancaya dokunan her istekte çalışır.

### Dinleyici Kaydedin

1. Ekleme metodunu kanca adı, bir öncelik ve ya bir closure ya da bir tanım dizisiyle çağırın.
2. Yalnız ihtiyacınız olan parametreleri bildirin; motor bunları konum sırasına göre eşler.
3. Bir değeri değiştirmek için o parametreyi referansla bildirin; yalnız referanslı varyant onu geri taşır.

### Öncelik Seçin

1. Küçük olan önce çalışır. Varsayılan yoktur, bu yüzden sayıyı bilinçli geçin.
2. Sonraki bir kaydı gölgelemek için küçük (rota ezmeleri böyle çalışır), en sona eklenmek için büyük bir sayı kullanın.

## Referans

### Kayıt

```php
class Hook
{
    // $properties YA bir callable YA da bir tanım dizisidir (aşağıdaki üç biçime bakın).
    public static function add($name, $priority, $properties = []): void;

    public static function run($name, ...$args): array;        // değerle
    public static function runRefs($name, &...$args): array;   // HER argüman referansla
    public static function runDetailed($name, ...$args): array; // dinleyici başına telemetri
}
```

| Çalıştırma metodu | Döndürdüğü | Dinleyici exception fırlatırsa |
| --- | --- | --- |
| `run` | null olmayan her dönüş, öncelik sırasında | loglanır, sonraki dinleyici çalışır |
| `runRefs` | aynısı, ayrıca referans düzenlemeleriniz çağırana ulaşır | loglanır, sonraki dinleyici çalışır |
| `runDetailed` | `[['source' => ['type','class','method','file','line'], 'value' => …, 'error' => ?string], …]`, null'lar korunur | `error` alanına yazılır |
| hepsi, bilinmeyen ad | boş dizi | hiçbir şey olmaz; yazım hatası sessizdir |

### Üç Kayıt Biçimi

```php
// 1. Closure. Çağrılabilir olan her şey doğrudan girer.
Hook::add('action:service.created', 10, function ($id, $data) {
    // ...
});

// 2. Örnek metodu. Sınıf argümansız olarak BİR KEZ kurulur ve istek boyunca
//    önbellekte tutulur, bu yüzden yapıcısı parametresiz çalışmalıdır.
Hook::add('filter:invoice.totals', 10, [
    'class'  => 'MyAddonHooks',
    'method' => 'adjustTotals',
]);

// 3. Statik metot. Hiçbir şey kurulmaz.
Hook::add('ui:admin.service_detail.bottom', 10, [
    'class'          => 'MyAddonHooks',
    'method::static' => 'renderPanel',
]);
```

Sınıf bulunamazsa ya da metot yoksa dinleyici null döner ve istek devam eder. Bu tam olarak hiç tetiklenmemiş bir kanca gibi görünür. Closure tercih edin.

### Argümanlar Dinleyicinize Nasıl Ulaşır

```php
// Dinleyicinizin $i konumunda bildirdiği her parametre için:
//   referansla bildirilmiş VE $i konumunda argüman var  -> referansla geçirilir
//   $i konumunda argüman var                            -> değerle geçirilir
//   $i konumunda argüman yok                            -> parametre düşürülür,
//                                                          yani varsayılanınız geçerli olur
$callArgs = [];
foreach ($reflection->getParameters() as $i => $param) {
    if ($param->isPassedByReference() && isset($args[$i])) $callArgs[] = &$args[$i];
    else if (isset($args[$i])) $callArgs[] = $args[$i];
}
```

Kancanın verdiğinden az parametre bildirmek güvenlidir. Bir referans, değişikliğinizi ancak kanca referanslı varyantla tetiklendiyse geri taşır. Değer geçen bir kancada `&$deger` sessizce hiçbir şeyi değiştirmez.

### Beş Kategori ve Her Birinin Beklediği Dönüş

| Önek | Dinleyiciniz ne yapar | Dönüş değeriniz |
| --- | --- | --- |
| `action:` | bir olaya tepki verir | yoksayılır |
| `filter:` | bir değeri kullanılmadan önce değiştirir | düzenleme referans parametresi üzerinden gider |
| `ui:` | işaretleme, stil ya da script enjekte eder | basılacak dize |
| `register:` | bir yetenek kaydeder: cron görevi, rota, menü öğesi, widget | kayıt verisi, ya da hiçbir şey kaydetmemek için `false` |
| `gate:` | bir işlemi veto eder | **boş olmayan değer engeller**, `null` devam ettirir |

Ad `kategori:domain.subject.action` biçimindedir; küçük harf, parçalar snake_case. Bir `ui:` adı yerleştirme kelimesiyle biter: dinleyicisine bağlam verilmez, hedefi addan çıkarır.

### Kanca Dosyasında Kendi Yapılandırmanızı Okuma

```php
// $nominc = true, sınıfı dahil ETMEDEN yapılandırmayı ve dil dosyasını yükler.
public static function Load($type = '', $name = '', $nominc = false, $status = '');
public static function Config($type, $module);
public static function getInstance(string $type, string $name, array $params = []): ?object;
```

```php
Modules::Load('Addons', 'MyAddon', true);              // yalnız yapılandırma, sınıf yok
$my_config = Modules::Config('Addons', 'MyAddon') ?: [];

if (($my_config['status'] ?? false) && License::valid_addon('my-addon')) {
    // ... dinleyicileri burada kaydedin
}
```

Örneği bir dinleyicinin içinde kurun. Kayıt yapıp yapmayacağınıza karar vermek için modülü her istekte kurmak, bir modülün paneli yavaşlatmasının en yaygın sebebidir.

## Örnek

Dört noktaya dokunan bir kanca dosyası: bir olay, bir değer, bir sayfa ve zamanlanmış bir görev.

```php
<?php

// src/ sınıfları otomatik yüklenmez: bu dosyanın andığı şeyi, anmadan önce include edin.
include_once __DIR__ . DS . 'src' . DS . 'Notifier.php';

use WISECP\Modules\Addons\MyAddon\Src\Notifier;

// Sınıfsız yapılandırma. Tek ucuz çağrı, aşağıda bir şey kaydedilip kaydedilmeyeceğine karar verir.
Modules::Load('Addons', 'MyAddon', true);
$my_config = Modules::Config('Addons', 'MyAddon') ?: [];

if (!($my_config['status'] ?? false)) return;

/* Bir olay. Bu kanca ($id, $data) ile tetiklenir: yeni users_products kimliği ve eklenen
   satır. Dönüş değeri yoksayılır, yani işi yapın ve hiçbir şey söylemeyin. */
Hook::add('action:service.created', 10, function ($id, $data) {
    Notifier::service_created((int) $id, is_array($data) ? $data : []);
});

/* Bir değer. $totals REFERANSLA bildirilir, çünkü bu kanca runRefs ile tetiklenir;
   $invoice ve $items salt okunur bağlamdır. */
Hook::add('filter:invoice.totals', 10, function (&$totals, $invoice, $items) {
    if ((int) ($invoice['legal'] ?? 0) !== 1) return;

    $fee = (float) ($GLOBALS['my_addon_fee'] ?? 0);
    if ($fee <= 0) return;

    $totals['total'] = round((float) $totals['total'] + $fee, 4);
});

/* İşaretleme. Dizeyi döndürün; yüzey onu basar. Örneği YUKARIDA değil BURADA kurun. */
Hook::add('ui:admin.service_detail.bottom', 20, fn ($service) =>
    Modules::getInstance('Addons', 'MyAddon')->render_service_panel(is_array($service) ? $service : []));

/* Bir yetenek. Kayıt kancaları besledikleri şeyin bootstrap'ı sırasında koşar. */
Hook::add('register:cronjobs', 1, function () {
    include_once __DIR__ . DS . 'cronjobs' . DS . 'SyncTask.php';

    CronJobQueue::register(
        \WISECP\Modules\Addons\MyAddon\CronJobs\SyncTask::TYPE,
        \WISECP\Modules\Addons\MyAddon\CronJobs\SyncTask::class
    );
});
```

Okuma tarafı: yukarıdaki değer kancasını tetikleyen çekirdek çağrısı.

```php
// coremio/helpers/Invoices.php, recalculate_totals() içinde: rakamlar kurulduktan sonra
// ve satıra yazılmadan önce.
Hook::runRefs('filter:invoice.totals', $totals, $invoice, $items);

// $totals artık dinleyicilerin içinde bıraktığı şeydir ve $persist onu yazar.
```

Kendi modülünüz bir genişletme noktası yayınladığında aynı özen geçerlidir:

```php
// Değerle: diğer modüllerin izleyebileceği bir olay.
Hook::run('action:myaddon.sync_finished', $summary, $startedAt);

// Referansla: burada bağlam olanlar dahil HER argüman referanstır, bu yüzden her biri
// önce düz bir değişken olmalıdır.
$context = ['id' => $recordId, 'lang' => Language::selected()];
Hook::runRefs('filter:myaddon.payload', $payload, $context);

// Bir kapı: herhangi bir dinleyiciden gelen boş olmayan dönüş işlemi durdurur.
$veto = Hook::run('gate:myaddon.export', $recordId);
if (array_filter($veto)) throw new Exception((string) current(array_filter($veto)));
```

## Tuzaklar

> **Referanslı varyantın her argümanı referanstır, bağlam olanlar dahil**
> 
> İmza variadic ve referanslıdır. *Herhangi bir* konumdaki sabit, cast, fonksiyon dönüşü ya da null-birleştirme ifadesi ölümcül hatadır. Her birini önce düz bir değişkene alın; değer geçen varyantta bu sorun yoktur.

> **Dinleyici içindeki exception yutulur**
> 
> Motor her throwable'ı yakalar, loga yazar ve devam eder. Entegrasyonunuz gerçekleşmez, sayfa normal görünür, hiçbir şey bunu söylemez; en sık sebep unutulmuş bir `include_once`'tur. Dinleyici hareketsiz görünüyorsa önce hata logunu okuyun.

> **İlk kancadan önce koşması gereken iş buraya ait değildir**
> 
> Kanca dosyaları tembel okunur, ilk çalıştırma çağrısında; kuruluş tarihi sıfır tarihiyken hiç okunmaz. Daha erken gereken her şey (bir rota, bir izin kataloğu) router kurulurken yüklenen `router.php` dosyasına konur.

> **Dosya gövdesi bir kancaya dokunan her istekte çalışır**
> 
> En üst seviyede modülünüzü kurmak, veritabanına sormak ya da bir servise çıkmak bunun bedelini her seferinde, müşteri sayfaları dahil ödetir. Yapılandırmayı sınıfsız yükleyiciyle okuyun ve örneği dinleyicinin içinde kurun.

> **Öncelik çakışması çözülür, bildirilmez**
> 
> Aynı sayıyla iki dinleyici kaydetmek ikincisine bir sonraki boş yuvayı verir; sıra kayıt sırasını, o da modül dizinlerinin alfabetik sırasını izler. Kalabalıktan uzak bir sayı seçin.

## İlgili Makaleler

- [Kancalar Nasıl Çalışır](https://dev.wisecp.com/tr/kancalar-nasil-calisir)
- [Kanca Kataloğu](https://dev.wisecp.com/tr/kanca-alanlari)
- [Kanca Dinleyicisi Yazma](https://dev.wisecp.com/tr/kanca-dinleyicisi-yazma)
- [API Ucu Açma](https://dev.wisecp.com/tr/api-ucu-acma)
- [Pano Widget'ı Ekleme](https://dev.wisecp.com/tr/pano-widgeti-ekleme)
- [Modül Anatomisi](https://dev.wisecp.com/tr/modul-anatomisi)
