Иногда маленькие вещи случаются не потому, что ты их планировал, а потому что... ну а кто ещё это сделает? Я пару месяцев подряд писал небольшие ML-прототипы на PHP (да, знаю, иногда судьба заносит в странные места), и каждый раз сталкивался с одним и тем же раздражением: массивы печатаются так, будто пытаются скрыть правду.

Я смотрю на этот разъехавшийся print_r(), и думаю: а что, если я просто хочу увидеть аккуратную табличку? А если я хочу форматирование как у Python? А если я хочу имитировать пайторчевский tensor([...])?

Спойлер: я таки написал маленький хелпер. И чем больше я его использовал, тем больше понимал - возможно, он не только про удобство, но и про какое-то внутреннее право на ясность.

Этот хелпер - не "фреймворк", не "революция", не "супер-мегапринтер", просто очень маленькая утилитка, которая делает одну вещь и делает её хорошо.
И иногда именно такие инструменты оказываются самыми честными.

Откуда растут ноги

Мне всегда нравилась идея, что инструменты должны подстраиваться под человека, а не наоборот. Но в PHP (особенно когда ты работаешь с массивами, которые пытаешься трактовать как тензоры), тебя быстро настигает весёлая реальность: вывод данных ощущается как наблюдение за городом, который проектировали три разных архитектора, ни один из которых не встречался с другими.

Вот, например, типичная сцена. Ты печатаешь невинную матрицу:

print_r($matrix);

и получаешь вот это:

Array
(
    [0] => Array
        (
            [0] => 1
            [1] => 23
            [2] => 456
        )

    [1] => Array
        (
            [0] => 12
            [1] => 3
            [2] => 45
        )

)

Да, формально - всё отлично. Но попробуй глазами сравнить столбцы. Или понять, "ровный" ли результат модели. Или вообще отличить строку от столбца, когда отступы живут своей жизнью. А если это матрица с разными типами данных, например: стринги, числа и булевы значения? Или матрица 3-х мерная с 100 столбцами?

В какой-то момент я поймал себя на том, что трачу больше времени на мысленное выравнивание, чем на саму отладку. А это уже красный флаг.

Кому это может мешать, спросите вы? Ну, мне, например и или вот ещё:

  • ML-прототипистам на PHP - когда матрицы растут, а их пытаются охватить одним взглядом.

  • Разработчикам, проверяющие логи - когда в логах цифры скачут как хотят, а мозг тратит дополнительную энергию буквально на "распознавание текста".

  • Всем, кто работает с 2D/3D структурами - print_r() и var_dump() видят массив, а не тензор. Им всё равно. А вам - нет.

И вот тут у меня впервые возник вопрос: почему простая потребность - увидеть структуру в структурированном виде - звучит как запрос на чудо?

Тогда-то у меня и родилась мысль: а почему бы не сделать свой маленький callable-pretty-printer, который ведёт себя как Python-овский pprint, но для PHP?

Так появился PrettyPrint - компактный помощник для вывода массивов в человеческом виде. Без зависимостей, без магии. Просто немного структурированной любви.

composer require apphp/pretty-print

Как это выглядит в работе

Немного примеров, потому что без примеров - это уже будет лекция, а не история.

Печать обычных значений

Тут всё тривиально

pprint('Hello', 123, 4.56, true);
// Hello 123 4.5600 True

Таблички: аккуратно и по линейке

Это мне нравится больше всего

pprint([1, 23, 456], [12, 3, 45]);
// [[ 1, 23, 456],
//  [12,  3,  45]]

Лейбл + матрица

pprint('Confusion matrix:', [[1, 23], [456, 7]]);
// Confusion matrix:
// [[  1, 23],
//  [456,  7]]

Псевдо-PyTorch стиль

pprint($matrix);
// tensor([
//   [ 1,  2,  3,  4,  5],
//   [ 6,  7,  8,  9, 10],
//   [11, 12, 13, 14, 15]
// ])

Нужен другой префикс? Пожалуйста!

pprint($matrix, label: 'arr');
// arr([
//   [ 1,  2,  3,  4,  5],
//   [ 6,  7,  8,  9, 10],
//   [11, 12, 13, 14, 15]
// ])

И, конечно же, свёртка больших массивов - голова/хвост, как у настоящих тензоров:

