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:
- İzoledir. Her hook çağrısı
try/catchiçindedir. Bir hook hata fırlatırsa hatamodule_quarantine.logdosyasına yazılır ve istek devam eder; sayfa 500 vermez. - Bağlam taşır. Hook'a bir
HookContextverilir, hook onu değiştirip döndürür. - Ö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>');
}
}
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ı.
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.