- Intro to WordPress Plugin Development
- Intro to WordPress Plugin Development: Best Practices
- Intro to WordPress Plugin Development: Using Filters
- Intro to WordPress Plugin Development: Using Actions
- Intro to WordPress Plugin Development: Register Custom Post Types
- Intro to WordPress Plugin Development: Shortcodes
- Intro to WordPress Plugin Development: Loading Scripts and Styles
- Intro to WordPress Plugin Development: Add a Menu to Dashboard
- Intro to WordPress Plugin Development: Adding a Settings Page
- Intro to WordPress Plugin Development: Sanitize and Validate Data
- Intro to WordPress Plugin Development: Object Oriented Programming
- Intro to WordPress Plugin Development: Separate Into Multiple Files
So far we’ve been working with procedural programming in the Intro to WordPress Plugin Development series. Now we’ll take a look at how we can use object-oriented programming (OOP). This tutorial is not going to go into all of the details behind OOP. Instead, it will go over the concepts and differences between OOP and procedural programming.
What’s the difference between procedural and OOP?
The main difference between procedural and OOP is that procedural code typically is written to accomplish one task. OOP code is usually written in a way that can be re-used in many different ways.
For an example, let’s look at my Subscriber Discounts for Easy Digital Downloads plugin. In that plugin, I have a class that creates a discount code for new subscribers to your mailing list. When the plugin was originally written, I only was focused on MailChimp and ActiveCampaign mailing lists. So I created one function that created a discount code for each.
So, as you can imagine, there was a lot of overlap between the two functions. Each function had a means for creating a discount code and emailing that code to the subscriber. The code that did these things was identical in the two functions.
The problem with this is that we could have a bug in the code somewhere. If we fix it in one place, we will then have to remember to fix it in the other place as well.
What if my plugin had 20 functions for different email programs? Now I’d have to fix the bug in 20 places. That seems like an awful lot of work for one bug.
Enter OOP
With OOP we’re able to create a class that contains a bunch of reusable functions, variables, and other code. Let’s refer to these as objects.
When we create a class, we come up with all of the functions needed to perform an operation. In this case, to create a discount code.
In my plugin, I needed objects that:
- Checks to see whether or not it should create the discount code
- Retrieves information from the email provider (i.e. MailChimp, etc.)
- Creates a name for the discount code
- Checks to see if the subscriber already had a discount code (i.e. one discount per email)
- Creates the discount code and emails it to the customer
Of the objects in the list, only the second one will need to be customized for a particular email provider. The rest of them are going to be the same whether we’re using MailChimp, ActiveCampaign, or any other email provider.
Create the base class
The base class is going to have all of the functions we need to complete the action (creating a discount code). The base class won’t be able to create the discount code with the information it is given since it will need additional information. We can access this additional information by extending the base class, which we will cover a little later.
The base class
class SDEDD_Create_Discount { public function __construct(){ $this->sdedd_options = get_option( 'sdedd_settings' ); } /** * Our discount type. * @var string * @since 1.1.0 */ public $discount_type = 'default'; /** * The key used in the webhook. * @return string * @since 1.1.0 */ public function get_key(){ $key = false; return $key; } /** * The name we'll give the discount when it is created * @since 1.1.0 * @return string */ public function get_discount_name(){ $discount_name = $this->sdedd_options[ 'discount_name' ] . ' ' . $this->get_email(); return $discount_name; } /** * Get email address from the webhook * * @access public * @since 1.1.0 * @return string */ public function get_email() { $email = ''; if( isset( $_POST['email'] ) ){ $email = $_POST['email']; } if ( ! is_email( $email ) ){ $email = ''; } return $email; } /** * Get contact name from the webhook * * @access public * @since 1.1.0 * @return string */ public function get_name() { $name = ''; if( isset( $_POST['data']['merges'] ) ){ $name = $_POST['data']['merges']['FNAME']; } if ( empty( $name ) ){ $name = $this->sdedd_options['name_placeholder']; } return $name; } /** * Can we create a discount? * * @access public * @since 1.1.0 * @return bool Whether the discount should be created */ public function can_discount() { $discount = true; if ( ! isset( $_GET['trigger-special-discount'] ) || ! isset( $_GET['discount-key'] ) || $_GET['discount-key'] != $this->get_key() || ! function_exists( 'edd_store_discount' ) ) { $discount = false; } if ( ! $this->already_discounted() ){ $discount = false; } if ( ! $this->get_key() ){ $discount = false; } if ( $this->get_email() == '' ){ $discount = false; } // Only create a discount if the type is another type if ( $this->discount_type == 'default' ){ $discount = false; } return (bool) $discount; } /** * Does discount exist already? * * @access public * @since 1.1.0 * @return bool Whether the discount already exists */ public function already_discounted() { global $wpdb; $name = $this->get_discount_name(); $query = " SELECT * FROM $wpdb->posts WHERE $wpdb->posts.post_title LIKE '$name%' AND $wpdb->posts.post_type = 'edd_discount' ORDER BY $wpdb->posts.post_title "; $results = $wpdb->get_results( $query ); $discount = true; // Check if we already created a discount for this email. if ( is_array( $results ) && count( $results ) ) { $discount = false; } return (bool) $discount; } /** * Create the discount code * * @access public * @since 1.1.0 * @return void */ public function create_discount(){ if ( !$this->can_discount() ){ return; } $timestamp = time(); $numbers_array = str_split( $timestamp . rand( 10, 99 ) ); $letters_array = array_combine( range( 1, 26 ), range( 'a', 'z' ) ); $final_code = ''; foreach ( $numbers_array as $key => $value ) { $final_code .= $letters_array[ $value ]; } $discount_args = array( 'code' => $final_code, 'name' => $this->get_discount_name(), 'status' => 'active', 'max' => $this->sdedd_options[ 'discount_max' ], 'amount' => $this->sdedd_options[ 'discount_amount' ], 'type' => $this->sdedd_options[ 'discount_type' ], 'use_once' => 'true' == $this->sdedd_options[ 'discount_use_once' ] ? 'true' : 'false', ); //Create the discount edd_store_discount( $discount_args ); //Send the discount to the subscriber $first_name = $this->get_name(); $vars = array( '{firstname}' => esc_html( $first_name ), '{code}' => esc_html( $final_code ), ); $message = strtr( $this->sdedd_options[ 'message' ], $vars ); $subject = $this->sdedd_options[ 'email_subject' ]; $headers = array( 'Content-Type: text/html; charset=UTF-8', 'From: ' . $this->sdedd_options[ 'from_name' ] . ' <' . $this->sdedd_options[ 'from_email' ] . '>' ); wp_mail( $this->get_email(), $subject, $message, $headers ); } } |
Extending the base class
In the base class, we have a bunch of functions that are passed to any classes that extend the base class. When we extend a class, the subclass will inherit all of the public and protected objects from the base class unless it overrides them.
So in our base class, we have some functions that we need to override.
- get_key: This returns false in the base class, but we need it to be the key the user enters into the plugin’s settings.
- get_email: This returns an empty string in the base class, but we need the email address that the email program passed in the webhook.
- get_name: This also returns an empty string or the value of a placeholder we let the user set in the plugin’s settings.
This is all much easier to override than having to recreate the functionality of the whole class all over again.
Extend the class
class SDEDD_Mailchimp_Create_Discount extends SDEDD_Create_Discount{ /** * Our discount type. * @var string * @since 1.1.0 */ public $discount_type = 'mailchimp'; /** * The key used in the webhook. * @return string * @since 1.1.0 */ public function get_key(){ $key = esc_attr( $this->sdedd_options[ 'mailchimp_key' ] ); return $key; } /** * Get email address from the webhook * * @access public * @since 1.1.0 * @return string */ public function get_email() { $email = ''; if( isset( $_POST['data']['merges'] ) ){ $email = wp_strip_all_tags( $_POST['data']['merges']['EMAIL'] ); } if ( ! is_email( $email ) ){ $email = ''; } return $email; } /** * Get contact name from the webhook * * @access public * @since 1.1.0 * @return string */ public function get_name() { $name = ''; if( isset( $_POST['data']['merges'] ) ){ $name = $_POST['data']['merges']['FNAME']; } if ( empty( $name ) ){ $name = $this->sdedd_options['name_placeholder']; } return $name; } } |
How does this work?
We know that the plugin can expect to be hearing from MailChimp if the user has set a special key in the plugin’s settings. We can call the new subclass we created if this special key is set, like this:
if ( isset( $sdedd_options[ 'mailchimp_key' ] ) && $sdedd_options[ 'mailchimp_key' ] != '' ){ $create = new SDEDD_Mailchimp_Create_Discount(); $create->create_discount(); } |
The second line $create = new SDEDD_Mailchimp_Create_Discount();
is basically saying:
- OK, the SDEDD_Mailchimp_Create_Discount class is extending the SDEDD_Create_Discount class.
- We need the SDEDD_Create_Discount class to continue.
- Anything that is in SDEDD_Mailchimp_Create_Discount that overrides something in SDEDD_Create_Discount will take precedent.
The next line $create->create_discount();
tells the class to run that function with the information it has received from both SDEDD_Mailchimp_Create_Discount and SDEDD_Create_Discount classes.
Conditional loading
The way the plugin is set up, it will run that create_discount
function on every page load. But we have a condition in the can_discount
function that checks to see if the webhook was triggered or not. If it wasn’t, then nothing else is run in the function. This prevents thousands of discount codes being created and emails being fired off to nowhere.
Leave a Reply