Çekirdeğe Dokunmadan Çalışma

1.6k görüntülenme Markdown

Platformun yayımladığı genişleme noktalarının işlenmiş bir kataloğu: her birinin neye ulaşıp neye ulaşamadığı ve önünüzdeki değişiklik için doğrusunun nasıl seçileceği.

Genel Bakış

Aşağıdaki her noktanın adı belli bir giriş dosyası, bir sözleşmesi ve bir sınırı var.

Noktalar birbirinin dengi değil. Bir kanca yalnız yayımlanmış olduğu yere uzanır. Bir modül kendi dizinine, kendi tablolarına ve kendi ayarlarına sahiptir ama on altı tip sözleşmesinden birine sığmak zorundadır. Bir tema website'i kapsar, admin panelinde karşılığı yoktur.

Ön Koşullar

  • Arkasında bir nokta olmayan dosyaya yükseltme koşusunun ne yaptığı için Güncellemeye Dayanıklı Çalışma İlkeleri.
  • Sistem dizinine yazma erişimi ve görebildiğiniz bir sayfayı yenileyebilme imkânı.
  • Adı konmuş bir hedef: değiştirmek istediğiniz ekran, akış ya da değer. "Ödeme akışını değiştirmek", "sepet toplamı filtresinin verdiği değeri değiştirmek" hâline gelir.

Yapı

Genişleme Noktası Haritası

Değiştirdiğiniz şeye uyan satırı bulun, sözleşmesini aşağıdan okuyun.

Neyi değiştirmeniz gerekiyorNoktaSınırı
Çekirdeğin hesapladığı bir değer (toplam, liste, veri yükü)Bir filter: kancasıYalnız yayımlanmış olan yerde. Jenerik bir "her metottan önce" araya girme yoktur
Olan bir şeye tepki verme (eşitleme, bildirim, denetim kaydı)Bir action: kancasıDönüş değeriniz atılır, istisnanız yutulur; loglamazsanız hatalarınız görünmez
Kendi kuralınıza göre bir işlemi reddetmeBir gate: kancasıReddet ya da izin ver, arası yok. İşlemi değiştiremezsiniz
Mevcut bir sayfaya işaretleme eklemeBir ui: kancasıYalnız var olan konumlarda. Admin paneli için şablon geçersiz kılma katmanı yoktur
Bir şey sağlama, ödeme alma, ad tescili, mesaj göndermeUygun tipte bir modülOn altı tip, her biri sabit bir metot sözleşmesiyle. Hiçbirine sığmayan ihtiyacın modül tipi yoktur
Tümüyle size ait bir admin sayfasıModül admin alanıOn altı tipin on birinde tanınır; kalanlar sessizce hiçbir şey olarak kaydedilir
Size ait bir HTTP ucuAPI rota filtresiİşleyiciniz modül örneğinde gerçek bir metot olmalıdır. Toplayıcı bir dağıtım yoktur
Mevcut bir sayfada yeni bir admin AJAX işlemiAdmin operation yedek kancasıSarmalayıcı korumalarının hiçbiri geçerli değildir: yetki kontrolü, demo kapısı ve istek başlığı kontrolü yok
Zamanlanmış işModülden kaydedilen bir cron işleyicisiYeniden deneme ve zamanlama kuyruğundadır. Diliminizin ne zaman koşacağını siz seçmezsiniz
Ziyaretçiye dönük görünümBir tema diziniYalnız website. Admin paneli, tema katmanı olmayan düz PHP şablonlarıdır
Bir bileşen metninin ifadesiÇeviri filtresiYalnız bileşen okumaları. Paket seviyesindeki metinlerin okuma anı filtresi yoktur
Şablona ulaşan değişkenlerŞablon değişkeni filtresiDeğerle çalışır ve dizi döndüren son dinleyici tüm kümenin yerine geçer
Müşteri, talep ya da ürün kaydındaki veriOperatörün tanımladığı özel alanlarKod değil yapılandırma. Depolama ve gösterim alırsınız, davranış değil

Dosyalar Nereye Gider

Hepsini tek bir dizin taşır; yukarıdakilerin hiçbiri bu ağacın dışına uzanmaz.

