Как я писал кодогенератор на PHP и что из этого получилось

Причины и проблемы, которые нужно было решить


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


В этом семестре на одном из предметов можно было использовать только PHP.


После бесконеного ренейма проекта Проект получил имя MelonPHP. Чтобы люди думали о еде когда произносили его имя? Но у нас тут статья не о генерации бреда, поэтому давайте я вам расскажу о причине его создания.


Написать надо было много, но это не проблема. Основная проблема заключалась в выводе HTML кода через PHP. Я постараюсь объяснить проблему ниже.


Например вот вывод текста через всем знакомое echo:


$text = "out text";
echo "<p>$text</p>";

Выглядит просто и понятно. Давайте возьмем отрезок кода моего друга, где он генерирует таблицу:


...
$sql = "SELECT * FROM table";$result = $conn->query($sql);
if($result->num_rows > 0) {
    echo "<b>Table table</b><br><br>";
    echo "<table border=2>";
  echo "<tr><td> name </td>"."<td> name </td>"."<td> name </td></tr>";
    while($row = $result->fetch_assoc()) {
        echo "<tr><td>".$row["name"]."</td><td>".$row["name"]."</td><td>".$row["name"]."</td></tr>";
    }
    echo "</table>";
} else {
echo "0 results";}
...

Это страшный код демонстрирует проблемы, которые я хотел решить:


  • Присутствие html в php коде, что делает его по моему мнению менее читаемым. Все-таки файл для одного яп должен содержать код только одного яп(а), по моему мнению
  • Нет разделения логики, все в каше. Хотелось более приятный "фронтенд" на PHP

Стоит отметить, что я относительно давно пишу на Flutter и мне очень нравится идея заложенная в его основе, связанная с написанием интерфейса с помощью постройки дерева из виджетов. Я решил позаимствовать оттуда идею с нодами (виджетами).


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


Изначально генератор занимался генерацией таблиц через функции. Но потом перерос в нечто более масштабное.


Основными идеями были следующими:


  • UI пишется из элементов/компонентов (привет React)
  • Удобные макеты (Избавиться от div, div, div, div...)
  • Чтобы весь UI писался на PHP (без JS, без HTML, без CSS).
  • Rebuild через callback события, через AJAX + JQuery не суждено
  • Удобная система роутов не суждено
  • Поддержка CSS (и не просто строку писать, на уровне "width: 100px", а полноценная поддержка прямо в PHP коде)
  • ООП

Особенности MelonPHP


  • Почти все элементы (кроме текста, кнопки и еще нескольких) по умолчанию имеют ширину и высоту в 100%, в том числе и документ.
  • Кроме того, если элементы будут выходить за пределы страницы, то скролла по умолчанию не будет. Для этого нужно использовать ScrollView.
  • Так же по умолчанию нельзя выделать никакие элементы.

Архитектура


Очень печально, что в PHP отсутствует передачу параметров по имени, и по этому я решил использовать вместо них функции. Я уверен что это не менее удобный аналог (Какого было мое удивление, когда Microsoft показали MAUI, который использует ту же идею с функциями).


Все классы в MelonPHP наследуется от Node. Это простой класс, который имеет только 2 функции: Generate(), static Create().


  • Generate() возвращает string — сгенерированный код.
  • Create() — это статическая функция. Она нужна чтобы было проще создавать ноды в дереве.

abstract class Node
{
  abstract function Generate() : string;

  static function Create() ...
}

Element

Element — это более высокоуровневый класс, который нужен для более комфортного написания своих элементов.


Элемент в основном занимается генерацией чистого html кода.


Элементами в Фреймворк являются такие сущности как контейнер, кнопка, таблица и тд.


Component

Основная идея компонента в том, что этот класс управляет, и состоит из дерева элементов в нем. Компонент наследуется от элемента (бредовая идея).


