Практикум по PHP: задачи с решениями

Раздел: Обучение PHP -> Примеры задач

Типовые задачи PHP и их решения

Как отсортировать массив по значению элементов?

Основное решение: использование usort с пользовательской функцией сравнения.


<?php
$users = [
    ['name' => 'Иван', 'age' => 30],
    ['name' => 'Мария', 'age' => 25],
    ['name' => 'Петр', 'age' => 35]
];

usort($users, function($a, $b) {
    return $a['age'] <=> $b['age'];
});

print_r($users);
?>

Php примеры задач (примеры задач на php)

Array
(
    [0] => Array ( [name] => Мария [age] => 25 )
    [1] => Array ( [name] => Иван [age] => 30 )
    [2] => Array ( [name] => Петр [age] => 35 )
)

Функция usort принимает массив и коллбек, который возвращает -1, 0 или 1. Оператор <=> (spaceship) упрощает сравнение. Этот метод подходит для сортировки по числовому или строковому значению.

Вариант 1: sort() для индексированных массивов.


$numbers = [3, 1, 4, 1, 5];
sort($numbers);
print_r($numbers);
Array ( [0] => 1 [1] => 1 [2] => 3 [3] => 4 [4] => 5 )

sort() переупорядочивает значения, сбрасывая ключи. Используется для простых списков.

Вариант 2: asort() для ассоциативных массивов.


$ages = ['Иван' => 30, 'Мария' => 25, 'Петр' => 35];
asort($ages);
print_r($ages);
Array ( [Мария] => 25 [Иван] => 30 [Петр] => 35 )

asort сохраняет связь ключ-значение. Полезно для ассоциативных данных.

Вариант 3: array_multisort() для сортировки по нескольким полям.


$users = [
    ['name' => 'Иван', 'age' => 30, 'salary' => 50000],
    ['name' => 'Мария', 'age' => 25, 'salary' => 60000],
    ['name' => 'Петр', 'age' => 35, 'salary' => 50000]
];
$ages = array_column($users, 'age');
$salaries = array_column($users, 'salary');
array_multisort($ages, SORT_ASC, $salaries, SORT_DESC, $users);
print_r($users);

Сортирует сначала по возрасту (по возрастанию), затем по зарплате (по убыванию). Требует извлечения столбцов.

Типичные ошибки и проблемы:

  • Использование usort без оператора <=> может привести к неожиданному порядку при сравнении строк с числами. Лучше приводить типы.
  • sort() и asort() модифицируют исходный массив. Если нужна копия, применяйте функции с префиксом 'array_'.
  • При сортировке многомерного массива с помощью usort ключи сбрасываются. Для сохранения ключей используйте uasort.
  • Ошибка: сортировка по умолчанию по возрастанию, для убывания используйте rsort() или отрицательное значение в колбеке.

Как прочитать данные из CSV файла?

Основное решение: использование fgetcsv в цикле.


<?php
$filename = 'users.csv';
$handle = fopen($filename, 'r');
if ($handle === false) {
    die('Не удалось открыть файл');
}
$headers = fgetcsv($handle); // первая строка - заголовки
$data = [];
while (($row = fgetcsv($handle)) !== false) {
    $data[] = array_combine($headers, $row);
}
fclose($handle);
?>

fgetcsv автоматически разбирает строку CSV с учетом разделителей и кавычек. Пример собирает ассоциативный массив, используя заголовки как ключи.

Вариант 1: SplFileObject для объектно-ориентированного подхода.


$file = new SplFileObject('users.csv');
$file->setFlags(SplFileObject::READ_CSV);
$headers = $file->fgetcsv();
$data = [];
foreach ($file as $row) {
    if ($row !== [null]) {
        $data[] = array_combine($headers, $row);
    }
}

SplFileObject удобен для итерации по большим файлам. Флаг READ_CSV включает автоматический парсинг.

Вариант 2: str_getcsv для разбора строки CSV.


$csvString = "Иван,30\nМария,25";
$lines = explode("\n", $csvString);
$data = [];
foreach ($lines as $line) {
    $data[] = str_getcsv($line);
}
print_r($data);

Удобно для данных, полученных из переменной, а не из файла.

Типичные ошибки и проблемы:

  • Не учитывать различие в разделителях (запятая, точка с запятой). Указывайте второй параметр fgetcsv($handle, 0, ';').
  • Наличие BOM (Byte Order Mark) в UTF-8 файлах. BOM добавляется в первую строку. Убирайте его: fgetcsv($handle) и затем удалить BOM из первой ячейки.
  • Пустые строки в конце файла: проверяйте $row !== [null] или используйте array_filter.
  • Закрытие файла после чтения (fclose) – обязательно, особенно при долгих скриптах.

Как отправить HTTP запрос через cURL?