coremio/modules/Addons/Acme/
Acme.php            # çekirdeğin örneklediği modül sınıfı
config.php          # ayarlar; modül yazar, bir sürüm asla yazmaz
hooks.php           # otomatik dahil edilir: her Hook::add burada yaşar
router.php          # admin yönlendirmesi için otomatik dahil edilir: admin alanını kaydeder
AdminArea.php       # size ait admin sayfaları
lang/en.php         # metinleriniz, istediğiniz gibi anahtarlanmış
cronjobs/Sync.php   # zamanlanmış işleyiciniz, hooks.php'den kaydedilir
src/AcmeClient.php  # size ait düz sınıflar, `new` ile kurulur
İki dosya sizin için yüklenir, geri kalanını siz dahil edersiniz

Kanca yükleyicisi her modül dizinindeki hooks.php dosyasını tarar, admin yönlendirmesi de router.php dosyasını okur. Diğer her şeye bu ikisinden ulaşılır: açık bir include_once ile ya da çekirdeğin sizin için kurduğu modül sınıfı üzerinden.

Adım Adım

Gerçekte Neyi Değiştirdiğinizin Adını Koyun

  1. Ekranı açın ve değiştirmek istediğiniz tam değeri, kontrolü ya da anı bulun.
  2. Onu üreten kodu bulun. Ekrandaki metni, formdaki alan adını ya da adresteki rota anahtarını arayın, değerin sahibi helper'a ya da operation'a kadar geri izleyin.
  3. Beş fiilden hangisinin geçerli olduğuna karar verin. Bir değeri dönüştürmek, bir olaya tepki vermek, bir eylemi reddetmek, işaretleme basmak, listeye kayıt eklemek. Kanca kategorisi o fiildir.

Noktayı Seçin

  1. Değişikliğinizin yaşadığı alanı hooks/INDEX.md kataloğunda arayın. Dizin her kancayı alanıyla ve tetiklendiği tam dosya ve satırla verir.
  2. Kanca varsa önce parametre tablosunu okuyun. Bir argümanın referansla gelip gelmediği o sayfadadır ve dinleyicinizin nasıl yazılacağına o karar verir.
  3. Kanca yoksa ama iş bütün bir yetenekse (bir panel, bir geçit, bir sağlayıcı) bu bir modüldür. Seçtiğiniz tip, çekirdeğin çağıracağı metotları sabitler.
  4. İkisi de uymuyorsa çekirdeği yamalamak yerine jenerik bir genişleme noktası isteyin. Modülünüzün, bayrağınızın ya da tablonuzun adını taşıyan kanca jenerik değildir; kararın adını taşıyan kanca jeneriktir.

Kaydedin

  1. Modül dizininizde hooks.php yoksa oluşturun. İstek başına bir kez, ilk kanca etkinliğinde otomatik olarak dahil edilir.
  2. Dosyanın tamamını modülünüzün etkin olmasına bağlayın. Yapılandırmayı hafif kipte yükleyip durumu okuyun, kaydı ondan sonra yapın.
  3. Bir öncelikle kaydedin. Düşük olan önce çalışır, alınmış bir numara boş bulunana kadar artırılır; hiçbir dinleyici sessizce düşmez.
  4. Modül örneğini dosyanın başında değil dinleyici gövdesinin içinde, kanonik fabrikayla kurun.

Sağ Çıkacağını Kanıtlayın

  1. Sayfayı yenileyin ve davranışın değiştiğini doğrulayın. Kaydı düzgün ama gövdesi hata fırlatan bir dinleyici, hiç ateşlenmemiş bir kancayla birebir aynı görünür. Hata yakalanır ve loglanır.
  2. Kendi ağacınızda çekirdek yollarını arayın. Düzenlediğiniz, coremio/classes, coremio/controllers, coremio/helpers ya da templates/admin altındaki her şey bir gelecek arızadır.
  3. Kendi dizininizin dışından çağırdığınız her kancayı, sınıfı ve metodu listeleyin. O liste, bir yükseltmeden sonra yeniden kontrol edeceğiniz uyumluluk sözleşmesidir.
  4. Modülü kapatın ve sayfayı yenileyin. Ekran hatasız biçimde kendi varsayılan davranışına dönmelidir. Dönmüyorsa size ait bir şey durum kapısının dışında çalışıyordur.

Referans

Kayıt İmzaları

imzalar
// coremio/classes/Hook.php
public static function add($name, $priority, $properties = []): void;

