Kur Modülü Yazma

1.7k görüntülenme Markdown

Kur modülü bir oran kaynağıdır. Tek bir soruyu yanıtlar: benim para birimimin bir birimi şu diğerlerinde ne eder? Zamanlanmış eşitleme yanıtı para birimi tablosuna yazar.

Genel Bakış

Kur, en küçük modül tipidir ve taban sınıfı olmayan tek tiptir. Sözleşme ördek tiplemesiyle kurulur: çekirdek sınıfınızı çözer, adına göre bir metot arar ve çağırır. Yedi modül gelir; ücretsiz varsayılan OpenRates ve platformun kendi WAtlas'ı dahil.

Taban sınıf olmadığı için sözleşme yalnız çağrı noktalarında görünür. Üç tanedir ve tek bir zorunlu metotta anlaşırlar.

çekirdeğin bir Kur modülünü çağırdığı her yer
// 1. coremio/helpers/Money.php: tek oran yolu, eşitleme görevi bunu kullanır
$instance = self::currency_module();               // yapılandırılmış modül, yoksa OpenRates
if (!$instance) return (string) Language::gc("admin/financial/currency-module-unavailable");
$rates = $instance->exchange_rates($from, $to);
if (!$rates) return $instance->error;              // modülün kendi mesajı operatöre ulaşır

// 2. coremio/operations/AdminFinancialCurrencies.php: ayar kaydı, ardından test butonu
$instance = Modules::getInstance("Currency", $module);
if ($instance && method_exists($instance, "save_config")) $instance->save_config($module_data[$module]);
// ...
if (!method_exists($instance, "exchange_rates")) throw new Exception("Module does not implement exchange_rates().");
$instance->config = array_replace_recursive($instance->config, $module_data[$module]);   // kaydedilmemiş form değerleri
$rates = $instance->exchange_rates($localCode, $targets);

// 3. coremio/api/Resources/Admin/Concerns/FinancialCurrencies.php: aynı ikisi, API üzerinden

exchange_rates() zorunludur; modülünüzün tek bir ayarı olduğu anda save_config() da zorunlu olur. page_settings() o ayarı gösterir. $config ve $error çağıran taraftan doğrudan okunur, yani var ve public olmak zorundadır.

Ön Koşullar

  • Bir temel para birimi ve karşılık kümesi dönen bir oran sağlayıcısı. Ücretsiz katmanına dikkat edin: eşitleme zamanlanmış çalışır.
  • Sistemin yerel para birimi tanımlı olmalıdır; her oran ona karşı ifade edilir.
  • Örnek Modules::getInstance('Currency', 'Acme') ile alınır.
  • Önce coremio/modules/Currency/OpenRates'i okuyun: yüz satırın altında, kimlik bilgisi istemez, sözleşmenin tamamı tek dosyada. ExchangeRateAPI buna API anahtarı ve ayar alanı ekler.

Yapı

coremio/modules/Currency/Acme/
Acme.php        namespace WISECP\Modules\Currency; class Acme  (extends yok)
config.php      DÜZ bir dizi, diğer modül tiplerindeki meta/settings ayrımı değil
lang/en.php
lang/tr.php
Yapılandırma dosyası burada düzdür

Sunucu, Ödeme ve Servis Sağlayıcı modülleri ['meta' => [...], 'settings' => [...]] döner. Kur modülü ayarların kendisini döner ve onları $this->config['api_key'] olarak okur. İç içe şekil, hep boş kalan bir anahtar verir.

Sınıf hiçbir şeyi genişletmez, yani kendi durumunu kendi bildirir. Çağıranların dokunduğu arayüz bu dört özelliktir.

Adım Adım

Sınıfı ve Durumunu Bildirin

Acme.php
<?php
namespace WISECP\Modules\Currency;

class Acme
{
    public string  $name   = "Acme";
    public ?array  $config = null;
    public ?array  $lang   = null;
    public ?string $error  = null;      // false döndüğünüzde çağıran bunu okur

    public function __construct()
    {
        // Global sınıflar bu ad alanının içinde baştaki ters bölüyü ister,
        // yoksa PHP WISECP\Modules\Currency\Modules sınıfını arar.
        $this->config = \Modules::Config("Currency", $this->name);
        $this->lang   = \Modules::Lang("Currency", $this->name);
    }
}

Oranları Çekin

Sağlayıcıdan $from'a karşı elinde ne varsa isteyin ve büyük harfli bir kod haritası döndürün. Sonucu $to'ya göre süzmeyin.

Ayarı Gösterin ve Kalıcılaştırın

page_settings() kur ayarları ekranı için ham işaretleme döner. Her girdiye module_data[{ModulAdi}][{anahtar}] adını verin: kaydetme operation'ının save_config()'a verdiği dizi budur. Test butonu aynı diziyi gönderir ve bellekteki $this->config'in üzerine bindirir. Bir anahtar kaydedilmeden önce denenebilir.

