Page 3 of 3

Posted: Fri Oct 05, 2007 12:09 pm
by Luke
Alright... well that's good. I look forward to your critique. :)

Posted: Sat Oct 27, 2007 11:56 am
by Luke
Alright I need some design advice. I decided to go with arborint's advice to seperate out io / rendering / etc. into their own classes. This means to render a standard icalendar-formatted calendar, I do something like:

Code: Select all

$cal = qCal::create();
// do stuff with calendar .. snip ..
$renderer = new qCal_Renderer_icalendar;
$renderer->render($cal)
That seems inconvenient to me. I mean, that would be fine if you were using a custom renderer (for hCal or xCal or whatever else you want to render it as), but most of the time you'll just be using the standard icalendar renderer. It would be more convenient (IMO) if you could just rely on the calendar object's render() method to know how to render. So if you haven't told it to do any different, it would just render w/default renderer (icalendar)

Code: Select all

$cal = qCal::create();
// since it hasn't been told any different, it just renders w/default renderer (to icalendar format)
$cal->render();
If you need to render it with a "special" renderer, you'd do this:

Code: Select all

$cal = qCal::create();
$cal->setRenderer(new qCal_Renderer_hCal);
$cal->render();
Or maybe even...

Code: Select all

$cal = qCal::create();
$cal->render(new qCal_Renderer_hCal);
What do you guys think?

Posted: Sat Oct 27, 2007 12:34 pm
by RobertGonzalez
I lean a little toward the setRenderer method myelf, but that is because I tend to do things like that myself when I develop. That is just me, not right or wrong.

Posted: Sat Oct 27, 2007 12:36 pm
by Luke
Any advantage of setRenderer over simply:

Code: Select all

$cal = qCal::create();
$cal->render(new qCal_Renderer_hCal);
Thanks Everah! :-D

Posted: Sat Oct 27, 2007 12:50 pm
by RobertGonzalez
Render is typically a final step. It might not make much sense, but what if you wanted to render something in one way, then immediately render it in another (like I said, not much sense, but bear with me).

Using something a la setRenderer would give you the ability to set a renderer without rendering. So you could feasibly change the rendering engine midstream, if you wanted to, with the need for actual output.

Does that make any sense?

Posted: Sat Oct 27, 2007 1:56 pm
by John Cartwright
The Ninja Space Goat wrote:Any advantage of setRenderer over simply:

Code: Select all

$cal = qCal::create();
$cal->render(new qCal_Renderer_hCal);
Thanks Everah! :-D
I would probably stick to this method, simply because it would be easier to test than having to test two seperate methods. They essentially are doing the exact same thing, but this does it in fewer steps, with a simpler interface to the object.
Using something a la setRenderer would give you the ability to set a renderer without rendering. So you could feasibly change the rendering engine midstream, if you wanted to, with the need for actual output.
If this is the case, I would probably have a setRender(), but you could easily accomplish this anyways with just a render method anyways.

Posted: Sat Oct 27, 2007 2:26 pm
by Luke
Thanks guys, that's precisely what I did... and it worked out nicely :)

Posted: Sat Oct 27, 2007 3:29 pm
by Luke
Alright, I'm having some trouble here again. I'm hoping somebody can just give me a push in the right direction. I've tried several things, but none seem flexible enough. I've got qCal_Component and qCal_Property. Components can have other components, as well as properties as children. My renderer needs to be able to traverse this tree of qCal_Components / qCal_Properties and render them based on logic provided by subclasses of qCal_Renderer. Does that make sense?

I have a composite pattern set up for the component / property tree and that is working out just fine. The issue comes in when I try and give flexibility to classes that extend qCal_Renderer (this way I could have qCal_Renderer_hCal, qCal_Renderer_xCal, etc.). I'm just having a difficult time figuring out where to put what logic. Here is what I've been trying:

Code: Select all

abstract class qCal_Renderer
{
    public function render(qCal_Component $component)
    {
        $ret = '';
        foreach ($component->getChildren() as $child)
        {
            if ($child instanceof qCal_Component) $ret .= $this->renderComponent($child);
            elseif ($child instanceof qCal_Property) $ret .= $this->renderProperty($child);
        }
    }
    abstract protected function renderComponent(qCal_Component $component);
    abstract protected function renderProperty(qCal_Property $component);
}

class qCal_Renderer_icalendar
{
    protected function renderComponent(qCal_Component $component)
    {
        foreach ($component->getChildren() as $child)
        {
            // render each child
        }
    }
    protected function renderProperty(qCal_Property $component)
    {
         // render property
    }
}
To render the data as standard icalendar format is dead easy, but I would like for it to also be easy to render the data in xhtml, xml, json, whatever. This just doesn't feel like it will provide that kind of flexibility, and also I end up looping through component's children twice here... which feels wrong. DRY?

Posted: Sat Oct 27, 2007 3:38 pm
by Christopher
The Ninja Space Goat wrote:Alright, I'm having some trouble here again. I'm hoping somebody can just give me a push in the right direction. I've tried several things, but none seem flexible enough. I've got qCal_Component and qCal_Property. Components can have other components, as well as properties as children. My renderer needs to be able to traverse this tree of qCal_Components / qCal_Properties and render them based on logic provided by subclasses of qCal_Renderer. Does that make sense?
I haven't had a chance to look and think about your code (hopefully I will get some time), but the above sounds wrong to me because components should render and properties should be essentially models for the components/views. Maybe I am not understanding your terminology.

Posted: Sat Oct 27, 2007 3:46 pm
by Weirdan
The Ninja Space Goat wrote:Is there any interest in a library such as this?
I might be interested to use this in our current project on a later stage

Posted: Sat Oct 27, 2007 3:53 pm
by Luke
Actually arborint, I think you are on exactly the right path. If / when you get a chance to look at the code, I'd love to hear your opinion. Thanks :)

Posted: Sat Oct 27, 2007 4:07 pm
by Kieran Huggins
I might be missing something here (and probably am) but shouldn't rendering different formats be entirely handled by different view templates? That would be DRY.

Posted: Sat Oct 27, 2007 4:14 pm
by Luke
Nope, you're not missing anything. That makes complete sense (and I believe that's what arborint was getting at...) that's exactly that push I needed :)

Posted: Sat Oct 27, 2007 6:07 pm
by Weirdan
This just doesn't feel like it will provide that kind of flexibility, and also I end up looping through component's children twice here... which feels wrong. DRY?
I believe component tree traversal should be embedded into the components themselves - that's what the composite pattern is about. And you could implement renderer as a visitor: something like this:

Code: Select all

<?php
interface qCal_Component_Visitor_Interface
{
    public function visitComponent(qCal_Component $component);
    public function visitProperty(qCal_Property $component);
}

class qCal_Renderer_icalendar implements qCal_Component_Visitor_Interface
{
    protected function visitComponent(qCal_Component $component)
    {
		// render only that pertaining to component itself
    }
    protected function visitProperty(qCal_Property $component)
    {
         // render property
    }
}

interface qCal_Component_Interface 
{
	public function traverse(qCal_Component_Visitor_Interface $visitor);
}

class qCal_Component implements qCal_Component_Interface 
{
	public function traverse(qCal_Component_Visitor_Interface $visitor) 
	{
		$visitor->visitComponent($this);	
		foreach ($this->getChildren() as $child) {
			$child->traverse($visitor);
		}
	}
}

class qCal_Property implements qCal_Component_Interface 
{
	public function traverse(qCal_Component_Visitor_Interface $visitor) 
	{
		$visitor->visitProperty($this);
	}
}
?>