// coremio/classes/Modules.php - sınıfı dahil etmeden yapılandırmayı yükler ($nominc = true),
// sonra okur. hooks.php içinde ucuz bir "modülüm açık mı?" kapısı için ikisi de gerekir.
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;

// coremio/classes/ModuleAdminArea.php - modülün router.php dosyasından çağrılır.
public static function register(string $areaClass): void;
public static function get(string $type, string $name): ?array;

// coremio/helpers/CronJobQueue.php - bir register:cronjobs dinleyicisinden çağrılır.
public static function register(string $type, string $handlerClass): void;
public static function dispatch(string $type, array $payload = [], array $opts = []): int;

// coremio/cronjobs/CronJobHandler.php - bir ARAYÜZDÜR, yani genişletmeyin, uygulayın.
// Başarı için true, başarısız olup yeniden denenmek için false, ya da admin Sonuçlar
// sekmesine bir yük de vermek için ['success' => bool, 'result' => array] döndürün.
public function handle(array $payload, array $job): bool|array;
Modules::Load() Üçüncü argüman $nominc = true, modül sınıfını dahil etmeden config.php ve dil dosyasını yükler. Kapı biçimi budur: hooks.php içinde her istekte koşacak kadar ucuzdur.
Modules::Config() Yalnız yükleme önbelleğinden okur. Load() çağrılmadan çağrılırsa hiçbir şey dönmez; bu "modül kapalı" diye okunur ve dosyanızın tamamını kapatır. Sıra isteğe bağlı değildir.
ModuleAdminArea::register() Sınıf adını alır, tip ve adı ad alanından çözer, üç rota ekler (slug, slug/(?), slug/(?)/(?)) ve menü kaydını yapar. Sınıfı reddettiğinde de dahil olmak üzere hiçbir şey döndürmez.
CronJobQueue::register() İlk argüman işleyicinin TYPE sabiti, ikincisi sınıf adıdır. Keşif işleyicisi (.discover ile biten bir tip) ayrıca bir FREQUENCY sabiti ister; zamanlayıcının okuduğu değer odur.

Her Nokta Karşılığında Ne Bekler

KancaAteşlenmesiSözleşme
register:admin.operationsrun(), argümanlar: controller adı, operation adıReddetmek için null döndürün. Dizi döndürürseniz JSON'a çevrilip yanıtın tamamı olarak gönderilir
filter:api.routesrunRefs(), argümanlar: referansla rota listesi, referansla hedef kitleListeye demet ekleyin. Dönüş değeri kullanılmaz. İlk eşleşme kazanır, gölgelemeye öncelik karar verir
register:cronjobsrun(), argümansızİşleyici dosyanızı dahil edin ve kuyruğun kayıt metodunu çağırın. Dönüş değeri kullanılmaz
filter:i18n.translationrunRefs(), argümanlar: referansla metin, anahtar, dil koduMetni yerinde değiştirin. Yalnız bileşen okumalarında ve yalnız çözülen değer bir dize olduğunda ateşlenir
filter:template.variablesrun(), argümanlar: şablon yolu, veri dizisiDeğiştirilmiş veri dizisinin tamamını döndürün. Dizi döndüren son dinleyici tek başına kazanır
register:admin.menurun(), argümansızMenü ağacına yazıp true döndürün ya da hiçbir katkı yapmamak için false döndürün
API rota demeti, konum konum
// [0] METHOD    [1] desen (/api/v1 sonrasındaki tam yol, {x} yakalar)
// [2] Group     [3] Action   [4] public?  [5] authOnly?  [6] audience
$routes[] = ['GET', 'acme/status', 'Module:Addons/Acme', 'status', false, true, 'admin'];

// "Module:{Type}/{Name}" grubu, modül örneğinin şu metoduna dağıtır
//   api_{Action}(WISECP\Api\Core\Request $request, array $match)
// ve kararı method_exists verir: __call yedeği yoktur. [3] içindeki bir yazım hatası
// görebileceğiniz bir hata değil, 404'tür.
//
// [4] public = true   → hiç kimlik bilgisi yok; modül kendi erişimini kendi denetler
// [5] authOnly = true → geçerli bir kimlik bilgisi yeter, kapsam kontrol edilmez (serbest
//                       yüzeydeki varsayılan, çünkü modül kapsamları katalogda değildir)
// [6] audience        → 'admin' | 'client' | 'any', yalnız public false iken bakılır

