İçeriğe geç

Hook, cron ve kuyruk

Bu sayfa, çekirdeği değiştirmeden davranış eklemenin üç yolunu anlatır: hook (bir noktaya bağlanma), cron (zamanlanmış iş), kuyruk (arka planda iş).

Hook'lar

Hook, çekirdeğin ya da bir modülün belirli bir noktada "burada bir şey eklemek isteyen var mı?" diye sorduğu genişletme noktasıdır. Üç özelliği vardır:

  1. İzoledir. Her hook çağrısı try/catch içindedir. Bir hook hata fırlatırsa hata module_quarantine.log dosyasına yazılır ve istek devam eder; sayfa 500 vermez.
  2. Bağlam taşır. Hook'a bir HookContext verilir, hook onu değiştirip döndürür.
  3. Önceliklidir. Aynı noktaya birden fazla hook bağlanabilir; küçük öncelik sayısı önce çalışır.

Hook bağlamı

$context = new HookContext(['locale' => 'tr', 'site_name' => 'Örnek']);
$context->set('limit', 5);
$context->get('limit', 10);          // 5
$context->has('limit');              // true
$context->appendHtml('<p>Ek</p>');   // HTML üreten hook'lar için
$context->getHtml();                 // birikmiş HTML

Yol 1: öznitelikle (DI'li, önerilen)

Bir servis metodunu #[CpHook] ile işaretleyin. Ek bir services.yaml etiketi gerekmez; HookRegistrationPass derleme sırasında toplar. Öznitelik metot üzerinde tekrarlanabilir.

final class SidebarHooks
{
    public function __construct(private readonly TagRepository $tags) {}

    #[CpHook('blog.render.sidebar', priority: 50)]
    public function onSidebar(HookContext $context): HookContext
    {
        return $context->appendHtml('<div class="my-box">…</div>');
    }
}
Sessiz hata

Bir hook hata verirse hook motoru onu karantinaya alıp sayfayı ayakta tutar; yani sidebar sessizce çizilmez ve kimse haber vermez. Hook yazarken module_quarantine.log dosyasını izleyin. Çıktıda kullanıcı verisini kaçışlamayı (htmlspecialchars) unutmayın; hook çıktısı güvenli HTML kabul edilir.

Yol 2: dosyayla (DI'siz)

Modül klasöründe Hooks/<hook_noktası>.php dosyası bir Closure döndürür:

<?php
declare(strict_types=1);

return (static function (App\Core\Hook\HookContext $context): App\Core\Hook\HookContext {
    return $context->appendHtml('<p>' . htmlspecialchars((string) $context->get('site_name')) . '</p>');
})($context);

Dosya hook'larında çevirmen (translator) yoktur; ihtiyaç varsa bağlama çevrilmiş bir etiket geçirin.

Hook noktalarını görmek

AACP → Otomasyon → Hook'lar ekranı (Hook Explorer) bulunan tüm hook noktalarını, tipini (dosya / öznitelik) ve kaynağını listeler. Komut satırından: cp:debug.

Çekirdeğin sunduğu hook noktaları

Hook noktası Ne zaman
entity.<tür>.pre_save Bir varlık kaydedilmeden önce
entity.<tür>.post_insert Yeni satır eklendikten sonra
entity.<tür>.post_update Güncellendikten sonra
entity.<tür>.pre_delete, .post_delete Silmeden önce / sonra
entity.any.<an> Aynı olaylar, tüm varlık türleri için (joker)
entity.<tür>.access, entity.any.access Yetki kararına eklenti noktası
workflow.<ad>.transitioned İş akışı geçişinden sonra (ör. workflow.editorial.transitioned)
theme.head.end, theme.footer.before, theme.header.alerts, theme.header.user_badge Tema yerleşim noktaları
studio.body.end Her Studio sayfasının sonunda (path bağlamla gelir): yüzen arayüz parçaları için. Notlar modülü yapışkan notlarını buradan çizer
page.render.content Sayfa içeriği çizilirken
blog.render.sidebar Blog yan çubuğu

<tür> varlık türü kimliğidir (ör. node). Modüller kendi noktalarını da tanımlar (Forum'da forum.like, forum.reply, forum.quote, forum.reputation).

Şablonda bir hook noktasını çizmek için Twig işlevi cp_hook:

{{ cp_hook('theme.footer.before', {site: site_name}) }}

Kendi hook noktanızı açmak

$context = $this->hooks->trigger('myshop.order.summary', new HookContext(['order' => $order]));
$html = $context->getHtml();

HookDispatcherInterface enjekte edin; böylece servisiniz somut HookManager'a bağımlı olmaz.

Hook mu, olay dinleyici mi?

Symfony olayına (kernel.*) doğrudan bağlanabilirsiniz ama hook tercih edilir. Olay dinleyicileriniz de çekirdek tarafından bir koruyucu (ModuleEventListenerGuard) ile sarılır; yine de hook'un izolasyon garantisi birinci sınıftır.

Cron

Zamanlanmış işler üç kaynaktan gelir ve tek bir çalıştırıcıda birleşir (CronManager):

Kaynak Ne zaman kullanılır
Veritabanı (cp_cron_jobs) Yönetici AACP'den iş ekler/düzenler; yalnızca cp:* komutları çalıştırabilir
Öznitelik (#[CpCronJob]) İş DI ve servis gerektirir
Dosya (Hooks/cron.<ad>.php) Basit, bağımsız iş

Öznitelikle iş

final class PublishScheduledPostsTask
{
    #[CpCronJob(schedule: '*/5 * * * *', name: 'blog.publish_scheduled', description: 'Publish scheduled blog posts')]
    public function execute(): string
    {
        // ...
        return 'Published 3 posts.';   // AACP'de son çalışmanın çıktısı olarak görünür
    }
}

Zamanlama klasik cron ifadesidir (*/5 * * * *, 15 3 * * *). Kod tabanlı işler bellekte yaşar; AACP'de [KOD] rozetiyle görünür ve son çalışma zamanı kalıcı olarak saklanmaz. Bir kod işinin zamanlaması AACP'den yeniden zamanlanabilir.

Çekirdek ve modüllerdeki bazı işler: blog.publish_scheduled (5 dk), seo.rebuild_sitemap (saatlik), seo.indexnow (10 dk), audit.purge (günlük), logging.purge, cpalius.origin_cache.purge (15 dk), core.media_offload_sweep.

Çalıştırma

* * * * * php /yol/cp-core/bin/console cp:cron:run          # her dakika
php cp-core/bin/console cp:cron:run-virtual blog.publish_scheduled   # tek bir kod işini elle

Sunucuda cron yoksa bir dış zamanlayıcı token'lı HTTP ucunu (/cron/execute) çağırabilir; token .env içindedir ve boşsa uç kapalıdır. Aynı CronDispatcher hem komut satırı hem HTTP için çalışır.

Güvenlik: komut beyaz listesi

Veritabanındaki bir cron işi yalnızca cp:* komutlarını çalıştırabilir; Doctrine ve vendor komutları hariçtir. Bu denetim dağıtım anında yeniden yapılır; yani veritabanına doğrudan yazılmış bir satır bile beyaz liste dışı komut çalıştıramaz.

Çakışma kilidi

Her dakika çağrılan cron ile on dakika süren bir iş üst üste biner: on kopya çalışır. CronLock bunu önler: aynı işin bir önceki çalışması sürerken yenisi atlanır. Kilit flock() ile uygulanır; işletim sistemi, kilidi tutan süreç ölünce kilidi kendisi bırakır. Süreli (TTL) bir kilit süreyi tahmin etmek zorunda kalırdı: kısa tahmin sürmekte olan işin altından kilidi alır, uzun tahmin çöken bir gecelik işin ertesi günü de engellemesine yol açardı.

Kapsam

flock aynı makinedeki süreçleri koordine eder; bir CPalius kurulumu tek belge kökü ve tek PHP havuzudur. Cron'u birden çok uygulama sunucusuna yayan bir dağıtım, işi içinde paylaşılan bir kilit almalıdır.

Kuyruk

Bir HTTP isteğinde yapılmaması gereken iş (webhook gönderimi, e-posta, ağır işlem) arka plana atılır. İki ayrı kuyruk vardır ve bilerek birleştirilmez:

Kuyruk Taşıdığı Çalıştırıcı
Messenger E-posta ve bildirimler messenger:consume / MessengerConsumeTask
AsyncJob (platform) Webhook'lar ve modül işleri cp:queue:work / QueueWorkTask

AsyncJob yazmak

Yalnızca sıraya koyar; teslimat HTTP iş parçacığında asla yapılmaz.

$this->jobs->dispatch('myshop.sync_stock', ['sku' => 'A-100']);   // AsyncJobDispatcherInterface
// ertelemek için: dispatch(..., availableAt: new \DateTimeImmutable('+10 minutes'))
final class SyncStockHandler implements AsyncJobHandlerInterface
{
    public function supports(string $type): bool { return $type === 'myshop.sync_stock'; }

    public function handle(AsyncJob $job): void
    {
        // Hata fırlatırsanız iş geri çekilmeyle (backoff) tekrar denenir; dönerseniz tamamlanır.
        $sku = $job->getPayload()['sku'];
    }
}

Sözleşme: hata fırlat = yeniden dene, dön = tamamlandı. İstisna mesajlarına sır sızdırmayın. Çalıştırıcı (QueueWorker) bir işleyiciyi yalıtılmış çalıştırır; işleyici hata verirse kernel 500 vermez. Her turda varsayılan 25 iş alınır.

Yönetim: AACP → Otomasyon → Kuyruk. İki kuyruğun ölçütleri ayrı gösterilir; başarısız Messenger iletileri buradan yeniden denenebilir ya da silinebilir.

Son güncelleme: 13.10.2026