Компонентами могут быть например дисплеи (аналог страниц в MelonPHP), списки, карточки, навигационные меню и тд.


abstract class Component extends Element
{
  function Initialize() ...

  abstract function Build() : Element;

  function Detach() ...
}

Компонент удобен тем, что вам не надо генерировать строки; чем занимается Element. Так же в нем есть удобные функции для использования логики.


Попробуем написать простой компонент. Создадим класс ListItem, наследуемый от Component.


Перезапишем функции Initialize() и Build().


Initialize() вызывается при создание компонента. В ней например можно инициализировать переменные или обработать логику.

Build() вызывается при генерации элемента. В ней обязательно должен возвращаться элемент. Обязательная для перезаписи.

Detach() вызывается при удалении компонента.

В Build() возвратим контейнер, а в качестве его ребенка элемент текста и присвоим ему текст из переменной класса $Text.


В Initialize() пропишем значение $Text по умолчанию.


Добавим функцию Text(string) в которой будет записываться значение пользователя в переменную $Text.


Обязательно надо возвращать $this в функциях, которые будут вызываться в дереве.

class ListItem extends Component
{
  private $Text;

  function Initialize() {
    $this->Text = "Name";
  }

  function Build() : Element {
    return Container::Create()
    ->Child(
      Text::Create()
      ->Text($this->Text)
    );
  }

  function Text(string $string) {
    $this->Text = $string;
    return $this;
  }
}

DisplayComponent

DisplayComponent — это компонент, который может выводит сгенерированный код на страницу. Для генерации нужно вызвать функцию Display.


Попробуем написать пример простого дисплея.


В функции Build() возвратим Document и присваиваем ему Title(string).


В DisplayComponent, в функции Build() всегда должен возвращаться Document. Document — это класс, который генерирует стандартную разметку HTML5.

Создадим функцию BuildList(), в которой через цикл заполним колонку созданными выше ListItem.


В качестве ребенка документа вызовем BuildList() функцию. Разделение дерева из нод на функции не дает ему превратиться в макаронного монстра.


Если будет ситуация что надо выполнить какую-то логику прямо в дереве, то для того есть класс Builder. Но так лучше не делать...

После тела класса вызовем функцию Diplay(), которая при переходе на данный файл, на сайте cгенерирует его и выведит.


class ListDisplay extends DisplayComponent
{

  function Build() : Document {
    return Document::Create()
    // название страницы
    ->Title("test page")
    ->Child($this->BuildList());
  }

  function BuildList() {
    $column = new Column;
    for($i = 0; $i < 10; $i++)
      $column->Children(
        ListItem::Create()
        ->Text("number: $i")
      );
    return $column;
  }

} ListDisplay::Display();

Макеты


Одна из целей преследуемых при создании фреймворка — удобство при создание макетов сайта.


Container


Container — это макет, который используется для декоративных целей.


Может содержать только одного ребенка.


Column и Row

У большинства элементов есть дети. Если у элемента доступен метод Child то он может иметь только одного ребенка, а если Children то у него может быть больше одного ребенка.


Так же Child перезаписывает переменную ребенка в то время как Children добавляет аргументы в верх стека.


Если в Children один элемент в аргументе, то не обязательно его заносить в массив.

Тоесть вместо Children([Text::Create()]) можно написать Children(Text::Create())

Column — это макет, который выравнивает его детей вертикально.


Обращаясь к функциям CrossAlign и MainAlign можно выравнивать детей внутри колонки.



Row идентичен Column, но выравнивает детей горизонтально.



Stack

Stack — это макет, дети которого не позиционированы. Это полезно если вы хотите чтобы ноды пересекались, для создания более красивого дизайна.



ScrollView, HorizontalScrollView, VerticalScrollView


Эти контейнеры который является областью для скороллинга.


HorizontalScrollView — в этом контейнере можно скроллить только по горизонтальной оси.


VerticalScrollView — в этом контейнере можно скроллить только по вертикальной оси.


