11-bo‘lim

Fayllar bilan ishlash

O'qish va yozish, CSV, JSON, katta fayllarni qatorma-qator qayta ishlash va yo'l xavfsizligi.

🕑 11 daqiqa o‘qish 📄 666 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. O'qish va yozish
  2. Qatorma-qator qayta ishlash
  3. CSV bilan ishlash
  4. JSON bilan ishlash
  5. Xavfsiz yo'llar
  6. Xulosa

Ma'lumotlar bazasi hamma narsa uchun kerak emas. Sozlamalar, jurnal, eksport, yuklangan fayllar - bularning hammasi oddiy fayllarda yashaydi.

O'qish va yozish #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
$ish = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "php_darslik";
if (!is_dir($ish)) {
    mkdir($ish, 0777, true);
}
PHP
$fayl = $ish . DIRECTORY_SEPARATOR . "eslatma.txt";

// Butun faylni bir marta yozish
file_put_contents($fayl, "Birinchi qator\n");

// Oxiriga qo'shish
file_put_contents($fayl, "Ikkinchi qator\n", FILE_APPEND);
file_put_contents($fayl, "Uchinchi qator\n", FILE_APPEND);

// Butun faylni o'qish
$matn = file_get_contents($fayl);
echo "file_get_contents:\n";
echo $matn;

