من أوائل ما تصادفه عند نقل مشروع Laravel إلى استضافة مشتركة أن أمراً موجوداً في كل الشروحات يفشل بصمت على خادمك أو يعطي خطأً غريباً. والسبب في الغالب سطر واحد في إعدادات PHP لم يُطلعك عليه أحد عند شراء الاستضافة: disable_functions.
يشرح هذا المقال ما الذي يكسره هذا السطر بالضبط، وكيف تعرض قيمته، وما البديل الذي ينجح فعلاً في كل حالة.
ما هي disable_functions ولماذا وُجدت#
disable_functions قائمة بأسماء دوال PHP التي لا يسمح المفسّر باستدعائها. أي استدعاء لها ينتج خطأً قاتلاً (fatal) حتى لو كانت الإضافة المعنية مثبتة.
على الخادم المشترك توجد عشرات الحسابات أو مئاتها على جهاز واحد. إن اختُرق أحد المواقع وتمكّن المهاجم من تنفيذ أوامر النظام صار الخادم كله في خطر. وتعطيل دوال تنفيذ الأوامر هو أرخص دفاع وأكثره فعالية. لذلك فالقيد منطقي ومن المرجح ألا يُرفع؛ والأفضل أن تُكيّف بنية مشروعك معه بدل أن تحاربه.
اعرض القائمة أولاً#
هناك ثلاث طرق لعرض القائمة، والأفضل تجربتها كلها لأن قيمة سطر الأوامر قد تختلف عن قيمة الويب.
من الطرفية:
php84 -r 'echo ini_get("disable_functions"), PHP_EOL;'ومن داخل التطبيق نفسه (مثلاً في مسار مؤقت أو أمر artisan):
dd(ini_get('disable_functions'));وفي cPanel يعرض قسم معلومات الخادم أو phpinfo القيمة نفسها.
على الخادم الذي يعمل عليه هذا الموقع تضم القائمة: exec وshell_exec وsystem وpassthru وpopen وproc_open وsymlink وhighlight_file. سنرى فيما يلي ما الذي تعطّله كل واحدة منها.
1. دوال تنفيذ الأوامر: exec وأخواتها#
الدوال exec وshell_exec وsystem وpassthru وpopen وproc_open تؤدي جميعها مهمة واحدة: تنفيذ أمر نظام من داخل PHP. وحين تُعطَّل لا يبقى أي طريق لاستدعاء برنامج خارجي من PHP.
خذ هذه الجملة على محمل الجد، لأن كثيراً من الحلول الشائعة قائم عليها تماماً:
| ما تريد فعله | الأداة الشائعة | الوضع على الاستضافة المشتركة |
|---|---|---|
| تغيير حجم الصور وتحسينها | ImageMagick عبر سطر الأوامر | غير ممكن |
| تحويل الفيديو وضغطه | ffmpeg | غير ممكن |
| إنشاء PDF من HTML | wkhtmltopdf أو chromium | غير ممكن |
| تحسين الصور | jpegoptim وoptipng | غير ممكن |
| أخذ نسخة احتياطية لقاعدة البيانات من داخل التطبيق | mysqldump | غير ممكن |
البدائل الحقيقية#
للصور: إضافة GD. GD جزء من PHP نفسها، ولا تحتاج إلى أمر نظام، وتغطي تغيير الحجم والتدوير والشفافية وتصدير WebP. جودتها كافية تماماً لمعظم أعمال الويب. بنينا خط معالجة الوسائط في هذا الموقع بـ GD الخام؛ كيف فعلنا ذلك وبأي ضوابط.
تحقق أولاً من أنها مثبتة:
var_dump(extension_loaded('gd'));
var_dump(function_exists('imagewebp'));لملفات PDF: مكتبات PHP الخالصة مثل عائلتي dompdf وmpdf. هي أبطأ وأقل دقة لكنها لا تحتاج إلى برنامج خارجي.
للنسخ الاحتياطي لقاعدة البيانات: بدل استدعاء mysqldump من داخل PHP ضعه في مهمة cron. يعمل cron من الطرفية ولا تؤثر فيه disable_functions. وهذا فرق مهم: القيد مفروض على مفسّر PHP لا على حسابك في الطرفية.
للفيديو: لا تعتمد على الاستضافة المشتركة في ذلك. استخدم خدمة خارجية أو خادماً منفصلاً.
2. الدالة symlink وقصة storage:link#
إذا كانت symlink معطّلة فإن الأمر المعروف التالي لن يعمل أساساً:
php84 artisan storage:linkيستخدم هذا الأمر الدالة symlink لربط public/storage بالمجلد storage/app/public. وتعطيلها يعني أنه لن ينجح.
والخبر الجيد: ما زال ممكناً من الطرفية. الأمر ln برنامج نظام ولا علاقة له بإعدادات PHP:
ln -s ~/app/storage/app/public ~/public_html/storageوفي cPanel فخ ثانٍ قد يجعل النتيجة خاطئة حتى لو كانت symlink مفتوحة — وقد شرحنا قصته كاملة في المقال المستقل عن storage:link.
3. الدالة highlight_file: أسوؤها لأنها صامتة#
تبدو هذه أقل أهمية من غيرها، لكنها في الواقع أخذت منا أكبر قدر من الوقت.
تطبع highlight_file شيفرة ملف PHP مع تلوين الصياغة. لا يستدعيها أحد مباشرة — لكن صفحة الخطأ الجميلة في Laravel تستخدم دالة من العائلة نفسها لإظهار مقطع الشيفرة عند موضع الخطأ.
والنتيجة حلقة قاسية:
- يقع خطأ حقيقي في شيفرتك.
- تريد Laravel عرض صفحة الخطأ مع مقطع الشيفرة.
- تصطدم أداة العرض بالدالة المعطّلة فتُنتج هي خطأً آخر.
- ما تراه في المتصفح هو خطأ الخطوة الثالثة لا خطأ الخطوة الأولى.
أي أن تفعيل وضع التصحيح على مثل هذا الخادم يُخفي السبب الحقيقي. وإن سبق أن حيّرك خطأ غامض لا صلة له بمشكلتك على استضافة مشتركة فربما كان هذا هو السبب. شرحناه بالتفصيل هنا.
4. دوال غير معطّلة لكنها مقيّدة#
هناك حالات أخرى لا تراها في قائمة disable_functions لكنها تتصرف بشكل مشابه:
- قد لا يكون لـ
set_time_limitأي أثر لأن الحد الفعلي يحدده خادم الويب. - الاستدعاء
ini_set('memory_limit', -1)لا يتجاوز عادةً سقف الحساب. - قد تكون
putenvمعطّلة أحياناً، فتعود بعض المكتبات بصمت إلى السلوك الافتراضي. - تكون
opcache_resetعادةً غير مفعّلة في الاستضافة المشتركة؛ ولمسح ذاكرة الشيفرة المؤقتة عليك الاعتماد على طرق أخرى.
5. الحزم التي تتوقف بصمت#
أسوأ الحالات ليست أن تستدعي أنت دالة معطّلة — فهذه تكتشفها بسرعة. الأسوأ أن تفعل ذلك حزمة داخل مجلد vendor ويبدو الفشل كأنه شيء آخر.
بعض الأمثلة الشائعة:
- مكتبات الصور التي تختار تلقائياً مشغّل ImageMagick ولا تنتقل إلى GD إلا بإعداد صريح.
- حزم النسخ الاحتياطي التي تنفذ أمراً لاستخراج قاعدة البيانات فتتوقف برسالة مبهمة هي «فشلت العملية».
- أدوات توليد PDF التي تشغّل متصفحاً بلا واجهة خلف الكواليس.
- مكتبات تحسين الصور التي ليست سوى غلاف حول عدة برامج خارجية، ومن دونها لا تفعل شيئاً بصمت.
القاعدة العملية: قبل إضافة أي حزمة تتعامل مع الملفات أو الوسائط ابحث في وثائقها عن كلمة binary أو requirements. فإن كانت تحتاج إلى برنامج خارجي فهي ليست خياراً لك على الاستضافة المشتركة.
أما قرارنا فكان أكثر جذرية: لا حزمة جديدة بلا فحص، ونكتب الأدوات الصغيرة بأنفسنا. والنتيجة مجلد app/Support يضم أصنافاً صغيرة محددة — مُطبّع النصوص العربية، ورافع الصور، ومولّد الروابط الدائمة (slug) — يبلغ كل منها بضعة عشرات من الأسطر ونعرف تماماً ما يفعله. وبالنسبة لمشروع متوسط فهذه التكلفة أقل من تكلفة تشخيص حزمة غير متوافقة.
6. تشخيص سريع: سكربت لفحص البيئة#
بدل أن تخمّن في كل مرة، اطبع كل شيء مرة واحدة. نفّذ هذا في الطرفية:
php84 -r '
$check = ["exec","shell_exec","system","passthru","popen","proc_open","symlink","highlight_file","putenv","set_time_limit"];
foreach ($check as $f) {
printf("%-16s %s\n", $f, function_exists($f) ? "متاحة" : "معطّلة");
}
printf("%-16s %s\n", "gd", extension_loaded("gd") ? "متاحة" : "غير موجودة");
printf("%-16s %s\n", "imagewebp", function_exists("imagewebp") ? "متاحة" : "غير موجودة");
printf("%-16s %s\n", "memory_limit", ini_get("memory_limit"));
'احتفظ بمخرجات هذا الأمر في مكان ما. في كل مرة تتخذ فيها قراراً معمارياً تستغرق نظرة إليها عشر ثوانٍ وتوفّر ساعات.
نفّذ الفحص نفسه عبر الويب أيضاً لأن القيم قد تختلف. استخدم مساراً مؤقتاً في Laravel لا يصل إليه إلا المدير، أو أمر artisan — ثم احذفه.
7. حين تحتاج فعلاً إلى ميزة معيّنة#
إذا كانت الميزة حيوية والاستضافة لا تتيحها فأمامك ثلاثة خيارات، وترتيبها مهم:
1. بديل PHP خالص. هو الخيار الأول دائماً. أبطأ لكنه بلا أي اعتماد خارجي.
2. خدمة خارجية. للمهام الثقيلة مثل معالجة الفيديو، استخدم خدمة منفصلة تُستدعى عبر API. لها تكلفة، لكنها قد تكون الحل الواقعي الوحيد على الاستضافة المشتركة.
3. الترقية إلى خادم افتراضي (VPS). إذا بلغ المشروع مرحلة تتعذر فيها عدة ميزات حيوية على الاستضافة المشتركة فإن مجاهدة القيود أغلى من الانتقال.
أما ما لا ينبغي أن تفعله فهو البحث عن طريقة لتجاوز disable_functions. أي أسلوب يدّعي تجاوز هذا القيد إما أنه لا يعمل أو أنه ثغرة أمنية، واستخدامها يضعك في مخالفة لشروط الخدمة وقد يؤدي إلى تعليق حسابك.
كيف تجعل شيفرتك مقاومة لهذا الوضع#
ثلاث قواعد عملية خرجنا بها من تجربتنا:
1. لا تفترض أبداً أن دالة ما موجودة. افحصها قبل الاستخدام وجهّز مساراً بديلاً:
if (! function_exists('imagewebp')) {
// مسار بديل أو رسالة واضحة للمستخدم
}2. انقل المهام التي تحتاج إلى الطرفية إلى cron. لدى cron طرفية كاملة. النسخ الاحتياطي والتنظيف والمعالجة الدفعية — مكانها الأفضل هناك.
3. أظهر رسالة خطأ واضحة بدل الفشل الصامت. أسوأ ما يحدث أن يرفع المستخدم ملفاً فلا يرى أي خطأ ولا يُحفظ الملف. إذا غابت دالة فاذكر صراحة أي ميزة غير متاحة.
الخلاصة#
لا يمكن تجاوز disable_functions ولا ينبغي لك ذلك؛ فهي جزء من سبب كون الاستضافة المشتركة أكثر أماناً مما كان يمكن أن تكون. الممكن هو أن تعرف قبل كتابة الشيفرة ما هو غير متاح فلا تبني المعمارية على شيء غير موجود.
إذا كنت تختار استضافة الآن فافحص هذه النقطة مع بقية النقاط في قائمة فحص الاستضافة المشتركة لـ Laravel؛ ففيها ستة أمور أخرى يجب أن تسأل عنها قبل الشراء.