ScrollView — в этом контейнере можно скроллить по всем осям.


Стилизация


Долга думая как лучше встроить css в фреймворк я пришел к идее с константами.


Например у нас в css есть "background-color". Я строку записываю в константу и в php коде можно будет использовать без "". Это намного удобнее.


...
const BackgroundBlendMode = "background-blend-mode";
const BackgroundAttachment = "background-attachment";
const Border = "border";
const BorderSpacing = "border-spacing";
const BorderRadius = "border-radius";
const BorderImage = "border-image";
...

Что касается например такой конструкции "34".Px. Тут идея с константами выглядит не читабельно. По этому я решил в таких ситуациях использовать функции для css — например Px(34). Выглядит понятно и вписывается в пхп код.


Простая стилизация

Для простой стилизации в элементе функция ThemeParameter(...). Первый аргумент — это название параметра, а второй аргумент — это или массив из значений/значение.


Рассмотрим пример.


В первом параметре мы изменим цвет фона на #f0f0f0.


Во втором параметре мы добавим отступы. Сверху и снизу 20px, справа и слева 15px.


Значения в массиве генерируются через пробел. Если вы хотите чтобы генерация происходила через запятую, то для этого есть функция CommaLine().

...
Container::Create()
->ThemeParameter(BackgroundColor, Hex("f0f0f0"))
->ThemeParameter(Padding, [Px(20), Px(15)]);
...

Как видно все очень просто и удобно, но если нам понадобятся модификаторы (hover например)? Для этого сделаны темы.


Темы

Темы в этом Фреймворк — это более продвинутый css, с media, keyframes, и модификаторами.


Напишем тему для контейнера с модификатором hover и active.


Для того надо создать класс темы и добавить в него ThemeBlock через метод ThemeBlocks.


Блоку темы нужно присвоить ключ / ключи. Я назову ключ my_container.


Дальше в блок темы можно добавить модификаторы. Я добавил: StandartModifier, HoverModifier, ActiveModifier. И задал для них параметры через метод Parameter(...). Parameter работает так же как ThemeParameter.


function GetMyTheme() : Theme {
  return Theme::Create()
  ->ThemeBlocks([
    ThemeBlock::Create()
    ->Keys("my_container")
    ->Modifiers([
      StandartModifier::Create()
      ->Parameter(BackgroundColor, Red)
      ->Parameter(Padding, [Px(10), Px(12)]),
      HoverModifier::Create()
      ->Parameter(BackgroundColor, Green),
      ActiveModifier::Create()
      ->Parameter(BackgroundColor, Blue)
    ])
  ]);
}

Дальше контейнеру я присвоил ключ (имя класса в css) темы через метод ThemeKeys. Но для того чтобы тему можно было использовать ее надо добавить в документ через метод Themes.


class TestThemeDisplay extends DisplayComponent
{

  function Build() : Document {
    return Document::Create()
    ->Themes(GetMyTheme())
    ->Child(
      Container::Create()
      ->ThemeKeys("my_container")
    );
  }

} TestThemeDisplay::Display();

После можно запустить дисплей и увидеть что тема по ключу применилась.


Продвинутые анимации

Для продвинутой анимации есть keyframes.


Для того чтобы добавить в тему keyframe, используйте метод FrameBlocks.


Добавим уже в существующую тему FrameBlock.


В FrameBlock есть метод Frames. Вызовем его и добавим несколько фреймов, так же для каждого фрейма надо указывать Value. Оно может быть в процентах (используйте функцию Pr(value)) или может быть константой From или To.


Каждому кадру можно добавлять параметры, которые будут уникальны и плавно изменятся между другими кадрами.


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


Создадим текст в предыдущем дисплее, в контейнере и присвоим ему ключ темы.


Так же на примере видно, что в параметры можно использовать и обычные строки.


