قراءة الملفات العملاقة في PHP سطراً سطراً دون قتل الخادم

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: "خطأ فادح" و 2 جيجابايت من السجلات



تخيل: أنت مطور، ويأتيك مسؤول النظام مع محرك أقراص فلاش. عليه ملف سجل لخادم الويب لآخر ستة أشهر. الحجم؟ 2.5 جيجابايت متواضعة. "أحتاج إلى العثور على جميع عناوين IP التي تمت منها محاولات التخمين"، يقول ويذهب لشرب القهوة.



تفتح بيئة التطوير المتكاملة وتكتب بشكل تلقائي:

$lines = file('huge_log.txt');foreach ($lines as $line) {    // نقوم بالتحليل...}

تضغط "تشغيل"... وترى شاشة بيضاء في المتصفح، وفي سجلات الأخطاء ترى Fatal Error: Allowed memory size exhausted. مألوف؟ هذا هو الفخ الكلاسيكي الذي وقع فيه تقريباً جميع مطوري PHP.



سؤال "How to read a large file line by line?" على Stack Overflow حصد مئات الآلاف من المشاهدات ليس من دون سبب. إنها إحدى تلك المهام التي يقتل فيها الحل "الساذج" الخادم، بينما ينقذ النهج الصحيح المشروع. اليوم سنشرح كيفية قراءة الملفات الضخمة في PHP دون تجاوز حد الذاكرة مع الحفاظ على أداء عالٍ. هيا بنا!



لماذا تعتبر file() و file_get_contents() شراً للبيانات الكبيرة



دعنا نبدد الأسطورة الرئيسية فوراً. الدالتان file() و file_get_contents() هما دالتان شرهتان. تقومان بتحميل الملف بأكمله إلى ذاكرة الوصول العشوائي دفعة واحدة. إذا كان وزن ملفك 2 جيجابايت، وكان حد memory_limit في php.ini مضبوطاً على 128 ميجابايت (القيمة القياسية للعديد من الاستضافات)، فستحصل بالتأكيد على خطأ.



علاوة على ذلك، يخزن PHP السلاسل النصية بترميز، مما قد يزيد استهلاك الذاكرة بمقدار 2-4 مرات مقارنة بحجم الملف على القرص. أي أن 2 جيجابايت من السجلات قد "تستهلك" 6-8 جيجابايت من ذاكرة الوصول العشوائي!



الخلاصة: إذا تجاوز حجم الملف 10-20% من حد الذاكرة لديك، انسَ أمر file(). نحن بحاجة إلى نهج التدفق.



الطريقة الصحيحة: fgets() والقراءة المتدفقة



الطريقة الأكثر موثوقية وكفاءة هي استخدام fopen() مع fgets(). هذه الدوال لا تقرأ الملف بأكمله، بل تقرأ سطراً واحداً في كل مرة. تخيل أنك تشرب عصيراً من خلال ماصة - أنت لا تشرب الكوب كله في جرعة واحدة، بل تمتصه شيئاً فشيئاً. هذه هي بالضبط طريقة عمل القراءة المتدفقة.



إليك مثال أساسي ولكنه عملي:



<?php$handle = fopen('huge_log.txt', 'r');if ($handle) {    while (($line = fgets($handle)) !== false) {        // معالجة سطر واحد        echo $line;    }    fclose($handle);}?>


ماذا يحدث هنا؟ fopen() يفتح الملف ويعيد مؤشراً (resource). fgets() يقرأ في كل مرة سطراً واحداً حتى حرف السطر الجديد (). يتم تخصيص الذاكرة فقط للسطر الحالي، وليس للملف بأكمله. بعد المعالجة، يتم تدمير السطر بواسطة جامع القمامة (أو يتم استبداله).



فروق أداء مهمة

  • حجم المخزن المؤقت: fgets() يقرأ افتراضياً حتى 8192 بايت في المرة الواحدة. إذا كانت الأسطر أطول، ستقوم الدالة بعدة تكرارات. هذا كافٍ للغالبية العظمى من السجلات.
  • التحقق من false: استخدم دائماً المقارنة الصارمة !== false. لماذا؟ لأن السطر قد يكون فارغاً ("") أو يحتوي على "0"، وقد يتوقف while ($line = fgets($handle)) قبل الأوان.
  • إغلاق الملف: استدعِ دائماً fclose(). نعم، سيقوم PHP بذلك بنفسه عند انتهاء السكربت، ولكن إذا كنت تعالج ملفاً في حلقة أو في سكربت خلفي، فإن المؤشرات غير المغلقة تمثل تسرباً للموارد.


شحن فائق: المولدات (yield) لإعادة الاستخدام



الإدارة اليدوية لـ fopen/fclose جيدة، ولكن إذا كنت بحاجة لقراءة ملفات مختلفة في أجزاء مختلفة من المشروع، سيبدأ الكود في التكرار. هنا تأتي المولدات للإنقاذ - وهي واحدة من أكثر ميزات PHP أناقة.



يسمح لك المولد بإنشاء دالة ت

المدونات

توصيات الكتب