Models
A model is a class that fetches data from the database and hands it to the view. Joomla has ready-made base models: for a list you write a single query, and the model does pagination itself. Let us replace the hard-coded posts with real ones from the database.
The table
The component needs its own table. For now create it by hand: open phpMyAdmin and run the query, replacing #__ with the table prefix of your site (you can see it in the names of the other tables):
CREATE TABLE `#__hello_posts` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`body` TEXT NOT NULL,
`published` TINYINT NOT NULL DEFAULT 1,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `#__hello_posts` (`title`, `body`) VALUES
('First post', 'Text of the first post'),
('Second post', 'Text of the second post');How to create the table automatically when the component is installed is shown in the chapter on releasing an extension.
Where models live
components/com_hello/src/
├── Controller/DisplayController.php
├── Model/
│ ├── PostsModel.php the list of posts
│ └── PostModel.php a single post
└── View/
├── Posts/HtmlView.php
└── Post/HtmlView.phpThe model name matches the view name: for view=posts Joomla creates PostsModel itself and passes it to the view.
The list model
// components/com_hello/src/Model/PostsModel.php
namespace My\Component\Hello\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class PostsModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
return $db->getQuery(true)
->select($db->quoteName(['id', 'title', 'created']))
->from($db->quoteName('#__hello_posts'))
->where($db->quoteName('published') . ' = 1')
->order($db->quoteName('created') . ' DESC');
}
}You describe only the query. The methods getItems() (the records of the current page) and getPagination() (the page links) already exist in ListModel.
Calling it from the view
// components/com_hello/src/View/Posts/HtmlView.php
public function display($tpl = null): void
{
$model = $this->getModel();
$this->posts = $model->getItems();
$this->pagination = $model->getPagination();
parent::display($tpl);
}Declare one more property in the class — public $pagination;. Now $this->posts is an array of objects, so the layout changes:
<?php
// components/com_hello/tmpl/posts/default.php
defined('_JEXEC') or die;
use Joomla\CMS\Router\Route;
?>
<h1>Blog</h1>
<ul>
<?php foreach ($this->posts as $post) : ?>
<li>
<a href="<?php echo Route::_('index.php?option=com_hello&view=post&id=' . (int) $post->id); ?>">
<?php echo $this->escape($post->title); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php echo $this->pagination->getListFooter(); ?>Open index.php?option=com_hello&view=posts — the posts from the database are listed, each with a link.
The single-record model
// components/com_hello/src/Model/PostModel.php
namespace My\Component\Hello\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\ItemModel;
use Joomla\Database\ParameterType;
class PostModel extends ItemModel
{
public function getItem($pk = null)
{
$id = (int) ($pk ?: Factory::getApplication()->getInput()->getInt('id'));
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__hello_posts'))
->where($db->quoteName('id') . ' = :id')
->where($db->quoteName('published') . ' = 1')
->bind(':id', $id, ParameterType::INTEGER);
$post = $db->setQuery($query)->loadObject();
if (!$post) {
throw new \Exception('Post not found', 404);
}
return $post;
}
}The post number comes from the id parameter of the address. If there is no such post, the exception with code 404 shows a "Not found" page.
The post page
// components/com_hello/src/View/Post/HtmlView.php
namespace My\Component\Hello\Site\View\Post;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
class HtmlView extends BaseHtmlView
{
public $post;
public function display($tpl = null): void
{
$this->post = $this->getModel()->getItem();
parent::display($tpl);
}
}<?php
// components/com_hello/tmpl/post/default.php
defined('_JEXEC') or die;
?>
<h1><?php echo $this->escape($this->post->title); ?></h1>
<p><?php echo nl2br($this->escape($this->post->body)); ?></p>Click a post in the list — the page index.php?option=com_hello&view=post&id=1 opens with the title and the text.
Common mistake
The model is named differently from the view: the view is posts, the model is PostListModel. Joomla looks for the model by the view name, does not find it, and $this->getModel() returns an empty value — the getItems() call ends with an error. For View\Posts the model must be PostsModel, for View\Post — PostModel.