Güvenmeden Önce Doğrulayın

  1. Modülü kur ayarları ekranından seçip test butonuna tıklayın. Yerel para birimine karşı iki hedefle exchange_rates() çağırır.
  2. Eşitlemeyi çalıştırın, sonra bir para biriminin currencies.rate değerini sağlayıcının kendi sitesiyle karşılaştırın.
  3. Bir çevrim yapın. Ters kaydedilmiş bir oran makul görünen ama yanlış fiyatlar üretir.

Referans

Sözleşme

çağrı noktalarının gerektirdiği
// ZORUNLU
public function exchange_rates(string $from = '', array $to = []): array|false;

// Modülün herhangi bir ayarı olduğu anda ZORUNLU
public function save_config(array $data = []): bool;

// İSTEĞE BAĞLI: kur ayarları ekranı için işaretleme
public function page_settings(): string;

// Çağıranların doğrudan okuduğu public durum
public string  $name;
public ?array  $config;    // test butonu bellekte bunun üzerine yazar
public ?array  $lang;
public ?string $error;     // exchange_rates() false döndüğünde operatöre gösterilir
$from Sistemin yerel para biriminin büyük harfli ISO kodu; general/currency'den çözülür. Sağlayıcınız küçük harf istiyorsa siz küçültün.
$to Yol göstericidir ve biçimi çağırana göre değişir. Eşitleme [para_birimi_id => 'KOD'] geçer; test butonu düz bir kod listesi geçer. Sonucunuzun anahtarı olarak asla kullanmayın.
dönüş değeri ['USD' => 32.15, 'EUR' => 35.02]; büyük harfli kodlar, float değerler ya da false. Boş dizi, test noktasında başarısızlık sayılır.
oran yönü $from'un bir biriminin hedef cinsinden karşılığı. Saklanan kolon tutar / oran_kaynak * oran_hedef olarak kullanılır; ters çevirmek her fiyatı bozar.
Money::currency_module() Seçili modülü çözer, o eksik ya da kaldırılmışsa OpenRates'e düşer. Yalnız OpenRates yüklenemezse null döner.
Money::get_exchange_rates() Sağlayıcıya giden tek yol. Başarıda dizi döner. Başarısızlıkta $error'ünüzü aynen geri verir: çağırana bir dize (hiç mesaj koymadıysanız null) ulaşır, asla false değil. Her çağıranın is_array() ile bakmasının sebebi budur.

Günlük Çağrı Tavanı

Yardımcı, modülünüze ulaşmadan önce günün çağrılarını coremio/storage/currency-overload-limit.php içinde sayar. 48 çağrıda sağlayıcıya dokunmadan admin/financial/currency-api-limit-exceeded mesajını etkin dilde döner. Bu, yanlış zamanlanmış bir görevden ücretsiz katmanı korur.

Eşitleme Yanıtınızla Ne Yapar

coremio/cronjobs/CurrencySync.php
$rates = Money::get_exchange_rates($localCode, $targets);

if (!is_array($rates))
    return ['success' => false, 'result' => ['error' => is_string($rates) && trim($rates) !== '' ? $rates : self::NO_RATES]];

// Genişletme noktası: sağlayıcının fiyatlayamadığı bir para birimini enjekte edin ya da birini ezin.
Hook::runRefs('filter:money.exchange_rates_fetch', $rates, $localCode, $targets);

// Büyük harfli kod => para birimi id'si; böylece her şeyi döndüren bir sağlayıcı
// yine de yalnız sistemde gerçekten var olan para birimlerini günceller.
$codeToId = [];
foreach ($targets as $cid => $code) $codeToId[strtoupper($code)] = (int) $cid;

foreach ($rates as $code => $rate) {
    $codeUp = strtoupper((string) $code);
    $rate   = (float) $rate;

    if (!isset($codeToId[$codeUp])) continue;          // burada bir para birimi değil: yok sayılır
    if ($rate <= 0 || $rate > 999999999999.99999999) { $skipped++; continue; }   // tutarlılık koruması

    WDB::update('currencies', ['rate' => $rate])->where('id', '=', $codeToId[$codeUp])->save();
}

Hook::run('action:money.exchange_rates_updated', $changes, $localCode);
filter:money.exchange_rates_fetch Çekilen harita üzerinde, hiçbir şey yazılmadan önce çalışır. Sağlayıcının fiyatlayamadığı bir para birimi buradan enjekte edilir.
action:money.exchange_rates_updated Kalıcılaştırmadan sonra, yalnız gerçekten değişen oranlarla çalışır.
filter:money.exchange_rate Eşitleme anında değil, her çevrimin içinde çalışır. Onu marj gibi politikalara saklayın, veri çekmeye asla.

