Your Opinion on Syntax Please

Hello,

I am working on a framework to write HTML pages in PHP. By that I mean a PHP class is responsible for creating HTML output, so you set up the inputs for the class instead of directly writing the HTML.

I was hoping I could get some feedback on the syntax, whether people think they would find it easy to use.

Here is an example, I will explain how it works afterwards:

<?php

p(0, 'html', ['lang', 'en-GB']);
p(1, 'head');
p(2, 'title', 'Example Page');
p(2, 'meta', ['name', 'description', 'content', 'An example page']);
p(2, 'meta', ['charset', 'utf-8']);
p(2, 'link', ['rel', 'icon', 'type', 'image/png', 'href', 'images/logo.png']);
p(2, 'link', ['href', 'css/standard-style.css', 'rel', 'stylesheet']);
p(1, 'body');
p(2, 'header');
p(3, 'img', ['src', 'images/logo.png', 'width', '200', 'alt', '']);
p(2, 'main');
p(3, 'h1', 'Example Page');
p(3, 'p', 'This is just an example page.');
p(3, 'h2', 'Look I can count!');
$countExample = '';
for ($i=1; $i<=25; $i+=1) {
    $countExample .= $i;
    if ($i != 25) {
        $countExample .= ', ';
    }
}
p(3, 'p', $countExample);
p(2, 'footer');
p(3, 'p', 'Where\'s My Shoe?');

?>

So the function can take up to 4 inputs.

  1. The first input must be the indentation as an integer. For each function call in relation to the last time the function was called, an increase of 1 indicates you want to add a child to the last element submitted, an increase of more than 1 throws an exception, if the indentation is the same as the last element, then the element is added as a sibling of the last element, and if there is a decrease, then the new element is added as a child of the last element of that level of indentation.
  2. The second input must be the element name as a string.
  3. The third and fourth inputs are optional. If you want your element to have child text, then the third input must be a string containing that child text. If you want to add attributes to your element, then the last input should be an array of strings in the order of attribute name, attribute value. You can add as many attributes as you want, and an array with a non-string, or an uneven number of inputs will throw an exception.

Please let me know what you think,
Many Thanks,
RT_ :smiley:

I like the general idea of what you have done here. It is very organized.
To some degree it feels like you have over complicated the act of writing HTML by trying to structure and simplify it.

I can see the value of this as a DSL from another script (BASH or another scripting language like Ruby)


My one comment, though, is that this could become tedious writing p(0, .... Can the first parameter be optional and assumed to be zero if omitted?

Maybe it will be better to use [key=>value] scheme for attribute arrays instead of [key, value]
This is what I mean:

p(3, 'img', ['src'=>'images/logo.png', 'width'=>'200', 'alt'=>'']);

I think that scheme is more readable.

Also I would use jQuery-like chaining, i.e.:

html(['lang' => 'en-GB'])->
t()->head()->   //t() makes indentation
t(2)->title("Example Page")->
t(2)->meta(['name'=>'description', 'content'=>'An example page'])->
t(2)->meta(['charset'=>'utf-8'])->
    //...and so on...

Cons:

  • that approach requires separate function for each tag;
    Pros:
  • easy to read
  • separate logic for each tag
  • custom arguments (in example above title() accepts simple string while meta() needs array)
  • autocomplete in IDE

Just some ideas =)

2 Likes

Thanks :smile:

It seems like over complicating it on first glance, but actually by doing it this way PHP can understand what you are doing with the HTML. This enables a number of advantages:

  1. On the fly html validation.
  2. Integration with other tools I have written to vastly improve security.
  3. Possibility of spell check.
  4. Points 2 and 3 combine to aid with SEO.
  5. Arguably more readable when you start creating large html pages. It makes it easier to keep track of the html indentation, encourages a linear code flow, and removes the opportunity to get into a messy html/php mish-mash.

I wont go into it all too much now, but will make all the source code and some tutorials available on my website when I finish.

