Monday 16 March 2009

How to decouple business logic from view logic and gain flexibility

A user asks: How can I improve the following code?

<div id="search_controls">

    <ul>
<?php

$entities_tab = ( $search_func != 'entities') ? "<a href=\"index.php?tab=entities$url_sm\">Entities</a>" : "Entities";
$entities_tab_class = ( $search_func != 'entities') ? "inactive_tab" : "active_tab";
?>
        <li class="<?php echo $entities_tab_class; ?>"><?php echo $entities_tab; ?></li>
<?php

$products_tab = ( $search_func != 'products') ? "<a href=\"index.php?tab=products$url_sm\">Products</a>" : "Products";
$products_tab_class = ( $search_func != 'products') ? "inactive_tab" : "active_tab";
?>
        <li class="<?php echo $products_tab_class; ?>"><?php echo $products_tab; ?></li>

<?php

$events_tab = ( $search_func != 'events') ? "<a href=\"index.php?tab=events$url_sm\">Events</a>" : "Events";
$events_tab_class = ( $search_func != 'events') ? "inactive_tab" : "active_tab";
?>
        <li class="<?php echo $events_tab_class; ?>"><?php echo $events_tab; ?></li>

    </ul>
<?php
$url_sm = ( $param_sm == "b" or $param_sm == NULL ) ? "&sm=a" : "&sm=b" ;
$sm_link_txt = ( $param_sm == "b" or $param_sm == NULL ) ? "Advance Search" : "Basic Search" ;
?>
    <a id="link_search_type" href="index.php<?php echo $url_tab.$url_sm.$url_sq; ?>"><?php echo $sm_link_txt ?></a>

</div>


A: separate the business logic from the view logic. The business logic should be restricted to working on data structures.
In the example above, you're generating some "search tabs" which look quite similar. So instead of mixing the decisional code,
like deciding what html code each $*_tab should contain ($entities_tab, $products_tab, $events_tab), you should aggregate
all these into one place, since they are all the same thing: tabs. For this, you could use classes or arrays.

For the sake of simplicity, I'll use arrays.

So first ask yourself what does every tab have? It has a label and an internal representation. For example, the entities tab above
has the label "Entities" and the internal name "entities", which is used for URL generation. You could also consider that a tab
has a content, but in our minimal example all the tabs get their content based on the other two properties mentioned above

So our $search_controls would look like this:
$search_controls = array(
    array(
        'name' => 'entities',
        'label' => 'Entities'
    ),
    array(
        'name' => 'products',
        'label' => 'Products'
    ),
    array(
        'name' => 'events',
        'label' => 'events'
    )
);

Now we also have the "search control" itself: a <div> embedding the search controls and a link for the "search type".
Beside, a search control also has an "active tab" - that's a name of an existing tab which should have special visualization hints. So let's make up a $search_control containing this meta data (meta - which describes something, as such here "meta data" means additional data
which describes the data structure itself). So we're going to come up with something like this:

$search_control = array(
    'active' => 'entities',
    'id' => 'search_controls',
    'controls' => array(
        array(
            'name' => 'entities',
            'label' => 'Entities'
        ),
        array(
            'name' => 'products',
            'label' => 'Products'
        ),
        array(
            'name' => 'events',
            'label' => 'events'
        )
    )
);

Now this looks nice. We have invented an internal representation for the data that's going to be rendered.
But why would you want to do this, after all? The answer is it's much more flexible. If sometime, in the future,
you decide that you want to save the "search tabs" in your database, or let's push it even further, if you
decide that every user can define his own favourite "search tabs", you will be able to pull this data from the database
without even touching the generation of the html code!

Beside, you could use this to define new types of "controls" like the "search control" by using the same algorithm -
which means less code, which in turns means less hassle and less room for bugs!

The reusage of code is especially flexible if you're not going to store the internal representation in multi-dimensional
arrays as above, but in classes.

Now enough talking, here's how a function for rendering the "search control" - or any such "control" for that matter of fact,
could look like:

function render_control($controls) {
    $r = '<div id="'.$controls['id'].'"><ul>';
    foreach($controls['controls'] as $control) {
        if($controls['active'] === $control['name']) {
            $class = 'active_tab';
            $innerHtml = $control['label'];
        }
        else {
            $class = 'inactive_tab';
            $innerHtml = '<a href="index.php?tab='.$control['name'].'">'.$control['label'].'</a>';
        }
        $r .= '<li class="'.$class.'">'.$innerHtml.'</li>';
    }
    $r .= '</ul></div>';
    return $r;
}


As you can see, I did leave out some things, like the "$url_sm", since I don't know how and where that variable comes from.
However, if you need more meta data in your control, simply invent a new parameter to the function or a new member in $controls, like
$controls['id']. You could also add a 'title' for the <div> box itself, or more parameters which allow further customization
of the html tags used instead of the standard '<ul>', '<li>', '<a>' and so on.

The sky is the limit!

The lesson is: a smart programmer not only writes code, but he writes it in a reusable and flexible way. Now you can throw data at
render_control() without writing any piece of html, it will simply generate it.

Isn't that a relief? Be smart, write less, code more!

No comments:

Post a Comment