Skip to content
This repository has been archived by the owner on Nov 5, 2022. It is now read-only.

001. Developing a Basic Component

Astrid edited this page Jan 3, 2020 · 12 revisions

Developing a Basic Component

In this chapter we will ...

First, we need to create a view on the back end.

t1_1

Newly created or Modified files

Newly created files

administrator/components/com_foos/Controller/DisplayController.php

administrator/components/com_foos/Extension/FoosComponent.php

administrator/components/com_foos/Service/HTML/AdministratorService.php

administrator/components/com_foos/View/Foos/HtmlView.php

administrator/components/com_foos/foos.xml

administrator/components/com_foos/script.php

administrator/components/com_foos/services/provider.php

administrator/components/com_foos/tmpl/foos/default.php

components/com_foos/index.html

All changes at a glance

Click here to see all changes compared to the last chapter

More detailed explanations

File Structure

For newcomers:

  • Each PHP file starts with a DocBlock used in automated documentation;
  • in namespaced files the next statement is the namspace, not used in tmpl files;
  • the first executable statement is always defined('_JEXEC') or die; which ensures the file has been loaded by Joomla and not called directly through a web url.

Newly created files

This is the administrator entry point to the Model-View-Controller part of the Foo component. It should be called DisplayController and it should extend BaseController.

The default task is display so the default controller is DisplayController. This controller does not do much other than call its parent controller libraries/src/MVC/Controller/BaseController.php. However, controllers can be used to do initial processing.

So, let us create the DisplayController. As usual, we start with a DocBlock. You will see a DocBlock before each class and function. All of the core code has these DocBlock comments which makes it easy for automated tools to generate documentation of APIs. It also helps some IDEs to provide code completion. And sometimes the comment is helpful for programmers. Here is an example of a typical DocBlock. How to use DocBlocks best you can read in the Joomla! Coding Standards.

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_foos
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

After the DocBlock you should insert the namespace. Namespaces are declared using the namespace keyword. A file containing a namespace must declare the namespace at the top of the file before any other code. I'll explain later how the namespace comes to its name with the file administrator/components/com_foos/foos.xml.

namespace Joomla\Component\Foos\Administrator\Controller;

After the namespace we insert the define or die, so this PHP file can not be executed directly

defined('_JEXEC') or die;

Next we import with the use keyword the specified namespace to the current scope.

use Joomla\CMS\MVC\Controller\BaseController;

We then create the class for the controller, which is Display followed by Controller. We are extending the BaseController class, so we get a lot of functionality for free.

/**
 * Foos master display controller.
 *
 * @since  1.0.0
 */
class DisplayController extends BaseController
{

Now we will set the default view. If nothing is defined, the view will default to the foos view using the default layout. We would not have had to set this variable. But I think it's always better to insert this.

When you call the URL, while using a component, you will notice the view and layout variables; for example index.php?option=com_foos&view=foos&layout=default would tell us to load the foos view using the default layout, so it would load components/com_foos/tmpl/foos/default.php if you are in the front end and administrator/components/com_foos/tmpl/foos/default.php if you are in the back end.

	/**
	 * The default view.
	 *
	 * @var    string
	 * @since  1.0.0
	 */
	protected $default_view = 'foos';

You only need to set the default view if the name differs from the name of your component. We shouldn't have set the variable here, because in the file JOOMLA/libraries/src/MVC/Controller/BaseController.php this is set by default. You can find the code

// Set the default view.
if (\array_key_exists('default_view', $config))
{
	$this->default_view = $config['default_view'];
}
elseif (empty($this->default_view))
{
	$this->default_view = $this->getName();
}

in the constructor. But to explain how the display is created, it is good if the variable assignment remains in the file.


As I already wrote is the display function the first function that is called if no specific task is executed for the component. In this function we call the display function in the parent class that we have extended. That class will do all the hard work and allow the component to display the view.

At the moment, this view is enough for us. Later on our component is going to have more views. A list view that displays a list of all the data, and an edit form where all the data is entered or modified. More on that later.

	/**
	 * Method to display a view.
	 *
	 * @param   boolean  $cachable   If true, the view output will be cached
	 * @param   array    $urlparams  An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
	 *
	 * @return  BaseController|bool  This object to support chaining.
	 *
	 * @since   1.0.0
	 *
	 * @throws  \Exception
	 */
	public function display($cachable = false, $urlparams = array())
	{
		return parent::display();
	}
}

administrator/components/com_foos/Extension/FoosComponent.php is the file for Booting the extension. It is the first file that is executed when Joomla! loads your component. The function boot is the function to set up the environment of the extension like registering new class loaders, etc.
If required, some initial set up can be done from services of the container, eg. registering HTML services.

