插件开发文档

💡 云策文档标注

概述

本文档介绍了在 WordPress 插件开发中组织代码的最佳实践,以避免命名冲突并提升代码结构。核心内容包括使用前缀、检查现有实现、面向对象编程方法、文件组织和架构模式。

关键要点

  • 避免命名冲突:通过为全局可访问的代码(如函数、类、命名空间、全局变量、选项和瞬态)添加唯一前缀来防止与其他插件冲突。
  • 检查现有实现:使用 PHP 函数如 function_exists() 和 class_exists() 验证实体是否存在,但注意避免在插件中过度使用以避免破坏性覆盖。
  • 面向对象编程方法:使用类封装插件代码,简化命名冲突管理,并通过类检查确保唯一性。
  • 文件组织:建议将插件文件组织到子文件夹中,如 /includes、/admin、/public,以保持结构清晰。
  • 插件架构:根据插件大小选择架构,小插件可使用简单函数,大插件建议使用类并分离样式和脚本文件。
  • 条件加载:使用 is_admin() 分离管理端和公共端代码,并执行能力检查以确保安全。
  • 避免直接文件访问:在文件顶部添加 ABSPATH 检查以防止未授权访问。
  • 架构模式:包括单文件函数、单文件类、主文件加类文件等模式,可参考现有教程和样板如 WordPress Plugin Boilerplate。

代码示例

// 使用前缀的函数示例
function ecpt_save_post() {
    // 函数实现
}

// 检查函数是否存在的示例
if ( ! function_exists( 'wporg_init' ) ) {
    function wporg_init() {
        register_setting( 'wporg_settings', 'wporg_option_foo' );
    }
}

// 面向对象编程示例
if ( ! class_exists( 'WPOrg_Plugin' ) ) {
    class WPOrg_Plugin {
        public static function init() {
            register_setting( 'wporg_settings', 'wporg_option_foo' );
        }
    }
    WPOrg_Plugin::init();
}

// 避免直接文件访问的代码
if ( ! defined( 'ABSPATH' ) ) {
    exit; // 如果直接访问则退出
}

注意事项

  • 前缀应至少 4-5 个字母长,避免使用常见英文单词或与 WordPress 核心冲突的前缀如 wp_、WordPress。
  • 在子插件(如 WooCommerce 扩展)中,同样需要避免使用其常见前缀。
  • 使用 if-exists 检查应谨慎,仅适用于共享库,以避免插件因其他代码先加载而中断。
  • 条件加载时,is_admin() 不验证用户身份或能力,需额外进行能力检查。
  • 架构选择应基于插件规模和预期扩展性,大插件建议从类开始设计。

📄 原文内容

Here are some best practices to help organize your code so it works well alongside WordPress core and other WordPress plugins.

Avoid Naming Collisions

A naming collision happens when your plugin is using the same name for a variable, function or a class as another plugin.

Luckily, you can avoid naming collisions by using the methods below.

Procedural Coding Method

By default, all variables, functions and classes are defined in the global namespace, which means that it is possible for your plugin to override variables, functions and classes set by another plugin and vice-versa. Variables that are defined inside of functions or classes are not affected by this.

Prefix Everything

All globally accessible code should be prefixed with a unique identifier. Prefixes prevent conflicts with other plugins and prevents them from overwriting your variables and accidentally calling your functions and classes.

In order to prevent conflicts with other plugins, your prefix should be at least 4 letters long, though we recommend 5. You should avoid using a common English word, and instead choose something unique to your plugin. We host tens of thousands of plugins on WordPress.org alone. There are hundreds of thousands more outside our servers. You’re going to run into conflicts.

A good way to do this is with a prefix. For example, if your plugin is called “Easy Custom Post Types” then you could use names like these:

  • function ecpt_save_post()
  • define( ‘ECPT_LICENSE’, true );
  • class ECPT_Admin{}
  • namespace EasyCustomPostTypes;
  • update_option( 'ecpt_settings', $settings );

Because you are making code as a part of the WordPress project, you must avoid the use of prefixes that have a high probability of conflicting with the core WordPress. This includes but is not limited to: __ (double underscores), wp_ , WordPress, or _ (single underscore)

If you are making code for a ‘sub’ plugin (such as a WooCommece extension), you would similarly need to avoid using any of their normal/common prefixes (i.e. Woo, WooCommerce).

You can use them inside your classes or namespace, but not as stand-alone function/namespace/class.

