SobesLab логотип SobesLab

Junior

  1. Какие знаете принципы ООП? Junior
  2. Какие типы ошибок существуют в PHP? Junior
  3. Какая система типизации используется в PHP? Каковы ее преимущества и недостатки? Junior
  4. Какие типы данных поддерживает PHP? Junior
  5. Что такое проверка типов (type hinting) в PHP? Junior
  6. Что такое ссылки (references) в PHP? Как они работают? Junior
  7. Что такое инкремент и декремент? Чем отличаются префиксный и постфиксный инкремент/декремент? Junior
  8. Что такое рекурсия? Junior
  9. В чем разница между операторами =, == и === в PHP? Junior
  10. Является ли PHP языком, чувствительным к регистру? Junior
  11. В чем разница между интерфейсом и абстрактным классом? Junior
  12. Какие модификаторы видимости есть в РНР? Junior
  13. Какие магические методы вы знаете и как они применяются? Junior
  14. Расскажите об обработке ошибок и исключениях (try catch, finally, throw). Junior
  15. Что такое namespace и зачем они нужны? Junior
  16. Расскажите о принципах SOLID. Junior
  17. Что такое PDO? Junior
  18. В чем разница между расширением mysqli и PDO? Junior
  19. В чем разница между include и require в PHP? Junior
  20. Для чего существуют include_once и require_once? Junior
  21. В чем разница между строками в одинарных и двойных кавычках в PHP? Junior
  22. Для чего используется оператор @ в PHP? Junior
  23. Что такое "переменные переменные" ($$) в PHP? Junior
  24. Чем отличаются функции isset(), empty() и is_null() в PHP? Junior
  25. Является ли PHP компилируемым языком или интерпретируемым? Junior
  26. Чем отличаются конструкции echo и print в PHP? Junior
  27. Какие суперглобальные переменные доступны в PHP? Junior
  28. В чем разница между GET и POST? Junior
  29. Что такое cookie и для чего используется? Junior
  30. Что такое сессия в PHP и как она работает? Junior
    Сессия в PHP — это способ хранения информации, которая будет использоваться на нескольких страницах всего веб-сайта. Информация не хранится на компьютере пользователя, в отличие от файлов cookie. Во временном каталоге на сервере сессией будет создан файл, в котором хранятся зарегистрированные переменные сессии и их значения. Эта информация будет доступна на всех страницах сайта во время этого посещения. Когда вы работаете с приложением, вы открываете его, вносите некоторые изменения, а затем закрываете. Это очень похоже на сеанс. Компьютер знает, кто вы такой. Он знает, когда вы запускаете и завершаете работу приложения. Но в интернете веб-сервер не знает, кто вы и чем занимаетесь, потому что HTTP-протокол не поддерживает состояние. Эта проблема решается с помощью переменных сессии путем сохранения пользовательской информации, которая будет использоваться на нескольких страницах (например, имя пользователя, любимый цвет и т.д.). По умолчанию переменные сессии будут сохраняться до тех пор, пока пользователь не закроет браузер. Таким образом, переменные сессии содержат информацию об одном пользователе и доступны для всех страниц в одном приложении.
  31. Что такое буферизация вывода (output buffering) в PHP? Junior
  32. Для чего используется функция header() в PHP? Junior
  33. Что такое статические свойства и методы класса в PHP? Когда их стоит использовать? Junior
  34. Является ли PHP компилируемым языком или интерпретируемым? Junior
  35. Чем отличается сессия от cookie? Junior
  36. Что такое PSR? Junior
  37. Приведите примеры стандартов PSR и их предназначение. Junior
  38. Что такое JIT-компиляция в PHP и как она работает? Junior
  39. Что такое архитектура MVC? Junior
  40. Назовите паттерны проектирования, с которыми приходилось работать. Junior
  41. Что такое TDD? Junior
  42. Расскажите о Unit Tests (required), Functional Tests (optional). Моки в PHP. Junior
  43. В чем разница между модульными (unit) и функциональными (integration) тестами? Junior
  44. Что такое mock-объект в тестировании PHP? Junior
  45. Что такое Composer и для чего он применяется? Junior