There could be some scope for making the first parameter optional. The first call could assume 0, and subsequent calls that omit that argument could assume to be a sibling to the previous element. I wonder if that would be a trade off between clarity and verbosity. Because being able to flow over the numbers might help the eye with visualising the indentation, although I have no evidence to support this idea. I will have a think and tinker :slight_smile:

Thanks, some interesting points there.

I was on the fence between key=>value pairs in the array and key, value. I went with the latter because it was a bit shorter. Being conscious of trying to keep the syntax as short and concise as possible, but seeing it written there I think the readability is improved significantly enough to warrant it.

I will think about the jQuery-like structure. It might be easier to work with.

Thanks :slight_smile:

For reference, here are 3 possibilities of the structure revised after the feedback. Which do you think looks easiest to read/understand? And easiest to write?

740 Characters:

<?php

p(0, 'html', ['lang', 'en-GB']);
p(1, 'head');
p(2, 'title', 'Example Page');
p(2, 'meta', ['name', 'description', 'content', 'An example page']);
p(2, 'meta', ['charset', 'utf-8']);
p(2, 'link', ['rel', 'icon', 'type', 'image/png', 'href', 'images/logo.png']);
p(2, 'link', ['href', 'css/standard-style.css', 'rel', 'stylesheet']);
p(1, 'body');
p(2, 'header');
p(3, 'img', ['src', 'images/logo.png', 'width', '200', 'alt', '']);
p(2, 'main');
p(3, 'h1', 'Example Page');
p(3, 'p', 'This is just an example page.');
p(3, 'h2', 'Look I can count!');
$countExample = '';
for ($i=1; $i<=25; $i+=1) {
    $countExample .= $i;
    if ($i != 25) {
        $countExample .= ', ';
    }
}
p(3, 'p', $countExample);
p(2, 'footer');
p(3, 'p', 'Where\'s My Shoe?');

?>

740 characters

<?php

p(0, 'html', ['lang'=>'en-GB']);
p(1, 'head');
p(2, 'title', 'Example Page');
p(2, 'meta', ['name'=>'description', 'content'=>'An example page']);
p(2, 'meta', ['charset'=>'utf-8']);
p(2, 'link', ['rel'=>'icon', 'type'=>'image/png', 'href'=>'images/logo.png']);
p(2, 'link', ['href'=>'css/standard-style.css', 'rel'=>'stylesheet']);
p(1, 'body');
p(2, 'header');
p(3, 'img', ['src'=>'images/logo.png', 'width'=>'200', 'alt'=>'']);
p(2, 'main');
p(3, 'h1', 'Example Page');
p(3, 'p', 'This is just an example page.');
p(3, 'h2', 'Look I can count!');
$countExample = '';
for ($i=1; $i<=25; $i+=1) {
    $countExample .= $i;
    if ($i != 25) {
        $countExample .= ', ';
    }
}
p(3, 'p', $countExample);
p(2, 'footer');
p(3, 'p', 'Where\'s My Shoe?');

?>

716 characters

<?php
    
p(0)->html(['lang'=>'en-GB']);
p(1)->head();
p(2)->title('Example Page');
p(2)->meta(['name'=>'description', 'content'=>'An example page']);
p(2)->meta(['charset'=>'utf-8']);
p(2)->link(['rel'=>'icon', 'type'=>'image/png', 'href'=>'images/logo.png']);
p(2)->link(['href'=>'css/standard-style.css', 'rel'=>'stylesheet']);
p(1)->body();
p(2)->header();
p(3)->img(['src'=>'images/logo.png', 'width'=>'200', 'alt'=>'']);
p(2)->main();
p(3)->h1('Example Page');
p(3)->p('This is just an example page.');
p(3)->h2('Look I can count!');
$countExample = '';
for ($i=1; $i<=25; $i+=1) {
    $countExample .= $i;
    if ($i != 25) {
        $countExample .= ', ';
    }
}
p(3)->p($countExample);
p(2)->footer();
p(3)->p('Where\'s My Shoe?');

?>

I think I prefer the third, and it is a bit shorter too.

The main problem in my opinion is that “indentation index”. I understand you have to use it to detect where tag should close. But it’s hard to maintain these numbers especially if you’ll decide to move part of elements in the other place in tree.

