插件开发的目标最佳实践?

时间:2010-08-23 作者:MikeSchinkel

启动社区wiki以收集objective 插件开发的最佳实践。这个问题的灵感来自@EAMann\'s comments on wp-hackers.

我们的想法是就什么样的客观最佳实践进行协作,以便我们最终能够在某些社区协作审查过程中使用它们。

UPDATE: 在看到最初的几个回复后,很明显,我们每个答案只需要一个想法/建议/最佳实践,人们应该在发布前查看列表,以确保没有重复。

36 个回复
最合适的回答,由SO网友:Arlen Beiler 整理而成

这句话的意思是:Use Actions and Filters

如果您认为人们希望添加或更改某些数据:provide apply_filters() before returning.

P、 有一件事我觉得有点令人失望,你的问题是只为最终用户设计的插件的百分比,即没有自己的挂钩。想象一下,如果WordPress的设计与大多数插件一样?这将是一个非常灵活的解决方案。

如果WordPress能够自动安装其他插件所依赖的插件,情况可能会有所不同?事实上,我通常必须从头开始编写大量所需的功能,因为客户端需要某种方式和可用的插件,而90%的插件不允许我灵活地更新剩下的10%。

我真的希望WordPress社区的领导者能够找到一种方法,确保插件在遵循最佳实践(例如为其他开发人员添加挂钩)的同时获得奖励,就像在StackExchange网站上奖励好的答案一样。

让我们以another question:

示例:当有人转发文章时,我想在插件中做一些事情。如果在流行的retweet插件中有一个自定义的钩子,我可以钩住它,然后启动它,那就太好了。没有,所以我可以修改他们的插件以包含它,但这只适用于我的副本,我不想尝试重新分发它。

相关的Offer Extensible Forms

SO网友:Rick Curran

使用加载脚本/CSSwp_enqueue_scriptwp_enqueue_style

插件不应加载/尝试加载JS/CSS文件的重复版本,尤其是jQuery和WP Core中包含的其他JS文件。

插件应始终使用wp_enqueue_scriptwp_enqueue_style 当链接JS和CSS文件时,不要直接通过<script> 标签。

相关的Re-Use existing functions

SO网友:EAMann

I18n支持

所有输出字符串都应链接到适当的文本域,以便相关方进行国际化,即使开发人员对翻译自己的插件没有兴趣。

请注意,在init 操作,以便用户可以挂接到该操作中。

参见法典:I18n for WordPress Developers

还有这篇文章:Loading WP language files the correctly.

由于WordPress 4.6+

WP 4.6更改了加载顺序和检查的位置,这使得开发人员和用户更容易进行加载。

考虑到一个带有textdomain“我的插件”的插件,WordPress现在将首先在中查找翻译文件:
/wp-content/languages/plugins/my-plugin-en_US.mo

如果在那里找不到,它会在插件告诉它要查找的地方查找(如果遵循codex,通常在Pluings“language”文件夹中):
/wp-content/plugins/my-plugin/languages/my-plugin-en_US.mo

最后,如果没有找到语言文件,它将检查默认位置:
/wp-content/languages/my-plugin-en_US.mo

第一个检查是在4.6中添加的,它为用户提供了一个定义的位置来添加语言文件,就像以前一样,他们需要知道开发人员在哪里添加了语言文件,现在用户只需要知道插件的textdomain:/wp-content/languages/plugins/TEXTDOMAIN-LOCAL.mo

以下是旧方法(由于WP 4.6+)不相关<最后,我想指出,这对我们来说很重要load custom user language files from WP_LANG_DIR before you load the language files that ship with the plugin. 当为同一个域加载多个mo文件时,将使用第一个找到的翻译。这样,插件提供的语言文件将作为用户未翻译字符串的后备。