function GetMyTheme() : Theme {
  return Theme::Create()
  ->ThemeBlocks([
    ThemeBlock::Create()
    ->Keys("my_container")
    ->Modifiers([
      StandartModifier::Create()
      ->Parameter(Padding, [Px(10), Px(12)]),
      HoverModifier::Create()
      ->Parameter(BackgroundColor, Green),
      ActiveModifier::Create()
      ->Parameter(BackgroundColor, Blue)
    ]),
    ThemeBlock::Create()
    ->Keys("shake_text")
    ->Modifiers([
      StandartModifier::Create()
      ->Parameter(Color, Red)
      ->Parameter(Animation, ["shake_text_anim", ".2s", "ease-in-out", "5", "alternate-reverse"])
    ])
  ])
  ->FrameBlocks(
    FrameBlock::Create()
    ->Key("shake_text_anim")
    ->Frames([
      Frame::Create()
      ->Value(Pr(0))
      ->Parameter(Transform, Translate(0, 0)),
      Frame::Create()
      ->Value(Pr(25))
      ->Parameter(Color, Hex("ff4040"))
      ->Parameter(Filter, Blur(Px(0.5))),
      Frame::Create()
      ->Value(Pr(50))
      ->Parameter(Filter, Blur(Px(1.2))),
      Frame::Create()
      ->Value(Pr(75))
      ->Parameter(Color, Hex("ff4040"))
      ->Parameter(Filter, Blur(Px(0.5))),
      Frame::Create()
      ->Value(Pr(100))
      ->Parameter(Transform, Translate(Px(10), 0)),
    ])
  );
}

class TestThemeDisplay extends DisplayComponent
{

  function Build() : Document {
    return Document::Create()
    ->Themes(GetMyTheme())
    ->Child(
      Container::Create()
      ->ThemeKeys("my_container")
      ->Child(
        Text::Create()
        ->ThemeKeys("shake_text")
        ->Text("Error text")
      )
    );
  }

} TestThemeDisplay::Display();


Адаптивность

Создадим еще 2 темы. Одна будет для мобильных девайсов, а вторая для пк. У темы есть функции: MinWidth, MaxWidth, MinHeight, MaxHeight, объявив которые вы можете указать на каком размере будет работать тема.


Теме для телефонов зададим MinWidth 800px.


Теме для пк зададим MaxWidth 800px.


Создадим блок темы где в стандартном модификаторе для мобильной версии будет присваиваться цвет фона зелёный, а на пк версии — желтый. Назовём блок adaptive_color.


Добавим обе темы в документ дисплея.


Добавим ключи темы к контейнеру.


function GetMobileTheme() : Theme {
  return Theme::Create()
  ->MinWidth(Px(800))
  ->ThemeBlocks(
    ThemeBlock::Create()
    ->Keys("adaptive_color")
    ->Modifiers(
      StandartModifier::Create()
      ->Parameter(BackgroundColor, Green)
    )
  );
}

function GetDesktopTheme() : Theme {
  return Theme::Create()
  ->MaxWidth(Px(800))
  ->ThemeBlocks(
    ThemeBlock::Create()
    ->Keys("adaptive_color")
    ->Modifiers(
      StandartModifier::Create()
      ->Parameter(BackgroundColor, Red)
    )
  );
}

class TestThemeDisplay extends DisplayComponent
{

  function Build() : Document {
    return Document::Create()
    ->Themes([
      GetMyTheme(), 
      GetDesktopTheme(), 
      GetMobileTheme()
    ])
    ->Child(
      Container::Create()
      ->ThemeKeys(["my_container", "adaptive_color"])
      ->Child(
        Text::Create()
        ->ThemeKeys("shake_text")
        ->Text("Error text")
      )
    );
  }

} TestThemeDisplay::Display();


Логика


Попробуем написать простой кликкер.


Для начала нам надо создать класс и наследовать его от DisplayComponent.


Создадим функцию Build() и возвратим в ней Document.


