/test-task

Решение тестовых задач

Primary LanguagePHP

PHP 7.4 MySQL 5.7 Symfony 5.3.10

О проекте

  1. Проект докеризирован для более простых тестирования и разработки
  2. Запускаем Docker - docker-compose up -d --build (для PhpStorm можно настроить конфигурацию - Run/Debug Configurations - конфигурация Docker-compose)
  3. После запуска контейнеров, в контейнере app запускаются консольные команды
    1. Композер - composer install
    2. Миграции - php bin/console doctrine:migrations:migrate
    3. Очистка кэша - php bin/console cache:clear
    4. Загрузка фикстур - php bin/console doctrine:fixtures:load
  4. Проект доступен по адресу localhost
  5. Настройка Xdebug в PhpStorm
    1. PHP - Настроить CLI Interpreter из Docker Compose
    2. PHP - Debug - Настроить порт Xdebug равным 9003
    3. PHP - Servers - Добавить сервер с именем _, хост - localhost и Project files = /app
  6. БД доступна по адресу - localhost, данные для подключения - в .env (т.к. задание тестовое)
  7. В докере поднята только конфигурация для разработки, для прода можно запилить отдельную

Общая архитектура

  1. Запросы к АПИ заворачиваются в ДТО и валидируются до попадания в экшен контроллера
  2. Все ошибки отлавливаются и отдаются в формате JSON с соответствующим кодом HTTP
  3. Критические ошибки дополнительно логируются в файл
  4. При изменении данных автомобиля дополнительно фиксируется история изменений
  5. Марка автомобиля вынесена в отдельную таблицу для простоты генерации UI и контроля за вводимыми данными. Также можно использовать для связанных справочных данных (например: Производитель - модель)
  6. В справочниках (марка и опции) в качестве данных для выпадающих списков используется поле код - оно уникально и не зависит от ИД
  7. Для ИД используется UUID для простоты генерации сущностей и избавления от race-condition
  8. Для хранения цены используется тип DECIMAL (т.к. у нас только хранение). Для полноценной работы с деньгами и валютами я бы использовал https://www.moneyphp.org

Тестовое задание №2

Реализуем простую систему подбора автомобиля. В нашем салоне есть новые и подержанные автомобили разных марок, годов выпуска, с разной ценой, и так далее.

Задача 1. API endpoint для поиска (фильтрации) автомобилей по параметрам:

  • Марка (в интерфейсе это выбор из списка)
  • Новый или подержанный
  • Год выпуска (возможность выбрать от и до, как вместе, так и по отдельности)
  • Цена (возможность выбрать от и до, как вместе, так и по отдельности)
  • Наличие датчика дождя

GET /api/v1/vehicles?filter[brand]=123&filter[isNew]=1

Структура запроса и ответа на усмотрение автора, формат обмена данными -- json.


Образец полного запроса (исходя из данных фикстур):

GET /api/v1/vehicles?filter[brand]=bmv&filter[isNew]=1&filter[yearFrom]=2010&filter[yearTo]=2020&filter[priceFrom]=100000&filter[priceTo]=2000000&filter[option][]=rain_sensor

GET параметры помещаются в ДТО, валидируются. В случае ошибок - отдается Json ответ с описанием ошибки и соответствующим Http-кодом. Далее происходит генерация запроса, выборка из БД и генерация форматированного ответа с нужными полями в Json с использованием компонента Symfony/Serializer.


Задача 2. Консольная команда для изменения пробега

Наш автосалон иногда креативно подходит к продажам, иногда требуется уменьшить пробег автомобилей. Для этого нам нужна консольная команда, которая всем машинам с пробегом более 150000 км уменьшит его на 30%.


Консольная команда

app:vehicle:drop-mileage-percent

Есть возможность задания необязательных параметров:

  1. mileage - Пробег, больше которого надо уже уменьшать
  2. percent - Процент уменьшения

Если заданы параметры, то происходит их проверка на валидность. В случае их отсутствия используются параметры по умолчанию: пробег 150000 и 30%. Далее запрашивается общее количество автомобилей, подходящих под условие, выводится прогресс-бар и происходит изменение пробега. Автомобили берутся пачками по константе PAGE_SIZE - для предотвращения ситуации с очень большим количеством подходящих автомобилей и превышения объема памяти. Сохранение происходит также, порциями по PAGE_SIZE штук.

В принципе, эту команду можно было сделать массовым аналогом запроса из задачи 3, но руки не дошли, да и время уже много прошло.


Задача 3. API endpoint для ручного скручивания пробега

POST запрос по адресу /api/v1/vehicles/{id}/drop-mileage { "type": "miles" | "percent", "value": 15000 } Пост запрос по указанному адресу где {id} -- идентификатор машины, с пэйлоадом в json с двумя полями:

  • type -- тип значения, может быть "miles" - скручиваем мили, "percent" - скручивает проценты
  • value -- количество процентов/миль зависимости от значения type

Валидация:

  1. Проценты -- целое число больше нуля
  2. Мили -- целое число больше нуля
  3. Нельзя скрутить больше 95% текущего пробега авто ни процентами ни милями
  4. Если пробег машины стал меньше 5000, ставим флаг, что она новая

Запрос отвечает 200 -- все окей 404 -- машина по айди не найдена 400 -- ошибка валидации


В качестве ИД используется Uuid из таблицы vehicles. Обработка данных запроса происходит аналогично задаче 1. Далее, находится подходящий автомобиль и обрабатывается сервисом из задачи 2.

Примечание С точки зрения построения Restful API, я бы использовал такой ендпойнт для такого запроса (согласно https://restfulapi.net):

PUT /api/v1/vehicles/{id}/mileage с JSON пэйлоадом