$matrix = [
    [ 1,  2,  3,  4,  5, true],
    [ 6,  7,  8,  9, 10, false],
    [11, 12, 13, 14, 15, true],
    [16, 17, 18, 19, 20, true],
    [21, 22, 23, 24, 25, true],
];
pprint($matrix, headRows: 2, tailRows: 1, headCols: 2, tailCols: 3);
// tensor([
//   [ 1,  2, ...,  4,  5, True],
//   [ 6,  7, ...,  9, 10, False],
//   ...,
//   [21, 22, ..., 24, 25, True]
// ])

А если вы совсем смелый — вот трёхмерный тензор с усечением блоков:

tensor3d = [
    [[1, 2, 3],[4, 5, 6]],
    [[7, 8, 9],[10, 11, 12]],
    [[13, 14, 15],[16, 17, 18]],
];
pprint($tensor3d, headB: 1, tailB: 1, headRows: 1, tailRows: 1, headCols: 1, tailCols: 1);
// tensor([
//  [[1, ..., 3],
//   [4, ..., 6]],
//
//  ...,
//
//  [[13, ..., 15],
//   [16, ..., 18]]
// ])

Ну и всякие приятные мелочи:

  • start — префикс

  • end — чем закончить

  • sep — чем разделять аргументы

  • ppd() — напечатать и умереть

Можно распечатать любые типы данных, в том числе на странице в браузере (не только к терминале). Но... настройки ограничены, чтобы не утонуть в возможностях.

А теперь немного мыслей вслух

��сли честно, я не делал это ради GitHub-звёздочек. Это скорее попытка чуть-чуть навести порядок в маленьком кусочке вселенной. В индустрии такие вещи часто проходят мимо радаров - "слишком маленькое" или "слишком несущественное". А потом мы удивляемся, почему разработчики часами смотрят на неудобный вывод данных и ошибаются в самых простых местах.

Я однажды видел в продакшен-ML-пайплайне (тоже PHP, да) отличный пример аккуратности: команда сделала свой собственный минималистичный логгер для матриц, потому что они решили, что "небрежность - это источник багов". И что бы вы думали? Он реально поднимал качество дебага. Вот такой подход - редкость. И, по-моему, стоило бы обращать на такие мелочи внимание гораздо чаще.

Почему вообще стоит задуматься об этом?

Наверное, не ради самой библиотеки. Она маленькая, без претензий. Её можно написать за выходные. Но иногда инструмент, который внешне незначителен, меняет то, как ты смотришь на процесс разработки.

Меня эта штука научила одному простому, почти бытовому наблюдению: если тебе что-то мешает - почини это. Даже если кажется, что это "слишком мелко".

Потому что накапливаются мелочи, а не катастрофы.
Потому что ясность стоит дороже усилий.
Потому что маленькие инструменты иногда делают больше, чем большие платформы.

Ну и да - приятнее смотреть на tensor([...]), чем на распухший var_dump.

Тесты, традиции и другие земные радости

Если вдруг кто-то захочет ковыряться:

composer install
composer test
composer test:coverage

Главное - включить Xdebug, иначе PHPUnit посмотрит на вас так, будто вы забыли что-то важное.

Без вывода

Не знаю, станет ли PrettyPrint кому-то полезным. Может быть, это одна из тех мелких утилит, которые живут тихо в чьём-то проекте и никогда не попадают в топ-репозитории.
И это нормально.

Но если после прочтения вы вдруг поймаете себя на мысли: "А какая маленькая штука в моём проекте меня бесит, и почему я её до сих пор не пофиксил?" - значит, эта статья сработала. Удачи вам в ваших проектах!

Комментарии (5)


  1. ponikrf
    04.12.2025 17:27

    А что в php до сих пор нет аналога console.table? Грустно немного конечно. Мне лично не совсем понятно зачем нужно печатать скобки когда можно распечатать просто таблицу.


    1. samako Автор
      04.12.2025 17:27

      Что-то подобное есть, вот например: https://github.com/deniskoronets/php-array-table или https://github.com/phplucidframe/console-table
      Но это не то, что нужно.

      Для дебага такие вещи не очень подходят и к тому же они не работают с 3-х мерными массивами.


      1. ponikrf
        04.12.2025 17:27

        Согласен, это просто ужас какой то.

        В JS это делается так:

        const matrix = [
          [1, 2, 3, 4],
          [4, 3, 2, 1]
        ];
        
        console.table(matrix);



        1. samako Автор
          04.12.2025 17:27

          увы, но в JS тоже


  1. edogs
    04.12.2025 17:27

    Symfony Console ?