Genişleme Noktasının Olmadığı Yerler

Dört boşluk. Hiçbirinin desteklenen bir kaçamağı yok.

Admin şablonları geçersiz kılınamaz Website'in teması vardır, admin panelinin yoktur. Şablonları düz PHP'dir ve tek bir sabit dizinden yüklenir. Tek giriş yolu, zaten var olan bir konumdaki ui: kancasıdır. Olmayan bir konum yeni bir kanca talebidir.
Beş modül tipi admin sayfası açamaz Admin alanı çözücüsü on bir tipi kabul eder: Servers, Payment, Registrars, Product, Addons, SMS, Mail, Authentication, Pipe, Imports ve Fraud. Bir SocialAuth, Captcha, Currency, IP ya da Storage modülü kimlik kontrolünde reddedilir ve register() hiçbir şey yapmadan, hiçbir yerde hata bırakmadan döner. Yanına bir Addons modülü koyun ya da tipin eklenmesini isteyin.
Paket metinlerinin okuma anı filtresi yoktur Çeviri filtresi yalnız bileşen okumasının içinde ateşlenir. Paket okumasıyla alınan metinler hiçbir kancadan geçmez. Birini değiştirmenin tek yolları operatör dil düzenleyicisi ve kendi dosyanızdaki kendi anahtarınızdır.
Bir çekirdek tablosuna eklediğiniz kolonun sahibi yoktur Bunu hiçbir şey yasaklamaz ve migration içe aktarıcısı da takılmaz: yinelenen kolon hatası "zaten uygulanmış" sayılır. Ama hiçbir çekirdek kodu onu okumaz ya da yazmaz. Sonraki bir sürüm aynı kolon adını eklerse atlanan onun tanımı olur; sizinki kalır, çekirdeğin beklentisi tutmaz. Adın önüne modülünüzü ekleyin, tercihen kendi tablonuzu kullanın.

Örnek

Dört noktayı kullanan, kendi dizininin dışına dokunmayan küçük bir eklenti.

coremio/modules/Addons/Acme/hooks.php
<?php

// Ucuz kapı: yalnız yapılandırma, sınıf dahil edilmez. Aşağıdaki her şey if'in içinde,
// yani kapalı bir modül hiçbir şey kaydetmez ve yalnız bir dosya okuması kadar tutar.
Modules::Load('Addons', 'Acme', true);
$acme = Modules::Config('Addons', 'Acme') ?: [];

if ($acme['status'] ?? false) {

    // 1. Bu modülün sahibi olmadığı bir sayfadaki AJAX eylemi. services controller'ında
    //    operation=acme_resync olarak ulaşılır. Bu çağrıyı hiçbir şey sarmalamaz,
    //    yani yetki kontrolünü yapmak bize düşer.
    Hook::add('register:admin.operations', 1, function ($cname, $operation) {
        if ($operation !== 'acme_resync')       return null;
        if ($cname !== 'services')              return null;
        if (!Admin::isPrivilege(['SERVICES_OPERATION']))
            return ['status' => 'error', 'message' => Language::gc('acme/no-privilege')];

        return Modules::getInstance('Addons', 'Acme')->resync_request();
    });

    // 2. Kimlik bilgisi isteyen admin yüzeyinde bize ait bir uç.
    Hook::add('filter:api.routes', 1, function (&$routes, &$audience) {
        if ($audience !== 'admin') return;
        $routes[] = ['GET', 'acme/status', 'Module:Addons/Acme', 'status', false, true, 'admin'];
    });

    // 3. Zamanlanmış bir görev. Dosya açılışta değil burada dahil edilir: cron kaydı
    //    yalnız çekirdek dizinini tarar.
    Hook::add('register:cronjobs', 1, function () {
        include_once __DIR__ . DS . 'cronjobs' . DS . 'Sync.php';
        CronJobQueue::register(\WISECP\Modules\Addons\Acme\CronJobs\Sync::TYPE,
                               \WISECP\Modules\Addons\Acme\CronJobs\Sync::class);
    });

    // 4. Bir dil dosyasını düzenlemeden tek bir bileşen metnini yeniden ifade edin.
    //    Referansla ve yalnızca ifadesinin sahibi olduğumuz tam anahtar için.
    Hook::add('filter:i18n.translation', 1, function (&$text, $key, $lang) {
        if ($key !== 'admin/services/status-active') return;
        $text = Language::gc('acme/status-live');
    });
}

