HelloWorld元件教學-前台篇


前言

最簡單的HelloWorld元件。本篇教學為前台部份,作為元件開發的入門基礎。另一篇MVC簡介有簡單的介紹。

XML定義檔

首先是先註冊這個元件到Joomla!系統中,讓元件可以在Joomla!系統的框架中執行。所有的安裝的定義都在這個XML定義檔的定義內容中:

/helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="component"
           version="3.2.0"
           method="upgrade">

<!-- 定義主要內容在此 -->

</extension>

在「<extension>...</extension>」標記裡面的內容,是關於這個元件的所有內容定義,我們會在這裡面寫下有關這個元件的資訊、安裝檔案,以及資料庫相關執行檔案。

接下來是是有關這個元件的作者名稱、email、說明、版權、版本等等的資訊:

/helloworld.xml

<name>Hello World!</name>
<creationDate>2015-09-30</creationDate>
<author>Eddy Chang</author>
<authorEmail>Email住址會使用灌水程式保護機制。你需要啟動Javascript才能觀看它</authorEmail>
<authorUrl>http://www.example.org</authorUrl>
<copyright>Copyright Info</copyright>
<license>License Info</license>
<version>0.0.1</version>
<description>Description of the Hello World component ...</description>

接下來是前台元件的檔案安裝部份,在元件的安裝檔中因為放在我們安裝檔案中的site資料夾,所以用folder="site"指示,這樣Joomla!的安裝程式才知道要去哪裡找這些檔案:

/helloworld.xml

<files folder="site">
        <!-- 前台檔案安裝(複製)部份 -->
        <filename>index.html</filename>
        <filename>helloworld.php</filename>
</files>

接下來是後台元件的檔案安裝部份,在元件的安裝檔中因為放在我們安裝檔案admin資料夾中,所以用folder="admin"指示:

/helloworld.xml

<administration>
        <!-- 管理區選單 -->
        <menu link='index.php?option=com_helloworld'>Hello World!</menu>
        <!-- 管理區檔案安裝(複製)部份 -->
        <files folder="admin">
            <filename>index.html</filename>
            <filename>helloworld.php</filename>
        </files>
</administration>

到這裡,我們的xml定義檔案的全部內容如下:

helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="component"
           version="3.2.0"
           method="upgrade">

<name>Hello World!</name>
<creationDate>2015-09-30</creationDate>
<author>Eddy Chang</author>
<authorEmail>Email住址會使用灌水程式保護機制。你需要啟動Javascript才能觀看它</authorEmail>
<authorUrl>http://www.example.org</authorUrl>
<copyright>Copyright Info</copyright>
<license>License Info</license>
<version>0.0.1</version>
<description>Description of the Hello World component ...</description>

    <files folder="site">
        <filename>index.html</filename>
        <filename>helloworld.php</filename>
    </files>

    <administration>
        <menu link='index.php?option=com_helloworld'>Hello World!</menu>
        <files folder="admin">
            <filename>index.html</filename>
            <filename>helloworld.php</filename>
        </files>
    </administration>

</extension>

對應上面的定義,你還需要建立所有的檔案與資料夾。

其中index.html檔案只是一個空白的html檔案,用來加強安全性,以免如果網站伺服器可以直接存取目錄時,造成檔案外露的風險。index.html檔案的內容如下:

index.html

<html><body bgcolor="#FFFFFF"></body></html>

將整個資料夾壓縮成zip檔案,就可以在管理區中的擴充套件管理中進行安裝,安裝完成的畫面:

在"元件"的選單中也可以看到有一個"Hello World!"的選單項目出現。

檔案結構

在前台的元件部份總共需要這麼多檔案與目錄,我們在接下來的章節內容會一一詳述:

  • com_helloworld
    • /helloworld.xml 元件定義檔案
    • /helloworld.php 啟動程式
    • /controller.php 主控制器
    • /models/helloworld.php 模型
    • /views/helloworld/view.html.php 視圖
    • /views/helloworld/tmpl/default.php 輸出樣版

