9

Currently, the way I do it is, on the plugins_loaded hook, I let the world know that my plugin has loaded:

add_action( 'plugins_loaded', function() {
    do_action( 'my_plugin_has_loaded' );
}, 10 );

And so, others can depend/only run when my_plugin_has_loaded fires, however, I see a lot of hooks plugin_loaded (5.1.0) and even some plugins firing their init before plugins_loaded. Is there a better way to make a plugin wait for another?

The problem I see with this approach is that I launch my own init on plugins_loaded, however, I also see a silver lining - plugins are meant to be done loading here, so, if there'd be any type of logic like this, it'd be here.


Issue #1 - Using class_exists:

  1. I can't force naming conventions as I would do for actions, such as always looking/hooking for plugin-$name%:init to ensure consistency through actions. If I know a plugin's name, then I can very easily predict it's init point so that I can run after, but if I don't, I have to know what class is its main controller, which leads me to the next point -
  2. If I rely on this way of checking, I am merely looking for the existence of a class, which, at best, lets me know a plugin is activated, but not if the plugin has finished its setup. With an action I can arbitrarily decide when a plugin has finished loading and initializing all it needs such that dependants can run.

  3. Continuing from (2), I am now forcing myself to write plugins in such a way that I'd always need a god-mode-controller class that runs all its stuff on __construct. People who will do class_exists( 'MyClass\From\Plugin\IWanna\DependOn' )will also inherently assume that I run everything on __construct, however, my plugin my have errors in initializing itself, but, because it's all run on __cosntruct, I can't debug that.

Issue #2 - Using TGMPA:

  1. Assume plugin A dependend on plugin B. If the user disables B, then A will throw errors. Sure, I can handle them, but that's exactly the point of the question, really - reverse this point I just said and you reach my problem: ensuring dependency while handling each case in which you interact with plugins. I need this to be completely off user-land territory.
5
  • Have you considered taking a look at TGM Plugin Activation? – Jack Johansson May 24 '20 at 2:50
  • @JackJohansson This is a user-land plugin. I need something that, by code (without user interaction) works. – Daniel Simmons May 24 '20 at 2:57
  • in my opinion best way to depend on another plugin is classes or functions. if the plugin uses a specific class or function to initiliase, then it would be easy way. e.g if your plugin depends on woocommerce you can easily check whether woocommerce is loaded or not by simply runing class_exists('WooCommerce'); – Raashid Din May 24 '20 at 6:45
  • @RaashidDin I can't create a naming convention that's predictable with class names because people prefer different naming conventions for these. With actions, however, I can always ensure a format of plugin\$plugin_name$:init. Also, while this is personal preference, it's easier to reason about do an action (init) on another action in an event-driven system rather than do an action if class exists. Additionally, when I check for the class WooCommerce, it only implies its existence, not its loading, if I go this way, then I can't check if the plugin has actually initialized. – Daniel Simmons May 24 '20 at 7:10
  • @JackJohansson I've updated the question with details that I realized were very good points once I saw some comments. – Daniel Simmons May 24 '20 at 7:20
3
+300

Consider plugin A:

$A_message;

add_action('plugins_loaded', function() {
    do_action('plugin-A:init');
});

// Hooks allow B to access A (both during initialization of A and elsewhere in WordPress) without having to check if A exists.

add_filter('plugin-A:set', function($msg) {
    $A_message = $msg;
}, PHP_INT_MAX);

add_filter('plugin-A:get', function($msg) {
    return $A_message;
}, PHP_INT_MIN);

Now, consider plugin B:

add_action('plugin-A:init', function() {
    // We're 100% sure that A has loaded (if it exists)
    // Store what Cheech says in A.  (Using a filter allows us to not have to check if A exists).
    do_action('plugin-A:set', "Brent! Open the door!");
});

add_filter('the_title', function($title) {
    // Get something from A (and we don't have to check if it exists).
    // If A exists, return what Cheech says; if A does not exist, return what Chong says.
    $title = apply_filters('plugin-A:get', "Dave's not here, man!");
    return $title;
});

