What’s Taking So Long?!
Many wonder what’s taking us so long with 2.0. Hopefully this little walk through code conversion will help answer that question. Rewriting all of ExpressionEngine to a new architecture is a time consuming process, there’s just no way around it. It’s not glamorous work, and we have a very small team of developers who carry many duties in addition to programming ExpressionEngine 2.0.
We knew it would be arduous when we began, but we were and remain firmly convinced that the change in architecture is the best for ExpressionEngine’s future. We’re setting a foundation for many many years of excellent development to come, from both first and third parties, that would have been difficult or impossible with ExpressionEngine 1.x. We readily admit, though, that we underestimated the time it would take.
It simply hasn’t been possible for me or Derek Allard to break from that work at regular intervals to share new things. When we’ve hit milestones with things that are legitimately new with 2.0, we’ve shared them with you. We haven’t, however, bored you with the drudgery I’m about to share with you. What follows is a very small code snippet from ExpressionEngine 1.6.6’s control panel, 30 or so lines of code including comments and white space. I’ll walk through how that code is changed to become code that works with the new architecture we’re establishing with 2.0. The code will do nothing new in the end, it will just do what it does in a different way. Functionally, it’s a member group selection box in the Communicate page.
Here’s where we start:
/** -----------------------------
/** Member group selection
/** -----------------------------*/
if ($DSP->allowed_group('can_email_member_groups'))
{
$r .= $DSP->table('tableBorder', '0', '', '300px').
$DSP->tr().
$DSP->td('tableHeading').
$DSP->qdiv('itemWrapper', $LANG->line('recipient_group')).
$DSP->td_c().
$DSP->tr_c();
$i = 0;
$query = $DB->query("SELECT group_id, group_title FROM exp_member_groups
WHERE site_id = '".$DB->escape_str($PREFS->ini('site_id'))."'
AND include_in_mailinglists = 'y' ORDER BY group_title");
foreach ($query->result as $row)
{
$style = ($i++ % 2) ? 'tableCellOne' : 'tableCellTwo';
$r .= $DSP->tr().
$DSP->td($style, '50%').$DSP->qdiv('defaultBold', $DSP->input_checkbox('group_'.$row['group_id'], $row['group_id'], (in_array($row['group_id'], $member_groups)) ? 1 : '').$DSP->nbs(1).$row['group_title']).$DSP->td_c()
.$DSP->tr_c();
}
$r .= $DSP->table_c();
}
In addition to syntax changes, for this to make the translation to ExpressionEngine 2.0 architecture, there are a few overarching goals, that will be familiar to many CodeIgniter users.
- Models provide access to data.
- Views control presentation.
- Controllers contain logic.
This is all in addition to switching to CI’s syntax, libraries, helpers, etc. One of the challenges we most underestimated that faces us is separating these things, particularly the elimination of the old control panel Display class. As you can see even in this simple code above, it’s interspersed throughout the logic, and indeed built as the logic is processed, which is the antithesis of programming with an MVC approach. It is necessary to get the data from the model, prepare it as needed, and then pass it off, complete and ready to go, to a display method.
So, let’s look at the controller first. It’s portion of the above code will become:
if ( ! $this->cp->allowed_group('can_email_member_groups'))
{
$vars['member_groups'] = FALSE;
}
else
{
$query = $this->member_model->get_member_groups();
foreach ($query->result() as $row)
{
$checked = ($this->input->post('group_id'.$row->group_id) !== FALSE OR in_array($row->group_id, $member_groups));
$vars['member_groups'][$row->group_title] = array('name' => 'group_'.$row->group_id, 'value' => $row->group_id, 'checked' => $checked);
}
}
ExpressionEngine 1.x’s global objects are gone, everything is handled through the super object. You’ll notice how little, if any, of the original 30 lines would survive a cut and paste. Even the foreach() is changed, since the database return object is different in 2.0. But gone is anything at all related to the display of the item or building string output. The controller gets the data from the member model, and then processes it into an array that will prove invaluable to our view file later. Before we go there, though, let’s look to see what happened to the data query. For that, we turn to the member model.
/*
* Get Member Groups
*
* Returns only the title and id by default, but additional fields can be passed
* and automatically added to the query either as a string, or as an array.
* This allows the same function to be used for "lean" and for larger queries.
*
* @access public
* @param array
* @param array array of associative field => value arrays
* @return mixed
*/
function get_member_groups($additional_fields = array(), $additional_where = array(), $limit = '', $offset = '')
{
if ( ! is_array($additional_fields))
{
$additional_fields = array($additional_fields);
}
if ( ! isset($additional_where[0]))
{
$additional_where = array($additional_where);
}
if (count($additional_fields) > 0)
{
$this->db->select(implode(',', $additional_fields));
}
$this->db->select("group_id, group_title");
$this->db->from("member_groups");
$this->db->where("site_id", $this->config->item('site_id'));
if ($limit != '')
{
$this->db->limit($limit);
}
if ($offset !='')
{
$this->db->offset($offset);
}
foreach ($additional_where as $where)
{
foreach ($where as $field => $value)
{
if (is_array($value))
{
$this->db->where_in($field, $value);
}
else
{
$this->db->where($field, $value);
}
}
}
$this->db->order_by('group_id, group_title');
return $this->db->get();
}
Why so complicated instead of the exact query that was used previously? Abstraction. This method can now be used by any controller needing access to information about member groups, simply by telling it how much information you want back. You’ll also notice (if you’re a CodeIgniter user) that we’re using Active Record.
Ok, so this method fetches the data from the database, which the controller used to build that snazzy array. Our view file knows what to do with it. Here is where the display and output occurs, and here’s what handles this particular array, in the new Communicate view file:
<?php if (is_array($member_groups)):?>
<h3><?=lang('recipient_group')?></h3>
<ul class="shun">
<?php foreach ($member_groups as $group => $details): ?>
<li class="<?=alternator('even', 'odd')?>"><label><?=form_checkbox($details)?> <?=$group?></label></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
The logic in the view file is simple: loops and basic conditionals only. No data processing. Helpers handle the nitty gritty, in this case the String Helper’s alternator() function replaces our old $switch logic, and the Form Helper’s form_checkbox() takes the array we built earlier, and generates the checkbox form element for us.
The end result is great. Our controller logic is clean and easy to follow, our model allows us to build methods to fetch data in an abstract way so we don’t have to write similar queries over and over, and the view file makes it incredibly easy to modify the markup in the control panel if it is necessary. But it is a slow, painstaking process to perform these conversions, all the while without introducing new bugs from the fresh code.
I warned you it wasn’t glamorous! In fact, some software developers declare such rewrites to be nothing short of sin. In some respects that’s rather true. But the technology landscape has shifted dramatically since the design of ExpressionEngine 1.0, and to setup for great things in the future, ExpressionEngine 2.0 is the exception to the rule, deserving the rewrite and wholly new architecture. Some of the pitfalls of such an undertaking still apply though. The process is slow and tedious, code, even your own well commented code, is harder to read and decipher full intent than it is to just write new code, we cannot neglect the existing version, and our time is dictated more by what code doesn’t exist yet than on what we, as developers, would enjoy working on.
Hopefully this will answer some community member’s questions as to what we’re spending our time with, why it’s taking so long, as well as why we do not stop every couple of weeks to talk about what was accomplished. The 1.x branch has not fallen by the wayside during this long process, and I suspect it will not fall away anytime soon. ExpressionEngine 1.x is a solid product, and an excellent choice of CMS for a wide variety of projects, and will remain so for some time. ExpressionEngine 2.0 isn’t a necessary addition to your arsenal today, but it will provide a foundation to keep ExpressionEngine in your arsenal five years from now. And we can’t wait to share it with you when it’s ready. Now, back to work!


