Skip to content
%70 lansman · 10$/saat · Teklif al

Event’lerle güncellemeye dayanıklı OpenCart 4 eklentisi yazmak

OCMOD çekirdek değiştiği gün neden çalışmayı bırakır ve event sistemi özelleştirmelerinizi güncellemeler boyunca nasıl ayakta tutar.

NE
Nazlı Erdoğan
Software Engineer — Node.js/Python · · 12 dk okuma

Event handler sınıfını gösteren kod editörü

Çekirdek dosyayı yamalamak neden bir yerde biter

Her mağaza er geç çekirdekte olmayan bir davranışa ihtiyaç duyar: sepete ekle düğmesinin altına bir teslimat notu, ERP’ye gönderilen bir alan. On yıl boyunca standart cevap OCMOD’du: çekirdek kaynakta bir kod satırını arayıp sizin kodunuzu etrafına ekleyen bir XML dosyası. O satır değişene kadar da kusursuz çalışır.

Sonra bir güncelleme dosyayı yeniden yazar, arama metni artık eşleşmez ve değişiklik sessizce uygulanmayı bırakır. İyi senaryo bu. Kötüsü, yaklaşık eşleşmedir: yama biraz farklı bir bağlama düşer ve bozuk değil yanlış bir sayfa üretir — bunu da bir müşteri fark edene kadar kimse görmez.

Event’ler ilişkiyi tersine çevirir. Kodunuzun başkasının dosyasının neresine sokuşturulacağını tarif etmek yerine, isteğin hangi noktasında çağrılmak istediğinizi söylersiniz ve sizi OpenCart çağırır. Güncellemeden sonra dosya bambaşka görünebilir, ama tetikleme noktası hâlâ oradadır. Bütün gerekçe bu; OpenCart 4’ün kendi hazır işlevlerinin önemli bir kısmını event’lerle kurmasının nedeni de bu.

  • Event’ler, tanımlı bir noktada davranış eklemek, sarmalamak veya değiştirmek istediğiniz her yerde kazanır: bir kontrolcü çağrısı, bir model metodu, işlenmiş bir şablon, yüklenen bir dil dosyası.
  • Event’ler, kendi tetikleyicisi olmayan bir Twig şablonunun derinindeki işaretlemeyi yeniden kurmanız gerektiğinde kaybeder — orada ya alt temada şablonu ezersiniz ya da işlenmiş çıktı üzerinde metin değiştirmeye düşersiniz.
  • Event’lerin bedeli bir pakettir. Tek dosyalık event diye bir şey yok: eklenti klasörü, bir manifest ve bir kurulum yordamı gerekir. İki satırlık bir değişiklik için ağır gelir — ilk güncellemeye kadar.

Event aslında nedir

Event, oc_event tablosundaki bir satırdır: bir tetikleyici, bir eylem, bir durum ve bir sıra değeri. OpenCart açılışta etkin satırları okur ve her birini bir dinleyici olarak kaydeder. Dört tetikleyici ailesi var ve erişmek isteyeceğiniz hemen her yeri kapsıyorlar.

Saklandığı yer
oc_event
Tetikleyici ailesi
controller · model · view · language
Çalışma sırası
sort_order, artan
  • Controller tetikleyicileri $this->load->controller() üzerinden yapılan çağrıların çevresinde işler; OpenCart sayfaları ve parçalarını böyle üretir.
  • Model tetikleyicileri $this->load->model() ile ulaşılan her metodun çevresinde işler; böylece sorgunun sahibi olmadan veriyi izleyebilir veya şekillendirebilirsiniz.
  • View tetikleyicileri $this->load->view() çevresinde işler — bir kez işleme öncesinde, veri dizisi hâlâ düzenlenebilirken; bir kez de sonrasında, elinizde işlenmiş HTML ile.
  • Language tetikleyicileri $this->load->language() çevresinde işler; arayüz metinlerini tam bir dil paketi göndermeden eklemenin veya ezmenin temiz yolu budur.

Her ailenin bir before bir de after biçimi var. Handler argümanları referansla gelir; bütün mesele de budur: before içinde $args’ı değiştirmek asıl çağrının ne alacağını değiştirir, after içinde $output’u değiştirmek ziyaretçinin ne göreceğini değiştirir. Bir kural kendi cümlesini hak ediyor: değer döndüren bir before handler’ı asıl çağrının tamamının yerine geçer. Bu gerçekten kullanışlı bir araçtır ve bir sayfayı kazara boşaltmanın en kolay yoludur.

Kendi tetikleyici metninizi yazmadan önce kurulumunuzun hazır getirdiklerini okuyun:

SELECT code, `trigger`, action, sort_order
FROM oc_event
WHERE status = 1
ORDER BY `trigger`;
Tetikleyiciyi ezberden yazmak yerine bir çekirdek satırının biçimini kopyalayın. İnsanların kaçırdığı iki ayrıntı var: uygulama öneki (catalog/ ya da admin/) ve model metodunu rotasından ayıran nokta. Bir karakteri yanlış olan tetikleyici hiç çalışmaz ve hiç hata vermez — sadece hiçbir şey yapmaz, ki bu kovalanacak en kötü hata türüdür.

Paket: tek klasör, tek manifest

OpenCart 4 eklentilere bir ev verdi. Gönderdiğiniz her şey extension/<kod>/ altında yaşar; kendi admin, catalog ve system ağaçları ve kökte bir install.json bildirimi ile. Rota, uygulama parçası çıkarılmış hâliyle klasör yolunu yansıtır; namespace de rotanın StudlyCase karşılığıdır.

extension/stock_note/
├── install.json
├── admin/
│   ├── controller/module/stock_note.php   -> extension/stock_note/module/stock_note
│   ├── language/tr-tr/module/stock_note.php
│   └── view/template/module/stock_note.twig
├── catalog/
│   ├── controller/event/product.php       -> extension/stock_note/event/product
│   └── language/tr-tr/module/stock_note.php
└── system/
    └── library/stock_note/client.php

// install.json
{
  "name": "Stock Note",
  "version": "1.0.0",
  "author": "Sirketiniz",
  "link": "https://example.com"
}

Namespace kuralı herkesi bir kez yakalar. extension/stock_note/catalog/controller/event/product.php dosyası Opencart\Catalog\Controller\Extension\StockNote\Event namespace’ini ve \Opencart\System\Engine\Controller’dan türeyen Product sınıfını tanımlar. Tek parçayı yanlış yazın, boş sayfa ve logda bir “class not found” satırı alırsınız — bu yüzden önce daima logu okuyun.

install() içinde kaydedin, uninstall() içinde temizleyin

Birisi eklentiyi Eklentiler ekranından kurduğunda veya kaldırdığında OpenCart yönetim kontrolcünüzdeki install() ve uninstall() metotlarını çağırır. Event satırları burada oluşturulur ve burada silinir. Başka hiçbir yerde değil: SQL’den elle eklenmiş bir event kaldırma işleminden sağ çıkar ve bir sonraki geliştiricinin başına iş açar.

<?php
namespace Opencart\Admin\Controller\Extension\StockNote\Module;

class StockNote extends \Opencart\System\Engine\Controller {
    public function install(): void {
        $this->load->model('setting/event');

        $this->model_setting_event->addEvent([
            'code'        => 'stock_note',
            'description' => 'Urun sayfasinda teslimat notu',
            'trigger'     => 'catalog/view/product/product/before',
            'action'      => 'extension/stock_note/event/product.note',
            'status'      => 1,
            'sort_order'  => 1
        ]);
    }

    public function uninstall(): void {
        $this->load->model('setting/event');
        $this->model_setting_event->deleteEventByCode('stock_note');
    }
}
OpenCart 4.0.0 ve 4.0.1, addEvent() metodunu sıralı argümanlarla getirdi; 4.0.2 ve sonrası yukarıdaki diziyi alıyor. admin/model/setting/event.php dosyasını açıp elinizdeki imzayla eşleyin. Kopyalanan bir eğitimin sessizce hiçbir şey yapmamasının en yaygın nedeni budur.

Paket başına tek bir code kullanın ve kaldırırken o code ile silin. Aynı code altında birden çok tetikleyici kaydetmek sorun değil, tercih edilen yol da bu: tek silme hepsini kaldırır ve yeniden kurulum asla çift dinleyici bırakmaz. Notun iki kez basılmasının klasik nedeni bu mükerrer satırlardır.

Handler’ı yazmak

Handler, catalog ağacındaki sıradan bir kontrolcüdür. İmzası tetikleyici ailesine göre değişir: before handler’ları rotayı ve argümanları, after handler’ları rotayı, argümanları ve çıktıyı alır. Parametre adları sizin, sırası değil.

Teslimat notunun temiz sürümü şöyle görünüyor — şablon işlenmeden önce, veri dizisi hâlâ düzenlenebilirken çalışıyor, yani değerin nerede görüneceğine tema karar veriyor:

<?php
namespace Opencart\Catalog\Controller\Extension\StockNote\Event;

class Product extends \Opencart\System\Engine\Controller {
    // catalog/view/product/product/before
    public function note(string &$route, array &$data): void {
        $product_id = (int)($this->request->get['product_id'] ?? 0);

        if (!$product_id) {
            return;
        }

        $this->load->language('extension/stock_note/module/stock_note');
        $this->load->model('catalog/product');

        $product = $this->model_catalog_product->getProduct($product_id);

        if ($product && (int)$product['quantity'] > 0) {
            $data['ships_in'] = $this->language->get('text_ships_today');
        } else {
            $data['ships_in'] = $this->language->get('text_backorder');
        }
    }
}

