Отчеты и графики — это как хлеб для бизнеса. Без них никуда: директор хочет видеть динамику продаж, менеджер — статистику по клиентам, а вы — чтобы всё это работало быстро и не разваливалось. В Yii2 для этого есть масса инструментов, но выбрать правильный путь не всегда просто. В этой статье я покажу, как строить сложные отчеты и графики, используя современные подходы и библиотеки. Вы узнаете, как не утонуть в коде и сделать так, чтобы отчеты были не только красивыми, но и полезными.

Digital-студия WNDER

С чего начать: выбираем инструменты для отчетов

Прежде чем писать код, давайте подумаем, что мы вообще строим. Отчеты бывают разные: простые таблицы, сводные данные, диаграммы, дашборды. Для каждого типа — свой инструмент. В Yii2 часто используют:

  • GridView — встроенный виджет для таблиц с сортировкой, фильтрацией и пагинацией.
  • Chart.js — легкая JavaScript-библиотека для красивых графиков.
  • Highcharts — более мощная, но платная для коммерции.
  • PhpSpreadsheet — для экспорта в Excel, когда нужно скачать отчет в файл.

Если вам нужно построить сложный отчет с группировками и итогами, GridView может быть маловат. Тогда придется писать собственные запросы с использованием Query Builder или DAO. Но не переживайте — это не так страшно, как звучит.

Как выбрать? Ориентируйтесь на задачи. Если отчет нужен для внутреннего использования — подойдут бесплатные решения. Если это клиентский продукт — возможно, стоит вложиться в Highcharts.

Строим сложный отчет с помощью Query Builder

Допустим, у нас есть интернет-магазин. Нужно построить отчет по продажам за месяц с разбивкой по категориям товаров и дням. Это классика. В Yii2 мы можем собрать данные одним запросом, используя Query Builder.

Создадим модель для отчета. Например, в контроллере:


public function actionSalesReport() {
    $from = Yii::$app->request->get('from', date('Y-m-01'));
    $to = Yii::$app->request->get('to', date('Y-m-t'));

    // Строим запрос
    $query = (new Query())
        ->select([
            'DATE(o.created_at) as day',
            'c.name as category',
            'SUM(oi.quantity * oi.price) as total',
            'COUNT(DISTINCT o.id) as orders_count'
        ])
        ->from(['o' => 'orders'])
        ->leftJoin(['oi' => 'order_items'], 'oi.order_id = o.id')
        ->leftJoin(['p' => 'products'], 'p.id = oi.product_id')
        ->leftJoin(['c' => 'categories'], 'c.id = p.category_id')
        ->where(['between', 'DATE(o.created_at)', $from, $to])
        ->groupBy(['DATE(o.created_at)', 'c.name'])
        ->orderBy('day');

    $rows = $query->all();

    // Передаем в вид
    return $this->render('sales-report', [
        'rows' => $rows,
        'from' => $from,
        'to' => $to,
    ]);
}

Здесь мы используем Query из Yii2, который позволяет строить запросы без написания сырого SQL. Это безопасно и удобно. Обратите внимание на группировку и агрегатные функции — они дают нам нужные итоги.

Полученные данные уже можно показать в виде таблицы в представлении. Но для сложности добавим итоги по каждой категории за период. Это можно сделать прямо в PHP:


// Группируем данные по категориям для итогов
$totals = [];
foreach ($rows as $row) {
    $cat = $row['category'];
    if (!isset($totals[$cat])) {
        $totals[$cat] = 0;
    }
    $totals[$cat] += $row['total'];
}

Теперь у нас есть массив с итогами, и мы можем вывести его внизу таблицы.

Добавляем графики с Chart.js

Таблица — это хорошо, но глаз хочет наглядности. Графики помогают увидеть тренды. Для этого подключим Chart.js. Он прост в использовании и отлично работает с Yii2.

Сначала подключим библиотеку через AssetBundle. Создадим свой ассет:


namespace app\assets;

use yii\web\AssetBundle;

class ChartAsset extends AssetBundle
{
    public $sourcePath = '@npm/chart.js/dist';
    public $js = [
        'chart.umd.js'
    ];
    public $depends = [
        'yii\web\YiiAsset',
    ];
}

