Cómo escribí un generador de código en PHP y qué salió de él

Razones y problemas a resolver



En este artículo les contaré cómo escribí un generador de código en php. Te contaré el camino que pasó de generar tablas simples a un generador bastante completo de código html y css. Daré ejemplos de su uso y mostraré proyectos ya terminados.



Este semestre solo se pudo utilizar PHP en una de las asignaturas.



Después del cambio de nombre del proyecto sin finEl proyecto se llamó MelonPHP. ¿Para que la gente piense en la comida cuando dice su nombre? Pero nuestro artículo aquí no trata de generar delirio, así que déjame contarte el motivo de su creación.



Había mucho que escribir, pero eso no es un problema. El principal problema era generar código HTML a través de PHP. Intentaré explicar el problema a continuación.



Por ejemplo, aquí está la salida de texto a través del eco familiar:



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


Parece simple y directo. Tomemos un fragmento del código de mi amigo donde genera una tabla:



...
$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";}
...


eso terrible el código demuestra los problemas que quería resolver:



  • La presencia de html en el código php, lo que lo hace menos legible en mi opinión. Aún así, el archivo para un yap debe contener el código de solo un yap, en mi opinión
  • No hay separación de lógica, todo está desordenado. Quería una interfaz PHP más agradable


Vale la pena señalar que he estado escribiendo en Flutter durante un tiempo relativamente largo y realmente me gusta la idea detrás de esto, relacionada con escribir una interfaz construyendo un árbol a partir de widgets. Decidí tomar prestada la idea con nodos (widgets) de allí.



, , .



. .



:



  • 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.



.



Imprima " Presione $ this-> TapCount times " en el texto .



Eso es todo, un simple clicker está listo.



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();




Salir



Me las arreglé para escribir un generador de código simple pero lo suficientemente poderoso.



Pasó de generar tablas simples a un generador html y css completo, en el que puede diseñar proyectos de manera conveniente y combinar el diseño con la lógica.



En este marco, escribí un proyecto de curso (capturas de pantalla a continuación), lo usé en el examen y realicé tareas de estudio sobre él.



Capturas de pantalla del proyecto del curso realizado en MelonPHP







Fuentes



GitHub - MelonPHP



Aleteo



MAUI




All Articles