So, challenge was accepted :slight_smile: and I’m back with my solution. I’ve written a simple class with magic method. Here is the usage code:

<?php
require('t.php');

$t = new T();

$t->create(
    $t->html(['lang'=>'en-GB'], [
        $t->head([
            $t->title("Example page"),
            $t->meta(['name'=>'description', 'content'=>'Example page']),
            $t->meta(['charset'=>'utf-8'])
        ]),
        $t->body([
            $t->header([
                $t->img(['src'=>'images/logo.png', 'width'=>200])
            ]),
            $t->article([
                $t->h1("Example page"),
                $t->p("This is just an example page."),
                $t->p("Use attributes!", ['align'=>'center']),
                $t->p("Use nesting!", [
                    $t->span('This is span!')
                ]),
            ]),
            $t->footer([
                $t->p("This is a footer")
            ])
        ])
    ])
);

echo $t->dump();

I think that syntax is quite readable and easy to modify with simple copy/move/paste.
Any method (except dump() and create()) will produce corresponding tag.
These methods accept 3 types of arguments:

  • string: will be used as tag content;
  • array: will be used as list of nested tags;
  • associative array: will be used as list of attributes

Arguments can be specified in any order, it doesn’t matter.

Here is output HTML produced by example above:

<html lang="en-GB">
    <head>
        <title>Example page</title>
        <meta name="description" content="Example page">
        <meta charset="utf-8">
    </head>
    <body>
        <header>
            <img src="images/logo.png" width="200">
        </header>
        <article>
            <h1>Example page</h1>
            <p>This is just an example page.</p>
            <p align="center">Use attributes!</p>
            <p>
                Use nesting!
                <span>This is span!</span>
            </p>
        </article>
        <footer>
            <p>This is a footer</p>
        </footer>
    </body>
</html>

You can see indentation is correct and it was made automatically.

And the class itself: http://pastebin.com/nC3mfhgW

You can use that class freely if you want.

1 Like

Thanks for going to that effort :slight_smile:

I think this is where subjectivity comes in though, because personally I find the code I wrote easier to read. Here is what I like about the approach I wrote:

  1. It enforces the ‘pseudo’ indentation, making sure it is readable.
  2. Each line is ‘atomic’, which means it is easy to build in a linear fashion. What I mean by that is with your solution, say you wanted to build a list from a loop for example, you would have to build the list beforehand and then insert it when you get to the section of the markup. Or you would have to build an insertion mechanism to add it in at the end. Both of these options are less readable in my opinion because you need to move away from the linear flow of the code. With my solution, you can just do the loop and carry on.
  3. If you are creating a page that has many elements and is very indented, it can soon become more difficult to read because the code is so horizontally wide. If you wrap the code then it can make the indentation confusing.

Using “indentation index” admittedly is less flexible in terms of moving code around. I am pondering solutions for that. The difficulty being absolute indexing gives a better sense of structure but is less flexible, relative indexing is more flexible but has less of a sense of structure. I do have a solution but it may be controversial so I’m going to ponder on it a bit.

It is interesting to watch this “intellectual exchange” of ideas. And, honestly, I can see BOTH SIDES of the argument.
The solution @megazoid provided is very elegant and clean. It has many merits.
Notwithstanding, though, your original concept is sound. And you made very good points to support the design decision.

Let me throw in a whacky (read creative?) idea here. Since everything is a child of some other element - except the root, of course - why don’t you provide the ability to name each level and then reference its parent (name:parent)?
The way I imagine this, for example, would be:

