Junior
Какие знаете принципы ООП?
Junior
Какие типы ошибок существуют в PHP?
Junior
В PHP можно выделить несколько уровней ошибок: notices (уведомления) — мелкие предупреждения, не прерывающие выполнение скрипта (например, обращение к неинициализированной переменной); warnings (предупреждения) — более серьёзные проблемы, также не прерывают выполнение (например, include несуществующего файла); fatal errors (фатальные ошибки) — критические ошибки, которые приводят к немедленному останову скрипта (например, вызов несуществующей функции). Начиная с PHP7 появилось понятие Error (на уровне исключений), представляющее фатальные ошибки как объекты, которые можно перехватывать. Также существуют парсинговые ошибки (parse errors), возникающие при разборе кода (например, синтаксическая ошибка) — они тоже фатальные.
Какая система типизации используется в PHP? Каковы ее преимущества и недостатки?
Junior
Какие типы данных поддерживает PHP?
Junior
Что такое проверка типов (type hinting) в PHP?
Junior
Что такое ссылки (references) в PHP? Как они работают?
Junior
Что такое инкремент и декремент? Чем отличаются префиксный и постфиксный инкремент/декремент?
Junior
Что такое рекурсия?
Junior
В чем разница между операторами =, == и === в PHP?
Junior
Является ли PHP языком, чувствительным к регистру?
Junior
В чем разница между интерфейсом и абстрактным классом?
Junior
Какие модификаторы видимости есть в РНР?
Junior
Какие магические методы вы знаете и как они применяются?
Junior
Расскажите об обработке ошибок и исключениях (try catch, finally, throw).
Junior
Что такое namespace и зачем они нужны?
Junior
Расскажите о принципах SOLID.
Junior
Что такое PDO?
Junior
В чем разница между расширением mysqli и PDO?
Junior
mysqli — расширение PHP для работы только с MySQL (и MariaDB), а PDO — более универсальный интерфейс, поддерживающий множество СУБД. PDO предоставляет объектно-ориентированный API и подготовленные запросы для разных баз данных, нужно лишь сменить строку подключения и драйвер. mysqli тоже поддерживает подготовленные запросы, но заточен исключительно под MySQL. Кроме того, PDO позволяет использовать именованные плейсхолдеры в запросах, а mysqli — только позиционные. В целом, PDO обеспечивает большую гибкость, если требуется поддержка нескольких видов СУБД, в то время как mysqli может быть использован, если проект рассчитан только на MySQL.
В чем разница между include и require в PHP?
Junior
Для чего существуют include_once и require_once?
Junior
В чем разница между строками в одинарных и двойных кавычках в PHP?
Junior
Для чего используется оператор @ в PHP?
Junior
Что такое "переменные переменные" ($$) в PHP?
Junior
Чем отличаются функции isset(), empty() и is_null() в PHP?
Junior
isset($var) проверяет, была ли переменная определена и не равна ли она NULL. Возвращает false, если переменная не существует или равна NULL, и true в обратном случае. empty($var) проверяет, считается ли значение переменной "пустым" — возвращает true, если $var эквивалентна ложному значению (0, "0", false, NULL, пустой строке, пустому массиву). is_null($var) строго проверяет, равна ли переменная NULL. Кратко: isset() отвечает на вопрос "существует ли переменная и не NULL?", empty() — "является ли значение переменной логически пустым?", is_null() — "имеет ли переменная значение NULL?".
Является ли PHP компилируемым языком или интерпретируемым?
Junior
Чем отличаются конструкции echo и print в PHP?
Junior
Какие суперглобальные переменные доступны в PHP?
Junior
PHP предоставляет ряд суперглобальных массивов, доступных из любой части программы: $_GET (данные из параметров URL-запроса GET), $_POST (данные, отправленные методом POST, например формы), $_COOKIE (данные cookie, отправленные клиентом), $_REQUEST (объединяет данные из $_GET, $_POST и $_COOKIE), $_SERVER (информация о сервере и текущем запросе — headers, IP, скрипт и т.п.), $_FILES (информация о загруженных файлах через форму), $_SESSION (данные текущей сессии пользователя), $_ENV (переменные окружения сервера) и $_GLOBALS (ассоциативный массив, содержащий все глобальные переменные). Эти переменные автоматически заполняются PHP и позволяют получать входные данные и параметры окружения.
В чем разница между GET и POST?
Junior
Что такое cookie и для чего используется?
Junior
Что такое сессия в PHP и как она работает?
Junior
Сессия в PHP — это способ хранения информации, которая будет использоваться на нескольких страницах всего веб-сайта. Информация не хранится на компьютере пользователя, в отличие от файлов cookie. Во временном каталоге на сервере сессией будет создан файл, в котором хранятся зарегистрированные переменные сессии и их значения. Эта информация будет доступна на всех страницах сайта во время этого посещения. Когда вы работаете с приложением, вы открываете его, вносите некоторые изменения, а затем закрываете. Это очень похоже на сеанс. Компьютер знает, кто вы такой. Он знает, когда вы запускаете и завершаете работу приложения. Но в интернете веб-сервер не знает, кто вы и чем занимаетесь, потому что HTTP-протокол не поддерживает состояние. Эта проблема решается с помощью переменных сессии путем сохранения пользовательской информации, которая будет использоваться на нескольких страницах (например, имя пользователя, любимый цвет и т.д.). По умолчанию переменные сессии будут сохраняться до тех пор, пока пользователь не закроет браузер. Таким образом, переменные сессии содержат информацию об одном пользователе и доступны для всех страниц в одном приложении.
Что такое буферизация вывода (output buffering) в PHP?
Junior
Буферизация вывода — механизм, при котором вывод скрипта не отправляется клиенту сразу, а накапливается во временном буфере. В PHP её можно включить функцией ob_start(). Пока буферизация активна, все данные, которые обычно ушли бы на браузер (через echo/print), сохраняются в памяти, и их можно получить через ob_get_contents(), очистить ob_clean() или отправить и отключить буфер ob_end_flush(). Буферизация полезна, когда нужно сперва сформировать всю страницу (или изменить заголовки отправки), а потом отправить её единым блоком, либо для модификации/сжатия выходящих данных перед отправкой клиенту.
Для чего используется функция header() в PHP?
Junior
Что такое статические свойства и методы класса в PHP? Когда их стоит использовать?
Junior
Статические свойства и методы принадлежат классу, а не конкретному объекту. Их объявляют с ключевым словом static. Такие методы можно вызывать без создания экземпляра (например, MyClass::myStaticMethod()), а к статическим свойствам обращаться через MyClass::$property. Статические члены удобны для хранения общих данных, разделяемых всеми объектами класса, или для утилитных функций, не зависящих от конкретного состояния объекта. Однако злоупотреблять static не рекомендуется: они похожи на глобальные переменные и усложняют тестирование. Используйте их, когда данные или функция логически связаны с классом в целом (например, счетчик созданных объектов, фабричный метод), но не зависят от индивидуального объекта.
Является ли PHP компилируемым языком или интерпретируемым?
Junior
Чем отличается сессия от cookie?
Junior
Cookie хранятся на стороне клиента (в браузере) и отправляются при каждом запросе, а данные сессии хранятся на сервере. Cookie обычно содержат только идентификатор или небольшие данные (которые видны пользователю и могут быть им изменены), тогда как сессия может содержать произвольные данные (объекты, массивы) на сервере и ассоциируется с клиентом посредством Session ID (как правило, передаваемого в cookie). Если cookie отключены или не используются, Session ID может передаваться и другими способами (например, в URL), но обычно сессия полагается на cookie. В целом, сессия более безопасна для хранения чувствительной информации, так как эти данные не передаются клиенту, а cookie удобны для простых клиентских настроек или идентификаторов.
Что такое PSR?
Junior
Приведите примеры стандартов PSR и их предназначение.
Junior
Что такое JIT-компиляция в PHP и как она работает?
Junior
Что такое архитектура MVC?
Junior
Назовите паттерны проектирования, с которыми приходилось работать.
Junior
Что такое TDD?
Junior
Расскажите о Unit Tests (required), Functional Tests (optional). Моки в PHP.
Junior
В чем разница между модульными (unit) и функциональными (integration) тестами?
Junior
Что такое mock-объект в тестировании PHP?
Junior
Что такое Composer и для чего он применяется?
Junior
Middle
Что такое вариативные функции и оператор "..." (spread) в PHP?
Middle
Что происходит от момента ввода URL в браузере до получения страницы (как происходит запрос/ответ)?
Middle
Происходит серия шагов: браузер разбирает введённый URL и определяет доменное имя, затем через DNS узнаёт IP-адрес сервера. Браузер устанавливает TCP-соединение с сервером по полученному IP (выполняется "рукопожатие" TCP). Далее браузер отправляет HTTP-запрос на сервер, содержащий метод (GET/POST), путь, заголовки (включая cookies, User-Agent и т.п.). Веб-сервер (например, Apache или Nginx) принимает запрос: если это статический ресурс, отдаёт его сразу, если динамический (PHP), передаёт запрос в интерпретатор PHP (через модуль или PHP-FPM). PHP-скрипт выполняется, генерируя HTML (или другой ответ), который сервер помещает в HTTP-ответ, добавляет нужные заголовки (статус, тип контента) и отправляет обратно браузеру. Браузер получает ответ (HTML), рендерит его; если в HTML есть ссылки на другие ресурсы (CSS, JS, изображения), браузер делает новые запросы к серверу для их получения. В итоге пользователь видит загруженную страницу.
Что такое позднее статическое связывание (Late Static Binding)?
Middle
Что такое копирование при записи (Copy-on-Write)?
Middle
Передача переменных в PHP: по значению или по ссылке?
Middle
Что такое генераторы (Generators) и ключевое слово yield?
Middle
Объясните разницу между глубоким (deep) и поверхностным (shallow) копированием объектов.
Middle
Что такое Dependency Injection и DI-контейнер?
Middle
Как работает автозагрузка классов (autoloading) в PHP?
Middle
Что такое Opcache и как он работает?
Middle
Чем отличаются статические методы от нестатических? В каком случае следует использовать статические методы?
Middle
Что такое "final" классы и методы?
Middle
Что такое Reflection API?
Middle
Что такое OWASP?
Middle
Какие типы уязвимостей веб-приложений вы знаете? Как защититься от них?
Middle
Среди распространённых уязвимостей веб-приложений: SQL-инъекции (внедрение вредоносных SQL команд), XSS (межсайтовый скриптинг, внедрение вредоносного JS на страницу), CSRF (межсайтовая подделка запроса, когда злоумышленник заставляет пользователя непреднамеренно выполнить действие), уязвимости, связанные с неверной авторизацией/аутентификацией, утечки чувствительных данных и др. Способы защиты: для SQL-инъекций — использование подготовленных выражений (prepared statements) и экранирование входных данных; для XSS — тщательная экранизация/кодирование вывода (HTML, JS) и Content Security Policy; для CSRF — использование токенов (CSRF-token) в формах и проверка их на сервере; общие меры — проверка входных данных (валидация), минимизация хранения чувствительной информации, обновление зависимостей и фреймворков, настройка правильных заголовков безопасности и т.д.
Что такое идемпотентность (idempotence) в контексте веб-методов? Какие HTTP-методы являются идемпотентными?
Middle
Что означает, что HTTP-протокол является stateless (без сохранения состояния)?
Middle
В чем разница между SOAP и REST?
Middle
SOAP и REST — подходы для взаимодействия между системами по сети. SOAP (Simple Object Access Protocol) — протокол, основанный на XML, предполагающий обмен сообщениями по строго заданному формату (XML-"конверты" с определённой схемой). SOAP часто работает поверх HTTP, но использует свой предопределённый XML-формат и обычно сопровождается описанием через WSDL. REST (Representational State Transfer) — архитектурный стиль, использующий возможности самого HTTP: различные методы (GET, POST, PUT, DELETE) для различных действий, URL-адреса как идентификаторы ресурсов, стандартные коды ответов. REST обычно передаёт данные в формате JSON или XML, но без жёсткого стандарта, проще и ближе к концепции ресурсов. Если кратко: SOAP — тяжеловесный протокол с жёстким стандартом (и расширениями безопасности, транзакций), REST — лёгкий стиль API, используя стандартные веб-механизмы.
Какие способы аутентификации и авторизации для веб-API вы знаете?
Middle
Для API распространены следующие методы аутентификации: HTTP Basic Auth (логин и пароль передаются в заголовке Authorization, закодирован Base64), более безопасный вариант — HTTP Bearer токен (например, JWT — JSON Web Token генерируется при логине и отправляется в каждом запросе), OAuth 2.0 (протокол авторизации, часто для стороннего доступа, предоставляет Access Token и Refresh Token), API-ключи (выдаётся клиенту ключ, который передается как параметр или заголовок и идентифицирует приложение). Ещё есть варианты: Digest Auth (реже используется), а также custom-схемы с HMAC-подписью запросов (как в Amazon Web Services). Для веб-приложений сессии (cookie с Session ID) тоже могут считаться одним из методов, но для RESTful API чаще используют без сохранения состояния — токены или ключи.
Что может содержать интерфейс в PHP (какие элементы могут быть определены внутри интерфейса)?
Middle
Если у объекта есть свойство, которое является объектом, что произойдет при клонировании этого объекта? Как сделать так, чтобы вложенный объект тоже копировался (глубокое клонирование)?
Middle
Что представляет собой паттерн Singleton и как его реализовать в PHP?
Middle
Почему паттерн Singleton считается анти-паттерном?
Middle
Что такое Redis и для чего он используется?
Middle
Что такое Memcached и для чего он используется?
Middle
В чем разница между Redis и Memcached?
Middle
Оба решения — высокопроизводительные хранилища данных в памяти, часто используемые для кеширования, но есть отличия. Redis умеет сохранять данные на диск и восстановливаться после перезапуска, Memcached — чисто в памяти и данные пропадают при рестарте. Redis поддерживает богатый набор типов данных (списки, множества, хэши и т.д.) и связанные с ними атомарные операции, тогда как Memcached оперирует простыми строковыми значениями (хотя можно сериализовать сложные объекты). Redis работает однопоточно, но может использовать ядра для разных операций (например, сохранение на диск), Memcached — мультипоточный (может задействовать несколько ядер для обслуживания запросов). В выборе между ними: Redis подходит, когда нужны структуры данных или персистентность, Memcached — когда нужен простой, распределённый и очень быстрый кеш для относительно независимых строковых данных.
Каковы преимущества и недостатки использования Redis/Memcached для кэширования?
Middle
Назовите основные отличия между веб-серверами Apache и Nginx.
Middle
Apache и Nginx — самые популярные веб-серверы, но они имеют разные архитектуры. Apache исторически использует модель с процессами/потоками: каждый запрос может обрабатываться отдельным потоком (или процессом) — это гибко, но при большом числе одновременных запросов потребляет много памяти. Nginx — событийно-ориентированный (асинхронный) сервер: он способен обслуживать множество соединений в одном потоке, используя неблокирующий ввод-вывод, что делает его очень эффективным по памяти при высокой нагрузке. Apache имеет богатую модульную систему и, например, поддерживает .htaccess-файлы для локальной конфигурации каталогов, чего нет в Nginx (конфигурация Nginx глобальна и обычно проще). Nginx обычно быстрее раздаёт статический контент, Apache гибче на уровне модулей (есть модули для PHP, Perl и т.д.). В современном стеке часто используется связка: Nginx как фронтенд (reverse proxy) для отдачи статики и балансировки, а Apache (или PHP-FPM) — для генерации динамики.
Senior
Опишите жизненный цикл HTTP-запроса в PHP-приложении (например, в рамках фреймворка).
Senior
Какие стратегии кеширования данных и HTML вы применяли? Как выбирали между Redis, Memcached и другими решениями?
Senior
Как вы организуете обработку долгих (тяжелых) задач в PHP-приложении?
Senior
Опишите, как вы проводите профилирование и отладку производительности приложения.
Senior
Какие подходы к обеспечению безопасности (Security) PHP-приложения вы считаете наиболее важными?
Senior
Как вы проектируете архитектуру большого приложения? На что обращаете внимание в первую очередь?
Senior
Что такое CQRS/Event Sourcing? В каких сценариях это уместно применять и почему?
Senior
Что такое когезия и связность (cohesion и coupling) в коде?
Senior
Когезия (сцепление) — это мера того, насколько элементы внутри модуля (класса, функции) связаны друг с другом и работают над единой задачей. Высокая когезия означает, что модуль сфокусирован, выполняет единственную роль или связанную группу функций (например, класс, у которого все методы относятся к одной области ответственности). Связанность (coupling) — это степень зависимости одного модуля от другого. Сильная связность — когда изменения в одном модуле могут потребовать изменений в другом, модули тесно знают детали друг друга; слабая связность — когда модули минимально взаимодействуют через чёткие интерфейсы. Хороший дизайн стремится к высокой когезии (каждый компонент отвечает за своё, не размывая функциональность) и низкой связности (компоненты минимально зависят друг от друга, взаимодействуют через абстракции, что облегчает изменения).
Можно ли использовать null в параметрах или возвращаемых значениях методов? Почему это считается плохой практикой?
Senior
С технической точки зрения метод может принимать null как параметр (если тип позволяет) и возвращать null. Однако злоупотребление null считается плохой практикой, так как ведёт к необходимости делать множество проверок на null и может приводить к ошибкам "обращение к свойству bool(null)" и т.п. Если метод возвращает null, вызывающий код всегда должен проверить результат на null, иначе риск ошибок. Часто null используют, чтобы обозначить "ничего" или ошибку, но лучше для ошибок бросать исключения, а для отсутствия результата применять шаблон Null Object или Maybe. Также передавать null в методы не всегда логично — лучше перегрузить метод или сделать несколько методов для разных сценариев, чем разрешать внутри параметр null. В общем, null увеличивает количество условий и потенциальных NPE (Null Pointer Exception) в коде, поэтому стоит избегать его, если можно выразить идею иначе.
Что такое паттерн Special Case / Null Object?
Senior
Null Object (он же Special Case) — поведенческий паттерн, суть которого – предоставить объект-заглушку с нейтральным "пустым" поведением вместо null. Вместо того, чтобы возвращать null и заставлять клиента делать проверки, метод возвращает особый объект, реализующий нужный интерфейс, но ничего не делающий ("пустую" реализацию). Например, если метод поиска не находит запись, он может вернуть объект-имплементацию интерфейса, которая внутри не содержит данных, но её методы безопасно ничего не делают. Клиентский код может работать с этим объектом, не опасаясь NullPointerException, и не знает, что объект "пустой". Это упрощает логику (не нужно if null), и концентрирует обработку отсутствия данных внутри Null Object-класса. Паттерн используется, когда наличие null усложнило бы код проверками, а использовать объект-заглушку проще и надёжнее.
Как тестировать код, который обращается к внешним API или сервисам?
Senior
Что такое Domain-Driven Design (DDD)?
Senior
Domain-Driven Design — это подход к разработке ПО, при котором основное внимание уделяется предметной области (бизнес-домена) и знаниям экспертов этой области. DDD предлагает выстраивать модель программного обеспечения, отражающую реальные сущности, правила и процессы домена, и организовать командную работу программистов и предметных экспертов для создания "Универсального языка" (Ubiquitous Language), понятного и тем, и другим. В DDD выделяют понятия: сущности (Entities) — объекты с идентификатором и жизненным циклом, значение (Value Objects) — объекты без уникального ID, определяемые своими значениями, агрегаты (Aggregates) — группы объектов, объединённых вокруг одной сущности (Root) для соблюдения инвариантов, хранилища (Repository) — для получения и сохранения агрегатов, сервисы домена (Domain Services) — для операций, не относящихся к конкретной сущности. Также вводится концепция ограниченных контекстов (Bounded Context) — различных частей системы, в каждой из которых своя модель и терминология. DDD особенно полезен для сложных предметных областей, позволяя создать понятную, эволюционирующую архитектуру, напрямую отражающую бизнес.
Что такое антипаттерны? Приведите примеры.
Senior
Как вы проводите рефакторинг большого legacy-кода и убеждаете руководство в его необходимости?
Senior
Рефакторинг legacy-проекта — постепенный, поэтапный процесс. Обычно я начинаю с участков кода, которые чаще всего изменяются или вызывают проблемы. Покрываю ключевые части автоматическими тестами (если их нет), чтобы иметь страховку при изменениях. Затем мелкими шагами переписываю код: выделяю функции, разбиваю слишком большие методы, устраняю дублирование, вводя общие модули, улучшаю читаемость (именование, структура). Важно каждый шаг проверять тестами, избегая изменения внешнего поведения. Чтобы убедить менеджмент/заказчика, делаю акцент на выгодах: снижение количества ошибок, упрощение внедрения новых функций, уменьшение расходов на поддержку. Можно привести недавние случаи, когда из-за "сложности и запутанности" мы долго исправляли баг или медленно внедряли изменения — показать, что рефакторинг окупится в будущем ускорением разработки и повысит качество. Часто практикуется рефакторинг при разработке новых фич (refactor by need): объясняю, что без улучшения существующего кода внедрение новой функции займёт гораздо больше времени или будет ненадёжным. Демонстрируя конкретные показатели (например, "после рефакторинга время на добавление аналогичной функциональности сократилось вдвое"), можно заручиться поддержкой руководства.
Сталкивались ли вы с утечками памяти в PHP? Как находили и устраняли их?
Senior
Да, в длительно работающих скриптах или сервисах на PHP (например, демонах, воркерах) можно столкнуться с утечкой памяти, хотя PHP обычно освобождает память после окончания скрипта. Причины: накопление данных в глобальных структурах или статических переменных, бесконтрольный рост массивов, хранение ссылок на объекты, которые уже не нужны (особенно при циклических ссылках в старых версиях PHP до внедрения сборщика цикла). Для поиска утечек я использовал профилировщики/инструменты мониторинга (например, расширение Xdebug с профилировкой памяти, либо специализированные инструменты вроде memory_get_usage() в ключевых точках, или external profilers) — смотрел, как растёт память при выполнении цикла. Ещё подход: если утечка, как правило, linear — значит где-то данные накапливаются. Найдя место, устранял причину: освобождал массивы (unset()), сбрасывал большие объекты после использования, избегал хранения неограниченных логов или кэшей внутри одного процесса. В случае, если проблема в циклических ссылках, можно вызывать gc_collect_cycles() периодически, хотя в новых версиях PHP сборщик мусора должен автоматически очищать циклы. В одном из проектов помогло разделение процесса: вместо бесконечного цикла, воркер обрабатывал N задач и перезапускался — таким образом даже при небольшой утечке она не успевала стать критичной. Главное — выявить, что именно растёт, и убрать ненужные накопления или пересмотреть архитектуру хранения данных.
Как работает сборщик мусора (Garbage Collector) в PHP? Когда имеет смысл вручную вызвать сборщик мусора?
Senior
Garbage Collector (GC) в PHP отвечает за обнаружение и устранение "циклических" ссылок — ситуаций, когда объекты ссылаются друг на друга, и поэтому счётчик ссылок не падает до нуля, хотя они более недостижимы. В PHP используется комбинация: счётчик ссылок освобождает память для всех "простых" случаев, а GC периодически проверяет циклы. Начиная с PHP 5.3 GC работает автоматически: по достижении определённого количества аллокаций он запускает цикл сбора: сканирует граф объектов, выявляет изолированные циклы, и освобождает их. Обычно программисту не нужно явно вмешиваться. Функция gc_collect_cycles() вручную запускает сборщик циклов. Имеет смысл вызывать GC вручную в редких случаях — например, внутри длинного скрипта после большого количества операций, если известно, что накопились циклические ссылки и они занимают много памяти, а ждать автоматического срабатывания не хочется. Но чрезмерно вызывать gc_collect_cycles() не стоит — это затратная операция, она сама потребляет CPU. Обычно необходимость ручного вызова связана с нестандартными случаями (например, при интенсивном создании объектов с взаимными ссылками в больших количествах).
Какие инструменты статического анализа кода (линтеры) для PHP вы использовали?
Senior
Существуют различные инструменты для статического анализа PHP-кода. Среди популярных: PHP_CodeSniffer — проверяет соответствие кода определённым стилевым стандартам (например, PSR-12), помогает поддерживать единообразный стиль. PHPStan и Psalm — мощные статические анализаторы, которые выявляют потенциальные ошибки, несоответствие типов, обращение к неопределённым переменным, неправильное использование API ещё до выполнения программы. PHPMD (PHP Mess Detector) — выявляет "плохие запахи" кода: слишком длинные методы, сложные условия, дублирование и т.д. Также IDE, такие как PhpStorm, имеют встроенные статические инспекции. Я обычно на проектах использую PHPStan (или Psalm) для проверки типов и ошибок, а CodeSniffer с правилами PSR-12 для форматирования и простых ошибок. Эти инструменты интегрируются в CI, что помогает автоматом ловить проблемы до ревью и запуска.
Какие инструменты профилирования и анализа производительности PHP-кода вы знаете?
Senior
Для профилирования PHP-кода используются как расширения, так и внешние сервисы. Одно из самых известных — Xdebug (включает профилировщик, который может записывать трассировку выполнения с замером времени и памяти). С помощью Xdebug профайла (в режиме profiler) можно получить файл cachegrind, который потом анализировать, например, утилитой KCacheGrind/QCacheGrind или веб-интерфейсами, видеть, какие функции сколько времени заняли, сколько раз вызывались. Другой инструмент — XHProf (разработан Facebook) — более лёгкий профилировщик, собирает агрегированные данные по времени выполнения функций, его форк UProfiler или продвинутый Tideways. Есть Blackfire — коммерческий сервис, очень удобный профилировщик с визуализацией, интегрируется через расширение. Для нагрузки/метрик можно применять APM (Application Performance Monitoring) инструменты: New Relic, Datadog, которые без прямого профилирования собирают статистику по транзакциям, запросам к БД. В своей практике я чаще использовал Xdebug/XHProf для детального профилирования кода во время оптимизации, а также тесты производительности (JMeter, ab) вместе с мониторингом профиля, чтобы понять узкие места.
Какой уровень покрытия тестами вы считаете достаточным? Всегда ли нужен 100% охват?
Senior
Достаточный уровень покрытия тестами зависит от природы проекта, его критичности и темпа изменений, но распространённая цель — 70-90% покрытие кода модульными тестами. 100% покрытие не является обязательным и не гарантирует отсутствие ошибок: важнее, какие случаи покрыты. Практически добиться 100% сложно и иногда не нужно: некоторые простые геттеры/сеттеры или тривиальный код нет смысла явно тестировать, это только тратит время. Кроме того, "coverage" измеряет лишь долю строк, выполненных в тестах, но не качество самих тестов (они могут не проверять правильность результата). Я считаю, что нужно стремиться покрыть тестами основную бизнес-логику, критические и сложные части системы. Если какой-то модуль не критичен или часто меняется, возможно, тесты на него писать избыточно. 100% охват может быть неоправдан по затратам: проще потратить усилия на тестирование ключевых сценариев и регрессий, нежели гоняться за формальным показателем. Также важно проводить интеграционные и end-to-end тесты, помимо unit. В итоге, полный охват — утопия, вместо этого метрика покрытия должна использоваться разумно: как подсказка, куда добавить тесты, но не самоцель.
Что такое триггер в SQL и для чего он используется?
Senior
Триггер — это хранимая процедура, которая автоматически выполняется при наступлении определённого события в базе данных (например, при вставке, обновлении или удалении записи в указанной таблице). Триггер привязан к таблице и конкретному событию (BEFORE/AFTER INSERT/UPDATE/DELETE). Когда такое событие происходит, СУБД запускает код триггера (это может быть, например, проверка или модификация данных, запись в журнал, синхронизация с другой таблицей). Используют триггеры для обеспечения сложных целостных ограничений, автоматического вычисления производных значений, аудита изменений (логирование в отдельную таблицу истории) и т.д. Однако злоупотребление триггерами может усложнить отладку и понимание системы, так как логика "спрятана" в базе, и неочевидно, что при изменении таблицы произойдёт что-то ещё. Поэтому триггеры применяют аккуратно для задач, которые действительно лучше решать на уровне БД (гарантия целостности, аудирование).
Какие уровни изоляции транзакций существуют в СУБД?
Senior
Стандарт SQL определяет четыре уровня изоляции транзакций: Read Uncommitted (самый низкий, "чтение неподтверждённых" данных) — транзакция может видеть изменения других незавершённых транзакций ("грязное чтение"), практически не используется из-за опасности; Read Committed — транзакция видит только подтверждённые на данный момент изменения (грязных чтений нет, но возможны неповторяемые чтения: один и тот же запрос в транзакции может вернуть разные данные, если другая транзакция изменила и закоммитила); Repeatable Read — транзакция гарантирует повторяемость чтений: если она прочитала запись, то пока она активна, другие транзакции не могут изменить или удалить эту запись (исключены грязные и неповторяемые чтения, но возможны "фантомы" — вставка новых строк другим коммитом может проявиться при повторном запросе); Serializable — самый строгий уровень, полный эффект как если бы транзакции выполнялись последовательно, исключены все аномалии, но достигается ценой блокировок и снижения параллелизма. Разные СУБД могут по-разному реализовывать эти уровни (например, MySQL InnoDB по умолчанию Repeatable Read, в котором уже нет фантомов из-за механизмов MVCC). Выбор уровня — компромисс между корректностью параллелизма и производительностью.
Чем реляционные СУБД отличаются от NoSQL баз данных? Примеры NoSQL баз.
Senior
Реляционные СУБД хранят данные в таблицах с фиксированной схемой (определённым набором столбцов), используют язык SQL для запросов и обеспечивают строгую согласованность транзакций (ACID). NoSQL базы — более широкий класс систем хранения, которые отходят от модели таблиц и SQL. Отличия: NoSQL обычно не требует фиксированной схемы (данные могут быть разной структуры), часто жертвуют сильной согласованностью ради горизонтального масштабирования (многие обеспечивают eventual consistency — итоговую согласованность). Есть несколько типов NoSQL: ключ-значение (например, Redis, Riak) – хранят данные как пару ключ:произвольное значение; документные базы (MongoDB, CouchDB) – хранят данные в виде документов (обычно JSON-подобных) с гибкой схемой; колоночные (Cassandra, HBase) – хранят данные колоночными семьями, оптимизированы под большие объёмы и распределённость; графовые (Neo4j, JanusGraph) – хранят узлы и рёбра графа, оптимальны для графовых связей. NoSQL базы часто лучше масштабируются горизонтально, могут справляться с очень большими объёмами данных и высоким трафиком, но могут не предоставлять таких же богатых гарантий транзакционности или удобства сложных JOIN-запросов, как SQL. Выбор зависит от задач: реляционные – для структурированных данных и сложных запросов, NoSQL – для больших распределённых систем, гибких форматов или особых типов данных (например, графов).
Что означает ACID в контексте транзакций баз данных?
Senior
В чем разница между Dependency Injection и Service Locator?
Senior
Оба подхода решают проблему передачи зависимостей, но по-разному. При Dependency Injection (внедрение зависимостей) объекты получают необходимые зависимости явно (через параметры конструктора, сеттеры или инициализацию) — то есть зависимость передаётся извне, и класс ничего не знает, откуда она пришла. При паттерне Service Locator у класса есть доступ к некоему глобальному объекту-локатору (или контейнеру), у которого он запрашивает нужную зависимость по имени/типу. Разница: DI делает зависимости явными и видимыми в интерфейсе класса, облегчая тестирование (можно подставить mock) и понимание (сразу видно, что требуется). Service Locator скрывает зависимости: внутри метода класс может внезапно дернуть глобальный локатор, получить некую сервис-зависимость — для пользователя класса это не очевидно. Поэтому Service Locator критикуют: он по сути является глобальной переменной, усложняет тестирование (надо как-то подменять глобальный локатор), увеличивает связность. DI предпочитают за явность, хотя Service Locator иногда применяется для совместимости или когда DI трудно внедрить.
Как вы обеспечиваете горизонтальную масштабируемость PHP-приложения?
Senior
Расскажите о вашем опыте оптимизации работы с базой данных в высоконагруженных проектах.
Senior
Что такое чувствительные данные (sensitive data)? Как их хранить и логировать безопасно?
Senior
Чувствительные (конфиденциальные) данные — это информация, раскрытие которой нежелательно или опасно. К ним относятся: персональные данные пользователей (имя, адрес, телефон, email), учетные данные (логины, пароли), финансовая информация (номера кредитных карт, банковские счета), медицинские данные и т.д. Безопасное хранение подразумевает, что такие данные в базе должны быть защищены: пароли, например, не хранят в открытом виде — только хэш (желательно bcrypt/argon2) + соль. Другие критичные данные могут храниться в зашифрованном виде (с использованием стойких алгоритмов шифрования, ключи не лежат рядом с данными). Логирование: в журналах нельзя оставлять полные конфиденциальные данные — например, логируя объект пользователя, необходимо маскировать или удалять поля с чувствительной информацией (заменять часть символов звёздочками, не писать пароли вообще). Также важно ограничивать доступ к логам. Цель — минимизировать риск компрометации: даже если злоумышленник получит базу, он не сможет мгновенно узнать пароли, ему придётся затратить огромные усилия на подбор по хешам.
Что такое Swoole или ReactPHP и какие задачи они решают?
Senior
Swoole и ReactPHP — решения для реализации асинхронного, событийно-ориентированного программирования в PHP. ReactPHP — это библиотека (фреймворк) на чистом PHP, предоставляющая цикл событий (Event Loop) и неблокирующие I/O операции (например, асинхронные сокеты, таймеры). С её помощью можно писать серверы (например, веб-сервер, веб-сокеты), работающие асинхронно (как Node.js). Swoole — это PHP-расширение на Си, которое интегрирует высокопроизводительный асинхронный движок прямо в PHP. Swoole позволяет создавать долговременно работающие процессы, веб-серверы, веб-сокеты, имеет корутины для упрощения асинхронного кода. Оба инструмента решают задачу: использовать PHP для долгоживущих сервисов, обслуживающих много одновременных соединений эффективно (не создавая новый процесс/тред на каждый запрос, как традиционный PHP-FPM). Это подходит для real-time приложений, игр, чатов, где нужна многосокетная обработка в одном процессе.
Что такое микросервисная архитектура?
Senior
Микросервисная архитектура — подход к построению приложения как набора мелких независимых сервисов, каждый из которых выполняет свою узкую функцию и взаимодействует с другими через хорошо определённые интерфейсы (обычно через сетевые API, например REST или сообщения). В отличие от монолита, где все функциональности в одном приложении, микросервисы раздельно развёртываются, масштабируются и обновляются. Каждый микросервис может быть реализован на своей технологии, иметь собственную базу данных (отделённый контекст). Преимущества: лучшая масштабируемость (можно масштабировать только горячие сервисы), гибкость разработки (разные команды могут работать над разными сервисами, обновлять их независимо), отказоустойчивость (падение одного сервиса не обрушит всю систему, если правильно обработаны ошибки). Недостатки: усложняется инфраструктура, возникают накладные расходы на коммуникацию между сервисами, сложнее обеспечивать консистентность данных, нужна система оркестрации, мониторинга, логирования распределённых компонентов.
Какие способы взаимодействия между микросервисами вы знаете?
Senior
Микросервисы могут взаимодействовать синхронно и асинхронно. Синхронные способы: HTTP/REST API — один сервис делает HTTP-запрос к другому (обычно RESTful JSON API), gRPC — бинарный протокол поверх HTTP/2 с автогенерацией кода (хорош для строго типизированного, быстрого RPC). Асинхронные способы: обмен сообщениями через брокеры (RabbitMQ, Apache Kafka, Amazon SQS) — сервис публикует сообщение в очередь/топик, другой сервис (или компоненты) подписывается и получает эти сообщения для обработки (это позволяет декуплировать их по времени и нагрузке); события (Event-Driven Architecture) — сервисы рассылают события о совершённых действиях, а другие, подпишившись, реагируют (например, обновляют свои данные). Выбор зависит от задач: для запроса данных и получения ответа подойдёт синхронный REST/RPC, а для декорреляции и масштабирования часто используют асинхронные очереди и сообщения.
Как вы относитесь к микросервисной архитектуре? В каких случаях её стоит применять, а в каких — нет?
Senior
Как изменить сообщение последнего коммита Git?
Senior
Чтобы изменить сообщение самого последнего коммита в локальном репозитории, используется команда: git commit --amend. Она откроет редактор, где вы можете отредактировать текущее сообщение коммита (или можно сразу указать новое сообщение через параметр -m "Новое сообщение"). После сохранения изменений последний коммит будет переписан с новым сообщением (и новым хешем). Важно: это изменение переписывает историю, поэтому, если коммит уже был отправлен (push) на удалённый репозиторий, не рекомендуется его менять, чтобы не создавать конфликтов с историей у других. Если же нужно исправить сообщение уже опубликованного коммита, придётся выполнить force-push (git push --force) после amend, но это может запутать других разработчиков, поэтому лучше избегать этого, если на коммит кто-то мог основывать свою работу.
Что делает команда `git rebase -i HEAD~3` и какие могут быть риски при ее использовании?
Senior
Команда git rebase -i HEAD~3 запускает интерактивный rebase для последних 3 коммитов (от текущей HEAD до HEAD~3). Откроется текстовый редактор со списком этих коммитов, где вы можете изменить порядок, объединить коммиты (squash), изменить сообщения или пометить коммиты на удаление. После сохранения Git перепроиграет эти коммиты поверх той же базы, но уже в изменённом виде. Результатом будет новая история (коммиты с новыми SHA). Риски: интерактивный rebase переписывает историю, поэтому, если эти коммиты уже были отправлены на общий репозиторий, переписывание истории потребует форсированного push (git push --force) и может привести к проблемам у коллег (конфликт истории, дубли коммитов). Ещё риск — при неправильном использовании можно случайно потерять коммиты (например, пометив лишнее на drop). Также при rebase возможны конфликты слияния, если изменяемые коммиты пересекаются по файлам с базовой веткой. Общий совет: интерактивный rebase удобен для очистки локальной истории перед пушем (например, слить "грязные" коммиты в логичные), но им не стоит пользоваться на уже опубликованных коммитах, чтобы не нарушать историю в удалённом репозитории.
Для чего нужна команда `git bundle`?
Senior
Команда git bundle используется для упаковки содержимого репозитория (или разницы между репозиториями) в единый файл. Проще говоря, git bundle создаёт файл, содержащий набор коммитов и объектов Git. Это полезно, когда нужно передать изменения в репозитории, но нет сетевого доступа (например, проект на закрытом контуре, передача через внешний носитель или email). При помощи git bundle можно, например, собрать все коммиты ветки master (git bundle create repo.bundle master) в файл repo.bundle. Затем на другом компьютере этот файл можно подключить как удалённый (git remote add) и выполнить fetch или pull, либо сразу распаковать (git clone repo.bundle). Это удобный способ переноса истории без доступа к центральному серверу. В целом, git bundle — это архив со снапшотом истории Git, содержащий объекты (блоб, дерево, коммит) — в отличие от обычного архива zip, который содержит только файлы, bundle сохраняет всю VCS-историю.
Как перенести отдельный коммит из одной ветки в другую (влить коммит)?
Senior
Как объединить несколько коммитов (squash) в один?
Senior
Есть несколько способов "сквошить" коммиты. Один вариант — интерактивный rebase: например, если нужно объединить последние 3 коммита, выполняем git rebase -i HEAD~3, в открывшемся списке помечаем второй и третий коммит как "squash" (или сокращённо "s") вместо "pick". Это приведёт к тому, что эти коммиты будут объединены с первым. Затем можно отредактировать итоговое сообщение коммита. После сохранения получим один коммит, включающий изменения всех трёх. Второй способ — git reset: перейти на коммит перед нужной серией (git reset --soft HEAD~3), это оставит изменения последних 3 коммитов в индексе (staged), затем сделать один git commit — объединённый. Однако reset меняет историю сразу (осторожно, если коммиты были пушены). Предпочтительный метод — интерактивный rebase, так как он более явный и позволяет сохранить желаемое сообщение. Стоит помнить, что squash меняет историю (новый коммит вместо нескольких старых), поэтому после него может понадобиться force-push, если изменения уже опубликованы.
Что такое дедлок (deadlock) в базе данных?
Senior
Дедлок — это ситуация взаимной блокировки, когда две (или более) транзакции ожидают освобождения ресурсов друг от друга и ни одна не может продолжить. В контексте базы данных дедлок обычно возникает, если транзакция A захватила блокировку на ресурс X и ждёт ресурс Y, а транзакция B захватила Y и ждёт X (или аналогичные сложные цепочки). Обе зациклились, каждая ждёт, что другая закончится и отпустит ресурс. СУБД обнаруживает такие циклы блокировок (обычно по таймауту или специальному алгоритму) и автоматически прерывает (откатывает) одну из транзакций, чтобы разблокировать ситуацию. Приложение получит ошибку о дедлоке (deadlock detected) для одной транзакции — и обычно должно повторить транзакцию. Дедлоки чаще случаются при интенсивных конкурентных обновлениях связанных таблиц в разных порядках. Чтобы избегать дедлоков, рекомендуется: блокировать (обновлять) ресурсы в согласованном порядке (например, всегда таблица A потом B), держать транзакции как можно короче, избегать длительных блокирующих операций внутри транзакции.
Что такое репликация базы данных и какие виды репликации вы знаете?
Senior
Репликация — это процесс копирования данных с одного сервера баз данных на другой в реальном (или почти реальном) времени. Обычно есть основной узел (master), куда выполняются записи, и один или несколько ведомых узлов (slave), которые получают изменения с мастера и применяют у себя. Виды репликации: основная — master-slave (один ведущий, несколько реплик только для чтения); multi-master — несколько узлов могут принимать записи и обмениваются изменениями (сложнее, требует разрешения конфликтов, применимо не во всех СУБД, пример — MySQL Group Replication, некоторые NoSQL тоже multi-master). Есть синхронная репликация — когда транзакция считается завершённой, только когда данные записаны на всех репликах (гарантирует консистентность, но медленнее), и асинхронная — мастер сразу подтверждает, а реплика догоняет самостоятельно (могут быть отставания). Цель репликации: масштабирование чтения (распределить нагрузки чтения по слейвам), отказоустойчивость (если мастер падает, можно переключиться на реплику), геораспределение (база ближе к пользователям). Популярная схема — master-master (активный-пассивный), где у двух узлов каждый реплицирует изменения на другой, но реально записывают обычно на один, а второй ждёт переключения при сбое.
Что такое шардинг базы данных?
Senior
Шардинг — это горизонтальное масштабирование базы данных путём разделения данных по разным узлам (серверным инстансам) по определённому принципу. Вместо того, чтобы хранить всю таблицу на одном сервере, шардинг распределяет строки между несколькими серверами. Например, можно шардинговать по диапазону ключа (пользователи с id 1-100000 на одном сервере, 100001-200000 на другом) или по хэшу ключа (каждая запись отправляется на определённый шард по результату хэш-функции от, скажем, идентификатора). Цель шардинга — преодолеть ограничения по объёму и нагрузке: каждый отдельный сервер обрабатывает только свою часть данных, тем самым можно хранить и обрабатывать значительно больше, чем на одном сервере. Однако шардинг усложняет архитектуру: нужно решать, как маршрутизировать запросы на нужный шард, как выполнять межшардовые запросы (например, join по шардам становится сложным), поддерживать балансировку данных. Шардинг часто применяется в очень крупных системах, когда вертикально масштабировать СУБД уже невозможно или экономически невыгодно.
Объясните суть теоремы CAP в распределённых системах.
Senior
CAP-теорема гласит, что в распределённой системе, хранящей данные, из трёх свойств — Consistency (согласованность), Availability (доступность) и Partition Tolerance (устойчивость к разделению сети) — одновременно на все 100% можно обеспечить только два. Расшифровка: Consistency — все узлы видят одни и те же данные в одно время (строгая консистентность данных между репликами); Availability — система всегда отвечает на запрос (каждый запрос к неперекрывшемуся узлу получает ответ, даже если некоторые другие узлы упали); Partition Tolerance — система продолжает работать при разделении сети (когда узлы потеряли связь друг с другом). Согласно CAP, при сетевом разделении (а его нельзя избежать в реальных сетях) разработчикам приходится делать выбор: либо пожертвовать строгой консистентностью (разрешить разным узлам иметь рассинхрон временно, но система останется доступной — это категория AP, например, Cassandra, которая отдаёт возможно устаревшие данные, но всегда отвечает), либо пожертвовать доступностью (система остановит обслуживание части запросов, но не вернёт неконсистентных данных — категория CP, например, MongoDB в режиме с подтверждениями: при проблемах с сетью остановит запись на части узлов для консистентности). Полностью CAP-совместимой быть нельзя: либо CP, либо AP (CA в распределённой системе невозможно, так как P всегда нужно учитывать). CAP теорема помогает понять компромиссы при проектировании систем хранения данных.
В чем разница между потоком (thread) и процессом?
Senior
Процесс — это экземпляр выполняющейся программы, со своим адресным пространством (памятью). Поток — лёгкий компонент, выполняющийся внутри процесса; все потоки одного процесса разделяют общее адресное пространство, дескрипторы файлов и другие ресурсы процесса. Различия: процессы изолированы друг от друга (один процесс напрямую не может изменять память другого), а потоки разделяют память, поэтому взаимодействовать между потоками проще и быстрее (но нужно синхронизироваться, чтобы не было гонок). Контекстный переключение между потоками в рамках одного процесса обычно легче, чем между процессами. Процессы чаще используют для сильной изоляции и разделения задач, потоки — для параллельного выполнения в рамках одной задачи. На примере веб-сервера: Apache может создавать новые процессы на каждый запрос (prefork), или потоки (worker) — потоки легче, но баг в одном потоке может уронить весь процесс и все потоки (т.к. общая память), в случае процессов повреждается только один запрос. Таким образом: процесс — отдельная программа с полной изоляцией, поток — "легковесное" исполнение внутри программы, разделяющее общие ресурсы.
Возможна ли многопоточность в PHP и как этого можно добиться?
Senior
Как получить значение приватного свойства объекта извне класса?
Senior
Что такое Event Sourcing?
Senior
Event Sourcing — это паттерн хранения состояния системы через последовательность событий. В традиционном подходе мы храним текущее состояние (например, в базе таблица с полями, которые обновляются при изменении). При Event Sourcing не сохраняется непосредственно последняя версия данных, вместо этого каждая изменение записывается как отдельное событие (например: "На счёт X зачислено 100", "Со счёта X списано 50"). Текущее состояние вычисляется как результат применения всех событий к начальному состоянию. Таким образом, "история" изменений хранится полностью. Плюсы: полная трассируемость действий (можно понять, как система пришла к текущему состоянию, можно откатиться или пересчитать), возможность "проиграть" события в другой контекст (например, построить проекции). Минусы: сложнее запросы текущего состояния (нужно суммировать/агрегировать события), требуется механизмы миграции событий при изменении логики. Часто Event Sourcing используется вместе с CQRS: события пишутся в лог (Event Store), а для чтения формируются проекции (например, материализованное состояние) для быстрых запросов. Пример: банковский счёт — вместо хранения баланса, храним все транзакции; баланс вычисляется суммированием транзакций. Это Event Sourcing.
Что такое eventual consistency (конечная согласованность) в распределённых системах?
Senior
Конечная согласованность — модель, при которой система не гарантирует мгновенную согласованность данных на всех узлах, но гарантирует, что если не будет новых обновлений, то спустя некоторый (обычно короткий) период все копии данных придут к одному значению. Это более слабая форма консистентности, часто используемая в распределённых или NoSQL системах ради доступности и производительности. Например, в системе с репликацией, если применена eventual consistency, то сразу после записи на одном узле, чтение с другого узла может ещё вернуть старое значение; но через некоторое время (миллисекунды или секунды, в зависимости от задержек) все реплики обновятся. Практически это означает, что приложение должно уметь работать с временными расхождениями. Такой режим применяется там, где абсолютная точность в реальном времени не критична (например, счетчики лайков, данные, которые могут чуть запаздывать). Это противоположность строгой консистентности (где чтения всегда отражают последние записи, но достичь этого тяжело при сетевых разделениях, см. CAP). Eventual consistency помогает строить масштабируемые, устойчивые к нагрузке системы, где компоненты слабо связаны и общаются через обмен сообщениями.
Что такое GraphQL и чем он отличается от REST?
Senior
GraphQL — это язык запросов и среда выполнения для API, разработанный Facebook. В GraphQL клиент описывает структуру необходимых данных (запрос GraphQL формулируется, указывая, какие поля нужны вложенно), а сервер возвращает ровно эти данные, одного эндпоинта, тогда как REST обычно оперирует множеством URL/эндпоинтов, каждый возвращает фиксированный набор данных для ресурса. GraphQL позволяет за один запрос получить данные из разных связанных объектов, определяя формат ответа в запросе, что устраняет проблему недогрузки или перегрузки данных. REST же часто требует нескольких запросов для связанных ресурсов, а возвращает фиксированный формат, возможно лишний. Таким образом, GraphQL даёт клиенту больше гибкости и сокращает число запросов, однако сложнее в настройке кеширования на уровне HTTP и требует схемы типов.
Что такое Docker и для чего он используется? Каково основное устройство Docker-контейнеров?
Senior
Docker — это платформа контейнеризации, которая позволяет упаковать приложение со всеми его зависимостями (библиотеками, средой) в единый контейнер. Контейнер Docker — изолированная среда, работающая на ядре хоста, но отделённая от остальных процессов (собственная файловая система, сетевые интерфейсы, ограниченные ресурсы). Основная идея: "контейнеры" легче, чем виртуальные машины, потому что не несут в себе отдельное ядро/ОС — они используют возможности ядра хостовой системы (cgroups, namespaces в Linux) для изоляции. Docker-контейнеры создаются на основе образов (image), которые представляют собой слоистую файловую систему (каждый слой — результат изменения, например, установки пакета). Docker широко применяется для стандартизации среды разработки и продакшена: если приложение запущено в Docker, то на любой машине с Docker оно поведёт себя одинаково. Это облегчает деплой (например, через Docker Compose или Kubernetes можно запускать множество контейнеров, описав их зависимости), масштабирование и изоляцию приложений друг от друга. Устройство: Docker-демон управляет образами и контейнерами, создаёт контейнеры с помощью Linux namespace (для изоляции) и cgroups (для ограничения ресурсов). Контейнер видит внутри себя как бы свою мини-ОС (с нужными утилитами, которые включены в образ), но на самом деле использует ядро хоста.
Чем отличается горизонтальное масштабирование от вертикального?
Senior
Вертикальное масштабирование означает увеличение ресурсов одной машины (серверы посильнее: добавить CPU, RAM, SSD, ускорить на существующем узле). Горизонтальное масштабирование означает добавление новых узлов (машин) в систему и распределение нагрузки между ними. Например, вертикально масштабировать базу — перейти на более мощный сервер; горизонтально — поднять кластер из нескольких серверов и делить данные или запросы между ними. Вертикальное масштабирование обычно проще (не требует изменений в архитектуре, просто улучшение железа), но имеет предел (есть физический максимум, и часто это дорого). Горизонтальное сложнее (нужно обеспечивать распределение запросов, консистентность данных между узлами, балансировку нагрузки), но даёт значительно лучшую масштабируемость — можно добавлять почти сколько угодно узлов. В веб-приложениях часто сначала масштабируются вертикально (вплоть до определённого предела), а затем переходят к горизонтальному (несколько серверов приложений за балансировщиком, шардинг или репликация баз данных, распределённые кэши). Итого: вертикальное – "ввысь" (больше ресурсов одному экземпляру), горизонтальное – "вширь" (больше экземпляров).
Что предпочтительнее: наследование или композиция? Поясните, в чем разница между ними.
Senior
Наследование и композиция – два разных подхода к организации кода. Наследование подразумевает отношение "является" (is-a): класс-наследник расширяет базовый класс, унаследуя его свойства/методы, и может переопределить или добавить новое поведение. Композиция означает, что один объект содержит в себе другой объект для использования его функциональности – отношение "имеет" (has-a). Вместо того, чтобы наследовать класс, мы включаем его как поле и вызываем его методы по мере необходимости. Вместо наследования часто предпочтительнее композиция, поскольку она обеспечивает более слабую связанность: изменения во включаемом классе не влияют на внешний интерфейс композитного класса, и мы можем более гибко менять компоненты. Наследование же связывает классы сильнее (наследник зависит от реализации родителя, может столкнуться с проблемами хрупкой иерархии). Правило "предпочитай композицию наследованию" означает: используйте наследование только когда отношение действительно логически является "is-a" и требуется полиморфизм, в остальных случаях лучше включить необходимые объекты. Композиция облегчает замену частей системы (можно подменить компонент другим классом, реализующим нужный интерфейс) и тестирование (можно замокать компоненты), а наследование часто ведёт к более жёсткой структуре и потенциальному дублированию кода, если иерархия выстраивается неправильно.
Какие паттерны используются в ORM (Object-Relational Mapping) библиотеках? (Примеры Active Record, Data Mapper)
Senior
В ORM прослойках встречаются два основных подхода (паттерна) сопоставления объектов и БД: Active Record и Data Mapper. Active Record – паттерн, при котором класс-модель совмещает в себе и данные, и методы для работы с БД (CRUD). Объект Active Record "знает" как сохраниться (например, метод save() внутри модели сохранит поля этой модели в базу). Примеры – Eloquent в Laravel, RedBeanPHP, компоненты AR в Ruby on Rails. Data Mapper – паттерн, где объекты доменной модели не знают о базе данных вообще; для переноса данных между объектами и БД используется отдельный слой – маппер (например, репозиторий или отдельный класс). Этот маппер берет объект и сохраняет его в БД, или наоборот, загружает из БД и строит объект. Пример – Doctrine ORM, где сущности – простые объекты (POPO), а EntityManager выступает в роли Data Mapper. Active Record проще в понимании и быстрее стартовать, но плохо разделяет ответственность (модель зависит от БД). Data Mapper сложнее, но лучше изолирует бизнес-логику от деталей хранения, что улучшает тестируемость и поддерживаемость большого проекта.
Как масштабировать PHP-приложение на несколько серверов (как хранить сессии в кластере)?
Senior
При масштабировании веб-приложения на несколько серверов важно учитывать состояние сессий пользователей. По умолчанию PHP хранит сессии в файловой системе сервера, и если запросы одного пользователя пойдут на разные машины, то он потеряет сессию. Есть несколько подходов: 1) "липкие сессии" (sticky sessions) на балансировщике — запросы от одного пользователя всегда отправляются на тот же сервер, на котором началась сессия; это просто, но снижает эффективность балансировки и надёжность (если сервер упал, пользователи теряют сессии). 2) Централизованное хранение сессий: настроить PHP на хранение сессий в общем хранилище, доступном всем узлам — например, в базе данных, Memcached или Redis. Тогда независимо от того, на какой сервер пришёл запрос, он достанет данные сессии из общего хранилища. Этот способ предпочтительнее, так как даёт независимость от конкретного веб-сервера. Реализуется изменением session.save_handler и соответствующих настроек (есть драйверы для Memcached, Redis и т.д.). 3) Использование JWT-токенов вместо серверных сессий — состояние хранится на клиенте в виде токена (подписанного), сервер при каждом запросе валидирует его; в этом случае сервер вообще не хранит сессионных данных, проблема синхронизации отпадает, но не подходит для больших объёмов данных. В итоге, для кластеризации PHP-приложения обычно выбирают хранить сессии в Redis/Memcached (быстро) или в SQL-базе (попроще, но медленнее), чтобы обеспечить единый источник сессионных данных для всех серверов.
Что такое OPCODE в PHP?
Senior
OPCODE (операционный код) — это низкоуровневые инструкции, в которые компилируется PHP-код перед выполнением. Когда PHP-интерпретатор читает исходный PHP-скрипт, он сначала парсит его и преобразует в набор опкодов (байт-код виртуальной машины Zend). Эти опкоды представляют собой простые команды типа "читать переменную", "сложить", "вызвать функцию" и т.д. Виртуальная машина Zend Engine затем выполняет последовательность этих опкодов. По сути, опкод — аналог машинного кода, только для виртуальной машины PHP. Именно опкоды кешируются в OPCache: при первом запуске скрипта вычисляется массив опкодов, и при следующем запуске их можно взять из кеша, не парся PHP заново. Разработчик обычно не оперирует опкодами напрямую (хотя есть функции opcache_get_status или инструменты disassembler для отладки), но понимание, что PHP работает через опкод, помогает понять работу OPCache и JIT.