Основное решение: стандартная последовательность curl_init, curl_setopt, curl_exec.


<?php
$url = 'https://api.example.com/data';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPGET, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
    echo 'Ошибка cURL: ' . curl_error($ch);
}
curl_close($ch);
$data = json_decode($response, true);
?>

curl_setopt настраивает параметры: возврат строки, метод запроса. После выполнения обязательно проверяется ошибка.

Вариант 1: file_get_contents с контекстом.


$options = [
    'http' => [
        'method' => 'GET',
        'header' => "Accept: application/json\r\n"
    ]
];
$context = stream_context_create($options);
$response = file_get_contents('https://api.example.com/data', false, $context);

Простой способ для GET запросов без дополнительных заголовков. Для POST используйте 'content'.

Вариант 2: библиотека Guzzle (composer require guzzlehttp/guzzle).


use GuzzleHttp\Client;
$client = new Client();
$response = $client->request('GET', 'https://api.example.com/data');
$body = $response->getBody()->getContents();

Guzzle предоставляет удобный интерфейс, обработку ошибок через исключения, поддержку асинхронных запросов. Рекомендуется для современных проектов.

Вариант 3: curl_multi для множественных запросов.


$urls = ['url1', 'url2'];
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($mh, $ch);
    $handles[$i] = $ch;
}
$running = null;
do {
    curl_multi_exec($mh, $running);
} while ($running > 0);
$results = [];
foreach ($handles as $i => $ch) {
    $results[$i] = curl_multi_getcontent($ch);
    curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);

Позволяет отправлять запросы параллельно, сокращая общее время ожидания.

Типичные ошибки и проблемы:

  • Не проверять curl_exec на ошибку: используйте curl_errno().
  • SSL сертификаты: если сайт использует самоподписанный сертификат, отключите проверку CURLOPT_SSL_VERIFYPEER = false (не рекомендуется для продакшена).
  • Таймауты: ставьте CURLOPT_TIMEOUT и CURLOPT_CONNECTTIMEOUT.
  • Редиректы: CURLOPT_FOLLOWLOCATION = true для автоматического перехода (но с осторожностью).

Как работать с датами и временем?

Основное решение: класс DateTime и его методы.


<?php
$date = new DateTime('2025-01-15 14:30:00', new DateTimeZone('Europe/Moscow'));
echo $date->format('d.m.Y H:i'); // 15.01.2025 14:30
$date->modify('+1 day');
echo $date->format('d.m.Y'); // 16.01.2025
?>

DateTime учитывает часовые пояса и позволяет выполнять арифметику с помощью modify и DateInterval.

Вариант 1: date() и strtotime() для простых операций.


echo date('d.m.Y', strtotime('2025-01-15 +1 month')); // 15.02.2025

Функции удобны для быстрого форматирования, но не обрабатывают сложные сценарии (разные часовые пояса, переход на летнее время).

Вариант 2: DateTimeImmutable для неизменяемых объектов.


$date = new DateTimeImmutable('2025-01-15');
$modified = $date->modify('+10 days');
echo $date->format('Y-m-d'); // 2025-01-15 (оригинал не изменился)
echo $modified->format('Y-m-d'); // 2025-01-25

Полезно для функционального стиля, где оригинальная дата не должна изменяться.

Вариант 3: библиотека Carbon (расширение DateTime).


use Carbon\Carbon;
$date = Carbon::parse('2025-01-15');
echo $date->addDay()->format('d.m.Y'); // 16.01.2025

Carbon добавляет множество удобных методов (diffForHumans, startOfDay, isWeekend и т.д.) и часто используется в Laravel.

Типичные ошибки и проблемы:

  • Проблемы с strtotime('+1 month'): в январе 31 день, результат может быть неожиданным (февраль укорочен). Используйте DateTime и modify с caution.
  • Отсутствие часового пояса: всегда указывайте DateTimeZone при создании объекта, иначе используется UTC.
  • Сравнение дат: используйте объекты DateTime напрямую с операторами сравнения (<, >, ==).
  • Формат вывода: проверяйте строки форматирования в документации, чтобы избежать путаницы между день/месяц.

Дополнительные продвинутые примеры

Рекурсивная сортировка многомерного массива по ключу

Функция сортирует все подмассивы по заданному ключу, сохраняя структуру.

Пример

function sortByKeyRecursive(array &$array, string $key, int $order = SORT_ASC): void
{
    uasort($array, function($a, $b) use ($key, $order) {
        $cmp = ($a[$key] ?? '') <=> ($b[$key] ?? '');
        return $order === SORT_DESC ? -$cmp : $cmp;
    });
    foreach ($array as &$item) {
        if (is_array($item)) {
            sortByKeyRecursive($item, $key, $order);
        }
    }
}