<?php 
p("root:")->html(['lang'=>'en-GB']);
p("head:root")->head();
p("title:head")->title('Example Page');
p(":head")->meta(['name'=>'description', 'content'=>'An example page']);
p(":head")->meta(['charset'=>'utf-8']);
p(":head")->link(['rel'=>'icon', 'type'=>'image/png', 'href'=>'images/logo.png']);
p("css:head")->link(['href'=>'css/standard-style.css', 'rel'=>'stylesheet']);
p("body:root")->body();
p("header:body")->header();
p(":header")->img(['src'=>'images/logo.png', 'width'=>'200', 'alt'=>'']);
p("main:header")->main();
p(":main")->h1('Example Page');
p(":main")->p('This is just an example page.');
p(":main")->h2('Look I can count!');
$countExample = '';
for ($i=1; $i<=25; $i+=1) {
    $countExample .= $i;
    if ($i != 25) {
        $countExample .= ', ';
    }
}
p(":main")->p($countExample);
p(":header")->footer();
p(":main")->p('Where\'s My Shoe?');

?>

The absence of a child indicates this is the root level. Perhaps - as I have shown in the example - an entity only needs a label IF it will be referenced by another. So, the absence of a parent creates a shorthand notation of-sorts.

{I may have inadvertently misrepresented some of your original structure. The intention was to demonstrate the concept, not details}

1 Like

What is the motivation for doing this?

Scott

I see what you have done there. From what I can see, the main advantage is if you move or insert some html, you only need to change the “parentTextID” of the direct children, rather than all the direct children’s children too. Also you can very quickly see what element is it’s parent because it is written.

However I would argue that the name part of the parentTextID is not required? As the method that is called can encapsulate that information. Also the parent need only be declared by the first child, and can be assumed by it’s siblings. (The oldest brother does the talking :stuck_out_tongue: )

Something like this:
757 Characters

<?php

p()->html(['lang'=>'en-GB']);
p("html")->head();
p("head")->title('Example Page');
p()->meta(['name'=>'description', 'content'=>'An example page']);
p()->meta(['charset'=>'utf-8']);
p()->link(['rel'=>'icon', 'type'=>'image/png', 'href'=>'images/logo.png']);
p()->link(['href'=>'css/standard-style.css', 'rel'=>'stylesheet']);
p("html")->body();
p("body")->header();
p("header")->img(['src'=>'images/logo.png', 'width'=>'200', 'alt'=>'']);
p("body")->main();
p("main")->h1('Example Page');
p()->p('This is just an example page.');
p()->h2('Look I can count!');
$countExample = '';
for ($i=1; $i<=25; $i+=1) {
    $countExample .= $i;
    if ($i != 25) {
        $countExample .= ', ';
    }
}
p()->p($countExample);
p("body")->footer();
p("footer")->p('Where\'s My Shoe?');

?>

I like this syntax. What do you guys think?

RT_

Sorry, I missed that.

Scott

I disagree. I was not assuming each element would be called in direct relational sequence.

Suppose, for example, I want to [later] insert an element in an Unordered List that was defined previously. THAT was my intention and thought behind the “label” concept.

Regardless, you are progressively creating something interesting and [most likely] useful.
Above all, the mental exercise is healthy and has been enjoyable for me!

It may be due to my own lack of experience, but this is eluding me.

Scott

How does one build a link? A link requires text and attributes, or do you always have to nest a span inside the <a> tag given the current implementation?

Ah! I see. I am trying to design it so that it encourages the user to write mark-up and code linearly. So that you can just read through from top to bottom and see relatively quickly and easily what is going on even if you have never seen the code before. I think being able to insert later makes it easier for the user to write confusing code, and produces an overhead of complexity which the user has to deal with on every line.

Really pleased to hear that :smile:

With the current state of the syntax, like this:

p()->a('I am a link, please click on me', ['href'=>'another-page.html']);

So any element function can accept two optional arguments.

  1. A string which is interpreted as the elements child text.
  2. A name-value array, which is interpreted as attributes required for the element.

However if you mean a link in as an inline element, currently, you just write inline elements in the string like text input. But I am thinking about how it might be implemented more elegantly.

1 Like

I understand that. Recall I had mentioned (above) that this could be useful in a ‘scripting’ environment.
And that is where the added flexibility could be valuable.

I just think it could complicate things unnecessarily when used as intended, building a page in php or similar language. But maybe you could give an example? It could be built to accommodate insertions but it’s very easy to drift away from the central objective when doing things like this. Do you know what I mean?