كل شرح لبناء مختصر روابط بيبدأ بـ Redis وطابور مهام وخدمة خارجية مدفوعة. طيب وإحنا اللي شغالين على استضافة مشتركة بـ 60 جنيه في الشهر نعمل إيه؟
الخبر الحلو إن مختصر الروابط من أبسط الحاجات اللي ممكن تتبني داخل ووردبريس نفسه: PHP و السيرفر.">MySQL وخلاص. الباقي كله قرارات تصميم، مش بنية تحتية.
القاعدة اللي بنيت عليها التجربة دي بسيطة: كل اللي التحويل محتاجه لازم يكون استعلامًا واحدًا على عمود مفهرس، وأي خاصية زيادة لازم تشتغل من غير خدمة إضافية.
قيود الاستضافة المشتركة
قبل الكود، اتفق مع نفسك على اللي مش متاح عندك:
- PHP و MySQL بس، غالبًا من غير Redis ولا Memcached.
- مافيش عمليات طويلة الأمد، يعني مافيش مستهلك طابور.
- المهام المجدولة (الشيفرة الخبيثة تلقائيًا بعد كل تنظيف.">WP-Cron) بتشتغل مع الزيارات، مش في وقت مضبوط بالثانية.
- في الغالب فيه شبكة توصيل محتوى (CDN) قدام الموقع، وهي مستعدة تمامًا إنها تخزّن التحويلات لو سيبتها.
القيد الأخير ده تحديدًا هو اللي بيوجع، وهنرجع له كمان شوية.
نموذج البيانات: جدولان
جدول للروابط وجدول لأحداث النقر:
CREATE TABLE short_urls (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(20) NOT NULL,
long_url TEXT NOT NULL,
clicks INT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
creator_ip VARCHAR(45) NOT NULL DEFAULT ”,
expires_at DATETIME NOT NULL,
disabled TINYINT(1) NOT NULL DEFAULT 0,
UNIQUE KEY code (code),
KEY expires_at (expires_at)
);
البطل الصامت هنا هو UNIQUE KEY على العمود code. هو اللي بيخلي الأسماء المخصصة تشتغل من غير تعارض: لو اتنين طلبوا نفس الاسم في نفس اللحظة، قاعدة البيانات (Database) بترفض التاني بنفسها.
القاعدة العامة: حط قيد التفرّد في قاعدة البيانات مش في كود PHP. أسرع، وصحيح تحت الضغط المتزامن.
التحويل: اعتراض للطلب قبل ما الصفحة تتبني
لأن كل ده جوّه ووردبريس، التحويل بيعيش على هوك (Hook) الـ init. أي مسار شكله /s/<code> بيتمسك ويتحل ويتبعت قبل ما القالب في Elementor تصميم محفوظ يُعاد استخدامه، مثل رأس الموقع أو التذييل أو صفحة المقالة أو صفحة المنتج. تصممه مرة وتحدد شروط ظهوره، فيُطبَّق على كل…">القالب يشتغل:
add_الإضافات والقوالب والمستخدمين والإعدادات، وما يظهر فيها يختلف حسب دور المستخدم.">لوحة التحكم أو بناء القائمة، فتربط به دالتك عبر add_action لتنفيذ مهمة محددة دون أن تُطالَب بإرجاع…">action(‘init’, function () {
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if (!preg_match('#^/s/([A-Za-z0-9_-]{3,20})/?$#', $path, $m)) return;
// stop any cache from ever storing a redirect
header('X-Robots-Tag: noindex, nofollow', true);
nocache_headers();
$row = get_link($m[1]);
if (!$row) return show_status(404, 'Link not found');
if ($row->disabled) return show_status(410, 'Link disabled');
if (expired($row)) { delete($row); return show_status(410, 'Link expired'); }
log_click($row); // one insert, no queue
wp_redirect($row->long_url, 302);
exit;
});
nocache_headers() هي اللي بتحافظ على انضباط Cloudflare و LiteSpeed. من غيرها، تحويل مخزّن هيبعت كل الناس لأول وجهة اتسجلت للكود ده، وتفضل تدوّر على المشكلة أيام.
ولاحظ استخدام 302 مش 301. التحويل الدائم بيتخزّن في المتصفح نفسه، فالزائر المتكرر مايوصلش للسيرفر وعدّاد النقرات بيقل من غير سبب واضح. ولاحظ كمان إن الرابط المعطّل أو المنتهي بيرجّع 404 أو 410، مش تحويلًا للصفحة الرئيسية، عشان الزواحف وفاحصات الروابط تعرف إنه مات.
انتهاء الصلاحية من غير مهمة مجدولة
مافيش تنظيف مجدول هنا. الانتهاء بيتفرض في مكانين بيشتغلوا أصلًا:
- عند كل تحويل: الرابط المنتهي بيتمسح لحظة ما حد يفتحه ويرجّع 410.
- عند كل إنشاء وكل فتح لصفحة الإحصائيات: مسح خفيف بسطر واحد.
DELETE FROM short_urls WHERE expires_at < UTC_TIMESTAMP();
يعني الرابط المنتهي عمره ما هيحوّل، حتى لو صفّه فضل في الجدول كام دقيقة لحد أول زيارة. لأداة روابطها بتعيش من 24 ساعة لـ 5 أيام، ده كفاية وبتكلفة بنية تحتية صفر.
إحصائيات تحترم الزائر
أغلب المختصرات المجانية إما بتخبي الأرقام ورا تسجيل دخول، أو بتتبع الناس عشان تبيع البيانات. البديل إنك تخزّن أقل حاجة تجاوب على السؤال الوحيد المهم: «المنشور ده اشتغل ولا لأ؟»
كل نقرة بتسجّل ثلاث قيم بس:
- الوقت والتاريخ.
- نطاق المصدر بدون المسار، يعني
l.facebook.comيتحول لـ Facebook. - فئة الجهاز المستخرجة من وكيل المستخدم: موبايل، تابلت، ديسكتوب.
مافيش عناوين IP، ولا وكيل مستخدم كامل، ولا كوكيز، ولا ملفات تعريف للزوار. وبعدها استعلامات GROUP BY عادية بتطلعلك رسمًا لسبعة أيام، وقائمة بأعلى المصادر، وتوزيع الأجهزة.
توحيد المصادر تفصيلة صغيرة بتفرق كتير: فيسبوك لوحده بيبعت زيارات من l.facebook.com وm.facebook.com وlm.facebook.com، ومن غير تجميع بتبقى قائمة المصادر ضوضاء. جدول بحث صغير بيجمّع النطاقات المعروفة تحت اسم العلامة.
ملحوظة للسوق المصري: أضف نطاقات واتساب وتليجرام لجدول التوحيد من أول يوم، لأن نسبة كبيرة من الزيارات هنا بتيجي من محادثات مش من منصات التواصل. من غير كده هتلاقي أغلب نقراتك في خانة «مباشر» وتفتكر إن المشاركة مابتشتغلش.
إزاي تمنعه من التحول لمدفع سبام؟
مختصر روابط مفتوح للكل هدية لمرسلي التصيّد، وبلاغ واحد كفاية إن الدومين كله يتعلّم عليه. خط دفاعين رخيصين بيعملوا أغلب الشغل:
- فحص الوجهة عند الإنشاء عبر Google Safe Browsing، فالرابط الخبيث بيترفض قبل ما يبقى له نسخة مختصرة أصلًا.
- حماية النموذج بـ Cloudflare Turnstile عشان البوتات ماتولّدش روابط بالجملة. مجاني، ولو الموقع أصلًا على Cloudflare فتركيبه دقائق.
خلي الاتنين اختياريين في الكود بحيث لو المفاتيح مش متظبطة الأداة تفضل شغالة محليًا. لكن على الإنتاج، دول الفرق بين أداة مفيدة وبين مسؤولية قانونية.
خمس نقاط تلخّص التجربة
- حط قيد التفرّد في قاعدة البيانات، مش في PHP.
- ابعت ترويسات منع التخزين على أي مسار بيحوّل، وإلا الـ CDN هيخلي التنقيح كابوسًا.
- استخدم 302 بدل 301 لو الأرقام تهمك.
- مش محتاج مهمة مجدولة للتنظيف الزمني طالما الزيارات العادية بتمر على الصفوف دي.
- الإحصائيات المحترمة للخصوصية مش أصعب في البناء، دي مجرد قرار إنك تخزّن أقل.
اقرأ أيضًا
الأسئلة الشائعة
س: أقدر أشغّل ده على استضافة مشتركة رخيصة؟ ج: أيوة، وده كان القيد الأساسي في التصميم. PHP و MySQL كفاية، ومافيش أي مكوّن بيحتاج عملية خلفية دائمة.
س: ليه 302 مش 301؟ ج: لأن 301 بيتخزّن في المتصفح، فالزائر المتكرر مايعدّيش على السيرفر وعدّاد النقرات بيقل. 302 مع منع التخزين بيحافظ على دقة الأرقام.
س: محتاج WP-Cron للتنظيف؟ ج: لأ. المسح بيحصل عند التحويل وعند الإنشاء وعند فتح الإحصائيات، وده كافي لأداة روابطها قصيرة العمر.
س: أستخدم الحل ده ولا إضافة جاهزة؟ ج: لو محتاج روابط مؤقتة وإحصائيات بسيطة وتحكمًا كاملًا في البيانات، الحل المخصص أخف وأوضح. لو محتاج صلاحيات فرق وتقارير متقدمة، الإضافات الجاهزة أوفر في الوقت.
س: إزاي أمنع البوتات من تضخيم عدّاد النقرات؟ ج: افلتر وكلاء المستخدم المعروفة للزواحف قبل تسجيل النقرة، وتجاهل الطلبات اللي مالهاش مصدر ولا تقبل كوكيز. مش حل كامل، لكنه بيشيل أغلب الضوضاء.
الخلاصة
المشروع ده مثال نضيف على فكرة إن القيود بتحسّن التصميم. من غير Redis ولا طوابير، طلع حل بيحوّل في استعلام واحد، وبيدير الصلاحية من غير جدولة، وبيدي إحصائيات كافية من غير ما يتتبع حد.
لو عندك موقع ووردبريس وبتنشر روابط على السوشيال، جرّب تبني النسخة دي على بيئة اختبار (Staging) الأول. ابن موقعك قطعة قطعة — وكل قطعة تثبت إنها مستاهلة مكانها قبل ما تدخل الإنتاج.
مرجع المقال (بالإنجليزية): I built a privacy-first URL shortener that runs on plain shared hosting
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.



