何时使用
- 为 Moodle 插件(
local_*/mod_*)开发自定义 Web 服务,暴露课程、测验、用户跟踪或报表能力。 - 为移动 App / 外部系统提供 REST 或 AJAX 后端接口。
- 需要遵循 Moodle 外部 API 框架(external_api 三方法模式)与编码规范。
不该用的边界:
- 不用于前端主题(theme)、JS/CSS 改造、块(block)渲染等纯展示层。
- 不用于修改 Moodle 核心源码或打补丁;自定义能力应走插件而非改核心。
- 不用于非 Moodle 的 LMS;本技能依赖 Moodle 的 DML、capability、context 体系。
- 缺少插件路径、所需 capability、读写类型(read/write)等关键输入时,先澄清再动手。
步骤 / 指令
外部 API 类必须实现严格的三方法模式:execute_parameters() 定义入参结构、execute() 写业务逻辑、execute_returns() 定义返回结构。返回结构必须与 execute() 实际返回完全匹配。
- 建类文件
local/yourplugin/classes/external/your_api_name.php:namespace local_yourplugin\external;,加defined('MOODLE_INTERNAL') || die();,require_once("$CFG->libdir/externallib.php");,类extends external_api。命名空间用local_pluginname\external或mod_modname\external。 - 写
execute_parameters():用external_function_parameters、external_value、external_single_structure(命名对象)、external_multiple_structure(数组)。常用类型PARAM_INT/PARAM_TEXT/PARAM_RAW/PARAM_BOOL/PARAM_FLOAT/PARAM_ALPHANUMEXT;标志VALUE_REQUIRED/VALUE_OPTIONAL/VALUE_DEFAULT,默认值。 - 写
execute()五步:①self::validate_parameters(self::execute_parameters(), [...])校验入参;②$context = \context_course::instance(...)+self::validate_context($context);③require_capability('moodle/course:view', $context)(跨用户访问再加viewhiddenactivities等);④全部用占位符参数化 SQL(:paramname),优先$DB->get_records()/get_field_sql()等;⑤组装并返回结构化数据。 - 写
execute_returns():结构与返回值逐字段对齐,每字段写描述,允许嵌套。 - 注册服务到
local/yourplugin/db/services.php:$functions中键名形如local_yourplugin_your_api_name,填classname(全命名空间类名)、methodname(恒为'execute')、classpath、type(read=SELECT /write=增删改)、ajax => true、capabilities、可选services(如MOODLE_OFFICIAL_MOBILE_SERVICE);$services定义服务包含的函数集合。 - 错误处理与日志:
execute()包 try-catch,分别捕获\invalid_parameter_exception、\moodle_exception、\Exception,记录时间戳、消息、最后 SQL($DB->get_last_sql())、堆栈,记完后重新抛出。日志写$CFG->dataroot/local_yourplugin/。 - 改完 services.php 后必须清缓存:站点管理 > 开发 > 清除所有缓存(否则报 Function not found)。
进阶:写操作用 $DB->start_delegated_transaction() + allow_commit(),异常时 $transaction->rollback($e);建课程模块用 add_course_module() + xxx_add_instance() + course_add_cm_to_section(),再回填 instance;按 group + availability JSON 做活动可见性限制。
示例
最简只读 API(统计某用户某课程测验提交数)核心:
public static function execute($userid, $courseid) {
global $DB;
self::validate_parameters(self::execute_parameters(), [
'userid' => $userid, 'courseid' => $courseid
]);
$sql = "SELECT COUNT(*) AS quiz_attempts
FROM {quiz_attempts} qa
JOIN {quiz} q ON qa.quiz = q.id
WHERE qa.userid = :userid AND q.course = :courseid";
$attempts = $DB->get_field_sql($sql, ['userid' => $userid, 'courseid' => $courseid]);
return ['quiz_attempts' => (int)$attempts];
}
public static function execute_returns() {
return new external_single_structure([
'quiz_attempts' => new external_value(PARAM_INT, 'Total number of quiz attempts')
]);
}
curl 测试(先取 token 再调用):
curl -X POST "https://yourmoodle.com/login/token.php" \
-d "username=admin" -d "password=yourpassword" -d "service=moodle_mobile_app"
curl -X POST "https://yourmoodle.com/webservice/rest/server.php" \
-d "wstoken=YOUR_TOKEN" \
-d "wsfunction=local_yourplugin_your_api_name" \
-d "moodlewsrestformat=json" \
-d "userid=2" -d "courseid=3"
AJAX 调用:require(['core/ajax'], ...) → ajax.call([{methodname, args}]),.done()/.fail() 处理。
注意事项
- 永远先
validate_parameters()、再validate_context()、再require_capability(),顺序不可省。 - 杜绝 SQL 注入:只用占位符,绝不拼接用户输入;改完服务必清缓存。
- 「Invalid parameter value」多因定义与实际类型/必选项/嵌套结构不一致。
- 事务要短,避免嵌套与死锁;写操作务必有提交或回滚。
- 遵循 Moodle 命名规范(小写 + 下划线),每个参数与返回字段都写描述。
- 常用表速查:
{user}{course}{course_modules}{modules}{quiz}{quiz_attempts}{question}{question_categories}{grade_items}{grade_grades}{groups}{groups_members}{logstore_standard_log}。 - 调试清单:开 debug 模式、查 Web 服务日志(站点管理 > 报告 > 日志)、看自定义日志、
$DB->set_debug(true)、用 admin 排除权限问题、清浏览器与 Moodle 缓存、查 PHP 错误日志。 - 本技能仅适配 Moodle 外部 API 场景,产出需在目标环境实测,不能替代专家评审与安全验证。
互见
- 官方文档:Moodle External API(functions / subsystems)、Database API(DML)、Coding Style — moodledev.io。
- 领域内可配合:插件
version.php/db/access.php(capability 定义)/lang语言串 /tests/external_test.php单测。
采编自 sickn33/antigravity-awesome-skills(MIT 许可)。