Applying SOLID Principles to WordPress Development

Applying SOLID Principles to WordPress Development

SOLID Principles in modern software development refers to the design approach intended in making software code more easily readable, extendable and maintainable.

It was first introduced by Uncle Bob (Robert C. Martin) in his 2000 paper titled Design Principles and Design Patterns, but became popular in 2004 when Michael Feathers coined the term SOLID.

In this article, my goal is to try and show how SOLID principles can be applied to WordPress development.


Single Responsibility Principle

This principle simply states that functions or methods used in classes should perform only one function or task at a time.

This simply means if a function or method is performing more than one function, then it is in violation of this principle or rule and should be modified to perform one task only.

Lets see an example below:

<?php

class Student {
    /**
     * Save user input to Student custom post type.
     *
     * @return void
     */
    public function save(): void {
        // Authenticate user.
        if ( ! is_user_logged_in() ) {
            return;
        }
        
        // Validate input.
        $name  = $this->validate( $_POST['name'], 'string' );
        $age   = $this->validate( $_POST['age'], 'int' );
        $phone = $this->validate( $_POST['phone'], 'string' );

        // Save student data.
        $post_id = wp_insert_post( 
            array(
                'post_type'   => 'student',
                'post_status' => 'publish',
                'post_title'  => $name
            ) 
        );
        add_post_meta( $post_id, 'age', $age );
        add_post_meta( $post_id, 'phone', $phone );
    }

    /**
     * Validate user input based on type.
     * 
     * @param string $input
     * @param string $type
     * @return string
     */
    public function validate( $input, $type ): string {
        // Validation logic here.
    }
}        

Let's examine closely the save method, shall we. From the above code, it is doing 3 things as follows:

1st thing

// Authenticate user.
if ( ! is_user_logged_in() ) {
    return;
}        

2nd thing

// Validate input.
$name  = $this->validate( $_POST['name'], 'string' );
$age   = $this->validate( $_POST['age'], 'int' );
$phone = $this->validate( $_POST['phone'], 'string' );        

3rd thing

// Save student data.
$post_id = wp_insert_post( 
    array(
        'post_type'   => 'student',
        'post_status' => 'publish',
        'post_title'  => $name
    ) 
);
add_post_meta( $post_id, 'age', $age );
add_post_meta( $post_id, 'phone', $phone );        

There are so many reasons why this set up is wrong.

Firstly, this violates the Single Responsibility Principle because a method is supposed to do one thing and one thing only. The save method in this case is authenticating, validating and then saving the user data to the Student custom post type.

Secondly, the Student class in this context acts like a model and is basically concerned with CRUD operations for the Student custom post type. This means it is not supposed to know or have implementation details for authentication and validation. Those pieces of logic should belong to the Authentication and Validation classes separately.

When refactored, our Student class would look like so:

<?php

class Student {
    /**
     * Save user input to Student custom post type.
     *
     * @param array $request POST/GET input.
     * @return void
     */
    public function save( $request ): void {
        // Save student data.
        $post_id = wp_insert_post( 
            array(
                'post_type'   => 'student',
                'post_status' => 'publish',
                'post_title'  => $request['name'],
            ) 
        );
        add_post_meta( $post_id, 'age', $request['age'] );
        add_post_meta( $post_id, 'phone', $request['phone'] );
    }
}        

We can also have a validation class like so:

<?php

class Validation {
    /**
     * Validate user input based on type.
     *
     * @param array $input POST input.
     * @return array
     */
    public function validate( $input ): array {
        // Validation logic here.
    }
}        

And an authentication class like so:

<?php

class Authentication {
    /**
     * Authenticate user.
     *
     * @return void
     */
    public function authenticate(): void {
        // Authenticate user.
        if ( ! is_user_logged_in() ) {
            return;
        }
    }
}        

These classes can now be injected (Dependency Injection) into a Student Service class that does the actual job of creating the data. The authentication class can be called in an earlier init hook or middleware and does not need be injected for this service.

<?php

class StudentService {
    /**
     * Service for creating Student Data.
     *
     * @param \Validation $validation Validation instance.
     * @param \Student    $student Student instance.
     * @return void
     */
    public function create( Validation $validation, Student $student ): void {
        $student->save( $validation->validate( $_REQUEST ) );
    }
}        

