Joomla框架实现REST风格 API的详细步骤

1. 简介

Joomla 是一款PHP语言开发的CMS系统,采用完全面向对象的MVC模式开发。支持多种开发方式。

Joomla开发扩展的方式:

  1. Component : 组件模式,开发一套完整的组件应用的视图、数据库、逻辑;
  2. Module : 模块模式,能够在网页中显示的组件,例如登录模块等;
  3. Plugin : 插件模式,用于整个系统中,进行系统各个事件的处理,无界面;
  4. Template : 模版模式,用于自定义各种组件Component的外观;

通常实现一个应用系统,会使用Component进行开发,利用Component可以开发前台展示端和后台管理端,因此本文使用Component作为实例说明。

1.1 REST 风格

REST风格是将HTTP网址、方法进行统一规范的方式,提供了一套通用的定义。例如:POST 方法用于创建资源,PUT方法用于更新资源,GET单纯获取资源;网址例如 /books 获取图书列表, /books/3.json 则是获取 id为 3 的图书信息。

1.2 实例定义

1. GET /api/books.json              返回图书列表,并且支持传递 page, size 参数;
2. GET /api/books/{id}.json       返回特定图书的信息
3. POST /api/books                    创建一个图书数据
4. DELETE /api/books/{id}.json 删除一个图书数据

2. 实现方式

2.1 Joomla Component 开发简介

Joomla的扩展开发需要一个关键的xml文件,这个文件定义了当前扩展的类型、基本信息、代码文件、配置信息和资源信息。通常都是在一个空目录中创建xml文件,并且增加对应的目录和代码,之后打包成zip/tar.gz 即可安装给Joomla。

2.2 REST 风格的关键技术

Joomla 中提供了一个 JRoute 类,这个类的方法会对传入的网址参数进行处理,映射成特定的 REST网址,可以进行网站的SEO优化。

对于客户端等请求 REST网址的方式来说,需要反向将REST网址解析为实际的组件中的页面、处理器。

实现上述方式都需要用到一个特定接口的实现: JComponentRouterInterface

Component只要实现了这个接口,就可以实现REST风格网址的生成和解析。

2.3 Joomla 网址请求规则

Joomla中的所有的网址都是使用如下的格式来定义:

index.php?option=com_xxx&view=vvv&layout=lll&task=ttt&format=fff

其中 task 和 view不会并存。

  • option : 代表请求哪一个组件,我们的例子为 com_restdemo
  • view : 代表请求组件的哪一个 view 视图
  • layout : 代表请求 view 的哪一个显示模版
  • task : 代表调用 请求哪一个控制器的哪一个任务 通常是 controller.xxx 的方式
  • format : 代表要求返回哪种数据格式

2.4 关键术语

  1. MVC : Joomla 使用MVC模式进行开发,每一个部分用于处理不同的需求
  2. Task : Joomla 组件通过网址访问,网址中使用 task 参数可以直接调用Controller 的方法,可以不需要网页显示
  3. 文件名称特性:PHP文件中可以包含 format 类型,这样可以让Joomla自动查找对应的文件,例如 view.html.php 代表输出 HTML格式的代码;view.json.php 则代表输出 json 格式的内容。

2.5 初始工程代码段

  1. 初始化步骤
    创建一个空的目录,例如 D:\restdemo 目录(Windows),或者 ~/restdemo 目录(Linux),在restdemo目录中创建任意名称的 xml 文件,例如 restdemo.xml。
<?xml version="1.0" encoding="utf-8" ?>
<extension type="component" version="3.8" method="upgrade">
    <name>COM_RESTDEMO</name>
    <creationDate>2020/2/8</creationDate>
    <author>vhly</author>
    <authorEmail>your@email.com</authorEmail>
    <authorUrl>http://your.url.com</authorUrl>
    <copyright>A copyright</copyright>
    <license>GNU General Public License version 2 or later; see LICENSE.txt</license>
    <version>1.0</version>
    <description>COM_RESTDEMO_XML_DESCRIPTION</description>

    <!-- Front-end files -->
    <files folder="com_restdemo">
        <filename>restdemo.php</filename>
        <filename>controller.php</filename>
        <folder>controllers</folder>
        <folder>models</folder>
        <folder>views</folder>
    </files>

    <administration>
            <!-- 暂时不加后台处理 -->
    </administration>

</extension>

在restdemo目录中创建 com_restdemo 目录,并且在这个目录中,继续创建restdemo.php文件,controller.php 文件,controllers 子目录,models 子目录,和views 子目录。

组件被执行的时候,会自动查找和加载 restdemo.php 文件,通过这个文件加载组件的处理器,这个处理器是通过 controller.php 定义的,也是自动加载的,处理器会根据请求的 view, format, layout, task 等参数,来自动的查找 controllers, views 子目录中的类,进行处理。

  1. restdemo.php 主入口
    主入口会创建主Controller,执行相关操作,也就是 controller.php 会被自动加载。对应的代码如下:
<?php
// Joomla 代码必须在第一行加入这句话
defined('_JEXEC') or die;

// 创建 Controller对象,名称前缀为 RestDemo的对象
//     Joomla 代码会自动将所有类的 名称转换为小写再加载文件
//     本代码会自动加载 controller.php 文件,并且创建对象
$controller = JControllerLegacy::getInstance('RestDemo');

// 处理器处理网址参数 task 并进行逻辑处理,
// 如果没有设置,默认处理 task=display
$controller->execute(JFactory::getApplication()->input->get('task'));
$controller->redirect();
  1. 主控制器 controller.php
    主控制器名称是 RestDemoController ,其中 RestDemo 就是 restdemo.php中指定的前缀。所有的 代码都是以 RestDemo 开头。主控制器默认的任务为 display,这个任务会自动加载view进行显示,默认名称为 restdemo 的view,可以设置 default_view 来修改。
<?php

defined('_JEXEC') or die;

/**
 * 主控制器,主控制器默认会处理 display 任务
 * display 会自动加载 views/restdemo 目录的 view
 */
class RestDemoController extends JControllerLegacy
{
    /**
     * 设置 display 任务时 默认显示为 views/index/view.html.php,
     * 如果不设置,则会要求加载 views/restdemo/view.html.php
     */
    protected $default_view = 'index';
}
  1. 制作index视图
    在 views 目录中,创建一个新的目录,名称为 "index", 并且在 index中创建view.html.php 的文件。
    最终的路径为 com_restdemo/views/index/view.html.php
<?php
defined('_JEXEC') or die;

// RestDemo 为前缀,View 代表是View类型,Index 就是对应的 "index" 页面
class RestDemoViewIndex extends JViewLegacy
{
    public function display($tpl = null)
    {
        return parent::display($tpl);
    }
}

默认的代码 display 会在当前 view.html.php 文件下查找 tmpl/default.php 的文件,作为实际的输出内容。完整路径为
com_restdemo/views/index/tmpl/default.php

<?php

defined('_JEXEC') or die;

echo 'Hello World by vhly.';
  1. 打包安装

默认的Component 已经完成,先进行安装测试,打包文件:

  • restdemo.xml
  • com_restdemo 目录

将这两个部分压缩成一个压缩包,可以采用 zip 方式,打包之后,在Joomla后台管理“扩展安装”菜单进行安装即可。

  1. 测试页面

http://xxx.xxx.xxx.xxx/index.php?option=com_restdemo

这个地址会默认查找 com_restdemo 组件的前台页面,会找到 restdemo.php 并且最终显示 index的内容。

默认测试页面

2.6 增加 REST 地址映射

由于Component组件包含前台代码和后台管理代码,相当于两套代码,通过 xml文件进行描述,本文没有编写后台管理代码,因此只处理前台代码的REST映射。

只要在 com_restdemo 文件夹增加 router.php 文件,并且实现代码,就可以增加REST映射了。

具体修改如下:

  1. 修改 restdemo.xml 文件
<?xml version="1.0" encoding="utf-8" ?>
<extension type="component" version="3.8" method="upgrade">
    <name>COM_RESTDEMO</name>
    <creationDate>2020/2/8</creationDate>
    <author>vhly</author>
    <authorEmail>your@email.com</authorEmail>
    <authorUrl>http://your.url.com</authorUrl>
    <copyright>A copyright</copyright>
    <license>GNU General Public License version 2 or later; see LICENSE.txt</license>
    <version>1.0</version>
    <description>COM_RESTDEMO_XML_DESCRIPTION</description>

    <!-- Front-end files -->
    <files folder="com_restdemo">
        <filename>restdemo.php</filename>
        <filename>controller.php</filename>
                <!-- 此处增加一个文件!!! -->
        <filename>router.php</filename>
        <folder>controllers</folder>
        <folder>models</folder>
        <folder>views</folder>
    </files>

    <administration>

    </administration>

</extension>
  1. 增加 com_restdemo/router.php 文件

创建 RestDemoRouter 类,并且实现 JComponentRouteInterface
需要实现三个方法,方法并不是必须要有实际的代码,根据需求来实现。

  • preprocess($query) 方法:在生成地址之前处理请求参数
  • build(&$query) 方法:自己实现生成 rest/seo 风格的地址
  • parse(&$segments) 方法:解析REST风格的网址 !!
<?php

defined('_JEXEC') or die;

class RestDemoRouter implements JComponentRouterInterface
{

    /**
     * 预处理 query 请求,这个是网址中的 各个请求参数
     */
    public function preprocess($query)
    {
        return $query;
    }

    /**
     * 这个方法生成 rest 风格的网址,或者是 SEO友好的网址
     * 将类似 index.php?option=com_restdemo&view=index 转化
     * 成类似这样的网址 index.php/com_restdemo/index.html
     */
    public function build(&$query)
    {
        // TODO: Implement build() method.
    }