Alt tema da {{ ships_in }} değerini ait olduğu yere basar. Bu tür bir handler’ı güvenli kılan iki alışkanlık var: id’yi $data içinde bir anahtarın var olduğuna güvenmek yerine istekten okumak ve varsaymak yerine erkenden çıkmak.

Şablona dokunmadan işaretlemeyi değiştirmek

Bazen şablonu düzenleyemezsiniz: sahibi olmadığınız, üreticisinin birkaç ayda bir güncellediği ticari bir tema. O zaman aynı tetikleyicinin after biçimini alır ve işlenmiş metin üzerinde çalışırsınız. Hızlıdır, meşrudur ve bu yazıdaki en kırılgan tekniktir; çünkü aradığınız çıpa, tam olarak bir tema güncellemesinin değiştirdiği şeydir.

// catalog/view/product/product/after
public function noteAfter(string &$route, array &$data, string &$output): void {
    $anchor = '<div id="product">';

    if (!str_contains($output, $anchor)) {
        return;   // tema degismis - sessizce cik, asla olumcul olma
    }

    $html = '<p class="ships-in">' . $this->language->get('text_ships_today') . '</p>';

    $output = str_replace($anchor, $anchor . $html, $output);
}

Çıpayı uzun ve kozmetik değil, kısa ve yapısal tutun; değiştirmeyi bir kez yapın; çıpa yoksa handler sessizce çıksın. View event’i içinde hata fırlatan bir handler, sayfanın tamamını da beraberinde götürür.

Event çalışmadığında

Event’ler tasarım gereği sessizce başarısız olur; o yüzden tahmin etmek yerine sabit bir sırayla ayıklayın:

  1. 1Satırın var ve etkin olduğunu doğrulayın: SELECT * FROM oc_event WHERE code = 'stock_note';
  2. 2Tetikleyici metnini, çalışan bir çekirdek satırıyla karakter karakter karşılaştırın.
  3. 3Eylemin çözümlendiğini kontrol edin: rota extension/<kod>/ altında gerçek bir dosyayı göstermeli, noktadan sonraki kısım da o sınıfta public bir metot olmalı.
  4. 4system/storage/cache ve tema önbelleğini temizleyin. OpenCart istekler arasında çoğu kişinin sandığından fazlasını önbelleğe alır.
  5. 5Eklentinin yalnızca yüklenmiş değil, kurulmuş olduğundan emin olun — yönetim listesine değil, oc_extension ve oc_extension_install tablolarına bakın.
  6. 6Handler’ın ilk satırına error_log(__METHOD__) koyun. Hiç basmıyorsa sorun sizin mantığınızda değil, kayıttadır.
  7. 7Aynı çıktıya iki eklenti dokunuyorsa sort_order’a bakın. Metin değiştirmeyi sonraki dinleyici kazanır ve hatanın tamamı bu sıradadır.

Event’lerin yanlış araç olduğu yerler

Event’ler evrensel bir cevap değil; öyleymiş gibi davranmak yavaş mağazalar üretir. Saygı duyulması gereken dört sınır:

  • Mağaza tarafındaki handler’lar eşleşen her sayfa görüntülemesinde çalışır. Sizinki dış bir API çağırıyorsa, başkasının çalışma süresini kendi ödeme akışınızın içine koymuş olursunuz — kuyruğa yazın ve akış dışında işleyin.
  • Event’ler veritabanı sütunu, rota veya yönetim menüsü girdisi oluşturamaz. Onlar install() işidir ve aynı pakete aittir.
  • Değer döndüren bir before handler’ı asıl çağrının yerine geçer. Bunu bilinçli bir ezme olarak kullanın, bir koşulu atlamanın kısayolu olarak değil.
  • Aynı HTML’i yeniden yazan iki eklenti, çalışma anında bir birleştirme çakışmasıdır. Şablon izin verdiği her durumda çıktı düzeyindeki tetikleyici yerine veri düzeyindekini tercih edin.

İyi yapıldığında kazanç sessizdir: OpenCart’ı güncellersiniz, mağaza teslimat notunu göstermeye devam eder ve kimse cumartesisini yamaları yeniden uygulayarak geçirmez.

Ne zaman bize yazın

Her güncellemede bozulan bir yığın OCMOD yamasıyla yaşıyorsanız, bunları event’lere çevirmek sınırları belli, tahmin edilebilir bir iştir. Lansman ücretiyle saatlik 10 $ + KDV üzerinden çalışırız, iş başlamadan saat sayısını veririz ve ilk mesajları Pazartesi–Cumartesi 09:00–22:00 arasında iki saat içinde yanıtlarız. Değişiklik listenizi ve OpenCart sürümünüzü gönderin.

NE
Nazlı Erdoğan
Software Engineer — Node.js/Python

Node.js ve Python ile API servisleri; test odaklı geliştirme.