注意

Joomla! CMS 从 4.2.0 版本开始,将其自身的编码规范切换为 PSR-12(之后切换为 PER 编码风格)编码规范。
本文档适用于 Joomla! < 4.2.0,其中部分内容仍然适用于 4.2.0 及更高版本。

本文档将很快更新以反映 PSR-12 编码规范。

语言结构

PHP 代码标签

始终使用完整的 <?php ... ?> 来分隔 PHP 代码,而不是 <? ... ?> 简写形式。这是在不同操作系统和设置上包含 PHP 代码最便携的方式。

对于仅包含 PHP 代码的文件,不应包含结束标签(?>)。PHP 不需要它。省略它可以防止意外将尾随空格注入输出,这可能会在 Joomla 会话中引入错误(请参阅 PHP 手册中关于 指令分隔 的内容)。

文件应始终以一个空行结束。

常规

根据 PSR-2 关键字和 True/False/Null

PHP 关键字 必须小写。
PHP 常量 truefalsenull 必须小写。

包含代码

在任何无条件包含文件的地方,使用 require_once。在任何有条件包含文件的地方(例如,工厂方法),使用 include_once。这两者都可以确保文件只包含一次。它们共享相同的filelist,因此您无需担心混合使用它们。使用 require_once 包含的文件不会被 include_once 再次包含。

注意

include_oncerequire_once 是 PHP 语言语句,而不是函数。正确的格式是

require_once JPATH_COMPONENT . '/helpers/helper.php';

您不应将文件名括在括号中。

与 E_STRICT 兼容的 PHP 代码

从 Joomla 1.6 版本开始,以及适用于所有版本的 Joomla 平台,都需要遵循 PHP 5.3+ 支持的面向对象编程实践。Joomla 致力于逐步使源代码符合 E_STRICT。

全局变量

不应使用全局变量。请使用静态类属性或常量代替全局变量,遵循 OOP 和工厂模式。

错误抑制

应避免使用 @ 进行错误抑制,并且仅限于在没有其他方法或解决方法可用时使用。

控制结构(通用代码)

对于所有控制结构,关键字和开括号之间都有一个空格,然后在开括号之后或闭括号之前都没有空格。这样做是为了区分控制关键字和函数名称。所有控制结构都必须在其花括号内包含其逻辑。

对于所有控制结构,例如 ifelsedoforforeachtrycatchswitchwhile,关键字都以换行符开头,并且每个花括号都放在新行上。

感叹号 !,在条件中使用的逻辑运算符 not,在感叹号前后不应有空格,如示例所示。

一个 if-else 示例

if ($test)
{
	echo 'True';
}

// Comments can go here.
// Note that "elseif" as one word is used.
elseif ($test === false)
{
	echo 'Really false';
}
elseif (!$condition)
{
	echo 'Not Condition';
}
else
{
	echo 'A white lie';
}

如果控制结构跨越多行,则所有行都必须缩进一个制表符,并且闭花括号必须与最后一行参数位于同一行。

if ($test1
	&& $test2)
{
	echo 'True';
}

一个 do-while 示例

do
{
	$i++;
}
while ($i < 10);

一个 for 示例

for ($i = 0; $i < $n; $i++)
{
	echo 'Increment = ' . $i;
}

一个 foreach 示例

foreach ($rows as $index => $row)
{
	echo 'Index = ' . $index . ', Value = ' . $row;
}

一个 while 示例

while (!$done)
{
	$done = true;
}

一个 switch 示例

使用 switch 语句时,case 关键字会缩进。假设在 case 中代码的缩进,break 语句以换行符开头。

switch ($value)
{
	case 'a':
		echo 'A';
		break;

	default:
		echo 'I give up';
		break;
}

一个 try catch 示例

try
{
	$table->bind($data);
}
catch (RuntimeException $e)
{
	throw new Exception($e->getMessage(), 500, $e);
}

混合语言用法(例如,在布局文件处)