These separation of concerns helps us achieve the following:

  1. It make our codebase more readable.
  2. It makes our codebase more organised so we know where to look for things.
  3. It makes our codebase easily maintainable.
  4. It makes our codebase unit-testable.


Open for Extension, Closed for Modification

Confusing right?

Well actually, this principle simply suggests that our classes should be designed in such a way that they are open for extension so that users and developers can add custom logic to them but closed for modification so they don't end up modifying our original lines of code which may end up breaking things in production.

And now I know you're wondering, is this even possible? Is there a way we can allow other developers extend our logic without modifying our own lines of code? Well, the answer is Yes! It turns out that Matt Mullenweg and the Automattic team (Maintainers of WordPress) created this tiny little thing called a Hook that makes this possible.

Hooks are WordPress' intelligent way of allowing developers add logic to an existing WP plugin or application for the purpose of extending it so they don't have to modify the original codebase.

There are many advantages to this:

Firstly, it is strongly frowned upon by the WordPress community to modify an existing codebase as written by an author since this could break an existing feature or function in production.

Secondly, any modifications you make to an existing codebase would disappear once the user upgrades their plugin and then your changes would be lost completely!

When building WordPress applications and plugins, it is important we provide ways for other developers to add custom logic or functionality to our piece of software with Hooks. In this way, Add-ons can be built to cater for several other needs that our WordPress plugin or application may not currently address.

Lets a take a look at the StudentService example we used earlier:

<?php

class StudentService {
    /**
     * Service for creating Student Data.
     *
     * @param \Validation $validation Validation instance.
     * @param \Student    $student Student instance.
     * @return void
     */
    public function create( Validation $validation, Student $student ): void {
        $student->save( $validation->validate( $_REQUEST ) );
    }
}        

Now, let's assume one of our plugin users needed to perform an action on the POST data before saving, the question is, how would they able to do that?

If they modified our plugin, their changes would be lost anytime they performed a plugin update! The best way to do this would be to plug in their changes using a hook! In this way, they could add custom logic to our plugin without worrying about if their changes would be lost when they updated their plugin.

Let's take a look at it again, shall we:

<?php

class StudentService {
    /**
     * Service for creating Student Data.
     *
     * @param \Validation $validation Validation instance.
     * @param \Student    $student Student instance.
     * @return void
     */
    public function create( Validation $validation, Student $student ): void {
        // Plug in user's custom logic before saving.
        do_action( 'before_save_student_data' );

        // Plug in custom user $_POST array.
        $student->save( $validation->validate( apply_filters( 'student_post_data', $_REQUEST ) ) );
        
        // Plug in user's custom logic after saving.
        do_action( 'after_save_student_data' );
    }
}        

Interesting... There are 3 things happening in the above scenario namely:

  1. We have provided a way for the user to add custom logic before the $_POST data is saved via the 'before_save_student_data' action hook.
  2. We have provided a way for the user to alter what the $_POST array returns before it is saved via the 'student_post_data' filter hook.
  3. We have provided a way for the user to add custom logic after the $_POST data is saved via the 'after_save_student_data' action hook.

In this way, we have made the StudentService class very extensible and now it can be made to do a lot much more without loosing changes during plugin updates.


Liskov Substitution Principle

When I first heard about this principle I was pretty confused but, its actually easy than it sounds. As the name implies, the principle simply suggests that Parents and Child Classes should be interchangeable.

The way we achieve this is by type-hinting parameters and return types for all class methods.

Let's take a look at the StudentService class we used earlier:

<?php

class StudentService {
    /**
     * Service for creating Student Data.
     *
     * @param \Validation $validation Validation instance.
     * @param \Student    $student Student instance.
     * @return void
     */
    public function create( Validation $validation, Student $student ): void {
        // Plug in user's custom logic before saving.
        do_action( 'before_save_student_data' );

        // Plug in custom user $_POST array.
        $student->save( $validation->validate( apply_filters( 'student_post_data', $_REQUEST ) ) );
        
        // Plug in user's custom logic after saving.
        do_action( 'after_save_student_data' );
    }
}        

