Extending Craft Commerce for Fun and Profit

By Bryan Fordham

Craft Commerce is a great plugin. Here at Focus Lab, we've used it for several projects, and it has helped us quickly put together great ecommerce experiences for our clients. Occasionally, though, it doesn't do exactly what we need it to.

Fun Profit3

This is not a shortcoming of Craft Commerce. It's just the nature of development. A plugin that's meant to appeal to as wide an audience as possible is not going to be able to do absolutely everything.

It can be tempting to just make changes to Craft Commerce itself. After all, the source code is right in front of you. There are some problems with this: First, it's hard to say what consequences you will cause. Second, you will doom yourself to be constantly updating the same code to keep up to date with changes to the original plugin.

The tactic we've taken to resolve this issue is very simple, and very powerful, in letting us make minor changes without modifying any of the base code in Craft.

One particular client was selling tickets to training events. For each ticket sold we needed to get information on the person who would be using the ticket. We used a custom field called registrant to hold this information. The issue we encountered was that Commerce knew nothing of this field and so would still consider the cart to be valid even if the information wasn't given.

So, how to simply fix this? First, we looked at the code in Commerce_CartController, at the method actionUpdateCart. There's a lot of logic there, and we certainly do not want to duplicate it. We just want to add a bit of validation to it, and run that at the appropriate place in the flow of the checkout process.

If you look at the Commerce code, you'll see that it calls the function redirectToPostedUrl when it is done. This method sends the user to the next step in the process, assuming there were no problems. This is where we were able to make our change.

There were three steps:

First, we created a method to check that there were valid registrants for every ticket in the cart.

Second, we created a controller with Commerce_CartController as the parent class. This new controller just has the redirectToPostedUrl method in it. Our version, however, has the check we need. It looks something like this:

namespace Craft;
class Foo_CartController extends Commerce_CartController
{
  public function redirectToPostedUrl($object = null, $default = null)
  {
    // validate that registrants are properly set
    if (craft()->foo->hasValidRegistrants())
    {
      // all good? carry on
      parent::redirectToPostedUrl($object, $default);
    }
    else
    {
      // go back and complain. Or at least let the user know
      $this->redirect('cart/registrants');
    }
  }
}

Third, we updated the appropriate template to no longer point to the main Commerce Controller, but to instead use our new controller. This was just a matter of updating the hidden field action.:

<input type="hidden" name="action" value="foo/cart/updateCart">

You may wonder how we can call the updateCart method, when we have not included it. The answer is that we did include it. By using Commerce_CartController as the parent, we got all the methods that controller contains. So it will work just as we expect it to. However, when our controller goes to redirect, it will use our new method. Everything else is the same.

This is a simple trick that's not really a trick at all. It is just taking advantage of the object-oriented nature of PHP. It cuts down on code duplication, simplifies what we have to maintain, and lets us make customizations as needed.

Craft and Craft Commerce are extensible by nature. Plugins can be used to not only add new functionality, but modify existing functions. As developers, we need to be strategic in how we do this, lest our changes create more headaches down the road for both us and our users. Using plugins to extend base classes, and changing only what we need, is a simple and clean way to make the changes we need, without having to forever copy and paste code from the original classes.