3

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.

  • Are you looking for reassurance or the correct way to do OO in WordPress? I'm afraid there is no canonical path to follow here, the only things I can share are generic OO things. E.g. don't create SEO_Plugin_Activation inside your SEO class, employ the dependency injection and pass it as an argument instead, and don't define and use a class in the same file, loading a file describing a class shouldn't also run it else it's impossible to write unit tests – Tom J Nowell Aug 10 '17 at 13:47
  • Thanks for the reply. I honestly don't care too much about sticking to the "100% Wordpress Way". Judging by your comment, it looks like I clearly lack some understanding of OOP principles. I was under the impression that I needed to create a new class inside of a class to call its methods. – Dan Zuzevich Aug 10 '17 at 13:50
  • You can do that, but it's not as flexible, e.g. if you want to test the SEO class, how would you replace the SEO_Plugin_Activation class with a mock object without modifying it? Anyway it seems this question is about a misunderstanding of how register_activation_hook works – Tom J Nowell Aug 10 '17 at 14:04
  • Got ya, thanks alot. I honestly don't think I will be performing tests on anything as that's a little over my head at the moment. – Dan Zuzevich Aug 10 '17 at 14:21
7

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

  • Thanks for the in-depth reply. From your explanation, atleast what I am getting from it, is that I need to come up with some way for my main plugin to be aware of all of the classes I have written? For example, I prob will have class files in an "inc" folder, and more class files in an "admin" folder. I was under the impression that anytime I wanted to use a class somewhere, I needed to do the "$var = new ClassName();" to be able to access those methods. That's why you see me creating the class inside the main class. – Dan Zuzevich Aug 10 '17 at 14:17
  • you don't have to create the object inside the class, just pass $var to it, constructors can take parameters too and objects can be passed around just like any other variable. You can do it the way you've done it, but there are disadvantages. The problem with accessing __FILE__ is one of them, that value has to come from the main plugin file, or it won't be correct, so it needs to be passed to other parts of the plugin – Tom J Nowell Aug 10 '17 at 14:25
  • Alrighty. Thanks. This answer will defnitely suffice. I know what I need to brush up on now. – Dan Zuzevich Aug 10 '17 at 14:28
  • The OO side of this question is somewhat offtopic for this site, I kept the question open on the pretext of using the register_activation_hook correctly. For the OO side of things look at tomjn.com/2015/06/24/root-composition-in-wordpress-plugins but there other ways of doing it. There is no correct way to do it, they all have their benefits and disadvantages, you could write your plugin using purely functional programming constructs haskell style – Tom J Nowell Aug 10 '17 at 14:28
  • Should I restate the question in any way, or is it okay? – Dan Zuzevich Aug 10 '17 at 14:29
1

You really need to be registering your activation/deactivation/uninstall hooks outside of your plugin class as per kaiser's answer here, which provides a much better rundown on the subject than I could write.

That should cover you if you're looking to write this plugin as a learning exercise. If all you're looking for is an SEO plugin that works well and isn't plastered with tacky ads like Yoast, I can highly recommend The SEO Framework.

  • This was helpful, and I appreciate the reply. – Dan Zuzevich Aug 10 '17 at 14:19
  • I've upvoted the answer as its definite helpful resource. – Dan Zuzevich Aug 11 '17 at 13:22
1

Tom McFarlin created a very extensive plugin boilerplate (now maintained by Devin Vinson), all written in OOP, that you can use to either create your new plugin, or just study the application flow to answer your question. I have used it for several custom plugins, and I have to say it really opened my eyes to some of the mysteries of OOP.

  • This is a very good idea. I completely forgot about the plugin boilerplate. I had checked that out awhile ago, and was thrown off by it being pretty confusing. But I am definitely going to download it again and use it as a reference. Thanks a lot for the suggestion. – Dan Zuzevich Aug 11 '17 at 13: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.