$data = [
    ['name' => 'Иван', 'children' => [
        ['name' => 'Петр', 'age' => 10],
        ['name' => 'Анна', 'age' => 8]
    ]],
    ['name' => 'Мария', 'children' => [
        ['name' => 'Ольга', 'age' => 5],
        ['name' => 'Илья', 'age' => 7]
    ]]
];
sortByKeyRecursive($data, 'age');
print_r($data);
Array
(
    [0] => Array
        (
            [name] => Мария
            [children] => Array
                (
                    [0] => Array ( [name] => Ольга [age] => 5 )
                    [1] => Array ( [name] => Илья [age] => 7 )
                )
        )
    [1] => Array
        (
            [name] => Иван
            [children] => Array
                (
                    [0] => Array ( [name] => Анна [age] => 8 )
                    [1] => Array ( [name] => Петр [age] => 10 )
                )
        )
)

Функция uasort сохраняет ключи верхнего уровня. Рекурсия обрабатывает все вложенные массивы. Пригодно для иерархических структур (категории, комментарии).

Потоковая обработка большого CSV файла с помощью генератора

Генератор позволяет читать файл построчно, не загружая его целиком в память.

Пример

function csvGenerator(string $filename, string $delimiter = ','): Generator
{
    $handle = fopen($filename, 'r');
    if ($handle === false) {
        throw new RuntimeException('Cannot open file');
    }
    $headers = fgetcsv($handle, 0, $delimiter);
    if ($headers === false) {
        fclose($handle);
        throw new RuntimeException('Empty file');
    }
    while (($row = fgetcsv($handle, 0, $delimiter)) !== false) {
        if ($row !== [null]) {
            yield array_combine($headers, $row);
        }
    }
    fclose($handle);
}

foreach (csvGenerator('huge_file.csv') as $record) {
    // Обработка одной записи
    echo $record['name'], "\n";
}

Используется для файлов размером гигабайты. Память расходуется только на одну строку.

Асинхронные HTTP запросы с curl_multi

Параллельная отправка нескольких запросов с обработкой результатов по готовности.

Пример

$urls = [
    'https://httpbin.org/delay/2',
    'https://httpbin.org/delay/1',
];

$mh = curl_multi_init();
$handles = [];
foreach ($urls as $id => $url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
    ]);
    curl_multi_add_handle($mh, $ch);
    $handles[$id] = $ch;
}

$active = null;
$status = null;
do {
    $mrc = curl_multi_exec($mh, $active);
    $info = curl_multi_info_read($mh);
    if ($info !== false && $info['msg'] === CURLMSG_DONE) {
        $ch = $info['handle'];
        $content = curl_multi_getcontent($ch);
        echo "Получен ответ: " . substr($content, 0, 50) . "...\n";
    }
} while ($active > 0 || $info !== false);

foreach ($handles as $ch) {
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}
curl_multi_close($mh);
Получен ответ: {"args":{}...
Получен ответ: {"args":{}...

Результаты появляются по мере завершения, общее время равно самому долгому запросу, а не сумме.

Расчет возраста с учетом часовых поясов и DST

Точный возраст на текущий момент с учетом перехода на летнее время.

Пример

function calculateAge(string $birthDate, string $timezone = 'Europe/Moscow'): int
{
    $tz = new DateTimeZone($timezone);
    $birth = new DateTime($birthDate, $tz);
    $now = new DateTime('now', $tz);
    $interval = $birth->diff($now);
    return $interval->y;
}

echo calculateAge('1990-06-15');
34 (если сейчас 2025 год)

Метод diff учитывает високосные годы и изменения часовых поясов (DST). Для более точного расчета (часы, минуты) можно использовать $interval->days и деление.

Расширенная версия с дробным возрастом:

Пример

function fractionalAge(string $birthDate, string $timezone = 'Europe/Moscow'): float
{
    $tz = new DateTimeZone($timezone);
    $birth = new DateTime($birthDate, $tz);
    $now = new DateTime('now', $tz);
    $diff = $birth->diff($now);
    // Разница в днях + доля от года
    $totalDays = (int)$diff->format('%r%a');
    return round($totalDays / 365.2425, 2);
}
echo fractionalAge('1990-06-15');
34.52 (примерно)

Коэффициент 365.2425 учитывает среднюю продолжительность года. Подходит для научных или демографических расчетов.

Возможные проблемы:

  • Не забывать про часовой пояс: если не указать, используется UTC и возраст может отличаться на 1 год при рождении в полночь.
  • При использовании strtotime('+1 year') в феврале 29 числа может возникнуть ошибка, если год не високосный. DateTime::modify('+1 year') корректно обрабатывает такие случаи.

Примеры задач на PHP - comments

En
Php примеры задач (php)