İlk noktanın diğer tarafı. Metot, çekirdeğin kodladığı diziyi döndürür; yanıtın şeklinin sahibi odur.

coremio/modules/Addons/Acme/Acme.php
public function resync_request(): array
{
    $id = (int) Filter::init('POST/id', 'rnumbers');
    if (!$id) return ['status' => 'error', 'message' => Language::gc('acme/id-required')];

    // Fırlatmak, modülün başarısızlığı bildirme yoludur; burada bir AJAX çağrısını
    // doğrudan yanıtlıyoruz, dolayısıyla hatayı bunun yerine elle şekillendiriyoruz.
    try {
        $rows = $this->client()->resync($id);
    }
    catch (\Throwable $e) {
        return ['status' => 'error', 'message' => $e->getMessage()];
    }

    return ['status' => 'successful', 'message' => Language::gc('acme/resynced'), 'count' => $rows];
}

// Yukarıda kaydedilen API ucu buraya iner. Ad, api_ artı demetteki Action'dır
// ve kararı veren tek şey method_exists'tir.
public function api_status(\WISECP\Api\Core\Request $request, array $match): array
{
    return ['data' => ['enabled' => true, 'last_sync' => Config::getd('acme_last_sync')]];
}

Admin sayfası: iki dosya ve tek satırlık bir kayıt.

router.php ve AdminArea.php
// coremio/modules/Addons/Acme/router.php
namespace WISECP\Modules\Addons\Acme;

include_once __DIR__ . DS . 'AdminArea.php';

\ModuleAdminArea::register(AdminArea::class);

// coremio/modules/Addons/Acme/AdminArea.php
namespace WISECP\Modules\Addons\Acme;

class AdminArea extends \ModuleAdminArea
{
    public static function manifest(): array
    {
        return [
            'title'      => 'Acme',
            'slug'       => 'acme',
            'privileges' => ['TOOLS_ADDONS'],
            'menu'       => ['path' => ['TOOLS'], 'name' => 'Acme'],
        ];
    }

    // /{admin}/acme          → page_home()
    // /{admin}/acme/report   → page_report()
    public function page_home(array $params): string|array
    {
        return ['content' => '<p>Acme</p>', 'page_title' => 'Acme'];
    }

    // Aynı adrese operation=refresh ile POST
    public function op_refresh(\Operation $operation): bool
    {
        $operation->demo();

        return $operation->output(['status' => 'successful']);
    }
}

Tuzaklar

Operation yedek kancası her sarmalayıcının dışında çalışır

Normal bir operation bir sarmalayıcıdan geçer. O sarmalayıcı yetki listesini kontrol eder, panelin AJAX katmanından gelmeyen isteği reddeder ve demo kipinde yazmaları engelleyen nesneyi kurar. Yedek kancasına ancak sarmalayıcı reddettikten sonra ulaşılır, yani bunların hiçbiri geçerli değildir. Yetkiyi kendiniz, dinleyicinin içinde, ilk iş olarak kontrol edin.

Şablon değişkeni filtresinde kazanan her şeyi alır

Değerle ateşlenir ve çağıran, dönen her diziyi bir öncekinin üzerine atar. Dizi döndüren son dinleyici kümenin tamamının yerine geçer; aynı şablondaki iki dinleyici birleşmez. Size verilen diziyi okuyun, değiştirin ve tamamını döndürün.

Reddedilen bir admin alanı kaydı sessizdir

Kayıt üç durumda hiçbir şey yapmadan döner. Sınıf bir alt sınıf değilse, ad alanı beklenen biçimde değilse ya da modül tipi tanınan on bir tipten biri değilse. İstisna yok, log satırı yok, menü kaydı yok. Sayfanız bulunamadı verdiğinde önce tipe bakın.

hooks.php içinde dosya seviyesinde yapılan iş her istekte çalışır

Kanca yükleyicisi, kancalarınızdan biri ateşlenecek olsun ya da olmasın dosyayı dahil eder. Bir dinleyici gövdesinin dışına yazılmış veritabanı sorgusu, uzak çağrı ya da modül örneği oluşturma her sayfa yüklemesinde ödenir. Yapılandırmayı yükleyin, durumu kontrol edin, geri kalanı bir closure'ın içine koyun.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.