Затем зарегистрируем его в представлении и передадим данные из контроллера в JavaScript. Например, для графика продаж по дням:


// В контроллере подготовим данные для графика
$days = [];
$totalsByDay = [];
foreach ($rows as $row) {
    $days[] = $row['day'];
    $totalsByDay[] = $row['total'];
}

В представлении:







Видите, как просто? Всего несколько строк кода — и у нас интерактивный график. Chart.js сам обрабатывает масштаб, подписи и анимации.

Экспорт отчета в Excel с PhpSpreadsheet

Часто отчеты нужно не только смотреть на экране, но и скачивать. Например, для бухгалтерии. Для экспорта в Excel отлично подходит библиотека PhpSpreadsheet. Установите её через Composer:


composer require phpoffice/phpspreadsheet

Теперь создадим действие для экспорта:


public function actionExportSales() {
    // Получаем те же данные, что и для отчета
    $rows = ...; // здесь код из actionSalesReport

    $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
    $sheet = $spreadsheet->getActiveSheet();

    // Заголовки
    $sheet->setCellValue('A1', 'День');
    $sheet->setCellValue('B1', 'Категория');
    $sheet->setCellValue('C1', 'Сумма');
    $sheet->setCellValue('D1', 'Кол-во заказов');

    // Данные
    $rowNum = 2;
    foreach ($rows as $row) {
        $sheet->setCellValue('A' . $rowNum, $row['day']);
        $sheet->setCellValue('B' . $rowNum, $row['category']);
        $sheet->setCellValue('C' . $rowNum, $row['total']);
        $sheet->setCellValue('D' . $rowNum, $row['orders_count']);
        $rowNum++;
    }

    // Отдаем файл на скачивание
    $filename = 'sales_report_' . date('Ymd') . '.xlsx';
    header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
    $writer->save('php://output');
    Yii::$app->end();
}

Важно не забыть вызвать Yii::$app->end() после отправки файла, чтобы Yii не выполнял дальнейший код.

Лайфхак: Чтобы не дублировать код получения данных для отчета и экспорта, вынесите логику в отдельный сервис или модель. Например, создайте класс SalesReportService с методом getData($from, $to). Тогда и контроллер, и экспорт будут использовать один источник данных — меньше дублирования, меньше ошибок.

Оптимизация производительности для больших данных

Когда данных много (миллионы строк), простые запросы могут тормозить. Что делать?

  • Используйте индексы в базе данных на поля, по которым фильтруете и группируете.
  • Грузите данные пачками, если нужно обработать всё, но не помещается в память.
  • Для агрегатов используйте кэширование: если данные меняются редко, можно кэшировать результат на 5-10 минут.
  • Вместо того чтобы получать все строки, делайте выборку только нужных полей.

Пример кэширования в Yii2:


$cacheKey = 'sales_report_' . $from . '_' . $to;
$rows = Yii::$app->cache->get($cacheKey);
if ($rows === false) {
    // Выполняем запрос
    $rows = $query->all();
    // Кэшируем на 300 секунд
    Yii::$app->cache->set($cacheKey, $rows, 300);
}

Это простой способ снизить нагрузку на базу, если отчет запрашивают часто.

Правила и советы по работе с отчетами

Вот несколько правил, которые я выработал за годы практики:

  • Всегда фильтруйте по датам. Отчет без периода — это не отчет, а бессмысленный список.
  • Используйте агрегатные функции на стороне БД, а не в PHP. Так быстрее и надежнее.
  • Не выводите все данные сразу. Используйте пагинацию или ограничение количества записей.
  • Проверяйте права доступа. Отчеты могут содержать конфиденциальные данные.
Правило: Никогда не используйте DataProvider для огромных выборок без необходимости. Он создан для таблиц с пагинацией, а не для агрегации. Для отчетов используйте Query Builder и получайте компактные данные.

Итоги

Построение сложных отчетов и графиков в Yii2 — задача решаемая. Главное — правильно подобрать инструменты и структурировать код. Используйте Query Builder для выборки данных, Chart.js для визуализации, а PhpSpreadsheet для экспорта. Не забывайте про кэширование и оптимизацию.

В итоге у вас будет быстрый и удобный отчет, который порадует и вас, и вашего директора. И не бойтесь экспериментировать — Yii2 дает много свободы.

Студия WNDER