Using register_activation_hook in classes

I am trying to develop a plugin for basic SEO purposes, as a lot of people I know don’t like using Yoast. I literally just started the plugin, and am building out the activation message displayed to the user when they activate the plugin. I am having trouble with a mix between OOP and built-in WordPress functions, and not sure where I am going wrong.

The only way I can get this to work, is by creating a new instance of the SEO_Plugin_Activation class inside my main files constructor, and then calling the activatePlugin method in that class. I feel like this is unnecessary. I also think how I am executing my functions in the activation class file don’t really make much sense either. But it’s the only way I can get it to work right now.

I am not sure if what I am doing is because I am not grasping OOP techniques 100%, or if I am not utilizing WordPress API correctly. I have included three code examples in the following order:

  1. Main plugin file
  2. Class file that handles my activation requirements
  3. What I really was hoping could be done.

seo.php (main plugin file)

<?php
/*
... generic plugin info
*/

require_once(dirname(__FILE__) . '/admin/class-plugin-activation.php');

class SEO {
  function __construct() {
    $activate = new SEO_Plugin_Activation();
    $activate->activatePlugin();
  }

}

new SEO();
?>

class-plugin-activation.php

<?php
class SEO_Plugin_Activation {

  function __construct() {
    register_activation_hook(__FILE__ . '../seo.php', array($this, 'activatePlugin'));
    add_action('admin_notices', array($this, 'showSitemapInfo'));
  }

  function activatePlugin() {
    set_transient('show_sitemap_info', true, 5);
  }

  function showSitemapInfo() {
    if(get_transient('show_sitemap_info')) {
      echo '<div class="updated notice is-dismissible">' .
              'Your sitemap files can be found at these following links: ' .
            '</div>';
      delete_transient('show_sitemap_info');
    }
  }

}
?>

seo.php(What I was hoping for)

<?php
/*
 ... blah blah blah
*/

require_once(dirname(__FILE__) . '/admin/class-plugin-activation.php');

class SEO {
  function __construct() {
    register_activation_hook(__FILE__, array($this, 'wp_install'));
  }

  function wp_install() {
    $activate = new SEO_Plugin_Activation();
    // Execute some method(s) here that would take care 
    // of all of my plugin activation bootstrapping
  }

}

new SEO();
?>

I tried doing it the way I outline in the third script, but am having no success. As of right now, the message does display properly, with no error messages.

4 s
4

Having reread your question, I think I see the issue, and it stems from a misunderstanding of how register_activation_hook works, combined with some confusion over how you’re bootstrapping your code and what it means to bootstrap

Part 1: register_activation_hook

This function takes 2 parameters:

register_activation_hook( string $file, callable $function )

The first parameter, $file is the main plugin file, not the file that contains what you want to run. It’s used the same way as plugins_url, so you need the value of __FILE__, specifically its value in the root plugin file with your plugin header.

The second parameter is a callable and works as you expect, but it’s really just using add_action internally

When a plugin is activated, the action ‘activate_PLUGINNAME’ hook is called. In the name of this hook, PLUGINNAME is replaced with the name of the plugin, including the optional subdirectory. For example, when the plugin is located in wp-content/plugins/sampleplugin/sample.php, then the name of this hook will become ‘activate_sampleplugin/sample.php’.

Part 2: Bootstrapping and __FILE__

A fundamental problem here is that __FILE__ will have different values in different locations, and you need a specific value.

You also have a problem, that bootstrapping should assemble the object graph, but you don’t do that. All your objects are created as soon as they’re defined, or created inside eachother, making it difficult or impossible to pass values to them.

As an example, I could write a plugin like this:

plugin.php:

<?php
/**
 * Plugin Name: My Plugin
 * Version: 0.1
 */

// loading step
require_once( 'php/app.php' );

// bootstrapping step
$app = new App( __FILE__ );

// execution step
$app->run();

I defined all my classes in the php subfolder, and loaded them all in the same place. PHP now knows what my classes are, their names etc, but nothing has happened yet.

I then create all my objects, passing them what they need, in this case App needs the value of __FILE__ so I pass it along. Note that this creates the objects in memory, but doesn’t do any work. The plugins application is ready to go, to pounce into action, but it’s in the preparation phase.

The next step may be to pipe these objects into a set of unit tests, but now I’m going to run them. I’ve finished my bootstrapping process, and the application is ready to run, so I trigger the run method. I shouldn’t need to pass anything to run as everything necessary has been passed to the constructors. During the run method, I add all my filters and hooks. It’s during those hooks that all the other parts of the plugin run.

The important part though is that I have a defined structure, and that I pass in what’s necessary during the construction/bootstrapping phase, with a defined life cycle

Leave a Comment