在MVC框架中,所有的MVC類別檔案都需要先建立完成,程式才會正常執行,也就是沒有辦法其中一個程式檔案能獨立執行。所以你應該先學會如何先架構出一個最簡單的雛形,然後再慢慢延伸擴充功能。

啟動程式

與元件同名稱的helloworld.php,它的功能是一個啟動程式(bootstrap),用於獲取網址上的task等參數,然後交由後面的控制器(controller)類去處理對應的功能,啟動程式的程式碼通常都很簡單,只有幾行而已:

/helloworld.php

<?php
// 不允許直接存取這個程式檔案
defined('_JEXEC') or die('Restricted access');

// 獲取前綴字為HelloWorld的控制器實體
$controller = JControllerLegacy::getInstance('HelloWorld');

// 執行要求的任務(task),$input數值是獲取客戶端要求的task參數值
$input = JFactory::getApplication()->input;
$controller->execute($input->getCmd('task'));

// 依照控制器所設定的值重新導向
$controller->redirect();

我們通常會使用網址來進行對元件的任務(task)的要求,例如像index.php?option=com_helloworld&task=[task name]這樣的網址,常見的任務(task)像是edit、save、new等等,預設的任務(就是當沒指明是哪一個任務)是單純地顯示項目的內容。

控制器(controller)

在元件的根目錄裡,與啟動程式同一資料夾中,新增一個名稱為controller.php的程式檔案,這個檔案我們通常稱為主控制器,它的任務通常是顯示預設內容之用,如果有需要其他的控制器,則會在這個資料夾中再新增一個名稱為controllers的資料夾,然後在裡面再新增其他的控制器。

因為這個元件的功能是相當簡單的,在前台部份只有顯示資料用途而已,所以只需要一個主控制器即可,controller.php的程式碼如下:

/controller.php

<?php
// 不允許直接存取這個程式檔案
defined('_JEXEC') or die('Restricted access');

class HelloWorldController extends JControllerLegacy
{

}

因為這個控制器(Controller)的主要功能是顯示內容資訊,所以這裡面只會用到一個display方法,它也是沒有指明任何任務時的預設方法。因為我們使用JControllerLegacy裡面的display方法,在這裡並沒有特別加上其他的程式功能,所以看起來類別裡面都是空白的。

前面章節有說明過,控制器中的display方法裡會實作的功能大致上有幾個:

  • 設定好各項參數值
  • 快取處理
  • 檢查存取的權限
  • 處理視圖與模型的連繫

display方法最後會傳給上層的父類中的display方法,父類的display方法會協助進行對應的模型(model)與視圖(view)的連繫工作,然後準備最終的內容輸出。

視圖(View)

視圖(View)的檔案名稱是與控制器或模型是不太一樣的,在元件根目錄中下面新增一個views資料夾,再下一層新增一個helloworld資料夾,在這個資料夾中新增一個view.html.php檔案,這個才是視圖(View)的類別檔案,裡面的內容如下:

/views/helloworld/view.html.php

<?php
// 不允許直接存取這個程式檔案
defined('_JEXEC') or die('Restricted access');

class HelloWorldViewHelloWorld extends JViewLegacy
{

    function display($tpl = null)
    {
        // 指派資料給視圖(View)
        $this->msg = 'Hello World';

        // 顯示視圖(View)
        parent::display($tpl);
    }
}

視圖中也有一個名稱為display的方法,不過它們兩者的傳入參數不太一樣。對照控制器(Controller)中的display方法,在視圖(View)中的display方法,任務工作是不同的,它的功能如下:

  • 獲取資料庫(或模型)的資料
  • 準備輸出的資料內容
  • 執行內容相關外掛
  • 設定要輸出的樣版

用一個最簡單的比喻來看,我們以餐廰中負責不同工作任務的服務生為例,控制器(Controller)中的display方法是在餐廰中負責點菜的服務生,客人點好菜後送到廚房(模型)中製作,然後視圖(View)中的display方法是負責送菜的服務生,負責把作好的菜端給客戶,這樣子思考就會更容易了解。