对于布局文件以及我们混合使用 PHP 和 HTML 的所有文件(view/tmpllayout 文件夹中的所有 PHP 文件),我们还将每一行都包装在 <?php ... ?> 块中,并使用控制结构的替代语法。这应该使代码更容易阅读,并使其更容易移动代码块而不会因缺少 <?php ... ?> 标签而导致致命错误。

控制结构示例

一个 if-else 示例
<?php if ($test) : ?>
	<?php $var = 'True'; ?>
<?php elseif ($test === false) : ?>
	<?php $var = 'Really false'; ?>
<?php else : ?>
	<?php $var = 'A white lie'; ?>
<?php endif; ?>

引用

使用引用时,引用运算符之前应该有一个空格,并且在它与函数或变量名称之间没有空格。

例如

$ref1 = &$this->sql;

注意

在 PHP 5 中,对象不需要引用运算符。所有对象都通过引用处理。

连接空格

连接运算符('.')前后始终应该有一个空格。例如

$id = 1;
echo JRoute::_('index.php?option=com_foo&task=foo.edit&id=' . (int) $id);

如果连接运算符是行上的第一个或最后一个字符,则不需要这两个空格。例如

$id = 1
echo JRoute::_(
    'index.php?option=com_foo&task=foo.edit&id=' . (int) $id
    . '&layout=special'
);

数组

数组中的赋值(=> 运算符)可以使用空格对齐。将数组定义拆分为多行时,最后一个值也应该有一个尾随逗号。这是有效的 PHP 语法,有助于将代码差异降到最低。Joomla 3 倾向于使用 array() 以向后兼容 5.3.10,Joomla 4.0.0 及更高版本默认情况下应使用短数组语法 []。(短数组语法是在 PHP 5.4 中引入的)。

例如

$options = [
	'foo'  => 'foo',
	'spam' => 'spam',
];

代码注释

