Программирование на PHP
Базовое программирование на PHP и стандартная библиотека — функции для массивов и строк, замыкания и стрелочные функции, именованные аргументы и первоклассные вызываемые, JSON против сериализации, многобайтовый текст, внедрение зависимостей и потоковая обработка больших файлов.
27 вопросов
JuniorТеорияОчень частоЧем array_map, array_filter и array_reduce различаются по возвращаемому значению?
Чем array_map, array_filter и array_reduce различаются по возвращаемому значению?
array_map применяет колбэк к каждому значению и возвращает массив того же размера, сохраняя ключи, если передан один массив. array_filter оставляет значения, для которых колбэк истинен, и сохраняет исходные ключи, поэтому в нумерации появляются дыры. array_reduce сворачивает весь массив в одно накопленное значение.
Типичные ошибки
- ✗Ожидать, что
array_filterпереиндексирует, и получить дыры при кодировании в JSON - ✗Считать, что
array_mapпередаёт колбэку ключ вторым аргументом - ✗Забыть, что
array_filterбез колбэка отбрасывает все ложные значения
Уточняющие вопросы
- →Почему отфильтрованный массив сериализуется в JSON-объект, а не в массив?
- →Что происходит с ключами, когда
array_mapполучает сразу два массива?
JuniorТеорияОчень частоЧем array_slice отличается от array_splice по тому, что он трогает и возвращает?
Чем array_slice отличается от array_splice по тому, что он трогает и возвращает?
array_slice ничего не разрушает: он оставляет исходный массив нетронутым и возвращает извлечённый кусок. array_splice принимает массив по ссылке, вырезает из него выбранный диапазон на месте, при желании подставляет замену и возвращает удалённые элементы. Splice всегда перенумеровывает целочисленные ключи.
Типичные ошибки
- ✗Звать
array_spliceв расчёте на копию и терять элементы, вырезанные из исходного массива - ✗Считать, что
array_sliceперенумеровывает и строковые ключи, тогда как он их сохраняет - ✗Забывать, что
array_spliceпринимает первый аргумент по ссылке
Уточняющие вопросы
- →Что меняет аргумент
preserve_keysу функцииarray_slice? - →Как вставить элементы в середину массива, ничего при этом не удаляя?
JuniorКодОчень частоЗамыкание-счётчик с захватом по ссылке
Замыкание-счётчик с захватом по ссылке
Верните анонимную функцию, захватывающую $in по ссылке через use (&$in). Захват по ссылке позволяет замыканию менять одну и ту же переменную между вызовами, поэтому накапливаемая сумма сохраняется. Без & каждый вызов стартовал бы от исходного $in, так как значение копируется в момент определения.
Типичные ошибки
- ✗Захват по значению (
use ($in)) с ожиданием, что сумма сохранится между вызовами - ✗Считать, что замыкания PHP автоматически связывают внешнюю область как в JavaScript, без явного
use - ✗Путать захват по ссылке
use (&$in)соstatic-переменной внутри замыкания
Уточняющие вопросы
- →Чем
use ($in)отличается отuse (&$in)в момент определения замыкания? - →Что автоматически захватывает стрелочная функция
fn() =>— по значению или по ссылке?
JuniorТеорияОчень частоЧто класс получает через конструктор при внедрении зависимостей?
Что класс получает через конструктор при внедрении зависимостей?
Он получает уже готовые объекты, с которыми работает. Вместо вызова new PdoUserRepository() внутри себя класс объявляет параметр типа UserRepository и получает экземпляр от того, кто его создаёт. Зависимость становится видна в сигнатуре, и тест может подсунуть подделку вместо настоящего объекта, не трогая сам класс.
Типичные ошибки
- ✗Звать
newдля зависимости внутри класса, пряча её от сигнатуры конструктора - ✗Путать внедрение с доставанием объекта из глобального контейнера прямо в методе
- ✗Объявлять тип конкретной реализации там, где хватило бы интерфейса
Уточняющие вопросы
- →Почему для тестируемости важно внедрять интерфейс, а не конкретный класс?
- →Кто строит и передаёт зависимости в приложении на фреймворке?
MiddleТеорияОчень частоЧем array_merge и оператор объединения + различаются на совпадающих ключах?
Чем array_merge и оператор объединения + различаются на совпадающих ключах?
array_merge перенумеровывает целочисленные ключи, поэтому записи дописываются, а не затираются, а при совпадении строкового ключа побеждает более поздний аргумент. Оператор + не перенумеровывает и не затирает ничего: для любого ключа, уже имеющегося в левом операнде, сохраняется левое значение. То есть + за первый массив, а array_merge — за последний.
Типичные ошибки
- ✗Сливать два списка через
+и молча оставить лишь хвост более длинного - ✗Ждать, что
array_mergeсохранит целочисленные ключи, а не перенумерует их - ✗Накладывать значения по умолчанию через
array_mergeтам, где нужен+, чтобы победил вызывающий
Уточняющие вопросы
- →Какой из двух вы возьмёте, чтобы наложить массив значений по умолчанию под опции пользователя?
- →Что сделает
array_merge, если один и тот же строковый ключ встретится в трёх аргументах?
JuniorТеорияЧастоПочему для получения первого ключа массива лучше array_key_first, чем reset?
Почему для получения первого ключа массива лучше array_key_first, чем reset?
reset и end создавались для внутреннего указателя массива: они двигают его и возвращают первое или последнее значение, но не ключ. array_key_first и array_key_last возвращают сам ключ, оставляют указатель на месте и на пустом массиве отдают null вместо false, так что никакой побочный эффект не утекает в окружающий код.
Типичные ошибки
- ✗Принимать значение, возвращённое
reset, за первый ключ массива - ✗Не замечать, что
resetиendпобочно двигают внутренний указатель - ✗Сравнивать результат с
false, тогда как на пустом массиве возвращаетсяnull
Уточняющие вопросы
- →Что вернёт
array_key_firstдля пустого массива и как это проверить? - →Где внутренний указатель массива всё ещё важен в современном коде на PHP?
JuniorТеорияЧастоЧто возвращают array_keys и array_values и чем их результаты различаются?
Что возвращают array_keys и array_values и чем их результаты различаются?
array_keys возвращает список ключей массива, array_values — список его значений. Обе отбрасывают исходную связь ключа со значением и отдают новый список, пронумерованный с нуля, — именно поэтому array_values и служит идиоматичным способом закрыть дыры, оставленные после array_filter.
Типичные ошибки
- ✗Ожидать, что
array_keysсохранит исходные ключи, а не вернёт список с нумерацией от нуля - ✗Считать, что
array_valuesсортирует значения, тогда как она лишь перенумеровывает ключи - ✗Собирать ключи вручную в цикле
foreach, хотя их уже возвращаетarray_keys
Уточняющие вопросы
- →Что делает необязательный второй аргумент функции
array_keys? - →Почему массив с дырами кодируется в JSON-объект, а не в JSON-массив?
JuniorТеорияЧастоКогда вы возьмёте array_walk вместо array_map?
Когда вы возьмёте array_walk вместо array_map?
array_map — это преобразование: он передаёт колбэку каждое значение и собирает новый массив из возвращённых значений. array_walk — это обход ради побочного эффекта: он передаёт колбэку и элемент, и его ключ, а возвращает лишь булево значение, поэтому меняет исходный массив на месте, и только если параметр значения объявлен по ссылке.
Типичные ошибки
- ✗Ждать от
array_walkизменённый массив вместо булева значения - ✗Менять значение внутри колбэка
array_walk, не приняв его по ссылке - ✗Считать, что
array_mapпередаёт колбэку ключ вторым аргументом, как это делаетarray_walk
Уточняющие вопросы
- →Как получить и ключ, и значение внутри колбэка функции
array_map? - →Что передаёт колбэку третий аргумент функции
array_walk?
JuniorТеорияЧастоЧто меняют именованные аргументы в вызове функции на PHP 8?
Что меняют именованные аргументы в вызове функции на PHP 8?
Именованный аргумент передаётся как name: value, поэтому вызывающий указывает параметр по объявленному имени, а не по позиции. Это позволяет пропустить все необязательные параметры, которые вам безразличны, и задать лишь нужный, а вызов с несколькими булевыми флагами становится читаемым. Именованные аргументы должны идти после позиционных.
Типичные ошибки
- ✗Ставить позиционный аргумент после именованного — это ошибка на этапе компиляции
- ✗Считать имя параметра внутренним, тогда как именованный вызов делает его частью публичного API
- ✗Верить, что именованный аргумент может повторить параметр, уже переданный позиционно
Уточняющие вопросы
- →Почему переименование параметра становится ломающим изменением, когда его зовут по имени?
- →Как именованные аргументы сочетаются с распаковкой массива со строковыми ключами?
JuniorТеорияЧастоЧем различаются sort, asort и ksort на ассоциативном массиве?
Чем различаются sort, asort и ksort на ассоциативном массиве?
Все три сортируют массив на месте, по ссылке, и возвращают булево значение, а не новый массив. sort упорядочивает по значению и выбрасывает ключи, перенумеровывая массив с нуля. asort тоже упорядочивает по значению, но сохраняет привязку каждого ключа к своему значению. ksort же упорядочивает по ключу.
Типичные ошибки
- ✗Писать
$sorted = sort($arr)и получатьtrueвместо отсортированного массива - ✗Брать
sortдля ассоциативного массива и молча терять все его ключи - ✗Путать
asortсksortи сортировать по значению там, где нужен ключ
Уточняющие вопросы
- →Какой функцией вы отсортируете по значению со своим правилом сравнения?
- →Что меняет аргумент с флагами
SORT_*в способе сравнения значений?
JuniorТеорияЧастоЧто делает ... в вызове, в литерале массива и в сигнатуре функции?
Что делает ... в вызове, в литерале массива и в сигнатуре функции?
В вызове f(...$args) распаковывает массив в отдельные аргументы; в литерале [...$a, ...$b] сшивает массивы. В сигнатуре наоборот — function f(...$rest) собирает оставшиеся аргументы в массив. С PHP 8.1 распаковка принимает и строковые ключи, которые ведут себя как именованные аргументы.
Типичные ошибки
- ✗Читать
...в сигнатуре как распаковку, а не как сбор оставшихся аргументов - ✗Считать, что распаковка массива со строковыми ключами — всегда фатальная ошибка
- ✗Ожидать, что
[...$a, ...$b]сохранит исходные целочисленные ключи
Уточняющие вопросы
- →Что происходит с целочисленными ключами при распаковке двух массивов в один литерал?
- →Как распаковка со строковыми ключами связана с именованными аргументами в вызове?
JuniorТеорияЧастоЧто заменили str_contains, str_starts_with и str_ends_with и почему?
Что заменили str_contains, str_starts_with и str_ends_with и почему?
Они заменили ошибкоопасные идиомы вокруг strpos. Поскольку strpos возвращает позицию совпадения и false, если его нет, совпадение по смещению 0 оказывается ложным, поэтому проверку приходилось писать как strpos($h, $n) !== false, а проверку префикса — через === 0. Три функции из PHP 8 возвращают обычное булево значение и прямо говорят, что делают.
Типичные ошибки
- ✗Писать
if (strpos($h, $n))— проверка молча падает на совпадении по смещению 0 - ✗Считать, что эти три функции учитывают многобайтность, а не работают с байтами
- ✗Ждать от них игнорирования регистра, тогда как все три сравнивают с учётом регистра
Уточняющие вопросы
- →Почему совпадение по смещению 0 — та самая ошибка, которую порождала идиома с
strpos? - →Чем вы проверите наличие подстроки без учёта регистра?
MiddleТеорияЧастоЧем стрелочная функция fn отличается от анонимной по тому, что она захватывает?
Чем стрелочная функция fn отличается от анонимной по тому, что она захватывает?
Анонимная функция ничего не захватывает неявно — каждую внешнюю переменную нужно перечислить в блоке use, и она захватывается по значению в момент создания замыкания. Стрелочная функция захватывает автоматически и по значению каждую внешнюю переменную, которую упоминает её единственное выражение, поэтому блок use ей не нужен, а тело из нескольких инструкций иметь она не может.
Типичные ошибки
- ✗Ждать, что стрелочная функция захватит по ссылке и увидит поздние изменения переменной
- ✗Пытаться дать стрелочной функции тело из нескольких инструкций в фигурных скобках
- ✗Забыть блок
useу анонимной функции и обратиться к неопределённой переменной
Уточняющие вопросы
- →Как захватить переменную по ссылке в анонимной функции и когда это уместно?
- →Почему стрелочная функция всё равно видит
$thisбез явной привязки?
JuniorТеорияИногдаЧем индексный массив в PHP отличается от ассоциативного под капотом?
Чем индексный массив в PHP отличается от ассоциативного под капотом?
Тип массива в PHP один: упорядоченная хэш-таблица с ключами int или string. Индексный массив — просто тот, чьи ключи оказались 0..n-1, что проверяет array_is_list(). Числовые строковые ключи приводятся к int, поэтому $a['8'] и $a[8] — один слот, а порядок задаётся вставкой.
Типичные ошибки
- ✗Считать индексный и ассоциативный массивы двумя разными структурами данных
- ✗Ожидать, что
$a['8']и$a[8]— два разных ключа - ✗Полагать, что
foreachвыдаёт ключи в отсортированном порядке, а не в порядке вставки
Уточняющие вопросы
- →Что проверяет
array_is_list()и когда отфильтрованный массив перестаёт быть списком? - →Какие типы ключей приводятся при записи и что произойдёт с ключом
trueилиnull?
JuniorКодИногдаTitle-case строки с поддержкой Юникода
Title-case строки с поддержкой Юникода
Используйте mb_convert_case($string, MB_CASE_TITLE). Многобайтовое семейство mb_* понимает UTF-8, поэтому корректно приводит первую букву каждого слова к верхнему регистру для кириллицы. Не-многобайтовый ucwords трактует каждый байт как символ и портит многобайтовые буквы, давая кракозябры на привет.
Типичные ошибки
- ✗Брать
ucwords/ucfirst, работающие побайтово и ломающие UTF-8 - ✗Считать, что UTF-8 кодировка исходника делает побайтовые функции безопасными для Юникода
- ✗Забыть задать или передать кодировку, оставив
mb_*угадывать неверную charset
Уточняющие вопросы
- →Почему
strlen('привет')возвращает 12, а не 6, и какая функция вернёт 6? - →Как заставить
mb_*считать кодировкой UTF-8 без передачи её в каждом вызове?
JuniorТеорияИногдаЧем preg_match и preg_match_all различаются по возврату и по заполняемым данным?
Чем preg_match и preg_match_all различаются по возврату и по заполняемым данным?
preg_match останавливается на первом совпадении: он возвращает 1, 0 или false при ошибке и заполняет $matches одним плоским массивом, где индекс 0 — всё совпадение целиком, а остальные — группы захвата. preg_match_all просматривает всю строку, возвращает число найденных совпадений и заполняет $matches двумерным массивом, сгруппированным по группам захвата.
Типичные ошибки
- ✗Читать
preg_matchтак, будто он собирает все совпадения, а не только первое - ✗Считать возврат
preg_matchбулевым и проглядеть случай ошибкиfalse - ✗Индексировать результат
preg_match_allкак плоский, забыв про внешний уровень групп
Уточняющие вопросы
- →Что меняет флаг
PREG_SET_ORDERв форме массива$matches? - →Как отличить несовпадение от ошибки в вызове
preg_match?
MiddleКодИногдаCache-aside кэширование в getByIds
Cache-aside кэширование в getByIds
Примените паттерн cache-aside (read-through). Прочитайте кэш по $ids, вычислите промахи через array_diff($ids, array_keys($cached)), выйдите рано, если их нет, иначе запросите недостающие id у клиента, запишите их обратно в кэш и верните array_merge($cached, $fetched). Внедрённые Cache и Client делают метод тестируемым.
Типичные ошибки
- ✗Diff id со значениями кэша, а не с
array_keys($cached), из-за чего попадания не исключаются - ✗Перезапись кэша только свежими элементами, теряя ранее закэшированные записи
- ✗Запрос всего списка id при каждом вызове, что сводит на нет смысл кэша
Уточняющие вопросы
- →Как фиксировать метрики hit/miss кэша, не меняя возвращаемое значение метода?
- →Что меняется, если кэш возвращает частичный набор по ключам id, а не плоский список?
MiddleТеорияИногдаПочему внедрение через конструктор предпочтительнее вытягивания сервисов из локатора?
Почему внедрение через конструктор предпочтительнее вытягивания сервисов из локатора?
Внедрение через конструктор делает каждую зависимость явной прямо в сигнатуре, поэтому класс нельзя построить в некорректном состоянии, а читающий сразу видит все его зависимости. Локатор сервисов прячет их в телах методов: класс начинает зависеть от самого контейнера, тесту приходится настраивать этот глобальный объект, а забытая привязка всплывает лишь в момент вызова.
Типичные ошибки
- ✗Внедрять сам контейнер и называть это внедрением зависимостей
- ✗Считать локатор тестируемым на том основании, что контейнер можно подменить глобально
- ✗Узнавать о забытой привязке лишь тогда, когда метод, который её достаёт, наконец выполнится
Уточняющие вопросы
- →Когда локатор сервисов всё же остаётся прагматичным выбором в легаси-коде?
- →Как продвижение свойств конструктора меняет цену внедрения множества зависимостей?
MiddleТеорияИногдаЧто порождает синтаксис первоклассного вызываемого strlen(...)?
Что порождает синтаксис первоклассного вызываемого strlen(...)?
Он порождает объект Closure, привязанный к этой функции или методу, не вызывая её. Многоточие здесь не распаковка: оно велит компилятору построить замыкание на этапе компиляции, поэтому опечатка в имени ловится сразу, область видимости и $this метода захватываются верно, и никакие строки или массивы-callable в этом не участвуют.
Типичные ошибки
- ✗Читать
...как распаковку аргументов, а не как маркер создания замыкания - ✗Ждать callable-строку, тогда как результат — настоящий объект
Closure - ✗Считать, что функция вызывается прямо здесь, а не просто берётся по ссылке
Уточняющие вопросы
- →Чем
$this->method(...)отличается отClosure::fromCallable([$this, 'method'])? - →Почему опечатку в имени здесь ловят раньше, чем при callable-строке?
MiddleТеорияИногдаКогда для хранения значения PHP вы выберете json_encode, а не serialize?
Когда для хранения значения PHP вы выберете json_encode, а не serialize?
JSON — формат обмена: его прочтёт любой язык, но информации о классе он не несёт, поэтому объект возвращается как stdClass или как массив. serialize — формат только для PHP, восстанавливающий исходный класс и даже его приватные свойства. Выбирайте JSON почти всегда: unserialize на недоверенных данных — вектор внедрения объектов.
Типичные ошибки
- ✗Звать
unserializeдля данных, пришедших из куки или тела запроса - ✗Ждать, что
json_decodeвернёт исходный класс, а неstdClass - ✗Забывать, что
json_encodeмолча пропускает приватные свойства объекта
Уточняющие вопросы
- →Что меняет реализация
JsonSerializableв способе кодирования объекта? - →Почему
unserializeдля пользовательского ввода — риск внедрения объектов и чем его снижают?
MiddleКодИногдаПодсчёт ошибок в большом логе за бюджет времени
Подсчёт ошибок в большом логе за бюджет времени
Читайте файл построчно через fgets в цикле — память остаётся постоянной независимо от размера файла. Для каждой строки explode(';', ...) и сравнивайте поле с $errorCode. Считайте время через microtime(true) и break при превышении 100 мс. Важно fclose дескриптор (в эталоне он утекает) и учитывать, что бюджет даёт частичный результат.
Типичные ошибки
- ✗Загрузка всего файла (
file,file_get_contents) вместо потока, что раздувает память - ✗Забыть
fclose, утекая файловый дескриптор (этот изъян есть в эталоне) - ✗Считать частичный результат по бюджету времени полным числом совпадений
Уточняющие вопросы
- →Почему
fgetsпотребляет память константно, аfile()— пропорционально размеру файла? - →Как сделать частичный результат честным — вернуть флаг, что скан был прерван?
MiddleТеорияИногдаЧто расширение URI из PHP 8.5 даёт такого, чего не даёт parse_url?
Что расширение URI из PHP 8.5 даёт такого, чего не даёт parse_url?
parse_url разбивает строку на массив частей по мере сил: он ничего не проверяет и не нормализует и спокойно принимает битый URL. Всегда включённое расширение uri даёт настоящие объекты, разбирающие адрес по спецификации: Uri\Rfc3986\Uri — по RFC 3986, а Uri\WhatWg\Url — по правилам WHATWG, и они отвергают некорректный ввод.
Типичные ошибки
- ✗Полагаться, что
parse_urlотвергнет битый URL, тогда как он ничего не проверяет и не нормализует - ✗Искать единый класс
Uri\Uriвместо классов для RFC 3986 и для WHATWG - ✗Считать, что расширение нужно ставить отдельно, тогда как оно доступно всегда
Уточняющие вопросы
- →Почему для RFC 3986 и стандарта URL от WHATWG нужны два разных парсера?
- →Что вы проверите, прежде чем отдать присланный пользователем URL в HTTP-клиент?
MiddleТеорияИногдаЧто ломается, когда strlen и substr применяют к тексту в UTF-8?
Что ломается, когда strlen и substr применяют к тексту в UTF-8?
Строка в PHP — это массив байтов, поэтому strlen считает байты, а не символы, и кириллическое слово показывает примерно вдвое большую длину, чем видно. Хуже того, substr режет по байтовому смещению, а значит может разрубить многобайтный символ пополам и выдать некорректный UTF-8. Функции mb_* из mbstring работают с символами в заданной кодировке.
Типичные ошибки
- ✗Читать
strlenкак число символов и обрезать текст не там, где нужно - ✗Резать через
substrи выдавать на границе разрубленный полусимвол - ✗Считать, что
strtoupperподнимает регистр букв с диакритикой так же, какmb_strtoupper
Уточняющие вопросы
- →Какую кодировку функции
mb_*предполагают, если её не передать явно? - →Как обрезать строку в UTF-8 до N символов, не разрубив ни одного?
JuniorТеорияРедкоКогда для разбиения строки хватает explode и когда нужен preg_split?
Когда для разбиения строки хватает explode и когда нужен preg_split?
explode разбивает по одному фиксированному литеральному разделителю, без всякого сопоставления с шаблоном — это верный инструмент для строки CSV или поля через двоеточие, и он быстрее из двух. preg_split разбивает по регулярному выражению, поэтому берите его, лишь когда разделитель непостоянен: любая последовательность пробелов или запятая, за которой могут идти пробелы.
Типичные ошибки
- ✗Передавать в
explodeшаблон регулярки, который тот принимает за литеральный разделитель - ✗Брать
preg_splitдля фиксированного разделителя и зря платить за движок регулярок - ✗Забывать, что
explodeс пустым разделителем бросаетValueError
Уточняющие вопросы
- →Что делает аргумент
limitвexplodeи что означает отрицательный предел? - →Как отбросить пустые куски, которые может дать разбиение?
MiddleТеорияРедкоЧто оператор конвейера |> из PHP 8.5 требует от своей правой части?
Что оператор конвейера |> из PHP 8.5 требует от своей правой части?
Правая часть должна вычисляться в callable ровно с одним параметром; в него передаётся значение слева, а результат становится значением всего выражения. Поэтому $x |> trim(...) |> strtolower(...) выстраивается в цепочку без вложенности. Стрелочную функцию справа обязательно брать в скобки, а callable, которому нужны два аргумента, туда не поместится.
Типичные ошибки
- ✗Писать справа голую стрелочную функцию, не заключив её в скобки
- ✗Пропускать по конвейеру значение в callable, которому нужен не один аргумент
- ✗Читать
|>как побитовый оператор, а не как применение функции
Уточняющие вопросы
- →Как пропустить значение по конвейеру в функцию, где оно не первый параметр?
- →Что
|>меняет в читаемости по сравнению с глубоко вложенными вызовами?
SeniorДизайнРедкоВаша команда только что перевела большое PHP-приложение на 8.5, и один из разработчиков предложил повсеместно перейти на новый оператор конвейера, переписав глубоко вложенные вызовы преобразований в слоях импорта и отчётности в виде цепочек. Части команды такие цепочки кажутся куда читаемее; другие опасаются лишних изменений, ревьюеров, не знающих синтаксиса, и того, что произойдёт, когда шагу понадобится больше одного аргумента. Вам нужно дать команде решение. Объясните, как вы будете оценивать предложение, на что разрешите применять оператор, а на что нет, как выкатите изменение на существующую кодовую базу и как потом поймёте, окупилось ли оно.
Ваша команда только что перевела большое PHP-приложение на 8.5, и один из разработчиков предложил повсеместно перейти на новый оператор конвейера, переписав глубоко вложенные вызовы преобразований в слоях импорта и отчётности в виде цепочек. Части команды такие цепочки кажутся куда читаемее; другие опасаются лишних изменений, ревьюеров, не знающих синтаксиса, и того, что произойдёт, когда шагу понадобится больше одного аргумента. Вам нужно дать команде решение. Объясните, как вы будете оценивать предложение, на что разрешите применять оператор, а на что нет, как выкатите изменение на существующую кодовую базу и как потом поймёте, окупилось ли оно.
Судите по читаемости в месте вызова, а не по новизне. Оператор ложится лишь на линейную цепочку callable с одним аргументом, поэтому разрешите его ровно там — внутри конвейеров преобразования — и запретите там, где шагу нужен второй аргумент или ветвление: заворачивать такое в замыкания хуже вложенности, которую вы убирали. Внедряйте сперва в новом коде, а не сплошным переписыванием.
Типичные ошибки
- ✗Переписывать работающую кодовую базу целиком ради синтаксиса, не меняющего поведения
- ✗Впихивать шаг с двумя аргументами в цепочку, заворачивая его в неуклюжее замыкание
- ✗Принимать оператор за оптимизацию, а не за выбор ради читаемости
Уточняющие вопросы
- →Что придётся написать для шага, которому нужен второй аргумент, и остаётся ли это выигрышем?
- →Как удержать связность смешанной кодовой базы, пока оператор применяет лишь новый код?
SeniorДизайнРедкоЭндпоинт вашего приложения на PHP принимает загрузку файлов: клиенты присылают выгрузки CSV, которые регулярно весят несколько гигабайт, и загруженный файл должен попасть в объектное хранилище, причём каждая строка ещё и проверяется до того, как вернётся ответ. Текущая реализация читает всю загрузку в строку, проверяет её и отдаёт эту строку клиенту хранилища; на реальных файлах она исчерпывает лимит памяти, и воркер умирает. Опишите, как вы перепроектируете путь загрузки, чтобы расход памяти оставался ровным при любом размере файла: где живут байты, пока запрос в полёте, как вы будете их читать и проверять, как они попадут в объектное хранилище и какие ограничения и сбойные случаи вы обработаете.
Эндпоинт вашего приложения на PHP принимает загрузку файлов: клиенты присылают выгрузки CSV, которые регулярно весят несколько гигабайт, и загруженный файл должен попасть в объектное хранилище, причём каждая строка ещё и проверяется до того, как вернётся ответ. Текущая реализация читает всю загрузку в строку, проверяет её и отдаёт эту строку клиенту хранилища; на реальных файлах она исчерпывает лимит памяти, и воркер умирает. Опишите, как вы перепроектируете путь загрузки, чтобы расход памяти оставался ровным при любом размере файла: где живут байты, пока запрос в полёте, как вы будете их читать и проверять, как они попадут в объектное хранилище и какие ограничения и сбойные случаи вы обработаете.
Никогда не материализуйте тело запроса. PHP и так сбрасывает загрузку во временный файл, поэтому откройте над ним поток, читайте построчно через fgetcsv, проверяйте на ходу, а клиенту хранилища отдайте дескриптор потока, а не строку, — тогда в памяти живут одна строка и буфер. Ограничьте размер и в веб-сервере, и в PHP, а при сбое подчистите временный файл.
Типичные ошибки
- ✗Читать тело запроса в строку прежде, чем что-либо с ним делать
- ✗Поднимать
memory_limitвместо того, чтобы убрать буфер на весь файл - ✗Передавать клиенту хранилища строку там, где подошёл бы дескриптор потока
Уточняющие вопросы
- →Куда PHP кладёт загруженный файл ещё до того, как его увидит ваш код?
- →Как ограничить максимальный размер загрузки до того, как байты вообще будут приняты?