Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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.php

The 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.

Thanks for reading!

Donate to the site — we work hard for you.