The call to register the Administrator Service is the place where the component is registered and booted. For more information about the service provider interface see this PR.

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_foos
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\Component\Foos\Administrator\Extension;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Categories\CategoryServiceInterface;
use Joomla\CMS\Categories\CategoryServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use Joomla\Component\Foos\Administrator\Service\HTML\AdministratorService;
use Psr\Container\ContainerInterface;
/**
 * Component class for com_foos
 *
 * @since  1.0.0
 */
class FoosComponent extends MVCComponent implements BootableExtensionInterface, CategoryServiceInterface
{
	use CategoryServiceTrait;
	use HTMLRegistryAwareTrait;
	/**
	 * Booting the extension. This is the function to set up the environment of the extension like
	 * registering new class loaders, etc.
	 *
	 * If required, some initial set up can be done from services of the container, eg.
	 * registering HTML services.
	 *
	 * @param   ContainerInterface  $container  The container
	 *
	 * @return  void
	 *
	 * @since   1.0.0
	 */
	public function boot(ContainerInterface $container)
	{
		$this->getRegistry()->register('foosadministrator', new AdministratorService);
	}
}

Although we are developing the code for a small component, some administrator files are needed. The administrator/components/com_foos/Service/HTML/AdministratorService.php file is later used to add functions/services like featured or multilingual associations. At the moment we do not need this functions. But we need the empty file, because we registered it in administrator/components/com_foos/Extension/FoosComponent.php and Joomla expects this.

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_foos
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\Component\Foos\Administrator\Service\HTML;
defined('JPATH_BASE') or die;
/**
 * Foo HTML class.
 *
 * @since  1.0.0
 */
class AdministratorService
{
}

At the moment our component has only one very simple view. This will later become the list view. But for now it only shows a static text.

There are several files that work together to create the view. We have already created the controller file that calls the view. Later we will create the model file that prepare the data. Now we skipped that part. But we do not skipp the part in that we will create the view files that display the information.

Actually the administrator/components/com_foos/View/Foos/HtmlView.php file is where all the toolbar buttons and title for the view are defined, and it calls the model to get the data ready to give to the view. But for now we only call the parent class display function to show the default template: parent::display($tpl);.

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_foos
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\Component\Foos\Administrator\View\Foos;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
/**
 * View class for a list of foos.
 *
 * @since  1.0.0
 */
class HtmlView extends BaseHtmlView
{
	/**
	 * Method to display the view.
	 *
	 * @param   string  $tpl  A template file to load. [optional]
	 *
	 * @return  void
	 *
	 * @since   1.0.0
	 */
	public function display($tpl = null): void
	{
		parent::display($tpl);
	}
}

This is an XML (manifest) file that tells Joomla! how to install our component.

Just like modules and plugins, components have an installation XML file that will tell Joomla! all about the component your are installing. This file is also known as an XML manifest.
The installation XML file contains details such as

  • the version number,
  • all the files and folders used by the component,
  • database installation file details,
  • and component parameter.

So, create a new file and call it foos.xml. This is the extension name without the com_ prefix. We will then go through each line and take a look at what it all does.

The first line is nothing Joomla! specific. It tells us that this is a XML file.

<?xml version="1.0" encoding="utf-8" ?>

Then we are telling Joomla! that this is a component. And we are saying, that it uses the upgrade installation method, so you can install over the previous version without losing any data. Sometimes you find a parameter with a version number. The intention of this number was to tell Joomla! that this component need this version as a minimum. But it is so that this number is not checked in reality so far. More informations you can find here.

<extension type="component" method="upgrade">

Then we define the name of the component. In this case COM_FOOS. More information here.

	<name>COM_FOOS</name>

The next lines are pretty self-explanatory. Just put in your own details.

	<creationDate>[DATE]</creationDate>
	<author>[AUTHOR]</author>
	<authorEmail>[AUTHOR_EMAIL]</authorEmail>
	<authorUrl>[AUTHOR_URL]</authorUrl>
	<copyright>[COPYRIGHT]</copyright>
	<license>GNU General Public License version 2 or later;</license>

This is the first version of the component. We will give it the version number 1.0.0. If we fix a minor bug the next version number would be 1.0.1. If we introduce a new feature we should call the next version 1.1.0. If we did some big changes, then the next version should be called 2.0.0. It is important that you use the three part version numbering, as it will make it easier when you create updates later.

Joomla strictly follows Semantic Versioning and you should do that as well.

	<version>1.0.0</version>

