Это не какие-то непонятные трюки. Это инструменты, поставляемые с современным PHP, и как только вы начнете их использовать, вы удивитесь, как вы жили без них.
1. Генераторы — обработка 1 миллиона строк с почти нулевой памятью
Вот сценарий, с которым сталкивался каждый бэкэнд-разработчик: вам нужно прочитать большой файл CSV, обработать его построчно и что-то сделать с данными. Инстинкт состоит в том, чтобы сначала загрузить все в массив.
Этот инстинкт убьет ваш сервер.
Традиционный подход:
function readCsv(string $path): array
{
$rows = [];
$handle = fopen($path, 'r');
while ($row = fgetcsv($handle)) {
$rows[] = $row; // everything lands in RAM
}
fclose($handle);
return $rows;
}
// 1 million rows = ~460MB RAM 💀
foreach (readCsv('big.csv') as $row) {
process($row);
}
С генератором:
function readCsv(string $path): Generator
{
$handle = fopen($path, 'r');
while ($row = fgetcsv($handle)) {
yield $row; // one row at a time, nothing more
}
fclose($handle);
}
// 1 million rows = ~0.3MB RAM ✨
foreach (readCsv('big.csv') as $row) {
process($row);
}
Единственная разница — доходность вместо $rows[] = . Но эффект драматичен.
Реальный тест — 1 миллион строк, файл размером 42 МБ:
| Традиционный | Генератор | |
|---|---|---|
| Время | 6,5 с | 2.67с |
| Память | 462 МБ | ~0,3 МБ |
| Результат | идентичный | идентичный |
Генератор в 2,4 раза быстрее и практически не использует память. PHP не нужно создавать огромный массив в памяти, прежде чем вы сможете начать обработку — он дает вам одну строку, вы ее обрабатываете, он выдает следующую.
выход приостанавливает функцию, передает значение вызывающему объекту, а затем возобновляет работу точно с того места, где она остановилась, когда вызывающий объект запрашивает следующее значение. Дескриптор файла остается открытым между вызовами — без возврата к началу.
За пределами CSV: Генераторы работают везде, где вы имеете дело с большими последовательностями: чтение результатов базы данных по частям, разбиение на страницы ответов API, создание бесконечных последовательностей или потоковая передача файлов журналов в режиме реального времени.
2. Именованные аргументы — никогда больше не перепутайте параметры
Быстрая викторина. Что делает этот код?
$result = array_slice($items, 1, true, 3);
Вероятно, вам нужно открыть документы. Является ли истинным флагом preserve_keys ? Является ли 3 длиной? Что что?
Это ошибка, ожидающая своего часа. И это случилось со всеми нами.
Именованные аргументы полностью исправляют эту проблему:
// ❌ Before — which true is which?
$result = array_slice($items, 1, 3, true);
// ✅ After — reads like documentation
$result = array_slice(
array: $items,
offset: 1,
length: 3,
preserve_keys: true
);
Но настоящая сила заключается не только в читабельности. Это пропуск необязательных параметров в середине.
PHP имеет множество встроенных функций с необязательными параметрами, спрятанными в середине их сигнатур. При использовании позиционных аргументов вам придется указывать каждый параметр, пока не дойдете до нужного:
// ❌ Before — had to repeat ENT_QUOTES and 'UTF-8' just to reach double_encode
$safe = htmlspecialchars($input, ENT_QUOTES, 'UTF-8', false);
// ✅ After — jump straight to what you care about
$safe = htmlspecialchars(string: $input, double_encode: false);
Именованные аргументы + распространение = чистое внедрение зависимостей:
$config = [
'table' => 'orders',
'limit' => 20,
'cache' => true,
'orderBy' => 'total',
];
// spread a named array directly into a constructor
$query = new QueryBuilder(...$config);
И прежде чем вы спросите: нет, производительность не влияет на производительность. В нашем тесте, состоящем из 1 миллиона вызовов, именованные аргументы фактически были немного быстрее , чем позиционные (внутренний оптимизатор PHP 8 справляется с ними эффективно).
3. Fibers — основа ReactPHP и Swoole
Волокна — самая непонятая особенность в этом списке. Это не волшебный async/await. Они не будут автоматически заставлять ваш код работать параллельно. Но они являются тем примитивом, который делает возможными современные асинхронные фреймворки PHP.
Fiber — это функция, которая может приостанавливать себя и передавать управление обратно вызывающей стороне , а затем возобновить ее позже именно с того места, где она остановилась.
$fiber = new Fiber(function(): string {
echo "[Fiber] starting" . PHP_EOL;
$received = Fiber::suspend('first pause'); // pause + send value out
echo "[Fiber] resumed, got: $received" . PHP_EOL;
$received = Fiber::suspend('second pause');
echo "[Fiber] resumed again, got: $received" . PHP_EOL;
return 'done';
});
echo "[Main] starting fiber" . PHP_EOL;
$yielded = $fiber->start(); // runs until first suspend
echo "[Main] fiber paused, sent: $yielded" . PHP_EOL;
$yielded = $fiber->resume('message one'); // resume, get next suspend value
echo "[Main] fiber paused again, sent: $yielded" . PHP_EOL;
$fiber->resume('message two'); // resume to completion
echo "[Main] fiber returned: " . $fiber->getReturn() . PHP_EOL;
Вывод:
[Main] starting fiber
[Fiber] starting
[Main] fiber paused, sent: first pause
[Fiber] resumed, got: message one
[Main] fiber paused again, sent: second pause
[Fiber] resumed again, got: message two
[Main] fiber returned: done
Обратите внимание на двустороннюю связь. Это то, что отличает волокна от генераторов — данные текут как внутрь, так и наружу. suspend() отправляет значение вызывающему объекту; resume($value) отправляет значение обратно в Fiber.
Ключевая идея: Волокна сами по себе не обеспечивают параллелизма. Они обеспечивают совместную многозадачность — каждое волокно добровольно передает управление в известной точке (ожидание ввода-вывода, сетевой вызов, запрос к базе данных). Затем планировщик может запустить другое волокно, пока первое ждёт.
Этот планировщик — это то, что предоставляют ReactPHP, Swoole и цикл событий Revolt. PHP 8.1 предоставил нам примитив; фреймворки построили на его основе асинхронный режим.
Если вы пишете код приложения, вы, вероятно, не будете создавать волокна напрямую. Но их понимание покажет вам , почему amphp и ReactPHP работают именно так — и почему асинхронный код PHP фундаментально отличается от однопоточного цикла событий JavaScript.
4. Первоклассный вызываемый синтаксис — функции как значения, наконец-то чистые
PHP всегда позволял передавать функции как значения. Но синтаксис был настолько болезненным, что большинство людей вместо этого просто писали встроенные замыкания.
// before PHP 8.1 — verbose wrappers everywhere
$fn = Closure::fromCallable('strlen');
$lengths = array_map(Closure::fromCallable('strtoupper'), $words);
// or inline closures that wrap a single function call
$lengths = array_map(function($w) { return strlen($w); }, $words);
В PHP 8.1 появился чистый синтаксис: functionName(...) создает первоклассное замыкание из любого вызываемого объекта — встроенных функций, статических методов, методов экземпляра — всех из них.
// ✅ PHP 8.1+
$fn = strlen(...);
$upper = strtoupper(...);
$method = $obj->format(...);
$static = Formatter::clean(...);
$lengths = array_map(strlen(...), $words);
Вот где он действительно хорош — шаблоны конвейера:
function pipeline(mixed $value, callable ...$fns): mixed
{
return array_reduce($fns, fn($carry, $fn) => $fn($carry), $value);
}
$result = pipeline(
" Hello, World! This is PHP. ",
trim(...),
strtolower(...),
fn($s) => str_replace([',', '!', '.'], '', $s),
fn($s) => explode(' ', $s),
array_filter(...),
array_values(...),
);
// ['hello', 'world', 'this', 'is', 'php']
Или цепочки проверки , где каждое правило является просто вызываемым:
function validate(mixed $value, callable ...$rules): array
{
return array_filter(array_map(fn($rule) => $rule($value), $rules));
}
$errors = validate(
$password,
fn($v) => strlen($v) < 8 ? 'Minimum 8 characters' : null,
fn($v) => !preg_match('/\d/', $v) ? 'Must contain a number' : null,
fn($v) => !preg_match('/[A-Z]/', $v) ? 'Must contain uppercase' : null,
);
Чистый, компонуемый, тестируемый — каждое правило можно независимо протестировать, а добавление нового — это однострочный процесс.
5. Недооцененные встроенные функции, которые вы продолжаете переопределять
В стандартной библиотеке PHP есть несколько действительно полезных функций, которым не уделяется должного внимания. Это не новые функции — они существуют уже некоторое время — но они решают проблемы, которые разработчики продолжают решать вручную.
array_is_list() — PHP 8.1+
// before: manual check
function isList(array $arr): bool {
return $arr === [] || array_keys($arr) === range(0, count($arr) - 1);
}
// now
array_is_list([1, 2, 3]); // true
array_is_list(['a' => 1, 'b' => 2]); // false
array_is_list([0 => 'a', 1 => 'b']); // true
Важно, когда вы решаете, следует ли кодировать JSON как [] или {} .
str_contains() , str_starts_with() , str_ends_with() — PHP 8.0+
// before
if (strpos($haystack, $needle) !== false) { ... }
// now
if (str_contains($haystack, $needle)) { ... }
if (str_starts_with($url, 'https://')) { ... }
if (str_ends_with($file, '.php')) { ... }
Больше никаких сравнений !== false . Больше не нужно случайно считать 0 ложным.
array_any() и array_all() — PHP 8.5+
// before: manual loops or array_filter tricks
$hasAdmin = count(array_filter($users, fn($u) => $u->role === 'admin')) > 0;
// PHP 8.5
$hasAdmin = array_any($users, fn($u) => $u->role === 'admin');
$allActive = array_all($users, fn($u) => $u->isActive());
Собираем это вместе
Ни одна из этих функций не требует новой инфраструктуры или серьезного рефакторинга. Они доступны в PHP 8.0–8.1+, которые уже работают в большинстве производственных сред.
Схема для всех пяти одинакова: меньше шаблонов, более ясные намерения, лучшее использование ресурсов. Генераторы держат память под контролем. Именованные аргументы делают подписи самодокументируемыми. Волокна питают асинхронную экосистему. Первоклассные вызываемые объекты делают функциональную композицию чистой. А встроенные убирают ручные переопределения.
Начните с того, что решает проблему, с которой вы уже столкнулись. Как только он щелкнет, все остальное произойдет само собой.
Все тесты проводились с использованием интерфейса командной строки PHP 8.3 в Ubuntu. Исходный код каждого примера доступен на GitHub . `
Комментарии (0)
Пока нет комментариев — будьте первым.