class ClickerDisplay extends DisplayComponent
{

  function Build() : Element {
    return Document::Create()
    ->Title("Clicker"); 
  }

} ClickerDisplay::Display();

Добавим колонку в качестве ребенка документа.


Так же в качестве детей колонки добавим текст и кнопку.


class ClickerDisplay extends DisplayComponent
{

  function Build() : Element {
    return Document::Create()
    ->Title("Clicker")
    ->Child(
      Column::Create()
      ->Children([
        Text::Create()
        ->Text("Pressed 0 times"),
        Button::Create()
        ->Text("Press")
      ])
    ); 
  }

} ClickerDisplay::Display();

Результат будет следующим.



Далее добавим простые ThemeParameter, чтобы сделать наш пример красивее.


class ClickerDisplay extends DisplayComponent
{

  function Build() : Element {
    return Document::Create()
    ->Title("Clicker")
    ->Child(
      Column::Create()
      ->ThemeParameter(Padding, Px(15))
      ->Children([
        Text::Create()
        ->ThemeParameter(PaddingBottom, Px(15))
        ->Text("Pressed 0 times"),
        Button::Create()
        ->ThemeParameter(Width, Auto)
        ->ThemeParameter(Padding, [Px(4), Px(10)])
        ->ThemeParameter(BackgroundColor, Blue)
        ->ThemeParameter(Color, White)
        ->ThemeParameter(BorderRadius, Px(4))
        ->Text("Press")
      ])
    ); 
  }

} ClickerDisplay::Display();

Выглядит куда лучше в несколько простых строчек.



Теперь можно добавить логику.


Для начала нужно инициализировать функцию Initialize() и создать приватную переменную TapCount.


Аналог form в фреймворке — это Action.

Добавим Action в наше дерево элементов. Action тип пусть будет Post. В качестве детей укажем нашу колонку, где находится наша кнопка.


Далее добавим click_count переменную в Action. А в качестве её значение присвоим TapCount.


В Initialize() через Action::GetValue(name, standart_value, action_type) получим наше переменную. В качестве значения по умолчанию укажем 0, а в качестве типа укажем Post.


Добавим инкремент для нашей переменной.


В тексте выведим "Press $this->TapCount times".


Все, простой кликкер готов.


class ClickerDisplay extends DisplayComponent
{
  private $TapCount;

  function Initialize() {
    $this->TapCount = Action::GetValue("click_count", 0 /* standart value */, ActionTypes::Post);
    $this->TapCount++;
  }

  function Build() : Document {
    return Document::Create()
    ->Title("Test page")
    ->Child(
      Action::Create()
      ->Type(ActionTypes::Post)
      ->Variable("click_count", $this->TapCount)
      ->Child(
        Column::Create()
        ->ThemeParameter(Padding, Px(15))
        ->Children([
          Text::Create()
          ->ThemeParameter(PaddingBottom, Px(15))
          ->Text("Press $this->TapCount times"),
          Button::Create()
          ->ThemeParameter(Width, Auto)
          ->ThemeParameter(Padding, [Px(4), Px(10)])
          ->ThemeParameter(BackgroundColor, Blue)
          ->ThemeParameter(Color, White)
          ->ThemeParameter(BorderRadius, Px(4))
          ->Text("Press")
        ])
      )
    );
  }
} ClickerDisplay::Display();


Итог


Мне удалось написать простой, но достаточно мощный кодогенератор.


Он прошел путь от генерации простых таблиц до полноценного генератора html и css, на котором можно удобно верстать проекты и совмещять верстку с логикой.


На данном фреймворке я написал курсовой проект (скриншоты ниже), использовал его на экзамене и делал на нём учебные задания.


Скриншоты курсового проекта сделанного на MelonPHP




Источники


GitHub — MelonPHP


Flutter


