Практикум по 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') корректно обрабатывает такие случаи.