If you’re using _n() or __() for translation, that’s fine. We’re only talking about functions you’ve created for your plugin, not the core functions from WordPress. In fact, those core features are why you need to not use those prefixes in your own plugin! You wouldn’t want to break WordPress for your users.

Remember: Good prefix names are unique and distinct to your plugin. This will help you and the next person in debugging, as well as prevent conflicts.

Code that must be prefixed includes:

  • Functions (unless namespaced)
  • Classes, interfaces, and traits (unless namespaced)
  • Namespaces
  • Global variables
  • Options and transients

Check for Existing Implementations

PHP provides a number of functions to verify existence of variables, functions, classes and constants. All of these will return true if the entity exists.

Keep in mind that using (!function_exists(‘NAME ‘)) { around all your functions and classes sounds like a great idea until you realize the fatal flaw. If something else has a function with the same name and their code loads first, your plugin will break. Using if-exists to replace/override a function or class should be reserved for shared libraries only.

Example

// Create a function called "wporg_init" if it doesn't already exist
if ( ! function_exists( 'wporg_init' ) ) {
    function wporg_init() {
        register_setting( 'wporg_settings', 'wporg_option_foo' );
    }
}

// Create a function called "wporg_get_foo" if it doesn't already exist
if ( ! function_exists( 'wporg_get_foo' ) ) {
    function wporg_get_foo() {
        return get_option( 'wporg_option_foo' );
    }
}

Object Oriented Programming Method

An easier way to tackle the naming collision problem is to use a class for the code of your plugin.

You will still need to take care of checking whether the name of the class you want is already taken but the rest will be taken care of by PHP.

Example

if ( ! class_exists( 'WPOrg_Plugin' ) ) {
    class WPOrg_Plugin {
        public static function init() {
            register_setting( 'wporg_settings', 'wporg_option_foo' );
        }

        public static function get_foo() {
            return get_option( 'wporg_option_foo' );
        }
    }

    WPOrg_Plugin::init();
    WPOrg_Plugin::get_foo();
}

File Organization

The root level of your plugin directory should contain your plugin-name.php file and, optionally, your uninstall.php file. All other files should be organized into sub folders whenever possible.

Folder Structure

A clear folder structure helps you and others working on your plugin keep similar files together.

Here’s a sample folder structure for reference:

/plugin-name
     plugin-name.php
     uninstall.php
     /languages
     /includes
     /admin
          /js
          /css
          /images
     /public
          /js
          /css
          /images

Plugin Architecture

The architecture, or code organization, you choose for your plugin will likely depend on the size of your plugin.

For small, single-purpose plugins that have limited interaction with WordPress core, themes or other plugins, there’s little benefit in engineering complex classes; unless you know the plugin is going to expand greatly later on.

For large plugins with lots of code, start off with classes in mind. Separate style and scripts files, and even build-related files. This will help code organization and long-term maintenance of the plugin.

Conditional Loading

It’s helpful to separate your admin code from the public code. Use the conditional is_admin(). You must still perform capability checks as this doesn’t indicate the user is authenticated or has Administrator-level access. See Checking User Capabilities.

For example:

if ( is_admin() ) {
    // we are in admin mode
    require_once __DIR__ . '/admin/plugin-name-admin.php';
}

Avoiding Direct File Access

As a security precaution, it’s a good practice to disallow access if the ABSPATH global is not defined. This is only applicable to files which contain code outside of class or function definitions, such as the main plugin file.

You can implement this by including this code at the top of the file:

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

Architecture Patterns

While there are a number of possible architecture patterns, they can broadly be grouped into three variations:

Architecture Patterns Explained

Specific implementations of the more complex of the above code organizations have already been written up as tutorials and slides:

Boilerplate Starting Points

Instead of starting from scratch for each new plugin you write, you may want to start with a boilerplate. One advantage of using a boilerplate is to have consistency among your own plugins. Boilerplates also make it easier for other people to contribute to your code if you use a boilerplate they are already familiar with.

These also serve as further examples of different yet comparable architectures.

  • WordPress Plugin Boilerplate: A foundation for WordPress Plugin Development that aims to provide a clear and consistent guide for building your plugins.
  • WordPress Plugin Bootstrap: Basic bootstrap to develop WordPress plugins using Grunt, Compass, GIT, and SVN.
  • WP Skeleton Plugin: Skeleton plugin that focuses on unit tests and use of composer for development.
  • WP CLI Scaffold: The Scaffold command of WP CLI creates a skeleton plugin with options such as CI configuration files

Of course, you could take different aspects of these and others to create your own custom boilerplate.