MAUI

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 44

    +8
    Вы мешаете всё в кашу, задумав универсальный инструмент, в то время как 90% «проблем», описываемых вами, и самая главная — разделение кода и представления, решились бы использованием грамотного шаблонизатора.
    Я только за написание велосипедов, но в данном случае это избыточно для статьи на хабре, хотя и полезно лично для вас, как учебный проект.
      0

      MelonPHP — не шаблонизатор. Он создавался для комфортного написания UI на PHP и вдохновлялся флаттером. Он похож по стилю написания ui на такие фреймворки как: SwiftUI, Jetpack Compose, MAUI. Я не думаю что он является велосипедом, так как на момент его написания я подобных фреймворков на яп пхп не видел.

        –2
        на момент его написания я подобных фреймворков на яп пхп не видел

        Быть может, это потому, что написать UI на серверном языке невозможно? Для UI в вебе, как ни крути, не обойтись без HTML/CSS/Js. А чем вы эту статику будете генерировать — руками в блокноте или в php-коде — ни на что не влияет.
        Да, еще можете, к примеру, глянуть Yii — там есть компоненты, отдающие готовый html/js.
        Либо, к примеру, InertiaJs — чем не генератор HTML-кода?
        –1
        Статья — привет из двухтысячных.
        –3

        Познакомься с функцией sprintf
        Генерировать код на стороне сервера… Лет 15 назад норм было.
        Даже, если допустить, что такой подход к генерации html и нужен (я не представляю для чего) то никто не будет вот это все использовать. Все эти классы и прочее.

          0

          Большое спасибо за sprintf! Я изначально планировал предгенерироать html, но отказался, так как хотел делать rebuild дерева с помощью ajax. Сейчас насколько я вижу модно юзать свои движки для рендеринга (например skia), но у меня бы не хватило на это времени (изначально была цель доделать его до конца семестра, что я и смог сделать). Интересно было узнать что 15 лет назад было модно юзать кодогенерацию html. Так же этот проект скорее — концепт/идея и я не вижу смысла везти его в продакшн.

            0

            Даже была одна либа с названием template. Вообще древняя. Разделение кода и html. Ибо в то время js фреймворки только наращивал экспансию. Возьми любую cms и там будет то о чем ты пишешь.
            Но проблема этих кодогенераторов ы том, что в результате благие начинания разбиваются о безалаберность писателей. Проще вклинить кусок html в код… Малкнький же кусочек.
            Сначала тут, потом там сям… Через год получаем портянку говнокода.
            Человеческий фактор.

              0

              Я согласен с вами. Я тут ещё подумал. Веб усложняется. Сначала если посмотреть глобально html, потом css, дальше js в вебе. Я думаю что в будущем всеравно появится централизация в вебе, так как чтобы написать простой проект, тебе (нужно быть жонглером?) нужно юзать слишком много не оправданно лишнего.

          0

          Я, возможно, что-то просмотрел, но нигде не нашёл HTML-экранирования никакого текста.

            0

            Я немного не понял вопроса, но если вы про генерацию чистых html тегов, то этим заниматься класс Tag. Могу скинуть ссылку на него.

              0

              Нет, я про то, что строка Привет, <username> должна отдаваться как Привет, &lt;username&gt;.

                0

                Нет, этого нет. Я хотел добавить это когда сделал бы поддержку markdown в тексте.

            0
            Обозначенная проблема заменена той же проблемой, только более многословной. Уже 15 лет назад для таких задач активно использовались шаблонизаторы (привет Smarty) и MVC.
              0

              Я знал на момент написания фреймворка о шаблонизаторах, но я решил сделать эксперемент. Я хотел попробовать создать что-то наподобие flutter, где в данной ситуации вы могли бы написать сайт/программу используя только один язык — php. Кроме того я пытался сделать это удобным. Кодогенкрация в html /css была выбрана для быстроты написания, чтобы проверить как будет смотреться и писаться код по тем идеям которые я позаимстовал у флаттера и добавил сам. Это скорее концепт написания UI на пхп и я сделал ошибку в статье сосредоточив внимание на кодогенрации.

                0
                В том-то все и дело, что, в отличие от Flutter, в вебе есть две части: серверная и клиентская. Вторая, так или иначе, потребует иного, отличного от серверной части языка.
              +1

              Вместо чистого и лаконичного css / html получилось нагромождение php примерно втрое большего объёма.


              Более того, посмотрите на Jade — это псевдо-код, очень удобный для генерации, который собирается в HTML, и посмотрите на LESS — тоже отличная штука для CSS.

                +1

                Печально что вам не понравилась идея написания UI на php.
                В целом по количеству кода тут никак меньше не сделать (если про html генерацию), по крайней мере у меня сейчас идей нет.

                  0

                  Понимаете, программно заменять такую гибкую штуку как HTML/CSS имеет смысл только в двух случаях:


                  • когда нужна последовательная генерация в цикле, вроде рядов таблицы
                  • когда нужно использование готовых компонентов/виджетов

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


                  Если на сайте решат сделать кнопки красными, а не синими, или задать не цвет, а смысл (кнопка цвета Submit Secondary), всё это будет жуткой головной болью. Да просто найти по готовому CSS обратно где этот CSS в коде генерится — уже весёлая задачка!


                  Я уж не говорю про autocomplete и прочие радости нормальной разработки. А вы вообще все свойства собираетесь загнать в константы? А поля 'data-...' будут?


                  Ну и писать всё это для того, чтобы вывести синюю кнопку с круглыми краями?


                  Сравните... а если таких кнопок 100?
                  <button style='width: auto; padding: 4px 10px; color: white; border-radius: 4px;background-color: blue;'>Press</button>

                  Button::Create()
                          ->ThemeParameter(Width, Auto)
                          ->ThemeParameter(Padding, [Px(4), Px(10)])
                          ->ThemeParameter(BackgroundColor, Blue)
                          ->ThemeParameter(Color, White)
                          ->ThemeParameter(BorderRadius, Px(4))
                          ->Text("Press")

                  Дело в том, что HTML/CSS уже и есть лучший инструмент для создания такой кнопки. Единственное, чего там не хватает — процедурной генерации и виджетов.

                0
                А какой основной (любимый) язык программирования? Чувствуется влияние то ли java, то ли шарпа.
                  0

                  Хороший вопрос. Как вы и написали есть большое влияние шарпов на код, но как такового основного языка у меня нет. Мне нравится шарпы, плюсы, dart, ts, rust, swift.

                    +1
                    В PHP немного другая сложившаяся практика структурирования и именования классов/методов/свойств. Почитайте PSR-*.
                    Помимо этого, скорей всего лучше воспользоваться doc.nette.org/en/3.0/php-generator =)
                      0

                      Спасибо, я понял в чем проблема

                        0

                        Проблемы как таковой нет, каждый пишет код как хочет и у каждого решения свои пользователи.
                        Если бы большинство из сообщества пыха были со вторым стеком java или шарп, вероятно текущие стандарты и подходы были бы ближе к вашему коду.
                        Я бы с радостью "звезданул" ваше решение, если вдруг вы решите когда-то отрефачить его под PSR и заюзать указанную библиотеку как основу)
                        А минуса, это фанатики джуниоры ставят)

                          0
                          Да, я отрефакторю. Я доработкой в свободное время занимаюсь. Что касается минусов, то их скорее ставят консерваторы, или люди привыкшие к одному стеку технологий, и не признающие другие стеки.
                +1
                А не проще «причесать» первую версию кода?
                Было
                <?php if ($result->num_rows > 0) {
                    echo "<b>Table table</b><br><br>";
                    echo "<table border=2>";
                    echo "<tr><td> name </td>" . "<td> name </td>" . "<td> name </td></tr>";
                    while ($row = $result->fetch_assoc()) {
                        echo "<tr><td>" . $row["name"] . "</td><td>" . $row["name"] . "</td><td>" . $row["name"] . "</td></tr>";
                    }
                    echo "</table>";
                } else {
                    echo "0 results";
                } ?>

                Стало
                <?php if ($result->num_rows > 0) { ?>
                    <b>Table table</b><br><br>
                    <table border=2>
                        <tr>
                            <td> name</td>
                            <td> name</td>
                            <td> name</td>
                        </tr>
                        <?php
                        while ($row = $result->fetch_assoc()) { ?>
                            <tr>
                                <td><?php echo $row["name"] ?></td>
                                <td><?php echo $row["name"] ?></td>
                                <td><?php echo $row["name"] ?></td>
                            </tr>
                        <?php
                        } ?>
                    </table>
                <?php
                } else {
                    echo "0 results";
                } ?>
                  +2
                  В шаблонах удобнее использовать альтернативный синтаксис.
                    +1
                    Благодарю за дополнение, так действительно удобнее:

                    <?php if ($result->num_rows > 0) : ?>
                        <b>Table table</b><br><br>
                        <table border=2>
                            <tr>
                                <td> name</td>
                                <td> name</td>
                                <td> name</td>
                            </tr>
                            <?php while ($row = $result->fetch_assoc()): ?>
                                <tr>
                                    <td><?php echo $row["name"] ?></td>
                                    <td><?php echo $row["name"] ?></td>
                                    <td><?php echo $row["name"] ?></td>
                                </tr>
                            <?php endwhile; ?>
                        </table>
                    <?php else:
                        echo "0 results";
                    endif; ?>
                      0

                      Можно было вместо


                      <?php echo $row["name"] ?>

                      Написать


                      <?= $row["name"] ?>
                        +1
                        Я в своих проектах всегда отключаю short_open_tag
                          0
                          Начиная с версии PHP 5.4.0 запись <?= стала доступна всегда.
                            0
                            А почему, если не секрет?
                              0
                              Потому что такое будет работать на любом shared-хостинге.
                                0
                                Стандарты PSR рекомендуют не использовать сокращенную запись.
                                  +1
                                  Сокращенная форма — это <? ?>. Её не рекомендуется юзать чтоб не было потенциальных конфликтов с xml
                                  В шаблонах юзать <?= ?> очень даже ок, тем более, что эта запись доступна даже при выключенном short_open_tag
                        +1

                        Я думал об этом, но не стал исправлять. Решил оставить оригинальный код.

                        0

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

                          +1
                          Автор, похоже, просто не работал с нормальными шаблонизаторами, вроде twig.
                          Выдумывание своих велосипедов с квадратными колесами это не плохо так то на этапе обучения.
                            0
                            Я не вижу велосипедов…
                            0
                            Бэкенд — это бэкенд, а фронтед — ну вы поняли… Я не собираюсь их объединять. Тут нет речи об этом.

                            «10 уровневые вложения десятков классов». Код удобно разделять на компоненты. А само дерево если грамотно писать (и делить), то вы не столкнетесь с проблемой вложенности.

                            Проблему связанная ", и попробуй не ошибиться в скобочке…" не является серьезной. В наше время есть IntelliSense и кода форматеры (хз как это пишется правильно).
                              0

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

                                0
                                Спасибо. Вам тоже не хворать )
                            0
                            По мне — это довольно свежий взгляд на создание UI со стороны PHP. Мне самому импонирует подход Flutter к созданию интерфейсов в таком стиле, тем более я еще нигде такого не видел.

                            Автор, пили есчо. Во всяком случае, я бы таким попользовался.
                              0
                              Я буду писать его в свободное время. У меня в планах сделать удобную систему навигации, более удобные темы. Добавить различные оптимизации и реализовать callback api. Пока это всё

                            Only users with full accounts can post comments. Log in, please.