Все статьи цикла:

  1. Разработка расширения для Joomla! 3.0 – подготовка
  2. Текущая статья
  3. Разработка расширения для Joomla! 3.0 – создаем ядро
  4. Разработка расширения для Joomla! 3.0 – больше функционала
  5. Разработка расширения для Joomla! 3.0 – интерфейс администратора и доработка кода

Для того чтобы иметь хороший рабочий пример для статей на тему разработки компонента для Joomla! 3.0, я (David Hurley) выбрал вариант прохождения непосредственно по процессу написания реального расширения. Этот компонент будет доступен для обзора и загрузки с сопроводительного веб-сайта, на который я буду ссылаться в этой и будущих статьях. Моя цель – это написание полноценного компонента, а не примера вроде "Hello World", для того чтобы продемонстрировать ключевые моменты реальной разработки компонента.

Шаг 1: Опишите основную схему компонента

Первым делом мы должны создать общий обзор файлов, папок, таблиц базы данных и связанных полей. Для нашего компонента мы создадим следующую систему.

Детали компонента

Название: Lendr
Название компонента: com_lendr
Описание: Lendr – это компонент Joomla! 3.0 (использующий Bootstrap), который позволит пользователям создавать профиль, добавлять книги в коллекции своей библиотеки, просматривать библиотеки других пользователей, попросить книгу в долг, добавлять книги в списки желаемых (wishlist), а также подписываться на список ожидания определенной книги.

Основные функции

Новый компонент Lendr будет иметь следующий набор возможностей:

  • Аккаунты пользователей / Профили
  • Книги / Библиотеки для пользователей
  • Списки для желаемых книг
  • Одалживание / Заимствование книги
  • Запросы на заимствование книги
  • Списки ожиданий на уже заимствованные книги

Теперь мы должны написать базовую структуру необходимых нам файлов. Это точно не будет исчерпывающий список, и он, скорее всего, будет изменяться в процессе написания компонента. Однако, имея изначальную схему, мы сможем хоть как-то придерживаться нашего пути. Вот первоначальная схема ключевых файлов необходимых для Lendr:

ControllersModelsViewsTablesMisc.
Save
List
Add
Edit
Lend
Delete
Wish
Review
Request
Default
Book
Default
Library
Profile
Review
Waitlist
Wishlist
Book
Wishlist
Library
Profile
Waitlist
Review
Book
Wishlist
Library
Waitlist
Review
Install
Router
XML

Основные файлы

Теперь, когда мы все выписали, мы начинаем создавать эти файлы в нашей структуре папок.

Шаг 2: Создайте файлы базы данных

Мы начинаем с создания файлов базы данных. Мы сохраняем эти файлы в папке таблиц, которая расположена во фронтэнде нашего компонента. Мы создаем все файлы, которые описали в нашей схеме. Вот один из этих фалов /components/com_lendr/site/tables/book.php:

<?php defined( '_JEXEC' ) or die( 'Restricted access' );
class TableBook extends JTable
{
    /**
    * Constructor
    *
    * @param object Database connector object
    */
    function __construct( &$db ) 
    {
        parent::__construct('#__lendr_books', 'book_id', $db);
    }
}



В нашем случае файл таблицы содержит одну функцию конструктора. Эта функция описывает имя таблицы, которая связана с этим JTable файлом и также определяет поле первичного ключа book_id.

Во время создания этих таблиц целесообразно начать процесс создания скрипта install.mysql.sql, который будет использоваться при установке компонента из панели управления Joomla!. Вот так выглядит начало файла /administrator/components/com_lendr/admin/install.mysql.sql:

CREATE TABLE IF NOT EXISTS `#__lendr_books` (
 `book_id` int(11) NOT NULL AUTO_INCREMENT,
 `user_id` int(11) DEFAULT NULL,
 `isbn` varchar(255) DEFAULT NULL,
 `title` varchar(255) DEFAULT NULL,
 `summary` text DEFAULT NULL,
 `pages` varchar(55) DEFAULT NULL,
 `image` varchar(255) DEFAULT NULL,
 `publish_date` varchar(255) DEFAULT NULL,
 `created` datetime NOT NULL,
 `modified` datetime NOT NULL,
 `lent` tinyint(2) DEFAULT NULL,
 `due_date` datetime NOT NULL,
 `lent_uid` varchar(255) DEFAULT NULL,
 `published` tinyint(2) DEFAULT 0,
 PRIMARY KEY (`book_id`)
);