In the description field we are using a language string. So this text will change based on the language files we provide in one of the next chapters. The description of the component is shown when you install it. It is also shown if you open the menu System and click on Manage | Extensions. [bild1]

	<description>COM_FOOS_XML_DESCRIPTION</description>

Next we set the namespace tag. In the previous chapter I have tell you, why we use namespaces.

What should the namespace of your components be?

  • The first item of the namespace should be your Company name. For this tutorial I used Joomla. The namespace is used in the extension to distinguish its code from code in other extensions. So it possible to use identical class names without conflict. The namespace is also used to register a service provider.

  • The second item is the type of extension: Component, Module, Plugin or Template.

  • The third item is the extension name without preceding com_, mod_, etc., Foos in this case.

	<namespace>Joomla\Component\Foos</namespace>

The script file allows you to run code when your component is installed, uninstalled, or updated.

        <scriptfile>script.php</scriptfile>

Like Joomla! itself, components have a front end and a back end. In this first chapter we do not use the front end. The administrator/components/com_foos folder contains all the files used by the back end. For individual files, you use the filename tag. If you want to include an entire folder with all files and sub folders within it, you better use the folder tag.
The files for the back end of your Joomla! component are all listed within the administration tag. Perhaps you notice that in here is a menu tag as well. This is the menu item that will appear on the component menu in the back end of your website. We are using the language string COM_FOOS, which will be replaced with text from our language file in chapter 8.

	<administration>
		<!-- Menu entries -->
		<menu view="foos">COM_FOOS</menu>
		<submenu>
			<menu link="option=com_foos">COM_FOOS</menu>
		</submenu>
		<files folder="administrator/components/com_foos">
			<filename>foos.xml</filename>
			<folder>Controller</folder>
			<folder>Extension</folder>
			<folder>Service</folder>
			<folder>View</folder>
			<folder>services</folder>
			<folder>tmpl</folder>
		</files>
	</administration>

We then can add the dlid-tag <dlid prefix="dlid=" suffix="" />. You need this, if you want to use the Download key manager. In general, you only need this for commercial extensions. For more information, visit Github.

	<dlid prefix="dlid=" suffix="" />

We then need to close off the extension tag, so Joomla! knows that it is the end of the file.

</extension>

In addition to the installation XML file, there are quite a few files you need to create to make a simple component.

The installer script file allows you to run code

  • when your component is installed,
  • before it is installed,
  • when your component is uninstalled,
  • before it is uninstalled,
  • or when your component is updated.

Create the file script.php and add the following:

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_foos
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('_JEXEC') or die;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
/**
 * Script file of Foo Component
 *
 * @since  1.0.0
 */
class Com_FoosInstallerScript
{
	/**
	 * Minimum Joomla version to check
	 *
	 * @var    string
	 * @since  1.0.0
	 */
	private $minimumJoomlaVersion = '4.0';
	/**
	 * Minimum PHP version to check
	 *
	 * @var    string
	 * @since  1.0.0
	 */
	private $minimumPHPVersion = JOOMLA_MINIMUM_PHP;
	/**
	 * Method to install the extension
	 *
	 * @param   InstallerAdapter  $parent  The class calling this method
	 *
	 * @return  boolean  True on success
	 *
	 * @since  1.0.0
	 */
	public function install($parent): bool
	{
		echo Text::_('COM_FOOS_INSTALLERSCRIPT_INSTALL');
		return true;
	}
	/**
	 * Method to uninstall the extension
	 *
	 * @param   InstallerAdapter  $parent  The class calling this method
	 *
	 * @return  boolean  True on success
	 *
	 * @since  1.0.0
	 */
	public function uninstall($parent): bool
	{
		echo Text::_('COM_FOOS_INSTALLERSCRIPT_UNINSTALL');
		return true;
	}
	/**
	 * Method to update the extension
	 *
	 * @param   InstallerAdapter  $parent  The class calling this method
	 *
	 * @return  boolean  True on success
	 *
	 * @since  1.0.0
	 *
	 */
	public function update($parent): bool
	{
		echo Text::_('COM_FOOS_INSTALLERSCRIPT_UPDATE');
		return true;
	}
	/**
	 * Function called before extension installation/update/removal procedure commences
	 *
	 * @param   string            $type    The type of change (install, update or discover_install, not uninstall)
	 * @param   InstallerAdapter  $parent  The class calling this method
	 *
	 * @return  boolean  True on success
	 *
	 * @since  1.0.0
	 *
	 * @throws Exception
	 */
	public function preflight($type, $parent): bool
	{
		if ($type !== 'uninstall')
		{
			// Check for the minimum PHP version before continuing
			if (!empty($this->minimumPHPVersion) && version_compare(PHP_VERSION, $this->minimumPHPVersion, '<'))
			{
				Log::add(
					Text::sprintf('JLIB_INSTALLER_MINIMUM_PHP', $this->minimumPHPVersion),
					Log::WARNING,
					'jerror'
				);
				return false;
			}
			// Check for the minimum Joomla version before continuing
			if (!empty($this->minimumJoomlaVersion) && version_compare(JVERSION, $this->minimumJoomlaVersion, '<'))
			{
				Log::add(
					Text::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA', $this->minimumJoomlaVersion),
					Log::WARNING,
					'jerror'
				);
				return false;
			}
		}
		echo Text::_('COM_FOOS_INSTALLERSCRIPT_PREFLIGHT');
		return true;
	}
	/**
	 * Function called after extension installation/update/removal procedure commences
	 *
	 * @param   string            $type    The type of change (install, update or discover_install, not uninstall)
	 * @param   InstallerAdapter  $parent  The class calling this method
	 *
	 * @return  boolean  True on success
	 *
	 * @since  1.0.0
	 *
	 */
	public function postflight($type, $parent)
	{
		echo Text::_('COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT');
		return true;
	}
}