解释代码的内联注释遵循 C (/* … */) 和 C++ 单行 (// ...) 注释的约定。C 样式块通常仅限于文件、类和函数的文档标头。C++ 样式通常用于添加代码备注。强烈建议使用代码备注,以帮助其他人,包括您未来的自己,了解代码的目的。始终在代码执行特别复杂的运算时提供备注。

PHP 文件中不允许使用 Perl/shell 样式注释(#)。

当然,代码块可以使用任何合适的格式注释掉以进行调试,但在提交补丁以回馈核心代码之前,应将其删除。

例如,不要包含诸如以下的功能提交

// Must fix this code up one day.
//$code = broken($fixme);

有关内联代码注释的更多详细信息,请参阅 内联代码注释 章节。

注释 Docblocks

文件、类、类属性、方法和函数中的 PHP 和 Javascript 代码的文档标头(称为 docblocks)遵循类似于 JavaDoc 或 phpDOC 的约定。

这些“DocBlocks”借鉴了 PEAR 标准,但有一些针对 Joomla 和 Joomla 平台的特定变化。

有关 DocBlocks 注释的更多详细信息,请参阅 DocBlocks 注释 章节。

函数调用

函数的调用在函数名称和开括号之间没有空格,在开括号和第一个参数之间没有空格;每个参数(如果存在)之间的逗号后有一个空格,最后一个参数和闭括号之间没有空格。等号前应该有空格,并且等号后正好有一个空格。允许跨多行进行制表符对齐。

// An isolated function call.
$foo = bar($var1, $var2);

// Multiple aligned function calls.
$short  = bar('short');
$medium = bar('medium');
$long   = bar('long');

函数定义

函数定义以新行开头,函数名称和开括号之间没有空格。此外,开花括号和闭花括号也放在新行上。指定返回值的行之前应有一个空行。

函数定义必须包含符合本文档注释部分的文档注释。有关 DocBlocks 函数注释的更多详细信息,请参阅 DocBlocks 注释 章节。

/**
 * A utility function.
 *
 * @param   string  $path  The library path in dot notation.
 *
 * @return  void
 *
 * @since   1.6
 */
function jimport($path)
{
	// Body of method.
}

如果函数定义跨越多行,则所有行都必须缩进一个制表符,并且闭花括号必须与最后一行参数位于同一行。

function fooBar($param1, $param2,
	$param3, $param4)
{
	// Body of method.
}

闭包/匿名函数

闭包/匿名函数在闭包/匿名函数的名称和开括号之间应该有一个空格。方法签名没有空格。

$fieldIds = array_map(
	function ($f)
	{
		return $f->id;
	},
	$fields
);

类定义

类定义以新行开头,开花括号和闭花括号也放在新行上。类方法必须遵循函数定义的准则。属性和方法必须遵循 OOP 标准并进行适当声明(根据需要使用 public、protected、private 和 static)。

类定义、属性和方法都必须提供 DocBlock,符合以下部分。

有关 DocBlocks 类注释的更多详细信息,请参阅 DocBlocks 注释 章节。

类属性 DocBlocks

有关类属性 DocBlocks 的更多详细信息,请参阅 DocBlocks 注释 章节。

类方法 DocBlocks

类方法的 DocBlock 遵循与 PHP 函数相同的约定。

有关 DocBlocks 类方法注释的更多详细信息,请参阅 DocBlocks 注释 章节。

类定义示例

/**
 * A utility class.
 *
 * @package     Joomla.Platform
 * @subpackage  XBase
 * @since       1.6
 */
class JClass extends JObject
{
	/**
	 * Human readable name
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $name;

	/**
	 * Method to get the name of the class.
	 *
	 * @param   string  $case  Optionally return in upper/lower case.
	 *
	 * @return  boolean  True if successfully loaded, false otherwise.
	 *
	 * @since   1.6
	 */
	public function getName($case = null)
	{
		// Body of method.
		return $this->name;
	}
}

命名约定

类应该使用描述性的名称。尽可能避免使用缩写。类名称应该始终以大写字母开头,并使用驼峰命名法编写,即使使用传统的大写首字母缩写词(例如 XML、HTML)。一个例外是 Joomla 平台类,它们必须以大写“J”开头,下一个字母也必须是大写。

例如

  • JHtmlHelper
  • JXmlParser
  • JModel

函数和方法

函数和方法的命名应该使用“驼峰式命名法”(也称为“驼峰大小写”或“小驼峰式命名法”)。名称的第一个字母小写,每个新“单词”开头的字母大写。Joomla 框架中的函数必须以小写字母 'j' 开头。

例如

  • connect();
  • getData();
  • buildSomeWidget();
  • jImport();
  • jDoSomething();

私有类成员(表示仅打算在其声明的同一类中使用的类成员)前面带有单个下划线。属性应以下划线格式编写(即,用下划线分隔的逻辑单词),并且应全部小写。

例如

class JFooHelper
{
	protected $field_name = null;

	private $_status = null;

	protected function sort()
	{
		// Code goes here
	}
}

命名空间

命名空间的格式应遵循以下流程。首先是文件文档块,然后是文件所在的命名空间。如果需要,命名空间后面跟着 defined 检查。最后,使用 use 关键字导入的类。所有命名空间导入都必须按字母顺序排列。

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

namespace Joomla\Module\Quickicon\Administrator\Helper;

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Router\Route;
use Joomla\Module\Quickicon\Administrator\Event\QuickIconsEvent;

常量

常量应始终全部大写,并使用下划线分隔单词。常量名称前缀应为其所属类/包的大写名称。例如,JError 类使用的常量都以 JERROR_ 开头。

普通变量和类属性

普通变量遵循与函数相同的约定。

类变量应设置为 null 或其他适当的默认值。

异常处理

异常应用于错误处理。

以下部分概述了如何语义化地使用 SPL 异常

逻辑异常

当 API 的使用方式存在明确问题时,将抛出 LogicException。例如,如果依赖项失败(您尝试操作尚未加载的对象)。

以下子类也可以在适当的情况下使用

BadFunctionCallException

如果回调引用未定义的函数或缺少某些参数,则可以抛出此异常。例如,如果 is_callable() 或类似函数在某个函数上失败。

BadMethodCallException

如果回调引用未定义的方法或缺少某些参数,则可以抛出此异常。例如,is_callable() 或类似函数在类方法上失败。另一个示例可能是,如果传递给魔术调用方法的参数丢失。

InvalidArgumentException

如果输入无效,则可以抛出此异常。

DomainException

此异常类似于 InvalidArgumentException,但如果值不符合定义的有效数据域,则可以抛出此异常。例如,尝试加载类型为“mongodb”的数据库驱动程序,但该驱动程序在 API 中不可用。

LengthException

如果对参数的长度检查失败,则可以抛出此异常。例如,文件签名不是特定数量的字符。

OutOfRangeException

此异常的实际应用很少,但在请求非法索引时可以抛出。

运行时异常

当某些外部实体或环境导致超出您控制范围的问题时(前提是输入有效),将抛出 RuntimeException。此异常是无法明确确定错误原因时的默认情况。例如,您尝试连接到数据库,但数据库不可用(服务器宕机等)。另一个示例可能是 SQL 查询失败。

UnexpectedValueException

当遇到意外结果时,应使用此类型的异常。例如,函数调用返回字符串而不是预期的布尔值。

OutOfBoundsException

此异常的实际应用很少,但如果某个值不是有效的键,则可能会抛出。

OverflowException

此异常的实际应用很少,但当您将元素添加到已满的容器中时可能会抛出。

RangeException

此异常的实际应用很少,但可能会抛出以指示程序执行期间的范围错误。通常,这意味着存在算术错误,而不是下溢/溢出。这是 DomainException 的运行时版本。

UnderflowException

此异常的实际应用很少,但在尝试从空容器中删除元素时可能会抛出。

记录异常

每个函数或方法都必须使用 @throws 标签注释它抛出的异常类型以及抛出的任何下游异常类型。每种异常类型只需注释一次。无需描述。

SQL 查询

SQL 关键字应大写,而所有其他标识符(显然除了带引号的文本)应小写。

所有表名都应使用 #__ 前缀来访问 Joomla 内容并允许应用用户定义的数据库前缀。查询还应使用 JDatabaseQuery API。表永远不应该使用静态前缀,例如 jos_

要查询我们的数据源,我们可以调用许多 JDatabaseQuery 方法;这些方法封装了数据源的查询语言(在大多数情况下是 SQL),隐藏了特定于查询的语法,并提高了开发人员源代码的可移植性。

使用查询链将多个查询方法一个接一个地连接起来,每个方法都返回一个可以支持下一个方法的对象,这提高了可读性并简化了生成的代码。自从引入 Joomla 框架以来,“查询链”现在已成为构建数据库查询的推荐方法。

表名和表列名应始终用 quoteName() 方法括起来,以转义表名和表列。
查询中检查的字段值应始终用 quote() 方法括起来,以在将其传递给数据库之前转义该值。查询中检查的整数字段值也应类型转换为 (int)

// Get the database connector.
$db = JFactory::getDbo();

// Get the query from the database connector.
$query = $db->getQuery(true);

// Build the query programatically (example with chaining)
// Note: You can use the qn alias for the quoteName method.
$query->select($db->qn('u.*'))
	->from($db->qn('#__users', 'u'));

// Tell the database connector what query to run.
$db->setQuery($query);

// Invoke the query or data retrieval helper.
$users = $db->loadObjectList();

更长的链式示例

// Using chaining when possible.
$query->select($db->quoteName(array('user_id', 'profile_key', 'profile_value', 'ordering')))
    ->from($db->quoteName('#__user_profiles'))
    ->where($db->quoteName('profile_key') . ' LIKE '. $db->quote('\'custom.%\''))
    ->order('ordering ASC');

使用 Select 数组和整数字段值的类型转换的链式示例

$query  = $db->getQuery(true)
	->select(array(
		$db->quoteName('profile_key'),
		$db->quoteName('profile_value'),
	))
	->from($db->quoteName('#__user_profiles'))
	->where($db->quoteName('user_id') . ' = ' . (int) $userId)
	->order($db->quoteName('ordering'));