Мы будем продолжать добавлять информацию в этот файл по мере прохождения по процессу создания наших таблиц.

Шаг 3: Начните создание папок и файлов компонента

После создания таблицы базы данных мы создаем структуру файлов для нашего компонента. Ниже приведена базовая структура директорий:

com_lendr/
    admin/
        controllers/
        models/
        views/
        index.html
        install.mysql.sql
        lendr.php
    site/
        assets/
        controllers/
        helpers/
        language/
        models/
        tables/
        views/
        index.html
        lendr.php
        router.php
install.php
lendr.xml



Все эти файлы и папки важны, но не все они необходимы. Вскоре мы начнем работу с каждым из этих файлов и функциями, которые они содержат. По ходу действия мы выясним назначение каждого их них.

Шаг 4: Создайте файлы установки, точку входа, контроллеры и контроллеры представления

На этом шаге мы добавим контент в некоторые файлы. Сначала мы рассмотрим файлы установки, потом мы поработаем с контроллерами и, наконец, добавим наши контроллеры представлений.

Файлы установки

Файлы корневого уровня, это файлы, которые использует Joomla! во время установки компонента. Они расположены в папке вашего компонента com_lendr вне папок site и admin. Это XML-файл (или по другому его называют «манифест-файл») установки, который содержит в себе описание компонента, связанные файлы, меню и языковые файлы. Также это файл install.php, который содержит в себе некоторые функции, выполняющиеся во время установки. Название файла может быть любым, но ссылка на него должна быть четко указана в XML-файле. Эти функции не обязательно использовать, но они могут помочь в выполнении каких-то дополнительных действий во время установки компонента.

lendr.xml

<extension type="component" version="2.5.0" method="upgrade">
    <name>COM_LENDR</name>
    <creationDate>2013-01-31</creationDate>
    <author>Spark</author>
    <authorEmail>info [at] sparkbuilt.com<;/authorEmail>
    <authorUrl>http://lendr.sparkbuilt.com</authorUrl>
    <copyright>Copyright Info</copyright>
    <license>License Info</license>
    <version>1.0.0</version>
    <description>COM_LENDR_DESCRIPTION</description>



