HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Sharing code across plugins

rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one"NY ✭✭✭

I have several plugins the use the same functions. Currently each has a copy within the plugin's source file. This seems an unproductive way to maintain these functions (any change need to be manually copied to the other versions).
My question is on the best practice to share functions across plugins. Hopefully the suggestions will address these related issues as well:

  • How to include them (what's the best way)?
  • Where to store them (which folder)?
  • How to distribute them through the plugin directory
  • Any performance impacts?

P.S. This should probably be in the "Development" category (but unfortunately I can't post there)

Comments

  • RiverRiver MVP
    edited September 2016

    I wouldn't share!

    unless you had a plugin like useful functions. that many plugins used.

    plugins should be standalone, unless they require one another for a very definite reason.

    it is often a pain to have to load multiple plugins, if you really only need one and there is a possibility only one plugin feature is needed.

    or combine all your plugins into one plugin.

    Pragmatism is all I have to offer. Avoiding the sidelines and providing centerline pro-tips.

  • R_JR_J Ex-Fanboy Munich Admin

    Bundle them in a plugin. You wouldn't be the first to do so:
    https://vanillaforums.org/addon/aeliafoundationclasses-plugin

    You can also put them in your conf/bootstrap.before.php

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    Thanks! Reading both answers tells me that this is not cut and dry. I'd be happy to hear more from you as well as from others.

    Let's use a concrete example - consider pluginA and PluginB use a common function code functionA.

    Placing them in a huge pluginAB would require users to have both functionalities, complicates the setup (which should activate just the A or B functionality), likely have unnecessary hooks, larger code, and of course unnecessary updates (for users who care little about the "other" functionality).

    Conversely, creating a separate plugin for FunctionA called PluginFunctionA would allow sharing of code (both PluginA and PluginB could require PluginFunctionA). So in this scheme PluginA and PluginB would have in their Plugininfo a "RequiredPlugin" for PluginFunctionA and also have a "require_once('..\PluginFunctionA\functions.php');" statement. I'm not absoutely sure the require_once would work or be sufficient (class scope?) and it just doesn't seem elegant to refer back to another folder, right?

  • R_JR_J Ex-Fanboy Munich Admin

    If you write plugins to distribute them, always including all needed functionality is the most user friendly thing you can do.
    Dependencies suck. You do not want to have them if you install new plugins. This adds an extra layer of complexity which isn't user friendly.

    If you write plugins mainly for yourself, having some sort of helper plugin is the best thing to keep things maintainable.
    Whenever you need another helper function or want to improve an existing, you only have to look at one file. No different versions you have to look at or anything like that.


    Concerning the how: create a class.helper.plugin.php with following structure:

    PluginInfo...
    
    
    class HelperPlugin  extends Gdn_Plugin {
        /**
         * Maybe something which expects a discussion controller.
         * The name should reflect that somehow...
         */
        public function helperMethodA($sender) {
            // put your magic here
        }
    }
    
    // Here is a good place for helper functions.
    
    if (!function_exists('helperFunctionA')) {
        function helperFunctionA(...) {
        }
    }
    

    As soon as this plugin is enabled you will be able to use helperFunctionA immediately. You would be able to access the method like that:

    $helper = new HelperPlugin();
    $helper->helperMethodA();
    
  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    Thanks @R_J and @River. Appreciate your insights. I'm still pondering the issue...
    @hgtonight , @Linc - any thoughts on this subject?

  • LincLinc Detroit Admin
    edited September 2016

    First, I think distributing a "generic functions" plugin is a very bad route. Generic functions go in core, that's the point. No one wants to maintain extra plugins - neither the author nor the end user. I'd take this off the table.

    Second, I would consider whether you're reinventing the wheel or bucking the system. Is there something already in core or an alternate way of thinking about what's causing you to have these "generic" functions? Are you doing it "The Vanilla Way" or are you trying to shoehorn your old habits into this framework?

    Third, is this some functionality ALL plugin authors might benefit from and therefore should be considered for core? Have you asked for a code review of what you're doing to see if anyone has specific ideas for ridding your code of these repeaters? Talking in the abstract like this can be quite a meandering road to better code.

    As a last resort, I would consider a build step for your plugins. Maybe they all go in one repo, and you maintain your own functions.general.php that you include in each plugin before distributing. That way you only have 1 copy of the code to maintain.

    My own personal experience has been that I have never needed such a thing and that any time I have seen it implemented it turned into onerous overhead, so I'd take a long hard look at my second point.

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    @Linc wrote: Second, I would consider whether you're reinventing the wheel or bucking the system. Is there something already in core or an alternate way of thinking about what's causing you to have these "generic" functions? Are you doing it "The Vanilla Way" or are you trying to shoehorn your old habits into this framework?

    Indeed it is best to use existing functions. It would be great if we had a documented function list rather than searching for them in the core and then researching each to ensure the passed parameters are correctly set.

    Third, is this some functionality ALL plugin authors might benefit from and therefore should be considered for core?

    Hard to tell. For example, I wrote an "explain" function that allows admins understand why a plugin return some results. It's different than full-scale debugging, not appropriate for all plugins but I see how useful it is for some of my plugins (published or not). I don't claim "explain" is ready for review, but I still want to use it with some of my plugins and not maintain several copies. Thus I thought that setting upfront the proper method for code-sharing-across-plugins would be helpful until it gets considered for the core (which of course it may never get there).

  • R_JR_J Ex-Fanboy Munich Admin

    @rbrahmson said:
    For example, I wrote an "explain" function that allows admins understand why a plugin return some results. It's different than full-scale debugging, not appropriate for all plugins but I see how useful it is for some of my plugins (published or not).

    That would make a good TroubleShooter-Plugin. You might end up with a DevelopmentTools-Plugin. But such code doesn't belong into a production plugin.
    I know that "Bug reports" are quite common produced by software packages, but those are far more complex than a simple plugin is. Why should a forum admin want to debug your code? If there are prerequisites for your plugin, check them in the setup() function.

    That's too simplified, I know, but my point is that you have a) users of your plugin and they are not willing/able to debug your code or you have b) developers and they might be happy about general development tools.

  • RiverRiver MVP
    edited September 2016

    @R_J said:

    @rbrahmson said:
    For example, I wrote an "explain" function that allows admins understand why a plugin return some results. It's different than full-scale debugging, not appropriate for all plugins but I see how useful it is for some of my plugins (published or not).

    But such code doesn't belong into a production plugin.

    Thats what I've been telling him all along as constructive criticism. All the debugging statements in those plugins make it some of the most unreadable code of any plugins, unfortunately. Very difficult to look at with all the clutter.

    I know that "Bug reports" are quite common produced by software packages, but those are far more complex than a simple plugin is. Why should a forum admin want to debug your code? If there are prerequisites for your plugin, check them in the setup() function.

    That's too simplified, I know, but my point is that you have a) users of your plugin and they are not willing/able to debug your code or you have b) developers and they might be happy about general development tools.

    right, should not be in a production plugin, if you want a clean readable plugin.

    @rbrahmson said:

    It would be great if we had a documented function list rather than searching for them in the core and then researching each to ensure the passed parameters are correctly set.

    most of the vanilla code has some documentation about the functions (granted some is out of date or wrong), but they do a pretty good job on the code documentation in my opinion.

    if you want to create your own, it is not hard.

    http://www.stack.nl/~dimitri/doxygen/download.html

    or you could use this e.g.

    http://fossies.org/dox/vanilla-Vanilla_2.2.1/functions.html

    you can also include graphics if you want. there are other documentation tools out there.

    I almost think you are seeking answers until you get someone to agree with what you want to do, but it might not happen.

    Pragmatism is all I have to offer. Avoiding the sidelines and providing centerline pro-tips.

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    @River - Bad example ("Explain") because it steered you into debugging, coding readability, etc. What if I wanted to share my modified PorterStemmer code across some plugins? You see, all the answers that had valuable pointed and unrelated criticism, did not really suggest an ideal method for sharing code across plugins.

    I am now wondering whether there is a build method that merges sources so that the distributed source would include the shared parts (that inevitably be duplicated across plugins). Not ideal either...

  • For your development stage, you could do something like this, maybe?


    and if you want to use some functions from your standard library, you can use it in this way:


  • R_JR_J Ex-Fanboy Munich Admin

    @Linc said:
    As a last resort, I would consider a build step for your plugins. Maybe they all go in one repo, and you maintain your own functions.general.php that you include in each plugin before distributing. That way you only have 1 copy of the code to maintain.

    a) Put your helper functions in a separate file
    b) Write a barebone plugin that has an include helper functions file line after the (empty) plugins class to be able to access your functions
    c) Use a batch to append that helper file to a plugin so that you can ship it with the helper functions included.

    This batch creates a temp folder, copies the plugins directory to it and appends a helper file. If you get a commandline zip program or use VBS, you'll be able to automatically zip that folder in order to distribute it. Save it as e.g. "makedist.bat" and use it like that: "makedist pluginName"

    set plugins_root=c:\www\vanilla\plugins
    set helper_file=c:\www\vanilla\plugins\helper_functions.php
    
    :: command line parameter
    set plugin_name=%1
    set plugin_dir=%plugins_root%\%plugin_name%
    set plugin_file=%plugin_dir%\class.%plugin_name%.plugin.php
    
    :: create a temp dir
    set dist_dir=%temp%\dist
    set target_dir=%dist_dir%\%plugin_name%
    rmdir /s /q "%dist_dir%"
    mkdir "%dist_dir%"
    
    :: do not copy empty directories
    xcopy "%plugin_dir%" "%target_dir%\" /s
    
    set dist_plugin=%target_dir%\class.%plugin_name%.plugin.php
    
    :: skip first line which should be <?php
    more +1 "%helper_file%"  >>"%dist_plugin%"
    
    :: zip...
    
  • LincLinc Detroit Admin

    Composer does this build process as well. It's how we're now handling dependencies, which each get their own repo.

    It would be great if we had a documented function list rather than searching for them in the core and then researching each to ensure the passed parameters are correctly set.

    I feel like you're overinflating the effort involved here a little. Most have docblocks explaining their parameters. And if some don't, that's a great way to contribute to core. There aren't that many utility methods, and they're all in the functions.* files under /library. If you open them in an IDE it'll make you a list in the sidebar. ;) I realize we're not at WordPress Codex-level docs yet but we've made great strides here. And it's certainly easier to peruse those files than writing novel functions!

    I agree we should maybe consider another Doxygen run. I may invest in that on the other side of API v2. I did work on that back in my pre-staff days, but I'll be honest: I never really ended up using it much after I made it. I'm sure I spent more time on the build than I ever spent looking up things in it.

    By the by, we've been building a replacement for Debugger internally that I think is almost ready. I'll poke Todd and see if we can release it. That might be the ultimate solution for your debug woes, tho I also recommend xdebug if you don't have that configured on your system yet.

  • rbrahmsonrbrahmson "You may say I'm a dreamer / But I'm not the only one" NY ✭✭✭

    This has been very valuable, thanks!
    The discussion should probably be moved to the development category.

Sign In or Register to comment.