views:

80

answers:

2

Hey all,

I have a question I have been toiling over for quite a while. I am building a templating engine with two main classes Template.php and Tag.php, with a bunch of extension classes like Img.php and String.php.

The program works like this:

A Template object creates a Tag objects. Each tag object determines which extension class (img, string, etc.) to implement.

The point of the Tag class is to provide helper functions for each extension class such as wrap('div'), addClass('slideshow'), etc.

Each Img or String class is used to render code specific to what is required, so $Img->render() would give something like <img src='blah.jpg' />

My Question is:

Should I encapsulate all extension functionality within the Tag object like so:

Tag.php

function __construct($namespace, $args) {
    // Sort out namespace to determine which extension to call
    $this->extension = new $namespace($this); // Pass in Tag object so it can be used within extension

    return $this; // Tag object
}

function render() {
    return $this->extension->render();
}

Img.php

    function __construct(Tag $T) {
        $args = $T->getArgs();
        $T->addClass('img');
    }

    function render() {
        return '<img src="blah.jpg" />';
    }

Usage:

$T = new Tag("img", array(...);
$T->render();

.... or should I create more of an inheritance structure because "Img is a Tag"

Tag.php

public static create($namespace, $args) {
    // Sort out namespace to determine which extension to call
    return new $namespace($args);

}

Img.php

class Img extends Tag {
    function __construct($args) {
        // Determine namespace then call create tag
        $T = parent::__construct($namespace, $args);
    }

    function render() {
        return '<img src="blah.jpg" />';
    }
}

Usage:

$Img = Tag::create('img', array(...));
$Img->render();

One thing I do need is a common interface for creating custom tags, ie I can instantiate Img(...) then instantiate String(...), I do need to instantiate each extension using Tag.

EDIT: Also just for clarification: the Tag class has functionality common to all extension classes, there shouldn't be any methods in the Tag class that need to be implemented in the extension classes. Tag class merely provides helper functions.

I know this is somewhat vague of a question, I'm hoping some of you have dealt with this in the past and can foresee certain issues with choosing each design pattern. If you have any other suggestions I would love to hear them.

Thanks! Matt Mueller

+2  A: 

Inheritance would make more sense than encapsulating all functionality for different tags inside a single Tag class. Separation of concern is important, so it's better to go with an inheritance-based approach here. Otherwise, you end up having a bunch of different tag-specific logic inside a single class, which is bad. It's a maintenance nightmare!

If you have Img-specific logic, put it in its own class. You can put all of the common methods inside the Tag class. If this was Java, I would have made Tag an abstract class or even an interface and have the different implementations (Img, Div, etc.) extend (in the case of an abstract class) or implement (in the case of an interface).

An even better approach would be to have a Tag interface, and then an AbstractTag abstract class that implements all the common logic. Then your specific tags can implement the Tag interface and extend AbstractTag. However, I do not know if this is exactly possible in PHP, but you can try to do something similar.

Vivin Paliath
Thanks so much for the advice. What about the static Tag::create(...) is that reasonable? It's been a while since I've got down and dirty with serious OOP. Also just for clarification: the Tag class has functionality common to all extension classes, there shouldn't be any methods in the Tag class that need to be implemented in the extension classes. Tag class merely provides helper functions.
Matt
I would do something like `Img::create()`. The constructor in `AbstractTag` should be abstract, and so the only way you can create a Tag is by calling the tag-specific constructor. You can either do that or have factories in your `Tag` class that create specific tag objects.
Vivin Paliath
+1  A: 

Neither new Tag('img') nor Tag::create('img') look convincing to me. I think, the decision which tag to use is taken in the wrong place - in the Tag class - while it clearly belongs to the Template. The Tag and its descendants should have no control on how they are going to be used.

I'd suggest the following: create the hierarchy of Tags

 abstract class Tag { ...common methods.... }
 class ImgTag extends Tag { ...image specific methods... }
 class SpanTag extends Tag { ...image specific methods... }

and in the Template just use new WhateverTag when you need it

 class Template...
    function insertImage
      $tag = new ImgTag($atts);
      $tag->render();

If tags are created in the multiple places, it's a good idea to have a factory method, but it's still should belong to the Template

 class Template...
    function createImage
      return new ImgTag($atts);

    function insertOneImage
      $tag = $this->createImage
      $tag->render();

    function insertAnotherImage
      $tag = $this->createImage
      $tag->render();

As a general advice, avoid static methods at any price. Statics are just fancy aliases for global functions, there's nothing object-oriented about them.

stereofrog
Good advice! The one problem I see with this for my situation is that Template has a specific call to createImage (or insertImage). I would like the template to have no idea what plugins or extensions (img, span) classes are available to it.. this makes it incredibly easy to extend. I do agree that the creation of tags should be in the template. I forgot to add that but new Tag(..) and Tag.create(..) are in Template.php. Thanks a lot for your write-up!
Matt