More on the New Service Architecture in ExpressionEngine 3
I was converting our Rating add-on for ExpressionEngine 3. Rating is one of our oldest and best selling add-ons. I first wrote it when I started working with ExpressionEngine when it first came out in 2004. I was new to coding PHP at that point and I got permission from EllisLab to model my Rating module on the native ExpressionEngine Comment module. Rating allows users to rate and review channel entries much the way they could comment on channel entries through the Comment module. Once a site had Rating up and running for a while, quite a number of ratings and reviews could accumulate in the system. In the control panel for Rating you needed a way to sort through all the data in order to perform various admin and management tasks. So we built up a rather complex set of searching, sorting and filtering functions. When it came time to covert all of this over to ExpressionEngine 3, I got a tummy ache.
So on my first pass at converting Rating I didn't include any of the searching and filtering functions. I instead went to the support team and asked them if those features were actually really needed by our customers. Were they used? Did people care? How many people cared? I needed to be convinced to spend the time to write new code. But then it occurred to me that perhaps EllisLab had exposed their own filtering and searching capabilities as a service in the new ExpressionEngine 3 architecture.
My first stop was to find a good example of the filtering functionality inside the native ExpressionEngine 3 control panel. It was as close as the channel entries editor.
This design pattern represents the array of filters you can use for searching through your data as if they were deletable tags assigned to your results table. For its visual simplicity I have always really liked this approach. I think it was executed quite well in ExpressionEngine 3. The best part is that EllisLab exposed the functionality as a service so that hacks like me can fold it into our own add-ons with relative ease. As a designer using ExpressionEngine this is good news because it unifies the look and feel of the tool that you will be exposing to your clients. As a client it is good news because it reduces the cost of the array of add-ons that you may purchase to augment the functionality of your CMS driven site. When complex UI interactions such as this one are made easier to execute, the cost to build and maintain an add-on goes down drastically.
Normally, building up this type of UI would be quite complex. First it has to be designed, then styled, then coded, then QA'd, and that's all just for the frontend HTML, CSS and Javascript. The whole process repeats for the back end code, the code that will process the application of the filters. With EllisLab abstracting this functionality as a service, I have to do little more than invoke a class, send it some configuration values and then read the incoming data as needed in my own code.
Here's Rating's use of the CP/Filter service:
You can see that I want to let people filter rating results by date, collection, status and rating value. But those are not just pulldowns you are looking at. Each of those filters is a complex type. Just look at the date filter:
In that filter you can choose some default date ranges but you can also supply your own. I don't want to write all of that Javascript code, especially if I know good and well that it's sitting in ExpressionEngine waiting for me. In comes the service.
Here's how I invoke the CP/Filter service in my module code. First, I define a variable for each of my filters and to that variable I assign a configured instance of the CP/Filter model like this:
$statuses = ee('CP/Filter')->make('filter_by_status', 'filter_by_status', $statuses);
Next I feed all of my filters into a combined set of filters like this:
$filters = ee('CP/Filter')
->add('Date')
->add($collections)
->add($statuses)
->add($rating_filters);
Notice how I could simple call 'Date' and ExpressionEngine's CP/Filter service would do the rest for me, creating a complex date field for me.
Next I render the set of filters so that I can send them to my view file so that you will see them when you load that page of the Rating control panel.
$this->cached_vars['filters'] = $filters->render($base_url);
So my filters have been composed and sent to the view file for display, but the same filters I created above can now be used to read from GET variables sent in the query string so that I can actually talk to the database and filter the data I write into a results table, like this:
$ratings->filter('collection', $collections->value());
In that line above, $collections->value() is syntax that allows me to ask the filter I created whether it detected someone having used that filter in the url, and if so, what was it's value.
All in all this is a tight little package of capabilities wrapped into a service that is readily available for use. And with it, you, the designer or client, has a UI that is totally consistent with the rest of ExpressionEngine, but one that lets you work with data in the CMS that is non-native, namely from a 3rd party add-on. This is ultimately saving you time and money as well as improving the consistency of your user experience.
As of this writing, the CP/Filter service has not yet been documented, but once EllisLab gets farther along with their release schedule for ExpressionEngine 3, I suspect it will make its way out there.