public function load_plugin_textdomain()
{
    $domain = \'my-plugin\';
    // The "plugin_locale" filter is also used in load_plugin_textdomain()
    $locale = apply_filters( \'plugin_locale\', get_locale(), $domain );

    load_textdomain( 
            $domain, 
            WP_LANG_DIR . \'/my-plugin/\' . $domain . \'-\' . $locale . \'.mo\' 
    );
    load_plugin_textdomain( 
            $domain, 
            FALSE, 
            dirname( plugin_basename(__FILE__) ) . \'/languages/\' 
    );
}

SO网友:John P Bloch

确保插件使用WP\\u DEBUG不会产生错误始终使用WP_DEBUG 在您的整个开发过程中,最好将其打开。插件不应抛出任何错误WP_DEBUG 在…上这包括不推荐使用的通知和未选中的索引。

要打开调试,请编辑wp-config.php 文件,以便WP_DEBUG 常量设置为true. 看到了吗Codex on Debug 更多细节。

SO网友:kaiser

如果可以,请首先使用WordPress Core中的现有功能:use existing functions included in WordPress core 而不是自己写。只有在WordPress core中没有适当的预先存在的函数时,才开发自定义PHP函数。

一个好处是您可以使用;记录弃用通知”轻松监控应更换的功能。另一个好处是用户可以查看Codex中的函数文档,更好地理解插件的功能,即使他们不是经验丰富的PHP开发人员。

相关的I18n support
  • Load Scripts/CSS with wp_enqueue_script and wp_enqueue_style
  • Offer Extensible Forms
  • SO网友:Travis Northcutt

    从WordPress安装中删除插件时,卸载应删除插件的所有数据,a plugin should delete all files, folders, database entries, and tables 它创造了option values 它创建了。

    插件可以提供导出/导入设置的选项,以便在删除之前将设置保存在WordPress之外。

    相关的Deactivation should not provoke Data-Loss

    SO网友:MikeSchinkel

    防止使用输入数据注入SQL插件应该sanitize all user input retrieved directly or indirectly (e.g. via $_POST or $_GET) 在使用输入值查询MySQL数据库之前。

    见:Formatting SQL statements.

    SO网友:Husky

    使用面向类和对象的PHP5代码,没有理由不编写干净的、面向对象的PHP5代码。PHP4支持将在下一版本(WP 3.1)后逐步取消。当然,您可以在所有函数名的前面加上无休止的\\u long\\u function\\u names\\u和\\u lots\\u下划线,但只需编写一个简单的类并将其中的所有内容捆绑起来就容易多了。此外,请将您的类放在一个单独的文件中,并相应地命名,以便您可以轻松地扩展和维护它:

    // in functions.php
    require \'inc/class-my-cool-plugin.php\';
    new MyCoolPlugin();
    
    // in inc/class-my-cool-plugin.php
    class MyCoolPlugin {
        function __construct() {
            // add filter hooks, wp_enqueue_script, etc.
    
            // To assign a method from your class to a WP 
            // function do something like this
            add_action(\'admin_menu\', array($this, "admin"));
        }
    
        public function admin() {
            // public methods, for use outside of the class
            // Note that methods used in other WP functions 
            // (such as add_action) should be public
        }
    
        private function somethingelse() {
            // methods you only use inside this class
        }
    }
    

    SO网友:John P Bloch

    为所有全局命名空间项添加前缀

    插件应为所有全局命名空间项(常量、函数、类、变量,甚至自定义分类法、帖子类型、小部件等)添加正确的前缀。例如,不要创建名为init(); 取而代之的是,将其命名为jpb_init().

    它的常见用法是在名字前面使用三个或四个字母的前缀,或者使用PHP Namespace Feature. 比较:Single-letter prefix for PHP class constants?

    相关的Global Namespace is a limited Resource

    SO网友:MikeSchinkel

    停用不应导致数据丢失插件不应delete any of its data upon deactivation.

    相关的Uninstall should remove all plugin\'s data

    SO网友:Denis de Bernardy

    仅包括您需要的文件。。。

    如果您位于前端,请不要包含与管理区域相关的代码。

    SO网友:MikeSchinkel

    插件卸载时宣布数据丢失

    Upon uninstallation 插件应该prompt a user that it will be deleting it\'s data 并收到用户在删除数据之前可以删除数据的确认,插件也应允许用户选择在卸载时保留数据。(这个想法来自@EAMann。)

    相关的Uninstall should remove all plugin\'s data
  • Deactivation should not provoke to Data-Loss
  • SO网友:AndyBeard

    更改插件的文件夹名称

    /plugins/pluginname/{各种}

    用于文件夹的“pluginname”应始终是可更改的。

    这通常通过定义常量并在整个插件中持续使用来处理。

    不用说,许多流行插件都是罪人。

    相关:

    • plugins_url() 为了方便链接到资源,插件中包含了这些资源

    SO网友:kaiser

    使用WordPress(内置)错误处理return; 如果某些用户输入错误。向他们提供一些信息,说明做得不对。

    function some_example_fn( $args = array() ) 
    {
        // If value was not set, build an error message
        if ( ! isset( $args[\'some_value\'] ) )
            $error = new WP_Error( \'some_value\', sprintf( __( \'You have forgotten to specify the %1$s for your function. %2$s Error triggered inside %3$s on line %4$s.\', TEXTDOMAIN ), \'$args[\\\'some_value\\\']\', "\\n", __FILE__, __LINE__ ) );
    
        // die & print error message & code - for admins only!
        if ( isset( $error ) && is_wp_error( $error ) && current_user_can( \'manage_options\' ) ) 
            wp_die( $error->get_error_code(), \'Theme Error: Missing Argument\' );
    
        // Elseif no error was triggered continue...
    }
    
    一个错误(对象)针对所有主题或插件,您可以在引导过程中为主题或插件设置一个全局错误对象:

    function bootstrap_the_theme()
    {
        global $prefix_error, $prefix_theme_name;
        // Take the theme name as error ID:
        $theme_data = wp_get_theme();
        $prefix_theme_name = $theme_data->Name;
        $prefix_error = new WP_Error( $theme_data->Name );
    
        include // whatever, etc...
    }
    add_action( \'after_setup_theme\', \'bootstrap_the_theme\' );
    
    以后,您可以根据需要添加无限错误:

    function some_theme_fn( $args )
    {
        global $prefix_error, $prefix_theme_name;
        $theme_data = wp_get_theme();
        if ( ! $args[\'whatever\'] && current_user_can( \'manage_options\' ) ) // some required value not set
            $prefix_error->add( $prefix_theme_name, sprintf( \'The function %1$s needs the argument %2$s set.\', __FUNCTION__, \'$args[\\\'whatever\\\']\' ) );
    
        // continue function...
    }
    
    然后你可以在主题结束时把它们全部取出来。这样,您就不会中断页面的呈现,并且仍然可以输出所有错误以进行开发

    function dump_theme_errors()
    {
        global $prefix_error, $prefix_theme_name;
    
        // Not an admin? OR: No error(s)?
        if ( ! current_user_can( \'manage_options\' ) ! is_wp_error( $prefix_error ) )
            return;
    
        $theme_errors = $prefix_error->get_error_messages( $prefix_theme_name );
        echo \'<h3>Theme Errors</h3>\';
        foreach ( $theme_errors as $error )
            echo "{$error}\\n";
    }
    add_action( \'shutdown\', \'dump_theme_errors\' );
    
    有关更多信息,请访问this Q. 修复“协作”的相关票据WP_Errorwp_die() 从那里链接,然后会有另一张票。评论、批评和;这是值得赞赏的。

    SO网友:hakre

    最小化添加到全局命名空间的名称reduce it\'s impact 尽可能地minimizing the number of names it adds to the global namespace.

    这可以通过将插件的函数封装到类中或使用PHP namespaces feature. 在所有内容前加前缀也有帮助,但没有那么灵活。

    除了函数和类之外,插件不应该引入全局变量。使用类通常会淘汰它们,从而简化插件维护。

    相关的Prefix Properly

    SO网友:kaiser

    使用add\\u选项之前的设置API,而不是通过add\\u选项功能将选项添加到DB中,您应该使用Settings API 那会为你照顾好一切。

    在add\\u选项之前使用主题修改APIModifications API 是一种非常简单的构造,也是一种允许添加和检索选项的安全方法。所有内容都将保存为数据库中的序列化值。简单、安全;易于理解的

    SO网友:kaiser

    使用PhpDoc的注释最佳实践接近PhpDoc样式。如果您不使用IDE,如;日蚀“;,你可以看看at the PhpDoc Manual.

    你不必知道这是怎么回事。专业开发人员无论如何都可以阅读代码,只需要将其作为摘要。爱好编码者和用户可能会欣赏你在相同知识水平上解释它的方式。

    SO网友:EAMann

    保护插件用户隐私(上一篇:匿名API通信)

    如果插件与外部系统或API(例如某些Webservice)通信,则应匿名通信,或为用户提供匿名选项,以确保与插件用户相关的数据不会泄露给第二方。

    SO网友:pixeline

    在WordPress上托管插件。org使用SVN 存储库provided on WordPress.org 用于托管插件。它有助于更轻松地更新用户体验,如果您以前从未使用过SVN,那么通过在证明其合理性的上下文中使用它,您可以真正理解SVN。

    SO网友:eddiemoya

    通过使用权限提供访问控制在许多情况下,用户可能不希望每个人都能访问您的插件创建的区域,特别是对于执行多个复杂操作的插件,单次硬编码功能检查可能不够。

    至少,对插件可以用于的所有不同类型的过程进行适当的功能检查。

    SO网友:kaiser

    组织您的代码总是很难读取未按执行顺序编写的代码。首先包括/要求、定义、wp\\u enqueue\\u style&_脚本等,然后是插件/主题所需的功能,最后是生成器(例如管理屏幕、主题中集成的东西等)。

    尝试将css和js等内容分别放在各自的文件夹中。还可以尝试使用仅起辅助作用的函数,如数组展平器等。保留;“主要”;当您试图在一年内进行更新,并且已经很长时间没有看到代码时,尽可能干净且易于阅读的文件是一种帮助用户、开发人员和您的方法。

    有一个你经常重复的结构也很好,这样你总能找到自己的出路。在一个已知的结构中开发不同的项目会给你时间让它变得更好,即使你的客户转向另一个开发人员,你也永远不会听到;他留下了一片混乱;。这会建立你的声誉,应该是一个长期目标。

    SO网友:hakre

    导入/导出插件设置在插件中并不常见,但如果你的插件有(一些)设置,它应该provide Import / Export of data like configuration and user input.

    导入/导出提高了插件的可用性。

    具有这种导入和导出功能(以及撤消机制)的插件示例如下Breadcrumb NavXT (Wordpress Plugin) (完全披露:其中一些小代码是我写的,大部分是mtekk写的)。

    相关的Uninstall should remove all plugin\'s data
  • Deactivation should not provoke Data-Loss
  • SO网友:kaiser

    有风格的模具

    die in a decent manner所有插件(甚至主题)功能都应该使用wp_die() 在关键的地方向用户提供一些关于发生了什么的信息。Php错误很烦人,而且wp_die 可以为用户提供一条关于插件(或他们)做错了什么的漂亮消息。另外,如果用户已经停用了调试,插件就会中断。

    使用wp_die() 还有助于你的插件/主题与wordpress testsuite.

    相关:

    SO网友:edelwater

    为用户提供帮助屏幕

    说RTFM(单击“帮助”)作为答案比一遍又一遍地回答问题更好。

    /**
      * Add contextual help for this screen
      * 
      * @param $rtfm
      * @uses get_current_screen
      */ 
      function ContextualHelp( /*string*/ $rtfm) 
      { 
         $current_screen = get_current_screen();
         if ($current_screen->id == $this->_pageid) 
         {
            $rtfm .= \'<h3>The WordPress Plugin - Screen A</h3>\';
            $rtfm .= \'<p>Here are some tips: donate to me \' .
         }
         return $rtfm; 
      }
    add_action(\'contextual_help\', array($this,\'ContextualHelp\'),1,1);
    
    update / note: (参见kaiser的评论):上述示例将在类中使用

    SO网友:kaiser

    提供可扩展的表单

    当插件提供输入数据的可能性时,它应该始终在末尾有一个挂钩,就在;“提交”;和/或;重置(&Q);按钮,因此开发人员不仅可以使用字段,还可以使用按钮轻松地扩展表单。

    见:Settings API

    相关的Use Actions and Filters
  • Re-Use existing functions
  • I18n support
  • SO网友:bueltge

    始终通过钩子而不是直接包含函数。

    例子:

    不要使用通过new Withook包含插件类

    使用加载的挂钩插件

    // add the class to WP                                   
    function my_plugin_start() {                                                               
        new my_plugin();   
    }                                                        
    add_action( \'plugins_loaded\', \'my_plugin_start\' );
    
    Update:一个小的活生生的例子:Plugin-svn-trunk-page还有一个伪例子

    //avoid direct calls to this file where wp core files not present
    if (!function_exists (\'add_action\')) {
            header(\'Status: 403 Forbidden\');
            header(\'HTTP/1.1 403 Forbidden\');
            exit();
    }
    
    if ( !class_exists( \'plugin_class\' ) ) {
        class plugin_class {
    
            function __construct() {
            }
    
        } // end class
    
        function plugin_start() {
    
            new plugin_class();
        }
    
        add_action( \'plugins_loaded\', \'plugin_start\' );
    } // end class_exists
    
    您也可以通过mu\\u plugins\\u加载到多站点安装,请参阅codex以获取操作参考:http://codex.wordpress.org/Plugin_API/Action_Reference你也看到了,这个钩子对wP的影响:http://adambrown.info/p/wp_hooks/hook/plugins_loaded?version=2.1&file=wp-settings.php我经常使用它,它不那么难,也不那么早,不如一个新的硬类();

    SO网友:EAMann

    在GPL兼容许可证下许可插件,插件和主题应在WordPress兼容许可证下许可。这使得它们可以通过WordPress作为;程序&引用;推荐的许可证是the GPL. 注意插件中包含的所有代码库都与同一许可证兼容

    (这有been a problem 而且很严重point of debate 过去和现在present.)

    SO网友:hakre

    最小化远程数据源和Web服务的副作用一个插件应该Cache/Shield Webservice 和/或XMLRPC/SOAP requests through a caching/data-provider layer 如果您使用它们来避免前端请求等待(缓慢的)webservice响应。

    这包括下载RSS提要和其他页面。设计他们在后台请求数据的插件。

    一个可能的步骤是(以发布到ping.fm为例):创建一个缓冲表,例如:ping\\u fm\\u buffer\\u post(日期、时间、消息、提交时间、状态)

    每次您要向ping提交更新时。fm,将其添加到此表中

  • ,因为我们有这个表,我们还可以列出提交给ping的每个消息。调频并检查每个帖子的状态。以防ping出现问题。fm方面,我们可以重新提交
  • SO网友:gabrielk

    Use WordPress\'s Coding Standards

    http://codex.wordpress.org/WordPress_Coding_Standards

    你知道更新自己编写的代码比更新别人编写的代码要容易得多吗?编码标准使任何从事项目工作的开发人员都能更容易地进入项目并了解情况。

    我们知道你的插件或主题是你自己的,你打断台词和添加花括号的方式是你个性的表现。每一个缩进都是经过深思熟虑的陈述。但是,通过自定义代码,即使您的代码不在核心应用程序中,您也为WordPress做出了贡献。编码标准可以帮助开发人员快速了解您的代码。

    SO网友:Fernando Briano

    测试你的插件

    我们的插件开发环境中肯定会有一些测试工具。

    基于this answer 通过Ethan Seifert 对于测试问题,应遵循以下良好做法:

    单元测试应该测试类可以执行的最小数量的行为

  • 当你达到功能测试的水平时,你可以用Wordpress依赖项测试你的代码
  • SO网友:kaiser

    关心未来的WordPress&;主题版本

    注意:在重新阅读此建议后,我现在退出此实践,因为检查每个功能是否存在可能会降低网站的速度。

    Check if functions are deprecated 直接在你的主题中。

    这是一个;“可能是这样的。”;实例

    if ( ! function_exists( \'wp_some_fn\' ) ) 
    {
        $theme_data = wp_get_theme();
        $error = new WP_Error( \'wp_some_fn\', sprintf( __( \'The function %1$s is deprecated. Please inform the author\', TEXTDOMAIN ), "Theme: {$theme_data->Name}: Version {$theme_data->Version}" );
    
        // abort
        if ( is_wp_error( $error ) )
            return print $error->get_error_message();
    } 
    // else if no error - the function works and exists
    wp_some_fn();
    
    有关正确/最佳做法的错误处理,请参阅以下答案:link

    您甚至可以将$cause放入函数中。这将帮助您和您的用户跟踪主题中可能更改的函数或类。

    SO网友:kaiser

    使用专有名称

    Name hooks and filters (类、函数和变量),所以that people can identify them in even a year, 当他们不记得了,那块蛋糕或代码是从哪里来的。钩子/过滤器名称是否变长并不重要。例如,youruniquename\\u hook/filter\\u whatitdoes。

    如果文件包含名为“dbdbInit”的类,则应将包含该类的文件命名为dbdbInit.class.php“如果你进入dbdbInit-类a函数,该函数注册ex.custom\\u post\\u类型,然后调用它register_custom_post_types().

  • 如果有一个数组包含自定义类型的名称,那么调用分配该数组的变量$custom_post_type_names.function array_handler( $array ) { // handle the array}..
  • 试着用一种你知道标志是什么的方式来命名事物dbdb“用于类、公共函数和变量/对象。这样,您可以在数百个文件中轻松找到它们。(Wordpress在主题之前加载64个文件,加载大约1550个函数,而不是挂钩和筛选器。)

  • SO网友:hakre

    与WordPress核心代码解耦插件应该reduce the impact of the WordPress API to the needed minimum 所以要把插件代码和WordPress代码分开。这减少了WordPress代码库中的更改对插件的影响。此外,这还提高了插件代码的跨版本兼容性。

    这并不意味着不使用WordPress函数(使用它们,如Re-Use existing functions 建议),但不要将您的代码与WordPress功能过度融合,而是将插件业务逻辑与WordPress功能分开。

    SO网友:hannit cohen

    将wp选项用于插件输出字符串

    为了使插件易于使用和自定义,所有输出字符串都应可修改。最好的方法是使用wp选项来存储输出字符串,并提供后端来更改默认值。插件不应使用无法使用插件后端轻松更改的显示字符串。

    例如:Sociable-使您能够更改图标部分“共享和享受:”

    SO网友:kaiser

    使用卸载、激活和停用挂钩有三种不同的挂钩:

    卸载register_uninstall_hook();停用register_deactivation_hook();register_activation_hook();A detailed instruction with a working example can be found here..

    SO网友:Husky

    使用某种模板机制,许多插件倾向于自由混合代码和html,这使得维护和调试非常困难。更好的方法是将所有html放在单独的文件中,并使用如下内容:

    function parseTemplate($file, $options) {
        $template = file_get_contents($file);
        preg_match_all("!\\{([^{]*)\\}!", $template, $matches);
    
        $replacements = array();
        for ($i = 0; $i < count($matches[1]); $i++) {
            $key = $matches[1][$i];
            if (isset($options[$key])) {
                $val = $matches[0][$i];
                $template = str_replace($val, $options[$key], $template);
            }
        }
    
        return $template;
    }
    
    然后像这样使用:

    // In mytemplate.html
    <h1>{title}</h1>
    <h2>{date}</h2>
    <p>{text}</p>
    
    // In your functions.php
    echo parseTemplate("mytemplate.html", array(
        "title" => $title,
        "date" => $date,
        "text" => $somethingelse
    ));
    

    结束

    相关推荐