As the name stayed the install function is run when your component is installed. At the moment it just echoed a text. You could potentially add some code in here, for example, to install sample data.

The uninstall function is run, when someone uninstalls the component com_foos, and currently it will just some text, too.

The update function is run whenever you update the component.

The preflight function runs before the component is installed. You could add code in here to check prerequisites, such as PHP version, or check if an other extension is installed or not.

The postflight function runs after your component has been installed. You could use this function to set default values for component parameters or for tiding up.

The administrator/components/com_foos/services/provider.php file is used to implement the component services. Through an interface the component class defines which services it provides. For this it uses a dependency injection container or DI Container. To register the Service Provider ComponentDispatcherFactory and MVCFactory is mandatory for every component. To register CategoryFactory is optional. This is here in advance for chapter 12 - Adding categories. Like that we can introduce new services later without a BC break and do not loose any type hinting.

If you are not familiar with the concept of DI Container you can see the explanation and some examples here:

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_foos
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('_JEXEC') or die;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\CategoryFactory;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\HTML\Registry;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Component\Foos\Administrator\Extension\FoosComponent;
/**
 * The foos service provider.
 * https://github.com/joomla/joomla-cms/pull/20217
 *
 * @since  1.0.0
 */
return new class implements ServiceProviderInterface
{
	/**
	 * Registers the service provider with a DI container.
	 *
	 * @param   Container  $container  The DI container.
	 *
	 * @return  void
	 *
	 * @since   1.0.0
	 */
	public function register(Container $container)
	{
		$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Foos'));
		$container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Foos'));
		$container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Foos'));
		$container->set(
			ComponentInterface::class,
			function (Container $container)
			{
				$component = new FoosComponent($container->get(ComponentDispatcherFactoryInterface::class));
				$component->setRegistry($container->get(Registry::class));
				return $component;
			}
		);
	}
};

More information you can find on Github.

And here in this file is the text we want to show. All the effort for the output of the text Hello Foos.

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_foos
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('_JEXEC') or die;
?>
Hello Foos

I told you in chapter 1 that the file index.html is no longer necessary. That's the way it is. Here I have only add this file because I want to put together an installation package, but Joomla reports an error during the installation if there is no folder for site, or if an empty folder is entered in the installation file. And right now we do not have any content for the site. The insertion of this file is therefore only a help to prevent error messages during installation.

<!DOCTYPE html><title></title>

Example in Joomla 4

Side Note

Why is there the line defined('_JEXEC') or die;?

The line defined('_JEXEC') or die; is commonly found at the start of Joomla! PHP files.

The defined or die check makes sure that _JEXEC has been defined in the pathway to get to the file. The check should be added to all files that when accessed directly cause a path exposure. This is used to ensure that a file that could expose path information because functions, variables or classes aren't defined in that file show PHP's error reporting and expose a path.

More information can be found on the page https://docs.joomla.org/JEXEC.

Test your component

Now you can zip all files and install them via Joomla Extension Manager. After that you can see a link to your component in the left side menu. Clicking on this link will open the basic back end view.

Concluding Remark

Now we have a basic view in the back. Up to now we have no view in the front end. We are going to work on this in the next chapter.

Component development for Joomla 4 is a fairly simple, straightforward process. Using the techniques described in this tutorial, an endless variety of components can be developed with little hassle.

Overview of all files

Overview