Middle

  1. Что такое вариативные функции и оператор "..." (spread) в PHP? Middle
  2. Что происходит от момента ввода 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, изображения), браузер делает новые запросы к серверу для их получения. В итоге пользователь видит загруженную страницу.
  3. Что такое позднее статическое связывание (Late Static Binding)? Middle
  4. Что такое копирование при записи (Copy-on-Write)? Middle
  5. Передача переменных в PHP: по значению или по ссылке? Middle
  6. Что такое генераторы (Generators) и ключевое слово yield? Middle
  7. Объясните разницу между глубоким (deep) и поверхностным (shallow) копированием объектов. Middle
  8. Что такое Dependency Injection и DI-контейнер? Middle
  9. Как работает автозагрузка классов (autoloading) в PHP? Middle
  10. Что такое Opcache и как он работает? Middle
  11. Чем отличаются статические методы от нестатических? В каком случае следует использовать статические методы? Middle
  12. Что такое "final" классы и методы? Middle
  13. Что такое Reflection API? Middle
  14. Что такое OWASP? Middle
  15. Какие типы уязвимостей веб-приложений вы знаете? Как защититься от них? Middle
    Среди распространённых уязвимостей веб-приложений: SQL-инъекции (внедрение вредоносных SQL команд), XSS (межсайтовый скриптинг, внедрение вредоносного JS на страницу), CSRF (межсайтовая подделка запроса, когда злоумышленник заставляет пользователя непреднамеренно выполнить действие), уязвимости, связанные с неверной авторизацией/аутентификацией, утечки чувствительных данных и др. Способы защиты: для SQL-инъекций — использование подготовленных выражений (prepared statements) и экранирование входных данных; для XSS — тщательная экранизация/кодирование вывода (HTML, JS) и Content Security Policy; для CSRF — использование токенов (CSRF-token) в формах и проверка их на сервере; общие меры — проверка входных данных (валидация), минимизация хранения чувствительной информации, обновление зависимостей и фреймворков, настройка правильных заголовков безопасности и т.д.
  16. Что такое идемпотентность (idempotence) в контексте веб-методов? Какие HTTP-методы являются идемпотентными? Middle
  17. Что означает, что HTTP-протокол является stateless (без сохранения состояния)? Middle
  18. В чем разница между SOAP и REST? Middle
  19. Какие способы аутентификации и авторизации для веб-API вы знаете? Middle
  20. Что может содержать интерфейс в PHP (какие элементы могут быть определены внутри интерфейса)? Middle
  21. Если у объекта есть свойство, которое является объектом, что произойдет при клонировании этого объекта? Как сделать так, чтобы вложенный объект тоже копировался (глубокое клонирование)? Middle
  22. Что представляет собой паттерн Singleton и как его реализовать в PHP? Middle
  23. Почему паттерн Singleton считается анти-паттерном? Middle
  24. Что такое Redis и для чего он используется? Middle
  25. Что такое Memcached и для чего он используется? Middle
  26. В чем разница между Redis и Memcached? Middle
    Оба решения — высокопроизводительные хранилища данных в памяти, часто используемые для кеширования, но есть отличия. Redis умеет сохранять данные на диск и восстановливаться после перезапуска, Memcached — чисто в памяти и данные пропадают при рестарте. Redis поддерживает богатый набор типов данных (списки, множества, хэши и т.д.) и связанные с ними атомарные операции, тогда как Memcached оперирует простыми строковыми значениями (хотя можно сериализовать сложные объекты). Redis работает однопоточно, но может использовать ядра для разных операций (например, сохранение на диск), Memcached — мультипоточный (может задействовать несколько ядер для обслуживания запросов). В выборе между ними: Redis подходит, когда нужны структуры данных или персистентность, Memcached — когда нужен простой, распределённый и очень быстрый кеш для относительно независимых строковых данных.
  27. Каковы преимущества и недостатки использования Redis/Memcached для кэширования? Middle
  28. Назовите основные отличия между веб-серверами Apache и Nginx. Middle
    Apache и Nginx — самые популярные веб-серверы, но они имеют разные архитектуры. Apache исторически использует модель с процессами/потоками: каждый запрос может обрабатываться отдельным потоком (или процессом) — это гибко, но при большом числе одновременных запросов потребляет много памяти. Nginx — событийно-ориентированный (асинхронный) сервер: он способен обслуживать множество соединений в одном потоке, используя неблокирующий ввод-вывод, что делает его очень эффективным по памяти при высокой нагрузке. Apache имеет богатую модульную систему и, например, поддерживает .htaccess-файлы для локальной конфигурации каталогов, чего нет в Nginx (конфигурация Nginx глобальна и обычно проще). Nginx обычно быстрее раздаёт статический контент, Apache гибче на уровне модулей (есть модули для PHP, Perl и т.д.). В современном стеке часто используется связка: Nginx как фронтенд (reverse proxy) для отдачи статики и балансировки, а Apache (или PHP-FPM) — для генерации динамики.