view.html.php檔案名稱的中間這個html字詞,在程式碼或網址列中會用format(格式)關鍵字來指定,所以會有像view.json.phpview.raw.php這樣的檔案名稱,這是針對不同輸出格式的視圖檔案,會以檔案名稱來區分。

樣版(Template)

視圖要輸出資料內容,還另外需要樣版(Template)檔案,因為視圖只負責處理最終要輸出的內容是什麼,真正呈現的樣式與格式,則是在樣版檔案裡面定義的。樣版檔案只負責元件的輸出格式,也就是在佈景主題中的主體(Mainbody)位置,通常是在中間最大的那一塊區域,其中會一並包含HTML與CSS樣式。

這兩個檔案(視圖與樣版)在程式執行時,可視為同一個程式檔案,只是因為細分工作範圍而區分出來,所以在視圖中的所有變數或數值,在樣版檔案中也是可以直接存取。

樣版檔案是在我們這個視圖(View)的目錄下,再新增一個名稱為tmpl的資料夾,然後在裡面新增一個default.php檔案,作為預設的樣版程式檔案,裡面的內容如下:

/views/helloworld/tmpl/default.php

<?php

// 不允許直接存取這個程式檔案
defined('_JEXEC') or die('Restricted access');
?>
<h1><?php echo $this->msg; ?></h1>

因為樣版(Template)檔案與視圖(View)檔案是連在一起執行的,所以在視圖(View)類別檔案中的變數值,在樣版檔案中也可以存取得到,所以在上面的視圖中所定義的$this->msg變數,一樣在樣版中可以存取得到。這裡的樣版檔案只是單純的輸出這個設定好的變數而已,所以程式碼也是很簡單的。

樣版(Template)程式檔案,在程式碼或網址列中會使用layout關鍵字來指定。

前台輸出結果

在前台的網址上加上index.php?option=com_helloworld,你應該可以看到的結果。

這只是單純的輸出資料到網頁上,所以沒有用上模型的部份,只需要視圖與控制器的部份有對應的程式就可以看到執行結果。

模型(Model)

模型(Model)主要提供資料來源,也就是與資料庫連接的主要部份。接下來我們把資料的部份放在模型之中,然後再進一步使用資料庫。

模型(Model)的檔案是位於元件根目錄下的models資料夾中,我們在裡面新增一個helloworld.php的檔案,裡面的內容如下:

/models/helloworld.php

<?php

// 不允許直接存取這個程式檔案
defined('_JEXEC') or die('Restricted access');

class HelloWorldModelHelloWorld extends JModelItem
{   
    protected $message;

    public function getMsg()
    {
        if (!isset($this->message))
        {
            $this->message = 'Hello World!';
        }

        return $this->message;
    }
}

裡面只有一個getMsg方法,對應這個方法,在視圖(View)中的display方法改用以下的程式碼來獲取要輸出的字串,程式碼如下:

/views/helloworld/view.html.php

<?php

// 不允許直接存取這個程式檔案
defined('_JEXEC') or die('Restricted access');

class HelloWorldViewHelloWorld extends JViewLegacy
{

    function display($tpl = null)
    {
        // 指派資料給視圖(View)
        $this->msg = $this->get('Msg');

        // 顯示視圖(View)
        parent::display($tpl);
    }
}

你可能會覺得有疑問,這個視圖中的get方法,可以存取到模型中的對應的getMsg方法?它的功能的確是可以除了存取在視圖中的屬性外,也可以存取到對應模型中對應的名稱方法。

這一切的原因主要是在控制器中的display方法執行後,視圖會和模型對應連繫起來,所以在視圖中可以這樣作也就是理所當然了,這是一個在表面上看不到的機制。

現在程式的輸出的畫面,和之前沒有使用模型時是一樣的。在前台的網址上加上index.php?option=com_helloworld,你應該可以看到一樣的結果。

資料庫查詢

再進一步,我們接下來要將資料庫建立起來,讓模型用於存取資料庫的內容。

模型的主要任務是與資料庫進行查詢的工作,因為我們的資料表現在尚未建立,所以現在先使用像phpMyAdmin之類的MySQL資料庫管理工具,新增一個資料表,它的結構很簡單,只有三個欄位而已,分別是用於主鍵的id欄位,儲存訊息用的message欄位,以及決定發佈與否的published欄位:

DROP TABLE IF EXISTS `prefix_helloworld`;
CREATE TABLE IF NOT EXISTS `prefix_helloworld` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `message` varchar(25) NOT NULL,
  `published` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;

INSERT INTO `prefix_helloworld` (`id`, `message`, `published`) VALUES
(1, '第一個訊息:哈囉', 1),
(2, '第二個訊息:你好', 1);

上面的prefix你需要改成目前安裝的Joomla!資料表所使用的前綴字。

再來是修改模型中的程式碼如下,讓原本獲取訊息的方法,改成使用資料表中的訊息內容,由於訊息變為多筆資料,所以也需要修改一下回傳值類型為物件:

/models/helloworld.php

<?php

defined('_JEXEC') or die;

class HelloWorldModelHelloWorld extends JModelItem
{
    protected $messages;

    public function getMessages()
    {
        if (!is_array($this->messages))
        {
            $this->messages = array();
        }

        $db = JFactory::getDbo();
        $query = $db->getQuery(true);

        $query ->select($db->quoteName(array('id','message')))
               ->from($db->quoteName('#__helloworld'))
               ->where($db->quoteName('published') . ' = '. $db->quote('1'))
               ->order('id DESC');

        $db->setQuery($query);
        $this->messages = $db->loadObjectList();

        return $this->messages;
    }
}

關於資料庫的查詢方法,可以查閱「通用基礎 - 資料庫互動函式庫」這一章節的內容

在視圖中的程式碼也需要修改一下,讓它能正常取得回傳的物件:

/views/helloworld/view.html.php

<?php

// 不允許直接存取這個程式檔案
defined('_JEXEC') or die('Restricted access');

class HelloWorldViewHelloWorld extends JViewLegacy
{

    function display($tpl = null)
    {
        // 然派資料給視圖(View)
        $this->messages = $this->get('Messages');

        // 顯示視圖(View)
        parent::display($tpl);
    }
}

最後是在樣版檔案中,用一個表格的格式來顯示所有的訊息:

/views/helloworld/tmpl/default.php

<?php

// No direct access to this file
defined('_JEXEC') or die('Restricted access');
?>
<table class="table table-striped table-bordered table-hover">
    <thead>
    <tr>
        <th>ID</th>
        <th>訊息</th>
    </tr>
    </thead>
    <tbody>
    <?php foreach ($this->messages as $item): ?>
        <tr>
            <?php echo '<td>' . $item->id . '</td><td>' . $item->message . '</td>'; ?>
        </tr>
    <?php endforeach; ?>
    </tbody>
</table>

這個輸出的樣版中,有兩個小地方需要注意一下:

  • 表格中套用了Joomla!系統本身的CSS樣式,這是Bootstrap裡定義的。
  • 在樣版檔案裡通常使用像上面的foreach...endforeach,或是if...else...endif這類的PHP敘述語法,目的是為了讓程式碼看得更清楚,提高可閱讀性,這只是一種建議的撰寫方式,不是強制的規定。

小結

這一章完成了一個最簡單的前台元件,這是一個包含最基礎的MVC框架的元件結構。如果你一開始對檔案的目錄結構不熟悉,也需要花點時間再了解一下。

我把這個章節,完整的前台元件的開發流程簡單的摘要一下:

  • 建立xml定義檔案,與基本的程式所需目錄與檔案,打包成zip壓縮檔案
  • 在Joomla!系統後台管理區中安裝這個元件
  • 啟動程式 helloworld.php
  • 主控制器 controller.php
  • 視圖 /views/helloworld/view.html.php,
  • 樣版/view/helloworld/tmpl/default.php
  • 模型(與資料庫) /models/helloworld.php

所繼承上層類別的也有一些小差異,其中JModelItemJModelLegacy的子類別,我們在後面的章節會再詳細說明這部份:

  • 主控制器 JControllerLegacy
  • 視圖 JViewLegacy
  • 模型 JModelItem