    /**
     * 此方法将 REST 风格网址解析成实际的请求地址
     * 这个方法是重要的
     */
    public function parse(&$segments)
    {
        // TODO: Implement parse() method.
    }
}

3. 实现REST风格API

3.1 测试生成 /api/books.json API

这个API请求需要注意几个关键特征:

  • 请求返回的格式必须是 json,也就是 application/json 类型
  • 请求的方法是 GET

设计思路:

  1. 请求的内容是 JSON,不需要使用页面,直接使用Controller即可;
  2. 使用Controller,需要定义 task 来对应请求;
  3. Controller 必须是支持JSON的,不能够是默认HTML的,需要单独创建;
  4. JSON 响应使用 JResponseJSON 来封装;
  5. Router 中只要实现 parse 即可,其余两个方法不是必须的;

实现步骤:

  1. 在 router.php 的 parse 方法,增加 api/books.json 的支持,映射到controller

每当请求为 api/books.json 那么调用实际的地址变成:
index.php?option=com_restdemo&task=api.books&format=json

/**
     * 此方法将 REST 风格网址解析成实际的请求地址
     * 这个方法是重要的
     */
    public function parse(&$segments)
    {
        $query = array(); // 返回查询字段,拼接成 key1=value1&key2=value2
        $slen = count($segments);
        if ($slen > 0) {
            // api 开头
            if ('api' === $segments[0]) {
                if ($slen > 1) {
                    // 请求为 api/books.json
                    if ('books.json' === $segments[1]) {
                        // 开始设置地址请求参数
                        // 1. 设置 task = api.books
                        $query['task'] = 'api.books';
                        // 2. 设置 format = 'json'
                        $query['format'] = 'json';
                    }
                }
            }
        }
        return $query;
    }
  1. 创建 API Controller,路径为 com_restdemo/controllers/api.json.php

名称 api.json.php 只有到 format=json时,回调用这个PHP文件

<?php

defined('_JEXEC') or die;

class RestDemoControllerApi extends JControllerLegacy
{
    /**
     * task = api.books 会自动映射到这个方法
     */
    public function books(){
        // 所有其余的请求都会通过input来获取
        $input = JFactory::getApplication()->input;

        $books = array();

        $book = new stdClass();
        $book->title = 'Book 001';
        $books[] = $book;

        $book = new stdClass();
        $book->title = 'Book 002';
        $books[] = $book;

        echo new JResponseJson($books, 'Books', false);
    }
}
  1. 打包测试

请求地址:http://XXX.XXX.XXX.XXX/index.php?option=com_restdemo&task=api.books&format=json

返回JSON数据:

JSON数据响应
  1. 获取实际的REST地址

实现 router.php 中的 build 方法,看一下最终生成的地址是什么样子的。
规则如下:

  1. build 方法检查 task 是否是 api.books
  2. 检查 format 是否是 json
  3. 通过检查,拼接地址
    public function build(&$query)
    {
        $segments = array(); // 地址拼接
        if (isset($query['task'])) {
            $task = $query['task'];
            if (isset($query['format'])) {
                $format = $query['format'];

                if ('json' === $format) {
                    if ('api.books' === $task) {
                        // 将 task=api.books&format=json
                        // 转换为 api/books.json
                        $segments[] = 'api';
                        $segments[] = 'books.json';

                        // 清除参数,注意保留业务参数
                        unset($query['task']);
                        unset($query['format']);
                    }
                }
            }
        }
        return $segments;
    }
  1. 测试REST地址
    Joomla中不需要直接使用 Router的代码,而是通过 JRoute::_() 方法来调用。
    参数就是请求地址,会自动传递给 build 方法。

在 com_restdemo/views/index/tmpl/default.php 中调用输出,进行测试:

<?php

defined('_JEXEC') or die;

$booksUrl = JRoute::_('index.php?option=com_restdemo&task=api.books&format=json');

echo 'Books URL: '.$booksUrl;


输出结果:

最终输出地址

最终输出地址为:
/index.php/component/restdemo/api/books.json

  1. 生成短地址

如果对于地址有更严格的限制,要求生成 http://IP/api/books.json 的方式,那么
需要将 com_restdemo 的内容设置为网站默认首页,就会隐藏掉 index.php 和 component 信息。

1. 在 com_restdemo/views/index/tmpl/ 目录,创建 default.xml 文件定义菜单链接

<?xml version="1.0" encoding="utf-8"?>
<metadata>
    <layout title="COM_RESTDEMO_RESTDEMO">
        <message>
            <![CDATA[COM_RESTDEMO_RESTDEMO_DESC_MENU]]>
        </message>
    </layout>
</metadata>

重新打包安装。

进入后台菜单项管理,创建新的菜单项

新建菜单项

选择菜单项类型为组件页面:

选择组件作为页面

设置为默认首页

即可生成: index.php/api/books.json 地址,直接访问 http://IP/api/books.json 同样可以返回数据

4. 附加链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容