Вот... Извиняюсь, что длинно, но в сети этого нет...
О формировании представлений в РНР.
Прежде всего, о начальных условиях.
Во-первых, имеется некий источник данных. Это может быть хэш или индексный массив, числовая последовательность, объект, инкапсулирующий реляционный запрос. Ключевой особенностью источника данных является то, что он представляет из себя матрицу, то есть, некий двухмерный объект. При этом, матрица может быть вырожденной: горизонтальным вектором, вертикальным вектором или даже таким странным образованием, как вертикальный 0-мерный вектор (см. ниже).
Во-вторых, имеется некий, если так можно выразиться, класс-парсер нижнего уровня. Экземпляр данного класса представляет из себя кусок HTML-кода и некоторое количество методов, позволяющих упомянутый код модифицировать. При этом, важнее всего наличие у класса-парсера нижнего уровня таких возможностей как: вставка текста по метке вида {SomeVariable}; изъятие блока, то есть, некоего куска кода, ограниченного, к примеру, тэгами комментария.
Насколько известно автору, существуют готовые парсеры (Smart). Однако, вообще-то говоря, не составляет ни малейшего труда написать подобный класс самостоятельно (что автор собственно и сделал).
Формулировка задачи. Требуется представить источник данных в HTML-формате.
Общий алгоритм формирования подобного представления состоит в следующем.
Из HTML-кода, внутри которого нужно сформировать требуемое представление, извлекается блок-шаблон сериализируемого HTML-элемента (строка или ячейка HTML -таблицы, опция списка и так далее...)
Затем, в цикле с шаблона сериализируемого элемента делается рабочая копия, и в рабочую копию вставляются все элементы соответствующей строки источника данных. Если источник данных представляет из себя вышеупомянутый вертикальный 0-мерный вектор, то в рабочую копию не вставляется вообще ничего, то есть, мы имеем категорию классов-представлений, которую можно условно назвать Repeater-ами. Иначе говоря, мы просто размножаем некий HTML-элемент заданное количество раз, без модификации.
Далее, рабочая копия сериализируемого элемента вставляется обратно в основной HTML-код. Цикл повторяется столько раз, сколько строк в источнике данных. Если в источнике данных только одна строка, то мы получаем класс-представление под условным названием Blank. В этом случае, роль сериализируемого элемента играет весь имеющийся HTML-код.
Совершенно очевидно, что формирование HTML-представления вышеописанным образом не зависит от конкретной структуры источника данных, по-скольку организовать циклы по его строками и столбцам без явного обращения к тем и другим - не является чем-то особо сложным.
Используя, например, в качестве источника данных объект-оболочку реляционного запроса (самый интересный случай), можно воспользоваться функциями РНР, типа mysql_list_fields, для получения списка всех полей данного запроса.
Таким образом, вид класса-представления зависит: во-первых, от типа источника данных; во-вторых, от формата представления (бланк, таблица, список...); но не зависит от конфигурации и содержимого данного конкретного экземпляра источника данных (количества строк и столбцов в матрице, значений ее элементов). То есть, если мы хотим представить, к примеру, реляционный запрос в виде таблицы, то неважно, что это будет за запрос, сколько в нем будет полей и как они будут называться.
Но есть одно «но». Что если часть элементов матрицы-источника является некой достаточно сложной функцией от других ее элементов? В принципе, подобную проблему можно решить на уровне источника данных, сформировав последний нужным образом. Однако, это тема для отдельного разговора. На уровне же класса-представления, автор решает данную проблему следующим способом...
Имеется базовый или стандартный, если угодно, класс-представление, формирующий представление источника данных определенного типа в определенном формате. При этом, к вышеописанному алгоритму формирования представления добавляется одна важная деталь. После заполнения очередной рабочей копии сериализируемого элемента всеми элементами текущей строки источника данных, вызывается некий метод (ParseExtras), которому, в частности передаются упомянутые рабочая копия и текущая строка. В базовом классе данный метод пуст, то есть, не делает вообще ничего.
В случае необходимости сгенерировать представление для источника данных со сложновычисляемыми значениями элементов, создается класс-представление, наследующий базовому. Во вновь созданном пользовательском классе, вышеупомянутый метод ParseExtras переопределяется таким образом, чтобы в каждую получаемую им рабочую копию вставлялись элементы, являющиеся сложными функциями от других элементов текущей строки.
В результате, мы получаем возможность очень легко и быстро создавать классы-представления для источников данных любого формата.
Теперь к вопросу о том, какими собственно должны быть базовые классы-представления. Казалось бы, совершенно очевидно: есть некий источник данных, скажем, реляционный запрос, есть некий тип представления, например, таблица, вот и имеем базовый класс.
В общем-то, ранее, автор именно так и поступал. Однако, на практике, оказалось, что вариативных возможностей у подобной концепции явно недостаточно. Дело в том, что в пользовательских задачах, зачастую, имеется необходимость в формировании представлений, являющихся, в свою очередь, вложенными друг в друга представлениями различных источников данных.
Например, таблица заказов, выглядящая следующим образом: строка с датой, затем, строки всех заказов, приходящихся на эту дату и так далее... Причем, вышеописанное представление - далеко не самое сложное.
Создавать базовые классы для каждого подобного типа представления автор счел бесперспективным. В качестве альтернативы, автором была разработана описанная ниже модель Parsers-Serialiser, каковая модель обладает очень высокой вариативностью и позволяет сформировать представление практически любой степени сложности.
Модель создания представлений Parsers-Serialiser, собственно, и опирается на две разновидности классов: Parser-ы и Serialiser.
Главными особенностями классов Parser-ов является то, что они...
Во-первых, имеют дело только с единичными экземплярами рабочей копии сериализируемого элемента, в то время, как классы-представления старого образца могли оперировать не только одной рабочей копией, но и целым их множеством.
Во-вторых, что очень важно, классы Parser-ы возвращают не только рабочую копию сериализируемого элемента, но и динамическое имя метки, в которую эту копию нужно вставить. Так, если в старых классах имя метки для вставки задавалось раз и навсегда (например, {ROW-Customer}), то в Parser-ах данное имя является, в общем случае, функцией от значений элементов текущей строки источника данных.
Функция класса Serialiser состоит в том, чтобы взять несколько классов-парсеров (неважно какого типа) и на их основании сформировать общее представление.
Например, в вышеупомянутом случае с таблицей заказов, мы имеем два Parser-а. В первом, источник данных - реляционный запрос, содержащий выборку заказами, а метка вставки меняется динамически, в зависимости от значения поля «ДатаЗаказа». Во-втором, источник данных - последовательность дат требуемого диапазона, а метка вставки - константа.
Передав оба Parser-а Serialiser-у, мы получим требуемую таблицу.
Проблема нестандартных элементов в источнике данных решается в данной модели так же, как и в предыдущей, то есть вызовом метода ParseExtras и передаче ему рабочей копии сериализируемого элемента и текущей строки источника данных.