From Joomla! Documentation
Revision as of 19:26, 19 September 2022 by Cmb (Some changes for Word2Watch compliance and other markup changes.)
This page describes the Model-View-Controller design pattern as implemented in Joomla.
When Joomla starts to process a request from a user, such as a GET for a particular page or a POST containing form data, one of the first things that Joomla does is to analyse the URL to determine which component will be responsible for processing the request and hand control over to that component.
It will do this by running the entry point component PHP file for that component. If the component is called com_example, then Joomla will run:
- on the Frontend site: components/com_example/example.php
- on the Backend admin: administrator/components/com_example/example.php
If you are developing a component you could actually put all of your component code into those two example.php files. However, an advantage of following the Joomla MVC pattern is that you can make full use the Joomla library MVC classes, which greatly reduces the amount of code you need to write.
Joomla MVC Overview
Entry Point Code
The main role of the entry point PHP file (example.php for com_example) is to determine which controller to run. It does this based on the task parameter (described in more detail later) and involves:
- determining what Controller class should be loaded
- determining where to find the code for that class
- getting an instance of that class
- calling the appropriate method of that class.
The controller is responsible for analysing the user's request, checking that the user is allowed to perform that action and determining how to satisfy the request. The latter will involve:
- determining which model (or models) will be needed to satisfy the request, and creating an instance of that model
- making calls to model methods to make any required database updates
- determining which view should be used to present the web page to the user, and creating an instance of that view or
- if instead the user should receive a redirect to another URL, then determining that redirect URL.
The view specifies what should appear on the web page, and collates all the data necessary for outputting the HTTP response.
After the controller creates the view instance it calls the view's setModel() method and passes the model instance. In this way the view knows which model to use, and calls the model's methods to obtain the data required for returning to the user.
The view doesn't output HTML but delegates this to the layout. The layout contains PHP code which runs within the context of the method (usually display()) of the view, which means that if the view holds the response data in, for example, $this->items then the layout can access that same $this->items when it is outputting the HTML.
Separating the view and layout like this enables another level of flexibility, as you can easily set up a layout override to output the view data using your own preferred HTML.
The model encapsulates the data used by the component. In most cases this data will come from a database, either the Joomla database, or some external database, but it is also possible for the model to obtain data from other sources, such as via a web services API running on another server. The model is also responsible for updating the database where appropriate. The purpose of the model is to isolate the controller and view from the details of how data is obtained or amended.
If the component is displaying a form which is defined in XML using the Joomla Form approach, the model handles the setting-up and configuration of the Form instance, ready for the layout to output fields using $form->renderField() etc.
The output from the component (specifically the HTML output from the layout) is not directly output as the HTTP response, but rather is captured and buffered by Joomla. Once the layout has generated the output, the component hands control back to the Joomla framework which then loads and executes the template. The template combines the output from the component, and any modules that are active on the current page, so that it can be delivered to the browser as a single page.
The HTTP Request task Parameter
Joomla uses the HTTP Request task parameter to determine which controller should be used. The task parameter can be sent in an HTTP GET or an HTTP POST – in fact, Joomla doesn't really make a distinction between GET and POST parameters – but the task parameter is just an ordinary HTTP parameter, nothing special.
In the core Joomla entry point PHP files you'll almost always see something like:
$controller = JControllerLegacy::getInstance('example'); $controller->execute(JFactory::getApplication()->input->get('task'));
with example replaced by Contact, Content, Modules etc.
The getInstance() method of JControllerLegacy (or BaseController, (as it is now known after Joomla 3.8) is where all the magic happens. It
- examines the task parameter
- based on this parameter it decides which controller class to load, and in which file it expects to find that class
- creates an instance of this controller class (which gets returned from this function)
- resets the task parameter to be the method which should be called.
The next line of code ($controller->execute(JFactory::getApplication()->input->get('task'));) then calls the execute() method of the controller instance with the parameter which is now the method to run, and the execute method basically runs the passed-in method.
The task parameter is of the form x.y, where x represents the controller type and y the method to call. The controller type may be absent in which case the method in controller.php is run. If the controller type is present then one of the controllers in the controllers folder is used. If the task parameter is not set at all then the display() method in controller.php is run. The table below lists the possibilities for a component called com_example.
|Form of task||Controller file||Controller class||Controller method|
|method(no controllerType set)||controller.php||ExampleController||method|
|(task not set)||controller.php||ExampleController||display|
The above is true both for the Frontend site and the Backend Administrator, except that for the Administrator it all happens under the administrator folder.
Joomla follows the Post/Redirect/Get pattern, which means that when a user submits a form in an HTTP POST, then rather than Joomla responding to the POST with an HTML page, redirects to another page which the browser will access with an HTTP GET.
This is typified when the Administrator displays the Content/Articles page, as shown in the first diagram. The action to be performed is indicated by the task parameter, and both it and related data are sent in the POST request. Once the action is performed the next web page to be displayed is defined in the HTTP Redirect.
The second diagram depicts the pattern when an administrator edits an article. In this case there is the additional step of displaying the edit form, which again Joomla handles by sending an HTTP Redirect in response to the original request to edit an article. (At point 3 Joomla (in the BaseDatabaseModel) stores an edit id in the session and component controllers check this id at point 4 to ensure that users can't just specify the URL of the form directly without going through point 3).
The red numbers in green circles in the diagram refer to the different types of HTTP requests which Joomla is handling. We'll see in the next section that different Joomla MVC classes have been defined to cater to these five cases.
Joomla MVC Classes
There are several Joomla library MVC classes under libraries/src/MVC and this section aims to give an overview of these classes and explain when your own component should use each class. It doesn't attempt to provide a full description of each class's functionality.
MVC Base Classes
The 3 MVC base classes are BaseController (previously called JControllerLegacy), HtmlView (previously called JViewLegacy) and BaseDatabaseModel (previously called JModelLegacy). The functionality within BaseController includes:
- the getInstance() and execute() methods described above, which together find the component controller and run the appropriate method, all based on the value of the task parameter.
- functions for finding the appropriate view class to use (based on the setting of the view parameter within the HTTP request), and where to look for the PHP file which should contain this class.
- running the class PHP file, and creating an instance of the view
- using the BaseDatabaseModel::getInstance() method to obtain an instance of the model which is associated with the view being used
- the default display() method, which gets instances of the view and model classes (including providing the view instance with a link to the model instance), and then calls the display() method of the view class.
The HtmlView class includes:
- the display() method, which runs the layout file
- code for finding the layout file, taking into account a layout override which may have been put into the template folder structure
- code for setting the model instance, and subsequently retrieving it
- a general purpose get() method which when called like get("something") converts it into a call on the model $model->getSomething().
The BaseDatabaseModel contains:
- the static getInstance() method which finds the PHP file containing the code for the model, runs it, and returns an instance of the model class.
- code for getting an instance of the Table class which will handle the interface to the underlying database table for this component.
- base code for creating a model "state". This feature is useful if the component and/or several modules shown on the web page all use the same base data. In this case they may share the same model instance and the model "state" acts like a container for sharing the values of items across the component and modules.
In general, using base classes is a good option if your component is displaying a single item on a site page.
Higher-level View Classes
There are 3 higher-level view classes, all of which inherit directly from the HtmlView base class. The name of the class (and so the name of the PHP file) gives a good indication of its use:
- CategoryView – for displaying a category and its children
- CategoriesView – for displaying all the categories at a certain level in the category hierarchy, and the number of items associated with each category
- CategoryFeedView – for generating a feed.
Using the CategoryView or CategoriesView classes could be useful if you're following the paradigm of how com_content outputs this information on the site, but otherwise probably not so useful.
The CategoryFeedView class will help if you're providing a feed, as described in Adding a Feed.
Higher-level Controller Classes
There are 2 higher-level controller classes, each of which inherit from BaseController.
AdminController contains methods which handle the types of operations that can be performed on multiple items, for example:
- changing the publishing state
- changing the relative ordering of records
However, note that it doesn't support the operations enabled by the Batch button on e.g. the Content/Articles page. The code generally calls the related model method to effect the operation, sets up the message based on the success of the model operation, and sets up the redirect back to the same page. For this reason it's very useful in case 2 shown in the diagrams above in Post/Request/Get pattern.
The name AdminController suggests that this controller is to be used only on the Backend Administrator functionality, however, this is not the case. It's appropriate to use it on the site Frontend as well.
FormController contains methods which are associated with editing an individual item
- handling a request to edit an item – which involves checking that the user is allowed to edit the item and that it isn't already checked out, and if these checks pass then the item is checked out (if that component has enabled checkout) and a redirect is issued to display the edit form
- handling a request to add a new item – which involves checking that the user is allowed to edit the item and results in a redirect to display a blank edit form
- handling the Save of an item being edited or created from new – the code checks that the user is allowed to perform the operation and calls the appropriate model method to save the new/edited item.
- handling the Cancel of an edit, redirecting the user back to the appropriate page (and checking-in the record if required).
- handling the operations initiated through the Batch button
The FormController is thus well suited to cases 3 and 5 shown in the diagrams above in Post/Request/Get pattern.
Higher-level Model Classes
The diagram shows the inheritance tree of the Joomla library MVC models.
ItemModel is almost the same as BaseDatabaseModel. It just has an extra getStoreId() method which is relevant when you have a component and/or several modules sharing the same model and you want to distinguish between data sets relevant to each.
In addition to getStoreId(), ListModel has capability relating to obtaining a set of records for display on a web page, including support for pagination. Note that the pagination capability may still be slightly different between the Frontend and Backend – see this issue. The ListModel is useful for supporting case 1 in the above diagrams.
FormModel includes support for Joomla forms, both for setting up the form so that it can be displayed, and also so that the form data sent in the POST can be validated. In addition, it has methods for implementing checkin and checkout of database records. So it's suitable for handling cases 3 and 4 in the diagrams above.
AdminModel extends FormModel, so it has all the capability for handling forms, but in addition has methods for handling database updates – including capability for adding, updating and deleting records – as well as support for handling operations in batch. So it's suitable for handling cases 2 and 5 in the diagrams above. As with the AdminController, this model is not just appropriate for the administrator functionality, but can be used on the Frontend as well.
However, although the FormModel and AdminModel support different cases in the flow associated with editing a record, in practice it's common to use the same model for all the different steps of the flow. All the Joomla core components use the same model across these steps, and so they all extend AdminModel instead of FormModel.
Something to be aware of if you are using the same model across the "edit item" flow is that your model code is performing 2 purposes:
- preparing data to be shown on a web page
- preparing a form, either for display on a web page or for validating POST data
When you're using the model because you're handling a POST (i.e. cases 3 and 5 in the diagram) then any effort expended in preparing the data for a web page is going to be wasted. (In fact, in the FormController getModel() method call, the parameter $config is set to array('ignore_request' => true) by default, which results in the model's populateState method not being run, perhaps to save this wasted effort.)
As we've seen Joomla:
- uses the task parameter for defining actions, generally resulting in some sort of database update, and always ending up with a redirect
- doesn't set a task parameter when a web page is to be displayed
- has rich functionality in higher level controller and model classes which can greatly simplify your code (all of which can be used on both the front end and back end).
The choice of which controller and model classes to extend is easier on the Backend, because you just follow the pattern of the Joomla core components.
For the Frontend here's a rough guide:
Simply displaying a record or a set of records, without providing the ability to change anything:
- controller extends BaseController
- model extends BaseDatabaseModel or (particularly if you're sharing a model between a component and modules) ItemModel if it's a single record, ListModel if it's multiple records.
Displaying a form with multiple records (but the form isn't defined in an XML file), including the providing the ability to select several records and apply some sort of operation to them (eg delete, publish):
- controller extends BaseController
- model extends ListModel – except if you're using the same model for displaying the form and handling the updates, in which case use AdminModel
Handling the HTTP POSTs from that form in subcontrollers:
- controller extends AdminController
- model extends AdminModel
Displaying a form with a single record, where the form is defined in an XML file, and allow the user to edit it, or a blank record and allowing the user to create a record:
- controller extends BaseController
- model extends FormModel – except if you're using the same model for displaying the form and handling the updates (as is usually the case), in which case use AdminModel
Handling the HTTP POSTs from that form in subcontrollers:
- controller extends FormController
- model extends AdminModel
- Understanding Output Overrides
- JModelLegacy and newer
- JModelAdmin and newer
- JViewLegacy and newer
- JControllerForm and newer
- JControllerAdmin and newer
Component Development Series