0

I'm looking for a way to have a single base class that can be extended by several child classes, only one of which would be active at a time. A very basic example:

class API_Base {

    public $context;

    public function __construct() {
        $this->init()
    }
}

class Mailchimp_API extends API_Base {

    public function init() {
        $this->context = 'mailchimp';
        $this->enabled = false; 
    }

    public function add_contact($email_address) {
        // mailchimp API for adding contact
    }
}

class Infusionsoft_API extends API_Base {

    public function init() {
        $this->context = 'infusionsoft';
        $this->enabled = true;
    }

    public function add_contact($email_address) {
        // infusionsoft API for adding contact
    }

}

Each child initializes itself and registers as an option for the user to select. After the user has chosen which integration to use, this is saved to the database. I'd like future access to the API_Base to look something like:

$api = new API_Base();
$api->context; // should be "infusionsoft"
$api->add_contact($email_address);

So when $api->add_contact() is run, it only runs the add_contact() function for the active API integration.

Eventually I'd like to somehow use get_class_methods(); to return the capabilities of just the active API, so functions accessing the API can know what is possible (i.e. some API's support email lists while others don't, or support creating custom fields, etc.).

I've had some success with calling parent::set_context($context); from the enabled class, but I still can't figure out how to get the parent to only execute the methods in the "enabled" child class.

1
  • It looks like you want some kind of factory. If you use a framework like e.g. Symfony, also read a bit about a service container. Commented Dec 25, 2015 at 17:33

3 Answers 3

2

This is not how inheritance works. Child subclasses inherit from their parent class.

To solve your problem you can add a factory method to API_Base which will create API implementation by its type:

class API_Base {
    public static function createByType($type)
    {
        switch ($type) {
            case 'mailchimp': return new Mailchimp_API();
            case 'infusionsoft': return new Infusionsoft_API();
            default: throw new \InvalidArgumentException(spintf('Invalid API type "%s"', $type));
        } 
    }
    // other methods
}

and use it like this:

$api = API_Base::createByType($user->selectedApi);
$api->context; // should be "infusionsoft"
$api->add_contact($email_address);
Sign up to request clarification or add additional context in comments.

3 Comments

That makes a lot of sense. The only issue would be that I need all of the API classes available at runtime so that they can present themselves as available options to the user. This is an extensible platform where other developers will be contributing integrations, so I won't be able to hard code the integrations into the API_Base class. I'm using an autoloader right now to import all of the APIs. I guess I could do it with having two classes, one for config and one for the API itself, but I wanted to keep it simple, in a single class if possible. Any ideas?
You can store class names for selected APIs and do something like this: $className = $user->selectedApi; $api = new $className();
Thanks. You've been a big help. Posted my final solution at the bottom for future Googlers.
0

You can consider Abstract Class Implementation . The abstract class works as the , who ever is extending the abstract class can execute the methods it have .

abstract class Something{
       function __construct(){
        // some stuff
       }

       function my_func(){
         $this->myTest ;
       }

      abstract function my_func(); 

      }

    class Some extends Something{

      function __construct(){
        parent::__construct() ;
      }

      function my_test(){
           echo "Voila" ;
      }

    }

1 Comment

But how would that work when I have multiple children, say Some1 and Some2, each with a my_test() function? I only want the my_test() to be executed in the "enabled" child.
0

I got it working in a way works perfectly for me, thanks to Ihor's advice. Here's what I ended up doing:

In the main plugin file, there's a filterable function where other devs can add new integrations if they need. The first parameter is the slug (for my autoloader) and the second is the class name.

public function get_apis() {

    return apply_filters( 'custom_apis', array(
        'infusionsoft-isdk'         => 'MYPLUGIN_Infusionsoft_iSDK',
        'infusionsoft-oauth'        => 'MYPLUGIN_Infusionsoft_oAuth',
        'activecampaign'            => 'MYPLUGIN_ActiveCampaign'
    ) );

}

Each integration contains the slug and the class name. Then in my API_Base class I have this in the constructor:

class API_Base {

    public $available_apis = array();

    public $api;

    public function __construct() {

        $configured_apis = main_plugin()->get_apis();

        foreach( $configured_apis as $slug => $classname ) {

            if(class_exists($classname)) {

                $api = new $classname();
                $api->init();

                if($api->active == true)
                    $this->api = $api;

                $this->available_apis[$slug] = array( 'name' => $api->name );

                if(isset($api->menu_name)) {
                    $this->available_apis[$slug]['menu_name'] = $api->menu_name;
                } else {
                    $this->available_apis[$slug]['menu_name'] = $api->name;
                }

            }

        }

    }
}

And in my main file, after all the includes, I run:

self::$instance->api_base       = new API_Base();
self::$instance->api            = self::$instance->api_base->api;

Now I can call self::$instance->api->add_contact($email); and it will trigger whichever is the current active API.

It seems to be the best approach as this way I can spin up the API only once when the plugin loads, instead of having to create a new instance each time I want to use it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.