Most of this code sounds like it is nothing new to you. When Foo is loaded, it initializes the Bar plugin by way of bar_on_plugins_loaded() and foo_load_bar(). What is new here is that Foo does not need to do any fancy checks to see if Bar exists or not. This is because foo_load_bar() executes a hook that is defined by Bar instead of a property of Bar itself. (Pretty cool, huh?)

Later on in your code when a title is requested (like in post list tables) foo_get_bar_message() and bar_get_message() will return Bar's value that was set during the initialization of Bar by way of bar_set_message(). Again, this is all done without the Foo plugin having to check for Bar's existence. And, in the event that Bar does not exist, the Foo default will be returned. (Special thanks to Cheech and Chong for the inspiration.)

Edit: In the above example, B depends more on A than the other way around. But, you asked for A depending on B and the same concept holds true here. Consider this addition to plugin A:

// This function is used somewhere in plugin-A ...
function a_func() {
    // Not sure if B exists, but we can make B do something if it does.
    do_actions('plugin-B:some_func', '*knock knock knock*', 'Who Is It?');
}

And this addition to plugin B:

add_action('plugin-B:some_func', function($cheech, $chong) {
    echo '<p>'. $cheech .'<br>'. $chong .'</p>';
}

In addition to B (if it exists) turning all the titles into either Dave or Brent's message, the beginning of Cheech and Chong's skit will output by plugin A when it calls its a_func() somewhere later on in its own code. As per your desire, A need not do anything to check if plugin B exists.

10
  • I've now posted my answer. Can you please check it? I believe it's the same thing I came up with before asking this but implemented otherwise. Also, what's the point of apply_filter in there and how do I care about that? – Daniel Simmons May 26 '20 at 10:53
  • 1
    plugins_loaded is the first hook that fires when when all the plugins are loaded and all the plugable functions are first made available (and before the template begins its output). Filtering is just a way plugin B is able to retrieve a default value in the event that plugin A does not exist. What is (new Init)->boot(); doing? Why is that needed in plugin A? (I'll update my answer to be congruent with yours: using A and B instead of Foo and Bar. Also, I can't reply to anything except this answer because I'm new. So, any upvotes would help me greatly.) – Mort 1305 May 26 '20 at 19:07
  • Ah, so you mean that apply_filter is a bonus on "don't depend on stuff like A->getValue('key')", instead, use apply_filters so that when a plugin isn't active, you don't get errors. While out of scope, it's a nice thought. However, not every single value that depends on coming from a plugin should be allowed to be changed. – Daniel Simmons May 27 '20 at 6:21
  • Also (new Init)->boo() is simply just a way to initialize your plugin. Well, everyone should write plugins to be error-checking wherever it's needed but almost no one does, so I guess it's a weird concept. Basically, if an error happened during the initialization of the plugin, that allows me to check for it. I wrote it in the question just because a class exists and was therefore loaded and it's a plugin's main one, doesn't mean the plugin was actually initialized properly, so, you can end up depending on something that actually isn't working -- it is there, just not in the right way. – Daniel Simmons May 27 '20 at 6:27
  • That is correct, @DanielSimmons. It eliminates the need for a "God plugin." In my example, B depends more on A than the other way around. But, that's not a big issue as they can depend on each other: I'll update my answer to show how. Also, it's not just apply_filters but also do_action ... think of these more as calling a function of the plugin where the hook name is the name of the function. (Function filtering was the basis of how the old MortCore ran back in 2001, if you're old enough to remember that.) Copy on the error thing: there really is no good way to check for that. – Mort 1305 May 27 '20 at 6:28
2

I've had this initial build that worked, even before the question. I've now seen @cjbj's answer as well as Mort's. Mort is on the same frequency, however, hear me out:

Inside my main plugin A, which B will depend on, inside index.php:

add_actions( 'plugins_loaded', function() {
    $boot = (new Init)->boot();

    if( \is_wp_error( $boot ) ) {
        return False;
    }

    /**
    * Fire if the plugin has successfully loaded itself.
    */
    do_action( 'plugin-A:init' );
});

Inside index.php of plugin B:

add_action( 'plugin-A:init', function() {
    //Great, so, we're 100% sure that A has successfully loaded.
});

I genuinely can't find any issues with this BUT hooking onto plugins_loaded smells weird. Am I just being paranoid for no reason?

5
  • Why do you initialize plugin A on plugins_loaded? – Himad May 26 '20 at 18:33
  • @Himad That's the earliest hook that I can reliably initialize it on. – Daniel Simmons May 27 '20 at 1:55
  • @DanielSimmons If my plugin were to depend on your plugin, then I'd thank you for providing that custom plugin-A:init hook - that is a reliable way to depend on a plugin, just like woocommerce_init. So we need to first study the technical of the plugin we're depending on, e.g. is there an "init" or "on-load" hook in that plugin. And if there's none, then we'd have to use the functionality checking like class_exists() and/or is_plugin_active() despite they're not bullet-proof as you may already know. – Sally CJ May 28 '20 at 23:55
  • @SallyCJ Yes, that's exactly the point. I am offering both to myself and to others, a way to 100% depend on myself. When you hook into plugin-A:init, you're 100% sure that the plugin has not only initialized, but had no errors in its setup of internals and whatever it needs.**The thing about this approach is that you can wait for the "init" of multiple plugins. Although not that nice to do 4-5 did_action inside an if, it's definitely reliable.** – Daniel Simmons May 29 '20 at 10:10
  • Well, I just wanted to add that there's a typo in your answer.. add_actions should be add_action.. cheers! – Sally CJ May 30 '20 at 5:55
0

Actually i use something very simple, because i have a plugin which i depend on for multiple purposes, developing themes/plugins, so i just define a constant inside it (e.g THE_CONSTANT), then say i have another plugin which depends on it for multiple operations which may plugins_loaded may not help, so just after the plugin definition i check if that constant is defined, if not!? just terminate the script, and you will then have only the plugin is listed in plugins page but like it is a brand new empty plugin. once the plugin been depended on is active the dependent just work fine.

Something like this:

<?php
/**
 * Plugin Name: Plugin name
 */

/**
 * Display a notification if one of required plugins is not activated/installed
 */
add_action( 'admin_notices', function() {
    if (!defined('THE_CONSTANT')) {
        ?>
        <div class="notice notice-error is-dismissible">
            <p><?php esc_html_e( 'Please activate/install {Wanted plugin name} , for this plugin can work properly' ); ?></p>
        </div>
    <?php }
});


if (!defined("THE_CONSTANT")) return;
4
  • If you put your if (!defined("THE_CONSTANT")) return; above your add_action() call, you won't need to make that same check in the anonymous function because nothing below that the checking of the constant will be executed if it doesn't exist. Actually, if you put it as the very first thing executed in your plugin, I don't think you'll ever have to make the check again anywhere in your plugin, unless, of course, that other plugin disables itself half-way thru running. (Also, woo-hoo! I can finally comment on answers other than my own!) – Mort 1305 May 27 '20 at 8:01
  • 1
    But if i put it above the action the, action will not execute because i return if the constant is not define, i want to make sure a message to be displayed for the user to activate/install the required plugin, and only if the plugin is not active – Mohamed Omar May 27 '20 at 8:04
  • Oopsies, missed that part. Nicely done! – Mort 1305 May 27 '20 at 8:06
  • While this is valid and I know very well that there's no better alternative, given my approach, all you'd have to do is did_action which would refer to the same centric init action dependent plugins use to see if a plugin has been initialized. So, you're dealing just with actions instead of actions and constants. Easier to write, reason about and keep consistent. – Daniel Simmons May 30 '20 at 6:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.