Senior

  1. Опишите жизненный цикл HTTP-запроса в PHP-приложении (например, в рамках фреймворка). Senior
  2. Какие стратегии кеширования данных и HTML вы применяли? Как выбирали между Redis, Memcached и другими решениями? Senior
  3. Как вы организуете обработку долгих (тяжелых) задач в PHP-приложении? Senior
  4. Опишите, как вы проводите профилирование и отладку производительности приложения. Senior
  5. Какие подходы к обеспечению безопасности (Security) PHP-приложения вы считаете наиболее важными? Senior
  6. Как вы проектируете архитектуру большого приложения? На что обращаете внимание в первую очередь? Senior
  7. Что такое CQRS/Event Sourcing? В каких сценариях это уместно применять и почему? Senior
  8. Что такое когезия и связность (cohesion и coupling) в коде? Senior
    Когезия (сцепление) — это мера того, насколько элементы внутри модуля (класса, функции) связаны друг с другом и работают над единой задачей. Высокая когезия означает, что модуль сфокусирован, выполняет единственную роль или связанную группу функций (например, класс, у которого все методы относятся к одной области ответственности). Связанность (coupling) — это степень зависимости одного модуля от другого. Сильная связность — когда изменения в одном модуле могут потребовать изменений в другом, модули тесно знают детали друг друга; слабая связность — когда модули минимально взаимодействуют через чёткие интерфейсы. Хороший дизайн стремится к высокой когезии (каждый компонент отвечает за своё, не размывая функциональность) и низкой связности (компоненты минимально зависят друг от друга, взаимодействуют через абстракции, что облегчает изменения).
  9. Можно ли использовать null в параметрах или возвращаемых значениях методов? Почему это считается плохой практикой? Senior
    С технической точки зрения метод может принимать null как параметр (если тип позволяет) и возвращать null. Однако злоупотребление null считается плохой практикой, так как ведёт к необходимости делать множество проверок на null и может приводить к ошибкам "обращение к свойству bool(null)" и т.п. Если метод возвращает null, вызывающий код всегда должен проверить результат на null, иначе риск ошибок. Часто null используют, чтобы обозначить "ничего" или ошибку, но лучше для ошибок бросать исключения, а для отсутствия результата применять шаблон Null Object или Maybe. Также передавать null в методы не всегда логично — лучше перегрузить метод или сделать несколько методов для разных сценариев, чем разрешать внутри параметр null. В общем, null увеличивает количество условий и потенциальных NPE (Null Pointer Exception) в коде, поэтому стоит избегать его, если можно выразить идею иначе.
  10. Что такое паттерн Special Case / Null Object? Senior
  11. Как тестировать код, который обращается к внешним API или сервисам? Senior
    При тестировании кода с внешними зависимостями (например, удалённый веб-сервис) применяют изоляцию: вместо реального API в тестах используют заглушки (stub) или моки, эмулирующие поведение внешнего сервиса. То есть реализацию внешнего сервиса подменяют тестовой версией, которая возвращает заранее подготовленные ответы. Для этого код стоит писать с Dependency Injection — чтобы зависимость (клиент API) можно было подменить. В юнит-тестах вы проверяете логику своего кода, используя фейковый API (который, например, всегда возвращает успех или определённый ответ). Так тесты будут быстрыми, независимыми от сети. Альтернативно, для интеграционных тестов можно поднять локальный тестовый сервер или использовать рекорд/воспроизведение запросов. Но ключевое: не обращаться к реальному внешнему API в тесте — это ненадёжно и медленно; вместо этого симулировать взаимодействие либо через моки (с проверкой вызовов), либо через стабы (с фиксированными ответами).
  12. Что такое Domain-Driven Design (DDD)? Senior
    Domain-Driven Design — это подход к разработке ПО, при котором основное внимание уделяется предметной области (бизнес-домена) и знаниям экспертов этой области. DDD предлагает выстраивать модель программного обеспечения, отражающую реальные сущности, правила и процессы домена, и организовать командную работу программистов и предметных экспертов для создания "Универсального языка" (Ubiquitous Language), понятного и тем, и другим. В DDD выделяют понятия: сущности (Entities) — объекты с идентификатором и жизненным циклом, значение (Value Objects) — объекты без уникального ID, определяемые своими значениями, агрегаты (Aggregates) — группы объектов, объединённых вокруг одной сущности (Root) для соблюдения инвариантов, хранилища (Repository) — для получения и сохранения агрегатов, сервисы домена (Domain Services) — для операций, не относящихся к конкретной сущности. Также вводится концепция ограниченных контекстов (Bounded Context) — различных частей системы, в каждой из которых своя модель и терминология. DDD особенно полезен для сложных предметных областей, позволяя создать понятную, эволюционирующую архитектуру, напрямую отражающую бизнес.
  13. Что такое антипаттерны? Приведите примеры. Senior
    Антипаттерн — это типичное неудачное решение какой-либо задачи разработки, повторяющееся из проекта в проект. Это как "паттерн наоборот": известная практика, которая кажется удобной, но ведёт к проблемам. Примеры антипаттернов: "Spaghetti Code" (спагетти-код) — плохо структурированный, запутанный код без чёткого разделения модулей; "God Object" (объект-бог) — объект, берущий на себя слишком много ответственности, знает и делает всё, нарушая принципы декомпозиции; "Golden Hammer" — применение одной знакомой технологии или подхода ко всем проблемам, независимо от их пригодности; "Copy-Paste Programming" — дублирование кода вместо выноса общих частей (нарушение DRY); Singleton (в чрезмерном использовании) тоже часто приводится как антипаттерн, из-за глобального состояния. Ещё пример — антипаттерн "Magic Number" (использование непонятных чисел/строк в коде вместо констант с ясными именами). Знание антипаттернов помогает их избегать и выбирать более удачные архитектурные решения.
  14. Как вы проводите рефакторинг большого legacy-кода и убеждаете руководство в его необходимости? Senior
    Рефакторинг legacy-проекта — постепенный, поэтапный процесс. Обычно я начинаю с участков кода, которые чаще всего изменяются или вызывают проблемы. Покрываю ключевые части автоматическими тестами (если их нет), чтобы иметь страховку при изменениях. Затем мелкими шагами переписываю код: выделяю функции, разбиваю слишком большие методы, устраняю дублирование, вводя общие модули, улучшаю читаемость (именование, структура). Важно каждый шаг проверять тестами, избегая изменения внешнего поведения. Чтобы убедить менеджмент/заказчика, делаю акцент на выгодах: снижение количества ошибок, упрощение внедрения новых функций, уменьшение расходов на поддержку. Можно привести недавние случаи, когда из-за "сложности и запутанности" мы долго исправляли баг или медленно внедряли изменения — показать, что рефакторинг окупится в будущем ускорением разработки и повысит качество. Часто практикуется рефакторинг при разработке новых фич (refactor by need): объясняю, что без улучшения существующего кода внедрение новой функции займёт гораздо больше времени или будет ненадёжным. Демонстрируя конкретные показатели (например, "после рефакторинга время на добавление аналогичной функциональности сократилось вдвое"), можно заручиться поддержкой руководства.
  15. Сталкивались ли вы с утечками памяти в PHP? Как находили и устраняли их? Senior
    Да, в длительно работающих скриптах или сервисах на PHP (например, демонах, воркерах) можно столкнуться с утечкой памяти, хотя PHP обычно освобождает память после окончания скрипта. Причины: накопление данных в глобальных структурах или статических переменных, бесконтрольный рост массивов, хранение ссылок на объекты, которые уже не нужны (особенно при циклических ссылках в старых версиях PHP до внедрения сборщика цикла). Для поиска утечек я использовал профилировщики/инструменты мониторинга (например, расширение Xdebug с профилировкой памяти, либо специализированные инструменты вроде memory_get_usage() в ключевых точках, или external profilers) — смотрел, как растёт память при выполнении цикла. Ещё подход: если утечка, как правило, linear — значит где-то данные накапливаются. Найдя место, устранял причину: освобождал массивы (unset()), сбрасывал большие объекты после использования, избегал хранения неограниченных логов или кэшей внутри одного процесса. В случае, если проблема в циклических ссылках, можно вызывать gc_collect_cycles() периодически, хотя в новых версиях PHP сборщик мусора должен автоматически очищать циклы. В одном из проектов помогло разделение процесса: вместо бесконечного цикла, воркер обрабатывал N задач и перезапускался — таким образом даже при небольшой утечке она не успевала стать критичной. Главное — выявить, что именно растёт, и убрать ненужные накопления или пересмотреть архитектуру хранения данных.
  16. Как работает сборщик мусора (Garbage Collector) в PHP? Когда имеет смысл вручную вызвать сборщик мусора? Senior
    Garbage Collector (GC) в PHP отвечает за обнаружение и устранение "циклических" ссылок — ситуаций, когда объекты ссылаются друг на друга, и поэтому счётчик ссылок не падает до нуля, хотя они более недостижимы. В PHP используется комбинация: счётчик ссылок освобождает память для всех "простых" случаев, а GC периодически проверяет циклы. Начиная с PHP 5.3 GC работает автоматически: по достижении определённого количества аллокаций он запускает цикл сбора: сканирует граф объектов, выявляет изолированные циклы, и освобождает их. Обычно программисту не нужно явно вмешиваться. Функция gc_collect_cycles() вручную запускает сборщик циклов. Имеет смысл вызывать GC вручную в редких случаях — например, внутри длинного скрипта после большого количества операций, если известно, что накопились циклические ссылки и они занимают много памяти, а ждать автоматического срабатывания не хочется. Но чрезмерно вызывать gc_collect_cycles() не стоит — это затратная операция, она сама потребляет CPU. Обычно необходимость ручного вызова связана с нестандартными случаями (например, при интенсивном создании объектов с взаимными ссылками в больших количествах).
  17. Какие инструменты статического анализа кода (линтеры) для PHP вы использовали? Senior
    Существуют различные инструменты для статического анализа PHP-кода. Среди популярных: PHP_CodeSniffer — проверяет соответствие кода определённым стилевым стандартам (например, PSR-12), помогает поддерживать единообразный стиль. PHPStan и Psalm — мощные статические анализаторы, которые выявляют потенциальные ошибки, несоответствие типов, обращение к неопределённым переменным, неправильное использование API ещё до выполнения программы. PHPMD (PHP Mess Detector) — выявляет "плохие запахи" кода: слишком длинные методы, сложные условия, дублирование и т.д. Также IDE, такие как PhpStorm, имеют встроенные статические инспекции. Я обычно на проектах использую PHPStan (или Psalm) для проверки типов и ошибок, а CodeSniffer с правилами PSR-12 для форматирования и простых ошибок. Эти инструменты интегрируются в CI, что помогает автоматом ловить проблемы до ревью и запуска.
  18. Какие инструменты профилирования и анализа производительности 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) вместе с мониторингом профиля, чтобы понять узкие места.
  19. Какой уровень покрытия тестами вы считаете достаточным? Всегда ли нужен 100% охват? Senior
    Достаточный уровень покрытия тестами зависит от природы проекта, его критичности и темпа изменений, но распространённая цель — 70-90% покрытие кода модульными тестами. 100% покрытие не является обязательным и не гарантирует отсутствие ошибок: важнее, какие случаи покрыты. Практически добиться 100% сложно и иногда не нужно: некоторые простые геттеры/сеттеры или тривиальный код нет смысла явно тестировать, это только тратит время. Кроме того, "coverage" измеряет лишь долю строк, выполненных в тестах, но не качество самих тестов (они могут не проверять правильность результата). Я считаю, что нужно стремиться покрыть тестами основную бизнес-логику, критические и сложные части системы. Если какой-то модуль не критичен или часто меняется, возможно, тесты на него писать избыточно. 100% охват может быть неоправдан по затратам: проще потратить усилия на тестирование ключевых сценариев и регрессий, нежели гоняться за формальным показателем. Также важно проводить интеграционные и end-to-end тесты, помимо unit. В итоге, полный охват — утопия, вместо этого метрика покрытия должна использоваться разумно: как подсказка, куда добавить тесты, но не самоцель.
  20. Что такое триггер в SQL и для чего он используется? Senior
    Триггер — это хранимая процедура, которая автоматически выполняется при наступлении определённого события в базе данных (например, при вставке, обновлении или удалении записи в указанной таблице). Триггер привязан к таблице и конкретному событию (BEFORE/AFTER INSERT/UPDATE/DELETE). Когда такое событие происходит, СУБД запускает код триггера (это может быть, например, проверка или модификация данных, запись в журнал, синхронизация с другой таблицей). Используют триггеры для обеспечения сложных целостных ограничений, автоматического вычисления производных значений, аудита изменений (логирование в отдельную таблицу истории) и т.д. Однако злоупотребление триггерами может усложнить отладку и понимание системы, так как логика "спрятана" в базе, и неочевидно, что при изменении таблицы произойдёт что-то ещё. Поэтому триггеры применяют аккуратно для задач, которые действительно лучше решать на уровне БД (гарантия целостности, аудирование).
  21. Какие уровни изоляции транзакций существуют в СУБД? Senior
    Стандарт SQL определяет четыре уровня изоляции транзакций: Read Uncommitted (самый низкий, "чтение неподтверждённых" данных) — транзакция может видеть изменения других незавершённых транзакций ("грязное чтение"), практически не используется из-за опасности; Read Committed — транзакция видит только подтверждённые на данный момент изменения (грязных чтений нет, но возможны неповторяемые чтения: один и тот же запрос в транзакции может вернуть разные данные, если другая транзакция изменила и закоммитила); Repeatable Read — транзакция гарантирует повторяемость чтений: если она прочитала запись, то пока она активна, другие транзакции не могут изменить или удалить эту запись (исключены грязные и неповторяемые чтения, но возможны "фантомы" — вставка новых строк другим коммитом может проявиться при повторном запросе); Serializable — самый строгий уровень, полный эффект как если бы транзакции выполнялись последовательно, исключены все аномалии, но достигается ценой блокировок и снижения параллелизма. Разные СУБД могут по-разному реализовывать эти уровни (например, MySQL InnoDB по умолчанию Repeatable Read, в котором уже нет фантомов из-за механизмов MVCC). Выбор уровня — компромисс между корректностью параллелизма и производительностью.
  22. Чем реляционные СУБД отличаются от NoSQL баз данных? Примеры NoSQL баз. Senior
    Реляционные СУБД хранят данные в таблицах с фиксированной схемой (определённым набором столбцов), используют язык SQL для запросов и обеспечивают строгую согласованность транзакций (ACID). NoSQL базы — более широкий класс систем хранения, которые отходят от модели таблиц и SQL. Отличия: NoSQL обычно не требует фиксированной схемы (данные могут быть разной структуры), часто жертвуют сильной согласованностью ради горизонтального масштабирования (многие обеспечивают eventual consistency — итоговую согласованность). Есть несколько типов NoSQL: ключ-значение (например, Redis, Riak) – хранят данные как пару ключ:произвольное значение; документные базы (MongoDB, CouchDB) – хранят данные в виде документов (обычно JSON-подобных) с гибкой схемой; колоночные (Cassandra, HBase) – хранят данные колоночными семьями, оптимизированы под большие объёмы и распределённость; графовые (Neo4j, JanusGraph) – хранят узлы и рёбра графа, оптимальны для графовых связей. NoSQL базы часто лучше масштабируются горизонтально, могут справляться с очень большими объёмами данных и высоким трафиком, но могут не предоставлять таких же богатых гарантий транзакционности или удобства сложных JOIN-запросов, как SQL. Выбор зависит от задач: реляционные – для структурированных данных и сложных запросов, NoSQL – для больших распределённых систем, гибких форматов или особых типов данных (например, графов).
  23. Что означает ACID в контексте транзакций баз данных? Senior
    ACID — набор свойств, гарантирующих надёжность выполнения транзакций в СУБД: Atomicity (атомарность) — транзакция либо выполняется целиком, либо не выполняется вовсе (все её операции считаются одним неделимым блоком); Consistency (согласованность) — транзакция переводит базу из одного консистентного состояния в другое (если все ограничения и правила были выполнены до транзакции, то и после коммита они не нарушены); Isolation (изолированность) — параллельное выполнение транзакций не должно приводить к тем же результатам, что их последовательное выполнение (уровни изоляции определяют степень видимости промежуточных результатов, но полная изоляция исключает взаимовлияние одновременно выполняющихся транзакций); Durability (долговечность) — после фиксации (COMMIT) результаты транзакции гарантированно сохраняются, даже в случае сбоя системы (данные записаны на диск, возможно, через журнал транзакций). Соблюдение ACID свойств обеспечивает надёжную работу с данными даже при различных сбоях или одновременной работе многих клиентов.
  24. В чем разница между Dependency Injection и Service Locator? Senior
    Оба подхода решают проблему передачи зависимостей, но по-разному. При Dependency Injection (внедрение зависимостей) объекты получают необходимые зависимости явно (через параметры конструктора, сеттеры или инициализацию) — то есть зависимость передаётся извне, и класс ничего не знает, откуда она пришла. При паттерне Service Locator у класса есть доступ к некоему глобальному объекту-локатору (или контейнеру), у которого он запрашивает нужную зависимость по имени/типу. Разница: DI делает зависимости явными и видимыми в интерфейсе класса, облегчая тестирование (можно подставить mock) и понимание (сразу видно, что требуется). Service Locator скрывает зависимости: внутри метода класс может внезапно дернуть глобальный локатор, получить некую сервис-зависимость — для пользователя класса это не очевидно. Поэтому Service Locator критикуют: он по сути является глобальной переменной, усложняет тестирование (надо как-то подменять глобальный локатор), увеличивает связность. DI предпочитают за явность, хотя Service Locator иногда применяется для совместимости или когда DI трудно внедрить.
  25. Как вы обеспечиваете горизонтальную масштабируемость PHP-приложения? Senior
  26. Расскажите о вашем опыте оптимизации работы с базой данных в высоконагруженных проектах. Senior
  27. Что такое чувствительные данные (sensitive data)? Как их хранить и логировать безопасно? Senior
    Чувствительные (конфиденциальные) данные — это информация, раскрытие которой нежелательно или опасно. К ним относятся: персональные данные пользователей (имя, адрес, телефон, email), учетные данные (логины, пароли), финансовая информация (номера кредитных карт, банковские счета), медицинские данные и т.д. Безопасное хранение подразумевает, что такие данные в базе должны быть защищены: пароли, например, не хранят в открытом виде — только хэш (желательно bcrypt/argon2) + соль. Другие критичные данные могут храниться в зашифрованном виде (с использованием стойких алгоритмов шифрования, ключи не лежат рядом с данными). Логирование: в журналах нельзя оставлять полные конфиденциальные данные — например, логируя объект пользователя, необходимо маскировать или удалять поля с чувствительной информацией (заменять часть символов звёздочками, не писать пароли вообще). Также важно ограничивать доступ к логам. Цель — минимизировать риск компрометации: даже если злоумышленник получит базу, он не сможет мгновенно узнать пароли, ему придётся затратить огромные усилия на подбор по хешам.
  28. Что такое Swoole или ReactPHP и какие задачи они решают? Senior
    Swoole и ReactPHP — решения для реализации асинхронного, событийно-ориентированного программирования в PHP. ReactPHP — это библиотека (фреймворк) на чистом PHP, предоставляющая цикл событий (Event Loop) и неблокирующие I/O операции (например, асинхронные сокеты, таймеры). С её помощью можно писать серверы (например, веб-сервер, веб-сокеты), работающие асинхронно (как Node.js). Swoole — это PHP-расширение на Си, которое интегрирует высокопроизводительный асинхронный движок прямо в PHP. Swoole позволяет создавать долговременно работающие процессы, веб-серверы, веб-сокеты, имеет корутины для упрощения асинхронного кода. Оба инструмента решают задачу: использовать PHP для долгоживущих сервисов, обслуживающих много одновременных соединений эффективно (не создавая новый процесс/тред на каждый запрос, как традиционный PHP-FPM). Это подходит для real-time приложений, игр, чатов, где нужна многосокетная обработка в одном процессе.
  29. Что такое микросервисная архитектура? Senior
    Микросервисная архитектура — подход к построению приложения как набора мелких независимых сервисов, каждый из которых выполняет свою узкую функцию и взаимодействует с другими через хорошо определённые интерфейсы (обычно через сетевые API, например REST или сообщения). В отличие от монолита, где все функциональности в одном приложении, микросервисы раздельно развёртываются, масштабируются и обновляются. Каждый микросервис может быть реализован на своей технологии, иметь собственную базу данных (отделённый контекст). Преимущества: лучшая масштабируемость (можно масштабировать только горячие сервисы), гибкость разработки (разные команды могут работать над разными сервисами, обновлять их независимо), отказоустойчивость (падение одного сервиса не обрушит всю систему, если правильно обработаны ошибки). Недостатки: усложняется инфраструктура, возникают накладные расходы на коммуникацию между сервисами, сложнее обеспечивать консистентность данных, нужна система оркестрации, мониторинга, логирования распределённых компонентов.
  30. Какие способы взаимодействия между микросервисами вы знаете? Senior
    Микросервисы могут взаимодействовать синхронно и асинхронно. Синхронные способы: HTTP/REST API — один сервис делает HTTP-запрос к другому (обычно RESTful JSON API), gRPC — бинарный протокол поверх HTTP/2 с автогенерацией кода (хорош для строго типизированного, быстрого RPC). Асинхронные способы: обмен сообщениями через брокеры (RabbitMQ, Apache Kafka, Amazon SQS) — сервис публикует сообщение в очередь/топик, другой сервис (или компоненты) подписывается и получает эти сообщения для обработки (это позволяет декуплировать их по времени и нагрузке); события (Event-Driven Architecture) — сервисы рассылают события о совершённых действиях, а другие, подпишившись, реагируют (например, обновляют свои данные). Выбор зависит от задач: для запроса данных и получения ответа подойдёт синхронный REST/RPC, а для декорреляции и масштабирования часто используют асинхронные очереди и сообщения.
  31. Как вы относитесь к микросервисной архитектуре? В каких случаях её стоит применять, а в каких — нет? Senior
  32. Как изменить сообщение последнего коммита Git? Senior
  33. Что делает команда `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 удобен для очистки локальной истории перед пушем (например, слить "грязные" коммиты в логичные), но им не стоит пользоваться на уже опубликованных коммитах, чтобы не нарушать историю в удалённом репозитории.
  34. Для чего нужна команда `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-историю.
  35. Как перенести отдельный коммит из одной ветки в другую (влить коммит)? Senior
  36. Как объединить несколько коммитов (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, если изменения уже опубликованы.
  37. Что такое дедлок (deadlock) в базе данных? Senior
    Дедлок — это ситуация взаимной блокировки, когда две (или более) транзакции ожидают освобождения ресурсов друг от друга и ни одна не может продолжить. В контексте базы данных дедлок обычно возникает, если транзакция A захватила блокировку на ресурс X и ждёт ресурс Y, а транзакция B захватила Y и ждёт X (или аналогичные сложные цепочки). Обе зациклились, каждая ждёт, что другая закончится и отпустит ресурс. СУБД обнаруживает такие циклы блокировок (обычно по таймауту или специальному алгоритму) и автоматически прерывает (откатывает) одну из транзакций, чтобы разблокировать ситуацию. Приложение получит ошибку о дедлоке (deadlock detected) для одной транзакции — и обычно должно повторить транзакцию. Дедлоки чаще случаются при интенсивных конкурентных обновлениях связанных таблиц в разных порядках. Чтобы избегать дедлоков, рекомендуется: блокировать (обновлять) ресурсы в согласованном порядке (например, всегда таблица A потом B), держать транзакции как можно короче, избегать длительных блокирующих операций внутри транзакции.
  38. Что такое репликация базы данных и какие виды репликации вы знаете? Senior
    Репликация — это процесс копирования данных с одного сервера баз данных на другой в реальном (или почти реальном) времени. Обычно есть основной узел (master), куда выполняются записи, и один или несколько ведомых узлов (slave), которые получают изменения с мастера и применяют у себя. Виды репликации: основная — master-slave (один ведущий, несколько реплик только для чтения); multi-master — несколько узлов могут принимать записи и обмениваются изменениями (сложнее, требует разрешения конфликтов, применимо не во всех СУБД, пример — MySQL Group Replication, некоторые NoSQL тоже multi-master). Есть синхронная репликация — когда транзакция считается завершённой, только когда данные записаны на всех репликах (гарантирует консистентность, но медленнее), и асинхронная — мастер сразу подтверждает, а реплика догоняет самостоятельно (могут быть отставания). Цель репликации: масштабирование чтения (распределить нагрузки чтения по слейвам), отказоустойчивость (если мастер падает, можно переключиться на реплику), геораспределение (база ближе к пользователям). Популярная схема — master-master (активный-пассивный), где у двух узлов каждый реплицирует изменения на другой, но реально записывают обычно на один, а второй ждёт переключения при сбое.
  39. Что такое шардинг базы данных? Senior
    Шардинг — это горизонтальное масштабирование базы данных путём разделения данных по разным узлам (серверным инстансам) по определённому принципу. Вместо того, чтобы хранить всю таблицу на одном сервере, шардинг распределяет строки между несколькими серверами. Например, можно шардинговать по диапазону ключа (пользователи с id 1-100000 на одном сервере, 100001-200000 на другом) или по хэшу ключа (каждая запись отправляется на определённый шард по результату хэш-функции от, скажем, идентификатора). Цель шардинга — преодолеть ограничения по объёму и нагрузке: каждый отдельный сервер обрабатывает только свою часть данных, тем самым можно хранить и обрабатывать значительно больше, чем на одном сервере. Однако шардинг усложняет архитектуру: нужно решать, как маршрутизировать запросы на нужный шард, как выполнять межшардовые запросы (например, join по шардам становится сложным), поддерживать балансировку данных. Шардинг часто применяется в очень крупных системах, когда вертикально масштабировать СУБД уже невозможно или экономически невыгодно.
  40. Объясните суть теоремы CAP в распределённых системах. Senior
    CAP-теорема гласит, что в распределённой системе, хранящей данные, из трёх свойств — Consistency (согласованность), Availability (доступность) и Partition Tolerance (устойчивость к разделению сети) — одновременно на все 100% можно обеспечить только два. Расшифровка: Consistency — все узлы видят одни и те же данные в одно время (строгая консистентность данных между репликами); Availability — система всегда отвечает на запрос (каждый запрос к неперекрывшемуся узлу получает ответ, даже если некоторые другие узлы упали); Partition Tolerance — система продолжает работать при разделении сети (когда узлы потеряли связь друг с другом). Согласно CAP, при сетевом разделении (а его нельзя избежать в реальных сетях) разработчикам приходится делать выбор: либо пожертвовать строгой консистентностью (разрешить разным узлам иметь рассинхрон временно, но система останется доступной — это категория AP, например, Cassandra, которая отдаёт возможно устаревшие данные, но всегда отвечает), либо пожертвовать доступностью (система остановит обслуживание части запросов, но не вернёт неконсистентных данных — категория CP, например, MongoDB в режиме с подтверждениями: при проблемах с сетью остановит запись на части узлов для консистентности). Полностью CAP-совместимой быть нельзя: либо CP, либо AP (CA в распределённой системе невозможно, так как P всегда нужно учитывать). CAP теорема помогает понять компромиссы при проектировании систем хранения данных.
  41. В чем разница между потоком (thread) и процессом? Senior
    Процесс — это экземпляр выполняющейся программы, со своим адресным пространством (памятью). Поток — лёгкий компонент, выполняющийся внутри процесса; все потоки одного процесса разделяют общее адресное пространство, дескрипторы файлов и другие ресурсы процесса. Различия: процессы изолированы друг от друга (один процесс напрямую не может изменять память другого), а потоки разделяют память, поэтому взаимодействовать между потоками проще и быстрее (но нужно синхронизироваться, чтобы не было гонок). Контекстный переключение между потоками в рамках одного процесса обычно легче, чем между процессами. Процессы чаще используют для сильной изоляции и разделения задач, потоки — для параллельного выполнения в рамках одной задачи. На примере веб-сервера: Apache может создавать новые процессы на каждый запрос (prefork), или потоки (worker) — потоки легче, но баг в одном потоке может уронить весь процесс и все потоки (т.к. общая память), в случае процессов повреждается только один запрос. Таким образом: процесс — отдельная программа с полной изоляцией, поток — "легковесное" исполнение внутри программы, разделяющее общие ресурсы.
  42. Возможна ли многопоточность в PHP и как этого можно добиться? Senior
    Сам PHP (в веб-контексте с Apache mod_php или PHP-FPM) работает по модели "каждый запрос — отдельный процесс (или поток Apache)". Внутри одного PHP-скрипта традиционно не используют потоки (PHP не имеет встроенной модели потоков на уровне языка). Однако существуют способы: есть расширение pthreads (в среде CLI), которое позволяет запускать параллельные потоки (Thread) в PHP-коде, но оно не поддерживается в веб-SAPI; можно использовать forking — расширение pcntl_fork() (только в Unix и CLI) для создания нового процесса-"дочери" из текущего скрипта; либо делать многопоточность на уровне задач: например, запуская несколько PHP-скриптов параллельно (вне самого языка, через системные средства или пула). В реальности, для "параллельности" обычно применяют не многопоточность, а многопроцессность (несколько воркеров) или асинхронные фреймворки (ReactPHP, Swoole) где один процесс обрабатывает много соединений асинхронно. Итого: в стандартном PHP веб-скрипте — нет, он выполняется последовательно, но вне веба с дополнительными расширениями можно организовать многопоточное выполнение.
  43. Как получить значение приватного свойства объекта извне класса? Senior
  44. Что такое Event Sourcing? Senior
    Event Sourcing — это паттерн хранения состояния системы через последовательность событий. В традиционном подходе мы храним текущее состояние (например, в базе таблица с полями, которые обновляются при изменении). При Event Sourcing не сохраняется непосредственно последняя версия данных, вместо этого каждая изменение записывается как отдельное событие (например: "На счёт X зачислено 100", "Со счёта X списано 50"). Текущее состояние вычисляется как результат применения всех событий к начальному состоянию. Таким образом, "история" изменений хранится полностью. Плюсы: полная трассируемость действий (можно понять, как система пришла к текущему состоянию, можно откатиться или пересчитать), возможность "проиграть" события в другой контекст (например, построить проекции). Минусы: сложнее запросы текущего состояния (нужно суммировать/агрегировать события), требуется механизмы миграции событий при изменении логики. Часто Event Sourcing используется вместе с CQRS: события пишутся в лог (Event Store), а для чтения формируются проекции (например, материализованное состояние) для быстрых запросов. Пример: банковский счёт — вместо хранения баланса, храним все транзакции; баланс вычисляется суммированием транзакций. Это Event Sourcing.
  45. Что такое eventual consistency (конечная согласованность) в распределённых системах? Senior
    Конечная согласованность — модель, при которой система не гарантирует мгновенную согласованность данных на всех узлах, но гарантирует, что если не будет новых обновлений, то спустя некоторый (обычно короткий) период все копии данных придут к одному значению. Это более слабая форма консистентности, часто используемая в распределённых или NoSQL системах ради доступности и производительности. Например, в системе с репликацией, если применена eventual consistency, то сразу после записи на одном узле, чтение с другого узла может ещё вернуть старое значение; но через некоторое время (миллисекунды или секунды, в зависимости от задержек) все реплики обновятся. Практически это означает, что приложение должно уметь работать с временными расхождениями. Такой режим применяется там, где абсолютная точность в реальном времени не критична (например, счетчики лайков, данные, которые могут чуть запаздывать). Это противоположность строгой консистентности (где чтения всегда отражают последние записи, но достичь этого тяжело при сетевых разделениях, см. CAP). Eventual consistency помогает строить масштабируемые, устойчивые к нагрузке системы, где компоненты слабо связаны и общаются через обмен сообщениями.
  46. Что такое GraphQL и чем он отличается от REST? Senior
  47. Что такое Docker и для чего он используется? Каково основное устройство Docker-контейнеров? Senior
    Docker — это платформа контейнеризации, которая позволяет упаковать приложение со всеми его зависимостями (библиотеками, средой) в единый контейнер. Контейнер Docker — изолированная среда, работающая на ядре хоста, но отделённая от остальных процессов (собственная файловая система, сетевые интерфейсы, ограниченные ресурсы). Основная идея: "контейнеры" легче, чем виртуальные машины, потому что не несут в себе отдельное ядро/ОС — они используют возможности ядра хостовой системы (cgroups, namespaces в Linux) для изоляции. Docker-контейнеры создаются на основе образов (image), которые представляют собой слоистую файловую систему (каждый слой — результат изменения, например, установки пакета). Docker широко применяется для стандартизации среды разработки и продакшена: если приложение запущено в Docker, то на любой машине с Docker оно поведёт себя одинаково. Это облегчает деплой (например, через Docker Compose или Kubernetes можно запускать множество контейнеров, описав их зависимости), масштабирование и изоляцию приложений друг от друга. Устройство: Docker-демон управляет образами и контейнерами, создаёт контейнеры с помощью Linux namespace (для изоляции) и cgroups (для ограничения ресурсов). Контейнер видит внутри себя как бы свою мини-ОС (с нужными утилитами, которые включены в образ), но на самом деле использует ядро хоста.
  48. Чем отличается горизонтальное масштабирование от вертикального? Senior
    Вертикальное масштабирование означает увеличение ресурсов одной машины (серверы посильнее: добавить CPU, RAM, SSD, ускорить на существующем узле). Горизонтальное масштабирование означает добавление новых узлов (машин) в систему и распределение нагрузки между ними. Например, вертикально масштабировать базу — перейти на более мощный сервер; горизонтально — поднять кластер из нескольких серверов и делить данные или запросы между ними. Вертикальное масштабирование обычно проще (не требует изменений в архитектуре, просто улучшение железа), но имеет предел (есть физический максимум, и часто это дорого). Горизонтальное сложнее (нужно обеспечивать распределение запросов, консистентность данных между узлами, балансировку нагрузки), но даёт значительно лучшую масштабируемость — можно добавлять почти сколько угодно узлов. В веб-приложениях часто сначала масштабируются вертикально (вплоть до определённого предела), а затем переходят к горизонтальному (несколько серверов приложений за балансировщиком, шардинг или репликация баз данных, распределённые кэши). Итого: вертикальное – "ввысь" (больше ресурсов одному экземпляру), горизонтальное – "вширь" (больше экземпляров).
  49. Что предпочтительнее: наследование или композиция? Поясните, в чем разница между ними. Senior
    Наследование и композиция – два разных подхода к организации кода. Наследование подразумевает отношение "является" (is-a): класс-наследник расширяет базовый класс, унаследуя его свойства/методы, и может переопределить или добавить новое поведение. Композиция означает, что один объект содержит в себе другой объект для использования его функциональности – отношение "имеет" (has-a). Вместо того, чтобы наследовать класс, мы включаем его как поле и вызываем его методы по мере необходимости. Вместо наследования часто предпочтительнее композиция, поскольку она обеспечивает более слабую связанность: изменения во включаемом классе не влияют на внешний интерфейс композитного класса, и мы можем более гибко менять компоненты. Наследование же связывает классы сильнее (наследник зависит от реализации родителя, может столкнуться с проблемами хрупкой иерархии). Правило "предпочитай композицию наследованию" означает: используйте наследование только когда отношение действительно логически является "is-a" и требуется полиморфизм, в остальных случаях лучше включить необходимые объекты. Композиция облегчает замену частей системы (можно подменить компонент другим классом, реализующим нужный интерфейс) и тестирование (можно замокать компоненты), а наследование часто ведёт к более жёсткой структуре и потенциальному дублированию кода, если иерархия выстраивается неправильно.
  50. Какие паттерны используются в 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 сложнее, но лучше изолирует бизнес-логику от деталей хранения, что улучшает тестируемость и поддерживаемость большого проекта.
  51. Как масштабировать PHP-приложение на несколько серверов (как хранить сессии в кластере)? Senior
    При масштабировании веб-приложения на несколько серверов важно учитывать состояние сессий пользователей. По умолчанию PHP хранит сессии в файловой системе сервера, и если запросы одного пользователя пойдут на разные машины, то он потеряет сессию. Есть несколько подходов: 1) "липкие сессии" (sticky sessions) на балансировщике — запросы от одного пользователя всегда отправляются на тот же сервер, на котором началась сессия; это просто, но снижает эффективность балансировки и надёжность (если сервер упал, пользователи теряют сессии). 2) Централизованное хранение сессий: настроить PHP на хранение сессий в общем хранилище, доступном всем узлам — например, в базе данных, Memcached или Redis. Тогда независимо от того, на какой сервер пришёл запрос, он достанет данные сессии из общего хранилища. Этот способ предпочтительнее, так как даёт независимость от конкретного веб-сервера. Реализуется изменением session.save_handler и соответствующих настроек (есть драйверы для Memcached, Redis и т.д.). 3) Использование JWT-токенов вместо серверных сессий — состояние хранится на клиенте в виде токена (подписанного), сервер при каждом запросе валидирует его; в этом случае сервер вообще не хранит сессионных данных, проблема синхронизации отпадает, но не подходит для больших объёмов данных. В итоге, для кластеризации PHP-приложения обычно выбирают хранить сессии в Redis/Memcached (быстро) или в SQL-базе (попроще, но медленнее), чтобы обеспечить единый источник сессионных данных для всех серверов.
  52. Что такое OPCODE в PHP? Senior
    OPCODE (операционный код) — это низкоуровневые инструкции, в которые компилируется PHP-код перед выполнением. Когда PHP-интерпретатор читает исходный PHP-скрипт, он сначала парсит его и преобразует в набор опкодов (байт-код виртуальной машины Zend). Эти опкоды представляют собой простые команды типа "читать переменную", "сложить", "вызвать функцию" и т.д. Виртуальная машина Zend Engine затем выполняет последовательность этих опкодов. По сути, опкод — аналог машинного кода, только для виртуальной машины PHP. Именно опкоды кешируются в OPCache: при первом запуске скрипта вычисляется массив опкодов, и при следующем запуске их можно взять из кеша, не парся PHP заново. Разработчик обычно не оперирует опкодами напрямую (хотя есть функции opcache_get_status или инструменты disassembler для отладки), но понимание, что PHP работает через опкод, помогает понять работу OPCache и JIT.