REST API маркетплейса VOVO. Тестовое задание — поиск товаров с фильтрацией, сортировкой и пагинацией.
- PHP 8.4 + Laravel 11
- MariaDB 11.4
- Redis 7.4 — кеш, сессии, очереди, rate limiting
- Nginx + PHP-FPM (Alpine)
spatie/laravel-query-builder— фильтры и сортировкаdarkaonline/l5-swagger— документация APIlarastan/larastanlevel 6 — статический анализlaravel/pint— форматирование кода
# 1. Клонировать репозиторий
git clone <repo-url> && cd vovo-api
# 2. Настроить окружение
cp .env.example .env
# 3. Поднять контейнеры и накатить миграции
make up
make migrate-fresh| Сервис | URL |
|---|---|
| API | http://localhost:8080/api/v1 |
| Swagger | http://localhost:8080/api/documentation |
| Health | http://localhost:8080/api/health |
| Adminer | http://localhost:8888 |
Adminer: сервер
db, пользователь и пароль из.env
make help # список всех команд
make up # запустить контейнеры
make down # остановить контейнеры
make build # пересобрать образы
make logs # логи всех контейнеров
make shell # bash внутри PHP-контейнера
make migrate # применить миграции
make migrate-fresh # пересоздать БД + залить сиды
make seed # залить сиды
make test # запустить тесты
make pint # проверить code style
make stan # статический анализ
make ci # pint + stan + test (как в GitHub Actions)
make swagger # сгенерировать Swagger-документациюПроверка состояния сервиса (БД + Redis).
{
"message": "ok",
"data": { "database": "ok", "cache": "ok" }
}Поиск товаров с фильтрацией, сортировкой и пагинацией.
Rate limit: 60 запросов в минуту.
Заголовок X-Cache: HIT если результат из кеша, MISS при первом запросе. Кеш инвалидируется автоматически при изменении любого товара.
| Параметр | Тип | Описание |
|---|---|---|
q |
string | Fulltext-поиск по названию |
price_from |
numeric | Цена от |
price_to |
numeric | Цена до |
category_id |
integer | ID категории |
in_stock |
boolean | В наличии (true / false) |
rating_from |
numeric | Рейтинг от (0–5) |
Фильтры передаются как
filter[q]=телефон,filter[price_from]=1000
price_asc · price_desc · rating_desc · newest
| Параметр | По умолчанию | Максимум |
|---|---|---|
page |
1 | — |
per_page |
15 | 500 |
GET /api/v1/products?filter[q]=телефон&filter[price_from]=1000&filter[in_stock]=true&sort=price_asc&page=1&per_page=10
{
"message": "Products retrieved successfully",
"data": {
"pagination": {
"page": 1,
"per_page": 10,
"total": 3,
"last_page": 1
},
"items": [
{
"id": 12,
"name": "Телефон Xiaomi Redmi 13",
"price": "12990.00",
"category_id": 2,
"category_name": "Электроника",
"in_stock": true,
"rating": 4.3,
"created_at": "2026-04-21T10:00:00.000000Z",
"updated_at": "2026-04-21T10:00:00.000000Z"
}
]
}
}| Решение | Почему |
|---|---|
| Fulltext вместо LIKE | Использует индекс на MariaDB, быстрее на больших таблицах |
Cache::tags(['products']) |
Групповая инвалидация — один flush() сбрасывает все закешированные страницы |
ProductObserver |
Кеш сбрасывается автоматически при любом изменении модели |
X-Cache заголовок |
Прозрачность для клиента и дебаггинга |
DB::listen() |
Медленные запросы (>500ms) логируются в storage/logs/laravel.log |
| Redis для rate limiting | Атомарные счётчики без race condition, общий для всех воркеров |
composer install
cp .env.example .env
# Настроить DB_* и REDIS_* в .env
php artisan key:generate
php artisan migrate --seed
php artisan servevendor/bin/phpunit # тесты (SQLite in-memory)
vendor/bin/pint --test # code style
vendor/bin/phpstan analyse # статический анализ