It's create method contains validation and student instances which contain type declarations and a return type:

public function create( Validation $validation, Student $student ): void        

Now let's say we wanted to handle the creation of data for Part-time Students and Full-time Students separately, how would we do that?

We could extend the Student Service like so:

<?php

class PartTimeStudentService extends StudentService {
    /**
     * Service for creating Part-time Student Data.
     *
     * @param \Validation $validation Validation instance.
     * @param \Student    $student Student instance.
     * @return void
     */
    public function create( Validation $validation, Student $student ): void {
        // Plug in user's custom logic before saving.
        do_action( 'before_save_student_data' );

        // Plug in custom user $_POST array.
        $student->save( $validation->validate( apply_filters( 'student_post_data', $_REQUEST ) ) );
        
        // Plug in user's custom logic after saving.
        do_action( 'after_save_student_data' );
    }
}        

And for the Full Time Students like so:

<?php

class FullTimeStudentService extends StudentService {
    /**
     * Service for creating Full-time Student Data.
     *
     * @param \Validation $validation Validation instance.
     * @param \Student    $student Student instance.
     * @return void
     */
    public function create( Validation $validation, Student $student ): void {
        // Plug in user's custom logic before saving.
        do_action( 'before_save_student_data' );

        // Plug in custom user $_POST array.
        $student->save( $validation->validate( apply_filters( 'student_post_data', $_REQUEST ) ) );
        
        // Plug in user's custom logic after saving.
        do_action( 'after_save_student_data' );
    }
}        

The above child classes can now be interchanged with the parent class because they contain the exact parameter type definitions and return types specified in the parent class.


Interface Segregation Principle

Interfaces in PHP are well known programming construct that helps us define abstractions for use by both high-level code and low-level concretion, implementation classes.

This principle simply suggests that interfaces should not be modified to accommodate new method definitions but instead new interfaces should be created to accommodate those changes.

Let's take a look at an example that will make this clearer:

Justin Hual

Multiple Business Owner | Solution Driven | Development Focused | Co-Founder and Chief Operating Officer at HIP Creative.

2 年

Great post Chigozie Orunta

要查看或添加评论,请登录

Chigozie Orunta的更多文章

  • Common Mistakes WP Engineers Make

    Common Mistakes WP Engineers Make

    WordPress is famous for being one of the most popular content management systems (CMS) worldwide, powering millions of…

  • Speed Up Your WordPress Website

    Speed Up Your WordPress Website

    As a website owner, you probably already know how important it is for your site to load fast and efficiently so that…

    1 条评论
  • Creating A Custom Divi Module or Extension Plugin in WordPress

    Creating A Custom Divi Module or Extension Plugin in WordPress

    Divi is one of the most powerful WordPress page builders out there in the market. Its ease of use and WYSIWYG nature…

    4 条评论
  • Building A Custom Gutenberg Block In WordPress

    Building A Custom Gutenberg Block In WordPress

    Gutenberg Blocks in WordPress have been around for a while, ever since the builder was released on the WordPress…

  • Working With WordPress Hooks, How To Create & Use Them.

    Working With WordPress Hooks, How To Create & Use Them.

    WordPress Hooks are one of the most powerful features in theme and plugin development. They enable WP developers to add…

  • Setting Up A PHP CodeSniffer For WordPress using Composer

    Setting Up A PHP CodeSniffer For WordPress using Composer

    A PHP CodeSniffer is a program that helps developers keep code clean and uniform or in sync across teams. In PHP, you…

    2 条评论
  • Most Useful WordPress Plugins in 2022

    Most Useful WordPress Plugins in 2022

    Plugins are an invaluable tool for today’s WordPress websites. There are extremely few websites that exist today that…

    1 条评论
  • Safeguarding Your WordPress Site

    Safeguarding Your WordPress Site

    WordPress accounts for almost 30% of all websites on the Internet and is one of the most popular CMS (Content…

  • 4 Ways To Style Your React Components

    4 Ways To Style Your React Components

    So you’ve built your first React Component but don’t know how to style it. Well, here are 4 quick ways to style your…

    1 条评论

社区洞察

其他会员也浏览了