Первый блок деталей определяет информацию о компоненте. Эта информация отображается в Менеджере расширений Joomla! и также сохраняется в таблице расширений (#__extensions).

<install>
    <sql>
    <file charset="utf8" driver="mysql">mysql.install.sql</file>
    </sql>
</install>


Этот блок говорит Joomla! где находятся SQL файлы компонента. Они выполняются во время установки для создания необходимых таблиц базы данных. Вы можете установить свой кодировку символов, а также тип драйвера.


Вы также можете определить блок uninstall со схожей структурой для определения SQL инструкций, которые необходимо выполнить при удалении компонента.

<files folder="site">
    <folder>assets</folder>
    <folder>controllers</folder>
    <folder>helpers</folder>
    <folder>languages</folder>
    <folder>models</folder>
    <folder>tables</folder>
    <folder>views</folder>
    <filename>index.html</filename>
    <filename>lendr.php</filename>
    <filename>router.php</filename>
</files>



Этот блок определяет папки, которые будут установлены во фронт-энд компонента. Не обязательно указывать каждый файл, просто папки и файлы корневого уровня. По всем папкам будет выполнен рекрусивный поиск, и все файлы будут добавлены.

<scriptfile>install.php</scriptfile>


Файл скрипта определяет набор функций, которые выполняются во время установки. В нашем случае мы назвали его install.php.

<languages folder="site">
    <language tag="en-GB">languages/en-GB/en-GB.com_lendr.ini</language>
</languages>


Раздел «languages» определяет необходимые языковый файлы. Они будут установлены в папку languages под соответствующий языковой тег.

<administration>
    <menu link="option=com_lendr" img="components/com_lendr/assets/images/lendr_icon.png">COM_LENDR</menu>
    <submenu>
       <menu view="settings" img="components/com_lendr/assets/images/settings_icon.png"
       alt="LENDR/Settings">COM_LENDR_SETTINGS</menu>
    </submenu>


Следующий блок определяет детали администраторской части, файлы компонента админки, а также пункты меню. Эти пункты меню могут быть связаны с картинками. Пути до картинок являются относительными папки administrator компонента.

<files folder="admin">
        <folder>controllers</folder>
        <folder>languages</folder>
        <folder>models</folder>
        <folder>views</folder>
        <filename>lendr.php</filename>
        <filename>index.html</filename>
        <filename>install.sql</filename>
    </files>
     
    <languages folder="admin">
        <language tag="en-GB">languages/en-GB/en-GB.com_lendr.ini</language>
        <language tag="en-GB">languages/en-GB/en-GB.com_lendr.sys.ini</language>
    </languages>
    </administration>
</extension>


Также внутри тега administration находятся папки и файлы, которые должны быть установлены в администраторскую часть, а также языковые файлы админки компонента.

Более подробно о манифест-файле читайте в статье "Разработка компонента для Joomla 2.5 - пакет установки". О языковых файлах можно почитать в статье "Разработка компонента для Joomla 2.5 - языковые файлы".

install.php

defined( '_JEXEC' ) or die( 'Restricted access' );
jimport('joomla.installer.installer');
jimport('joomla.installer.helper');


В первом блоке мы определяем классы установщика и хелпера, которые хотим подключить.

/**
* Method to install the component
*
* @param mixed $parent The class calling this method
* @return void
*/
function install($parent)
{
    echo JText::_('COM_LENDR_INSTALL_SUCCESSFULL');
}


Функция install выполняется после окончания установки компонента и обычно включает в себя сообщение о успешной установке. Текст должен использовать языковые строки, которые определены в администраторской языковой папке в файле XX-XX.com_lendr.sys.ini.

/**
* Method to update the component
*
* @param mixed $parent The class calling this method
* @return void
*/
function update($parent)
{
    echo JText::_('COM_LENDR_UPDATE_SUCCESSFULL');
}


Функция обновления выполняется тогда, когда метод установки определен как обновление.

/**
* Method to run before an install/update/uninstall method
*
* @param mixed $parent The class calling this method
* @return void
*/
function preflight($type, $parent)
{
    ...
}
 
function postflight($type, $parent)
{
    ...
}



Здесь вы можете определить специфические функции, которые хотите выполнить либо до начала установки, либо после её завершения. Подпробнее об этих функциях вы можете узнать из статьи "Разработка компонента для Joomla 2.5 - скрипт установки / обновления / удаления и сервер обновлений".

Корневой файл (lendr.php)

Файл lendr.php в корне папки site, это первый файл, который распознает и читает Joomla! после установки компонента. Это точка входа нашего компонента, которая перенаправляет задачи на контроллеры, подключает хелперы, CSS-файлы и JS-файлы, библиотеки плагинов и другие базовые вещи, которые нужны компоненту. Файл будет загружать таблицы, связанные с компонентом; импортировать все плагины, которые существуют в группе плагинов "lendr"; определять запрошенный пользователем контроллер и затем выполнять соответствующий контроллер, основываясь на этом запросе. По мере написания компонента этот файл будет постоянно расширяться.

<?php // No direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
//sessions
jimport( 'joomla.session.session' );
 
//load tables
JTable::addIncludePath(JPATH_COMPONENT.'/tables');
 
//load classes
JLoader::registerPrefix('Lendr', JPATH_COMPONENT);
 
//Load plugins
JPluginHelper::importPlugin('lendr');
 
//application
$app = JFactory::getApplication();
 
// Require specific controller if requested
if($controller = $app->input->get('controller','default')) {
    require_once (JPATH_COMPONENT.'/controllers/'.$controller.'.php');
}
 
// Create the controller
$classname = 'LendrController'.$controller;
$controller = new $classname();
 
// Perform the Request task
$controller->execute();

Контроллеры

Контроллеры в Joomla! 3 компоненте создаются как класс с одной функцией. Как правило, название контроллера определяет задачу этого контроллера. Это отличается от предыдущих версий Joomla!, где контроллер выполнял сразу несколько задач относящихся к конкретным областям компонента. Создание компонентов с одной функцией выполнения позволяет связывать контроллеры в единую цепочку, формируя при этом легкодоступный путь для отслеживания действий и устранению ошибок. Располагаются они в папке site/controllers/. Ниже приведен пример одного из контроллеров, который мы определим в Lendr, а также пример контроллера по умолчанию с базовыми функциями.

/components/com_lender/controllers/edit.php

<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrControllersEdit extends LendrControllersDefault
{
    function execute()
    {
        $app = JFactory::getApplication();
        $viewName = $app->input->get('view');
        $app->input->set('layout','edit');
        $app->input->set('view', $viewName);
        //display view
        return parent::execute();
    }
}


Это довольно простая реализация контроллера. Она будет усложнена в будущих статьях.

Обратите внимание на то, что наш контроллер расширяет LendrControllersDefault. LendrControllersDefault (контроллер по умолчанию) очень важен. Мы расширили наш контроллер по умолчанию для того, чтобы отобразить нужный шаблон.

Ниже приведен пример контроллера по умолчанию /components/com_lendr/controllers/default.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' );
class LendrControllersDefault extends JControllerBase
{
    public function execute()
    {
        // Get the application
        $app = $this->getApplication();
        
        // Get the document object.
        $document = $app->getDocument();
        
        $viewName = $app->input->getWord('view', 'dashboard');
        $viewFormat = $document->getType();
        $layoutName = $app->input->getWord('layout', 'default');
        
        $app->input->set('view', $viewName);
        
        // Register the layout paths for the view
        $paths = new SplPriorityQueue;
        $paths->insert(JPATH_COMPONENT . '/views/' . $viewName . '/tmpl', 'normal');
        
        $viewClass = 'LendrViews' . ucfirst($viewName) . ucfirst($viewFormat);
        $modelClass = 'LendrModels' . ucfirst($viewName);
        
        if (false === class_exists($modelClass))
        {
            $modelClass = 'LendrModelsDefault';
        }
        
        $view = new $viewClass(new $modelClass, $paths);
        $view->setLayout($layoutName);
        
        // Render our view.
        echo $view->render();
        
        return true;
    }
}


Этот контроллер получит из запроса представление, найдет соответствующий файл представления, загрузит соответствующую модель и отобразит представление. В Joomla! 3 с каждым файлом представления должна быть загружена модель, поэтому следуя соглашению по наименованию представлений и моделей мы можем связать их без дополнительных строк кода. Если модель по какой-то причине не существует, мы загружаем модель по умолчанию.

Стоит упомянуть, что такое SplPriorityQueue. В PHP это массив, который является реализацией специального множества и обеспечивает основные функциональные возможности приоритетной очереди.

Контроллеры представлений

Joomla! уникальна в плане обработки представлений. Joomla! использует вторичные контроллеры, которые помогают в рендеринге данных и назначении переменных, используемых в шаблонах. Вторичный контроллер находится в папке представления компонента (site/views/view_name/) и часто называется в зависимости от желаемого типа рендеринга (например, html.php для рендеринга html, phtml.php для рендеринга частичного шаблона, raw.php для рендеринга чистых данных и т.д.). В прошлых версиях Joomla! эти файлы назывались как view.html.php, view.raw.php и т.д. Ниже представлены некоторые из контроллеров представлений, которые использует Lendr.

/components/com_lendr/views/book/html.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrViewsBookHtml extends JViewHtml
{
    function render()
    {
        $app = JFactory::getApplication();
        $type = $app->input->get('type');
        $id = $app->input->get('id');
        $view = $app->input->get('view');
         
        //retrieve task list from model
        $model = new LendrModelBook();
         
        $this->book = $model->getBook($id,$view,FALSE);
        //display
        return parent::render();
    }
}


Этот контроллер представлений отображает детали конкретной книги, основываясь на id. Функция модели getBook() будет определена в следующей статье. Обратите внимание, что переменные, которые будут использоваться в шаблоне назначаются непосредственно текущему объекту. В зависимости от обстоятельств контроллеры представлений могут содержать различное количество логики. Представление выше содержит минимальное количество логики.

/components/com_lendr/views/book/raw.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrViewsBookRaw extends JViewHtml
{
    function render()
    {
        $app = JFactory::getApplication();
        $type = $app->input->get('type');
        $id = $app->input->get('id');
        $view = $app->input->get('view');
         
        //retrieve task list from model
        $model = new LendrModelBook();
         
        $this->book = $model->getBook($id,$view,FALSE);
        //display
        echo $this->book;
    }
}


Этот контроллер представления отображает чистые (неформатированные) детали конкретной книги, основываясь на id.

Шаг 5: создайте модели

Модели в Joomla! работают так же, как и в большинстве MVC систем. Они занимаются обработкой и извлечением большинства данных. На моделях Lendr мы сконцентрируемся в следующей статье, а сейчас взглянем только на базовую структуру.

/components/com_lendr/models/book.php

<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrModelsBook extends LendrModelsDefault
{
    function __construct()
    {
        parent::__construct();
    }
 
    function store()
    {
        …
    }
 
    function getBook()
    {
        …
    }
 
    function getBooks()
    {
        …
    }
     
    function populateState()
    {
        …
    }
}


И опять, ради простоты, в этой статье мы опустили детали каждой функции. Мы разберем их в следующей статье. Важно отметить, что мы снова реализовали наш Default класс. Таким образом, мы можем добавить общие функции в одну модель и использовать их в каждой модели.

/components/com_lendr/models/default.php

<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrModelsDefault extends JModelBase
{
    var $__state_set = null;
    var $_total = null;
    var $_pagination = null;
    var $_db = null;
    var $id = null;
     
    function __construct()
    {
        parent::__construct();
        $this->_db = JFactory::getDBO();
         
        $app = JFactory::getApplication();
        $ids = $app->input->get("cids",null,'array');
         
        $id = $app->input->get("id");
        if ( $id && $id > 0 ){
            $this->id = $id;
        }else if ( count($ids) == 1 ){
            $this->id = $ids[0];
        }else{
            $this->id = $ids;
        }
    }
     
    /**
    * Modifies a property of the object, creating it if it does not already exist.
    *
    * @param string $property The name of the property.
    * @param mixed $value The value of the property to set.
    *
    * @return mixed Previous value of the property.
    *
    * @since 11.1
    */
    public function set($property, $value = null)
    {
        $previous = isset($this->$property) ? $this->$property : null;
        $this->$property = $value;
     
        return $previous;
    }
     
    /**
    * Gets an array of objects from the results of database query.
    *
    * @param string $query The query.
    * @param integer $limitstart Offset.
    * @param integer $limit The number of records.
    *
    * @return array An array of results.
    *
    * @since 11.1
    */
    protected function _getList($query, $limitstart = 0, $limit = 0)
    {
        $db = JFactory::getDBO();
        $db->setQuery($query, $limitstart, $limit);
        $result = $db->loadObjectList();
     
        return $result;
    }
     
    /**
    * Returns a record count for the query
    *
    * @param string $query The query.
    *
    * @return integer Number of rows for query
    *
    * @since 11.1
    */
    protected function _getListCount($query)
    {
        $db = JFactory::getDBO();
        $db->setQuery($query);
        $db->query();
     
        return $db->getNumRows();
    }
     
    /* Method to get model state variables
    *
    * @param string $property Optional parameter name
    * @param mixed $default Optional default value
    *
    * @return object The property where specified, the state object where omitted
    *
    * @since 11.1
    */
    public function getState($property = null, $default = null)
    {
        if (!$this->__state_set)
        {
            // Protected method to auto-populate the model state.
            $this->populateState();
             
            // Set the model state set flag to true.
            $this->__state_set = true;
        }
     
        return $property === null ? $this->state : $this->state->get($property, $default);
    }
    /**
    * Get total number of rows for pagination
    */
    function getTotal()
    {
        if ( empty ( $this->_total ) )
        {
            $query = $this->_buildQuery();
            $this->_total = $this->_getListCount($query);
        }
        return $this->_total;
    }
     
    /**
    * Generate pagination
    */
    function getPagination()
    {
        // Lets load the content if it doesn't already exist
        if (empty($this->_pagination))
        {
            $this->_pagination = new JPagination( $this->getTotal(), $this->getState($this->_view.'_limitstart'), $this->getState($this->_view.'_limit'),null,JRoute::_('index.php?view='.$this->_view.'&layout='.$this->_layout));
        }
        return $this->_pagination;
    }
}



Модель по умолчанию включает в себя важные функции, которые будут использоваться повторно в компоненте. Мы вернемся к ним в ближайших статьях.

Это файлы только двух моделей, которые будут созданы с компонентом Lendr. Другие модели, схожие по природе и базовой структуре, будут написаны в следующей статье.

Итоги начала разработки

Теперь, когда мы создали базовую структуру папок компонента, создали таблицы базы данных, файлы установки, контроллеры, контроллеры представлений и модели, наш компонент стал устанавливаемым. Конечно, функциональности еще нет, и компонент просто создает пустую оболочку, но наличие устанавливаемого компонента создает некоторое чувство удовлетворения.

Не забудьте изучить наш Github репозиторий, и посмотреть на другие таблицы базы данных, контроллеры, контроллеры представлений и модели, которые не были описаны в этой части.

В следующей части мы погрузимся в написание непосредственно функционала моделей.

Скачать com_lendr v1.0