İkisinin de API ikizi vardır: POST /financial/currency-modules/test canlı sağlayıcı çağrısını yapar, POST /financial/currencies/sync görevi anında dağıtır.

Örnek

Tek kimlik bilgisi olan eksiksiz bir modül. Üçünü bağlayan şey alanın adıdır: page_settings()'in gösterdiği şey save_config()'un aldığı şeydir, o da exchange_rates()'in okuduğu şeydir.

Acme.php
public function exchange_rates(string $from = '', array $to = []): array|false
{
    $apiKey = (string) ($this->config["api_key"] ?? '');

    if ($apiKey === '') {
        $this->error = "Acme api_key is not configured.";
        return false;
    }

    $url = "https://api.example.com/v1/" . urlencode($apiKey) . "/latest/" . urlencode(strtoupper($from));

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);

    $response = curl_exec($ch);
    if ($response === false) $this->error = curl_error($ch) ?: "Currency provider request failed.";
    curl_close($ch);

    // Erken dönüşlerden ÖNCE kaydedin: bozuk bir anahtarı ayıklayan operatörün
    // yalnız hatayı değil, hatayı üreten isteği de görmesi gerekir.
    \Modules::save_log("Currency", $this->name, "exchange", [
        'api_url' => $url,
        'from'    => $from,
        'to'      => $to,
    ], $response, $this->error);

    if ($this->error) return false;

    $decoded = \Utility::jdecode(trim((string) $response), true);

    if (!is_array($decoded) || ($decoded["result"] ?? '') !== 'success') {
        $this->error = (string) ($decoded["error-type"] ?? 'Acme returned an error.');
        return false;
    }

    // Büyük harfli anahtarlar, float değerler. $to süzgeç olarak KULLANILMAZ: eşitleme
    // tanıdığını tutar, kalanını yok sayar.
    $result = [];
    foreach ((array) ($decoded["conversion_rates"] ?? []) as $code => $rate)
        $result[strtoupper((string) $code)] = (float) $rate;

    return $result;
}
Acme.php, ayar ve gidiş dönüşü
public function page_settings(): string
{
    $apiKey = htmlspecialchars((string) ($this->config["api_key"] ?? ''), ENT_QUOTES);

    // Girdinin ADI, kaydetme operation'ıyla yapılan sözleşmedir:
    // module_data[Acme][api_key], save_config() içine $data['api_key'] olarak gelir.
    return '<div class="row mb-0 align-items-center">'
        . '<label for="acme-apikey" class="col-sm-3 col-form-label fw-semibold">API Key</label>'
        . '<div class="col-sm-9">'
        . '<input type="text" class="form-control" id="acme-apikey"'
        . ' name="module_data[Acme][api_key]" value="' . $apiKey . '" placeholder="api_key">'
        . '</div></div>';
}

public function save_config(array $data = []): bool
{
    // Birleştirin, değiştirmeyin: kısmi bir gönderim diğer anahtarları silmemelidir.
    $merged = array_replace_recursive($this->config ?: [], $data);

    // file_write() bir .php hedefinde opcode önbelleğini geçersiz kılar; kaydedilen
    // değerin daha bir sonraki istekte okunabilmesinin sebebi budur.
    return (bool) \FileManager::file_write(
        __DIR__ . DS . "config.php",
        \Utility::array_export($merged, ['pwith' => true])
    );
}
config.php, save_config'in yazdığı dosya
<?php
return [
    'api_key'   => '',
    'help-link' => 'https://example.com',
];

Tuzaklar

Yanıtınızı $to'ya göre süzmeyin

Anahtarları eşitlemeden gelirken para birimi id'si, test butonundan gelirken düz tam sayıdır. Ona göre süzmek iki çağıranın farklı sonuç almasına yol açar. Her şeyi döndürün, eşlemeyi kodla çekirdek yapsın.

Hata $error'ı doldurup false dönmelidir

Çağıran $instance->error'ı aynen yazar. Mesaj yoksa operatör yalnız "kur modülü kullanılabilir bir kur döndürmedi" gibi genel bir not görür; bu not sebebi söylemez.

Günde kırk sekiz çağrı, sizin kodunuz koşmadan sayılır

Sayaç başarılı çağrı başına değil, çağrı başına artar; bir test döngüsü zamanlanmış çalışmayla aynı bütçeyi yakar. Sağlayıcıdan şüphelenmeden önce tavana bakın.

Global sınıflarda baştaki ters bölüyü kullanın

Dosya namespace WISECP\Modules\Currency bildirir; nitelenmemiş bir Modules::Config() o ad alanındaki bir sınıfa çözülür ve çalışma anında patlar.

Eksik bir modül fiyatlamayı bozmaz

Seçili modül yüklenemediğinde çözümleyici kimlik bilgisi istemeyen OpenRates'e düşer. Oranlar kaybolmaz, bayatlar.

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.