echo "\n";
// Qatorlar massivi sifatida o'qish
$qatorlar = file($fayl, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
echo "file() bilan: " . count($qatorlar) . " qator\n";
foreach ($qatorlar as $i => $q) {
    echo "  [{$i}] {$q}\n";
}

echo "\n";
printf("Hajmi:    %d bayt\n", filesize($fayl));
printf("Mavjudmi: %s\n", file_exists($fayl) ? "ha" : "yo'q");
printf("O'qish:   %s\n", is_readable($fayl) ? "mumkin" : "mumkin emas");
printf("Yozish:   %s\n", is_writable($fayl) ? "mumkin" : "mumkin emas");

unlink($fayl);
echo "\nO'chirilgandan keyin mavjudmi: " . (file_exists($fayl) ? "ha" : "yo'q") . "\n";
Natija
file_get_contents:
Birinchi qator
Ikkinchi qator
Uchinchi qator

file() bilan: 3 qator
  [0] Birinchi qator
  [1] Ikkinchi qator
  [2] Uchinchi qator

Hajmi:    45 bayt
Mavjudmi: ha
O'qish:   mumkin
Yozish:   mumkin

O'chirilgandan keyin mavjudmi: yo'q
file_get_contents katta fayl uchun yaramaydi

file_get_contents butun faylni xotiraga yuklaydi. 2 GB lik jurnal fayli uchun bu quyidagini anglatadi:

KOD
PHP Fatal error: Allowed memory size of 134217728 bytes
exhausted (tried to allocate 2147483648 bytes)

Fayl hajmi oldindan noma'lum bo'lsa, qatorma-qator o'qing:

PHP
$f = fopen($fayl, "r");
while (($qator = fgets($f)) !== false) {
    // bir vaqtda faqat BITTA qator xotirada
}
fclose($f);

Qaysi biri qachon:

VaziyatUsul
Kichik sozlama faylifile_get_contents
JSON, XML (to'liq kerak)file_get_contents
Jurnal, CSV, eksportfgets tsikli
Hajmi noma'lumfgets tsikli

Quyidagi misolda aynan shu usulni qo'llaymiz.

Qatorma-qator qayta ishlash #

PHP
$jurnal = $ish . DIRECTORY_SEPARATOR . "server.log";

// Sinov uchun jurnal yaratamiz
$satrlar = [];
$darajalar = ["INFO", "WARN", "ERROR", "INFO", "INFO", "ERROR"];
for ($i = 1; $i <= 60; $i++) {
    $daraja = $darajalar[$i % count($darajalar)];
    $satrlar[] = sprintf("2024-09-%02d 10:%02d:00 %s xabar-%d", ($i % 28) + 1, $i % 60, $daraja, $i);
}
file_put_contents($jurnal, implode("\n", $satrlar) . "\n");

echo "Jurnal yaratildi: " . count($satrlar) . " qator, "
   . filesize($jurnal) . " bayt\n\n";

// Qatorma-qator o'qib, statistika yig'amiz
$hisob = ["INFO" => 0, "WARN" => 0, "ERROR" => 0];
$xatolar = [];

$f = fopen($jurnal, "r");
if ($f === false) {
    exit("Fayl ochilmadi\n");
}

while (($qator = fgets($f)) !== false) {
    $qator = rtrim($qator, "\n");
    foreach ($hisob as $daraja => $_) {
        if (str_contains($qator, " {$daraja} ")) {
            $hisob[$daraja]++;
            if ($daraja === "ERROR" && count($xatolar) < 3) {
                $xatolar[] = $qator;
            }
            break;
        }
    }
}
fclose($f);

echo "Darajalar bo'yicha:\n";
foreach ($hisob as $daraja => $soni) {
    printf("  %-6s %3d  %s\n", $daraja, $soni, str_repeat("#", $soni));
}

echo "\nBirinchi uchta xato:\n";
foreach ($xatolar as $x) {
    echo "  {$x}\n";
}

unlink($jurnal);
Natija
Jurnal yaratildi: 60 qator, 2051 bayt

Darajalar bo'yicha:
  INFO    30  ##############################
  WARN    10  ##########
  ERROR   20  ####################

Birinchi uchta xato:
  2024-09-03 10:02:00 ERROR xabar-2
  2024-09-06 10:05:00 ERROR xabar-5
  2024-09-09 10:08:00 ERROR xabar-8
Butunlay o'qish va oqim bilan o'qish file_get_contents BUTUN FAYL xotiraga yuklanadi 2 GB fayl = 2 GB xotira Kichik fayl uchun - qulay va tez Katta fayl = dastur qulaydi fgets tsikli qator qator qator ... Xotirada bir vaqtda bitta qator Fayl hajmi ahamiyatsiz 100 GB ham muammo emas Yo'lni foydalanuvchi kiritgan qiymatdan yasamang file_get_contents("/data/" . $_GET["fayl"]); // ../../etc/passwd Yechim: oq ro'yxat yoki basename() + realpath() tekshiruvi. Yo'l ajratgichi: har doim DIRECTORY_SEPARATOR yoki "/" Windows ikkalasini ham tushunadi; Linux esa faqat / ni. Kodni ko'chma qiling.
Fayl hajmi noma'lum bo'lsa - har doim oqim bilan o'qing

CSV bilan ishlash #

PHP
$csv = $ish . DIRECTORY_SEPARATOR . "mahsulotlar.csv";

$malumot = [
    ["id", "nom", "narx", "soni"],
    [1, "Klaviatura", 350000, 12],
    [2, "Sichqoncha; simsiz", 120000, 40],
    [3, 'Monitor 24"', 2400000, 5],
    [4, "Quloqchin, katta", 89000, 0],
];

// Yozish - fputcsv qo'shtirnoq va vergulni O'ZI hal qiladi
$f = fopen($csv, "w");
foreach ($malumot as $qator) {
    fputcsv($f, $qator, ",", '"', "\\");
}
fclose($f);

echo "Fayl mazmuni (xom):\n";
echo file_get_contents($csv);

echo "\nO'qish (fgetcsv):\n";
$f = fopen($csv, "r");
$sarlavha = fgetcsv($f, 0, ",", '"', "\\");

$qatorlar = [];
while (($q = fgetcsv($f, 0, ",", '"', "\\")) !== false) {
    $qatorlar[] = array_combine($sarlavha, $q);
}
fclose($f);

printf("  %-3s %-20s %10s %6s\n", "ID", "NOM", "NARX", "SONI");
echo "  " . str_repeat("-", 42) . "\n";
foreach ($qatorlar as $q) {
    printf("  %-3s %-20s %10s %6s\n", $q["id"], $q["nom"], $q["narx"], $q["soni"]);
}

echo "\nOmborda bor mahsulotlar: ";
$bor = array_filter($qatorlar, static fn(array $q): bool => (int) $q["soni"] > 0);
echo count($bor) . " ta\n";

unlink($csv);
Natija
Fayl mazmuni (xom):
id,nom,narx,soni
1,Klaviatura,350000,12
2,"Sichqoncha; simsiz",120000,40
3,"Monitor 24""",2400000,5
4,"Quloqchin, katta",89000,0

O'qish (fgetcsv):
  ID  NOM                        NARX   SONI
  ------------------------------------------
  1   Klaviatura               350000     12
  2   Sichqoncha; simsiz       120000     40
  3   Monitor 24"             2400000      5
  4   Quloqchin, katta          89000      0

Omborda bor mahsulotlar: 3 ta
CSV ni hech qachon explode(",") bilan o'qimang

Yuqoridagi ma'lumotga qarang: bitta nomda vergul, ikkinchisida nuqta-vergul, uchinchisida qo'shtirnoq bor.

explode(",", $qator) bularning hammasida buziladi. fgetcsv esa CSV qoidalarini biladi:

QoidaMisol
Vergulli qiymat qo'shtirnoqqa olinadi"Quloqchin, katta"
Ichidagi qo'shtirnoq ikkilanadi"Monitor 24"""
Qator ichida yangi qator bo'lishi mumkinKo'p qatorli izoh

Uchinchi qoida ayniqsa muhim: CSV maydonida \n bo'lishi mumkin, ya'ni bitta yozuv bir necha faylda qatorni egallashi mumkin. file() bilan o'qigan kod buni hech qachon to'g'ri qayta ishlamaydi.

Excel uchun eksport qilayotgan bo'lsangiz yana ikki narsa:

PHP
// 1. UTF-8 BOM - aks holda Excel kirill/lotin harflarni buzadi
fwrite($f, "\xEF\xBB\xBF");

// 2. Rus/o'zbek Excel da ajratgich - nuqta-vergul
fputcsv($f, $qator, ";");

JSON bilan ishlash #

PHP
$json = $ish . DIRECTORY_SEPARATOR . "sozlama.json";

$sozlama = [
    "sayt" => "Softromeda",
    "til" => "uz",
    "sahifa_hajmi" => 20,
    "moduller" => ["darslik", "imtihon", "sertifikat"],
    "pochta" => ["host" => "smtp.example.com", "port" => 587, "tls" => true],
];

// Yozish - o'qishga qulay shaklda
file_put_contents(
    $json,
    json_encode($sozlama, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);

echo "Fayl mazmuni:\n";
echo file_get_contents($json) . "\n";

echo "\nO'qish:\n";
$xom = file_get_contents($json);
$qayta = json_decode($xom, true, 512, JSON_THROW_ON_ERROR);

printf("  sayt:          %s\n", $qayta["sayt"]);
printf("  sahifa_hajmi:  %d (turi: %s)\n", $qayta["sahifa_hajmi"],
       get_debug_type($qayta["sahifa_hajmi"]));
printf("  moduller:      %s\n", implode(", ", $qayta["moduller"]));
printf("  pochta porti:  %d\n", $qayta["pochta"]["port"]);

echo "\nBuzuq JSON bilan nima bo'ladi:\n";
try {
    json_decode("{buzuq}", true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $x) {
    echo "  JsonException: " . $x->getMessage() . "\n";
}

unlink($json);
Natija
Fayl mazmuni:
{
    "sayt": "Softromeda",
    "til": "uz",
    "sahifa_hajmi": 20,
    "moduller": [
        "darslik",
        "imtihon",
        "sertifikat"
    ],
    "pochta": {
        "host": "smtp.example.com",
        "port": 587,
        "tls": true
    }
}

O'qish:
  sayt:          Softromeda
  sahifa_hajmi:  20 (turi: int)
  moduller:      darslik, imtihon, sertifikat
  pochta porti:  587

Buzuq JSON bilan nima bo'ladi:
  JsonException: Syntax error
json_decode ni JSON_THROW_ON_ERROR siz chaqirmang

Standart holatda json_decode buzuq JSON uchun null qaytaradi - hech qanday xato bermasdan.

Muammo shundaki, null haqiqiy javob ham bo'lishi mumkin: json_decode("null") ham null qaytaradi.

Ya'ni bu kod ikki holatni ajrata olmaydi:

PHP
$m = json_decode($xom, true);
if ($m === null) {
    // JSON buzuqmi yoki haqiqatan null mi?
}

Uch yechim bor, eng yaxshisi birinchisi:

UsulIzoh
JSON_THROW_ON_ERRORIstisno tashlaydi - eng aniq
json_last_error()Qo'shimcha chaqiruv kerak
json_validate()PHP 8.3+, faqat tekshiradi

Yana ikki bayroq deyarli har doim kerak:

BayroqNima qiladi
JSON_UNESCAPED_UNICODEO'zbek harflari \uXXXX kodiga aylanmaydi
JSON_UNESCAPED_SLASHESURL dagi / \/ bo'lib ketmaydi

Usiz "O'zbekiston" faylda o'qib bo'lmaydigan ko'rinishda saqlanadi.

Xavfsiz yo'llar #

PHP
function xavfsizYol(string $asos, string $soralgan): ?string
{
    // 1. Faqat fayl nomini olamiz - katalog qismini tashlaymiz
    $nom = basename($soralgan);

    // 2. Yashirin fayllarni rad etamiz
    if ($nom === "" || str_starts_with($nom, ".")) {
        return null;
    }

    // 3. To'liq yo'lni yasab, haqiqiy joyini aniqlaymiz
    $toliq = realpath($asos . DIRECTORY_SEPARATOR . $nom);
    if ($toliq === false) {
        return null;
    }

    // 4. Natija ASOS katalog ichida ekanini tekshiramiz
    $asosHaqiqiy = realpath($asos);
    if ($asosHaqiqiy === false || !str_starts_with($toliq, $asosHaqiqiy)) {
        return null;
    }

    return $toliq;
}

// Sinov fayllari
file_put_contents($ish . DIRECTORY_SEPARATOR . "hisobot.txt", "ma'lumot");
file_put_contents($ish . DIRECTORY_SEPARATOR . ".maxfiy", "yashirin");

$sinovlar = [
    "hisobot.txt",
    "../../../etc/passwd",
    "..\\..\\windows\\win.ini",
    ".maxfiy",
    "yoq-fayl.txt",
    "",
];

printf("%-32s %s\n", "SO'RALGAN", "NATIJA");
echo str_repeat("-", 50) . "\n";
foreach ($sinovlar as $s) {
    $natija = xavfsizYol($ish, $s);
    printf("%-32s %s\n", "'{$s}'", $natija === null ? "RAD ETILDI" : "ruxsat: " . basename($natija));
}

unlink($ish . DIRECTORY_SEPARATOR . "hisobot.txt");
unlink($ish . DIRECTORY_SEPARATOR . ".maxfiy");
Natija
SO'RALGAN                        NATIJA
--------------------------------------------------
'hisobot.txt'                    ruxsat: hisobot.txt
'../../../etc/passwd'            RAD ETILDI
'..\..\windows\win.ini'          RAD ETILDI
'.maxfiy'                        RAD ETILDI
'yoq-fayl.txt'                   RAD ETILDI
''                               RAD ETILDI
Katalogdan chiqish - eng eski veb zaifliklardan biri

../ ketma-ketligi katalogdan yuqoriga chiqaradi. Yetarlicha takrorlansa - fayl tizimining istalgan joyiga:

KOD
/var/www/yuklamalar/../../../etc/passwd
= /etc/passwd

Yuqoridagi funksiya to'rt qatlamli himoya qo'yadi va har biri kerak:

QadamNimadan saqlaydi
basename()../ va katalog yo'llaridan
. tekshiruvi.env, .git kabi yashirin fayllardan
realpath()Simvolik havolalar orqali chiqishdan
Prefiks tekshiruviQolgan hamma holatdan

Uchinchi qadam ko'pincha unutiladi: basename simvolik havolani ko'rmaydi. Kimdir yuklamalar/ ichiga /etc ga ishora qiluvchi havola qo'ysa, basename o'tkazib yuboradi - realpath esa haqiqiy joyni ochib beradi.

Eng yaxshi yechim esa - yo'l bilan umuman ishlamaslik: fayllarni bazaga id bilan yozing va foydalanuvchidan faqat id ni oling.

Amaliy topshiriq
  1. file_put_contents bilan fayl yarating, FILE_APPEND bilan qo'shing.
  2. file() va file_get_contents natijalarini solishtiring.
  3. 1000 qatorli fayl yarating va uni fgets bilan o'qing.
  4. fputcsv bilan vergulli qiymat yozing va faylni oching.
  5. Xuddi shu faylni explode(",") bilan o'qib ko'ring - buzildimi?
  6. json_encode ni JSON_UNESCAPED_UNICODE siz sinang.
  7. json_decode ga buzuq JSON bering - null qaytdimi?
  8. JSON_THROW_ON_ERROR qo'shing va istisnoni tuting.
  9. ../../etc/passwd yo'lini basename bilan tozalang.
  10. realpath va prefiks tekshiruvini qo'shib to'liq himoya yozing.

Xulosa #

  • file_get_contents butun faylni xotiraga yuklaydi - katta fayl uchun yaramaydi.
  • Hajmi noma'lum bo'lsa fgets tsikli ishlating: xotirada bir vaqtda bitta qator.
  • fopen natijasini har doim tekshiring va fclose ni unutmang.
  • CSV ni hech qachon explode(",") bilan o'qimang - fgetcsv ishlating.
  • CSV maydonida vergul, qo'shtirnoq va hatto yangi qator bo'lishi mumkin.
  • Excel uchun UTF-8 BOM va ; ajratgichi kerak bo'lishi mumkin.
  • json_decode ga JSON_THROW_ON_ERROR bering - aks holda null ikki ma'noli.
  • json_encode ga JSON_UNESCAPED_UNICODE qo'shing, aks holda o'zbek harflari buziladi.
  • Foydalanuvchi bergan yo'lni hech qachon to'g'ridan-to'g'ri ishlatmang.
  • Himoya to'rt qatlamli: basename, yashirin fayl tekshiruvi, realpath, prefiks.
  • Eng yaxshisi - yo'l o'rniga id bilan ishlash.

Keyingi bo'limda obyektga yo'naltirilgan dasturlash - sinflar va obyektlar bilan tanishamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.