Advantages of PHP 8.3 with WordPress

on in Blog
Last modified on

I have upgraded my hosting server to PHP 8.3 for all my WordPress and non-WordPress websites. While I am yet to see a speed improvement, I did a bit of research and this is what I came up with. Note that you won’t see this code too soon in my themes and plugins, because hosts need to upgrade to PHP 8.3 across the board.

This article will focus on WordPress advantages only. You can read about new PHP 8.3 features elsewhere on the internet.

What’s New?

What are the advantages of PHP 8.3 with WordPress in terms of speed, performance, RAM consumption, and security?

Let’s add a bit of context, and then we’ll jump to examples.

Table of Contents

Speed and Performance

  • JIT (Just-In-Time) compilation: JIT compilation is a technique that compiles PHP code into machine code on the fly. This means that PHP code does not need to be interpreted each time it is executed, which can significantly improve performance. In WordPress, JIT compilation can be particularly beneficial for sites that handle a lot of traffic, such as e-commerce sites or news sites.
  • Union Types: Union types allow you to specify multiple possible types for a variable. This can help to improve performance by reducing the need for type casting. Type casting is a process of converting one type of data to another, and it can be a bottleneck in PHP code. In WordPress, union types can be particularly beneficial for code that handles user-generated data, such as comments or form submissions.
  • Fibers: Fibers are a new way to write concurrent code in PHP. Concurrent code is code that can handle multiple tasks at the same time. This can be beneficial for WordPress sites that need to handle multiple requests at once, such as sites that use a lot of AJAX or Web Sockets.

RAM Consumption

  • Reduced memory usage: PHP 8.3 includes a number of changes that can help to reduce the memory usage of WordPress sites. These changes include:
    • More efficient garbage collection: Garbage collection is the process of reclaiming memory that is no longer being used. PHP 8.3 includes a number of improvements to garbage collection, which can help to reduce the amount of memory that is used by WordPress sites.
    • Improved memory management for objects: Objects are a fundamental data type in PHP. PHP 8.3 includes a number of improvements to memory management for objects, which can help to reduce the amount of memory that is used by WordPress sites.
    • Reduced memory usage for strings: Strings are another fundamental data type in PHP. PHP 8.3 includes a number of improvements to memory usage for strings, which can help to reduce the amount of memory that is used by WordPress sites.

Security

  • Improved security: PHP 8.3 includes a number of security improvements that can help to protect WordPress sites from attacks. These improvements include:
    • Support for Argon2i password hashing: Argon2i is a more secure password hashing algorithm than the algorithms used in previous versions of PHP. This means that it is more difficult for attackers to crack passwords.
    • Improved string handling: PHP 8.3 includes a number of improvements to string handling, which can help to prevent attacks such as SQL injection. SQL injection is a type of attack that allows attackers to inject SQL code into a website.
    • A new readonly property for classes: The readonly property allows you to prevent unauthorized access to class properties. This can help to prevent attackers from modifying the data of a website.

Additional Benefits

  • Improved error handling: PHP 8.3 includes a number of improvements to error handling, which can make it easier to debug WordPress sites. These improvements include:
    • More informative error messages: PHP 8.3 includes more informative error messages, which can help you to identify the cause of an error more quickly.
    • A new Throwable interface: The Throwable interface provides a unified way to handle errors and exceptions. This can make it easier to write error-resistant code.
  • New language features: PHP 8.3 includes a number of new language features, such as the null coalescing operator, which can make it easier to write more concise and expressive code.
  • Improved support for PHP extensions: PHP 8.3 includes a number of improvements to support for PHP extensions, which can make it easier to use third-party libraries with WordPress.

Now let’s jump to some real life examples.

Using Union Types to Improve Theme Compatibility

// Before PHP 8.3
function get_template_part( $slug, $name = '' ) {
    if ( ! empty( $name ) ) {
        $template = $slug . '-' . $name . '.php';
        if ( locate_template( $template ) ) {
            load_template( $template );
            return true;
        }
    }

    $template = $slug . '.php';
    if ( locate_template( $template ) ) {
        load_template( $template );
        return true;
    }

    return false;
}

Becomes:

// With PHP 8.3
function get_template_part( string|string[] $slug, string $name = '' ) {
    if ( is_string( $slug ) ) {
        $slug = [ $slug ];
    }

    foreach ( $slug as $part ) {
        if ( ! empty( $name ) ) {
            $template = $part . '-' . $name . '.php';
            if ( locate_template( $template ) ) {
                load_template( $template );
                return true;
            }
        }

        $template = $part . '.php';
        // ... rest of the code ...

Using Union Types to Avoid Type Errors

// Before PHP 8.3
function get_user_data( $user_id ) {
    $user = get_user_by( 'id', $user_id );
    if ( ! $user ) {
        return null;
    }

    $user_data = [
        'name'  => $user->user_nicename,
        'email' => $user->user_email,
    ];

    return $user_data;
}

Becomes:

// With PHP 8.3
function get_user_data( int|WP_User $user_id ): ?array {
    if ( is_int( $user_id ) ) {
        $user = get_user_by( 'id', $user_id );
    } else {
        $user = $user_id;
    }

    if ( ! $user ) {
        return null;
    }

    $user_data = [
        'name'  => $user->user_nicename,
        'email' => $user->user_email,
    ];

    return $user_data;
}

In this example, the get_user_data function can receive either an integer user ID or a WP_User object. The union type int|WP_User allows the function to handle both types of input without the need for explicit type checks.

Using Fibers to Handle Multiple AJAX Requests

// Before PHP 8.3
add_action( 'wp_ajax_my_action', 'my_ajax_handler' );

function my_ajax_handler() {
    // Process the AJAX request
    $data     = $_POST['data'];
    $response = process_data( $data );

    // Send the response
    wp_send_json( $response );
}

Becomes:

// With PHP 8.3
add_action( 'wp_ajax_my_action', 'my_ajax_handler' );

function my_ajax_handler() {
    // Spawn a new fiber for each AJAX request
    new Fiber(
        function ( $data ) {
            // Process the AJAX request
            $response = process_data( $data );

            // Send the response
            wp_send_json( $response );
        },
        $_POST['data']
    );
}

In this example, the my_ajax_handler function handles AJAX requests from the my_action action hook. The fibers feature allows the function to handle multiple concurrent AJAX requests without blocking the main thread.

Using JIT Compilation to Optimize Template Rendering

// Before PHP 8.3
function render_template( $template_name, $data ) {
    ob_start();
    include get_template_directory() . "/$template_name.php";
    $content = ob_get_clean();

    return $content;
}

Becomes:

// With PHP 8.3
function render_template( $template_name, $data ) {
    extract( $data );
    ob_start();
    include get_template_directory() . "/$template_name.php";
    $content = ob_get_clean();

    return $content;
}

In this example, the render_template function renders a WordPress template file with the provided data. The JIT feature can optimize the PHP code in the template file, leading to faster template rendering and improved page load times.

Using Union Types to Handle User-Generated Data

// Before PHP 8.3
function sanitizeUserData( $data ) {
    if ( is_string( $data ) ) {
        $data = htmlspecialchars( $data );
    } elseif ( is_array( $data ) ) {
        foreach ( $data as $key => $value ) {
            $data[ $key ] = sanitizeUserData( $value );
        }
    }
    return $data;
}

echo sanitizeUserData( $_POST['username'] ); // Sanitizes user input

Becomes:

// With PHP 8.3
function sanitizeUserData( string|array $data ) {
    if ( is_string( $data ) ) {
        $data = htmlspecialchars( $data );
    } elseif ( is_array( $data ) ) {
        array_map( 'sanitizeUserData', $data );
    }
    return $data;
}

echo sanitizeUserData( $_POST['username'] ); // Sanitizes user input

Using Fibers to Handle Multiple Ajax Requests

// Before PHP 8.3
function handleAjaxRequest() {
    // Process each AJAX request sequentially
    $data = json_decode( file_get_contents( 'php://input' ), true );
    processAjaxRequest( $data );
}

while ( true ) {
    handleAjaxRequest();
}

Becomes:

// With PHP 8.3
function handleAjaxRequest() {
    // Spawn a new fiber for each Ajax request
    new Fiber( function ( $data ) {
        processAjaxRequest( $data );
    }, json_decode( file_get_contents( 'php://input' ), true ) );
}

while ( true ) {
    handleAjaxRequest();
}

Using JIT Compilation to Improve Performance of Plugin Activation

// Before PHP 8.3
function activate_plugin( $plugin_file ) {
    require_once $plugin_file;
    register_activation_hook( $plugin_file, 'my_plugin_activation' );
    do_action( 'activate_plugin', $plugin_file );
}

Becomes:

// With PHP 8.3
function activate_plugin( $plugin_file ) {
    opcache_compile( $plugin_file, true ); // Enable JIT compilation for the plugin
    require_once $plugin_file;
    register_activation_hook( $plugin_file, 'my_plugin_activation' );
    do_action( 'activate_plugin', $plugin_file );
}

In this example, JIT compilation is enabled for the plugin file using the opcache_compile() function. This can significantly improve the performance of plugin activation, especially for plugins that contain a lot of code.

Here is a breakdown of the code:

  1. The require_once statement loads the plugin file into the PHP interpreter.
  2. The opcache_compile() function enables JIT compilation for the plugin file. This tells the Opcache opcode cache to compile the plugin code into machine code on the fly, which can significantly improve performance.
  3. The register_activation_hook() function registers a hook to be executed when the plugin is activated. This hook is used to perform any necessary plugin activation tasks.
  4. The do_action() function fires the activate_plugin action, which allows other plugins to perform any necessary actions when the plugin is activated.

By enabling JIT compilation for the plugin file, the PHP interpreter can execute the plugin code much faster, which can improve the overall performance of plugin activation.

Using Union Types to Simplify Theme Template Functions

// Before PHP 8.3
function display_post_content( $post_id ) {
    $post = get_post( $post_id );
    if ( $post ) {
        echo $post->post_content;
    } else {
        echo 'Post not found';
    }
}

Becomes:

// With PHP 8.3
function display_post_content( WP_Post|null $post ) {
    if ( $post ) {
        echo $post->post_content;
    } else {
        echo 'Post not found';
    }
}

By using union types, the code can be more concise and avoid the need for an explicit null check.

Using Fibers to Handle Asynchronous User Registration

// Before PHP 8.3
function register_user( $username, $email, $password ) {
    $user_id = wp_create_user( $username, $password, $email );
    if ( $user_id ) {
        // Send welcome email
        wp_mail( $email, 'Welcome to WordPress!', 'Thank you for registering.' );
    } else {
        // Handle registration error
    }
}

Becomes:

// With PHP 8.3
function register_user( $username, $email, $password ) {
    new Fiber( function ( $username, $email, $password ) {
        $user_id = wp_create_user( $username, $password, $email );
        if ( $user_id ) {
            // Send welcome email in a separate fiber
            new Fiber( function ( $email ) {
                wp_mail( $email, 'Welcome to WordPress!', 'Thank you for registering.' );
            }, $email );
        } else {
            // Handle registration error
        }
    }, $username, $email, $password );
}

By using fibers, the registration process can be handled asynchronously, improving the user experience and reducing server load.

Using JIT Compilation to Optimize Custom Post Type Queries

// Before PHP 8.3
function get_recent_posts( $post_type, $limit ) {
    $args = [
        'post_type'      => $post_type,
        'posts_per_page' => $limit,
    ];
    $posts = get_posts( $args );
    return $posts;
}

Becomes:

// With PHP 8.3
function get_recent_posts( string $post_type, int $limit ): array {
    global $wpdb;
    $posts = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}posts WHERE post_type = :post_type ORDER BY post_date DESC LIMIT :limit", [':post_type' => $post_type, ':limit' => $limit]);

    return $posts ?? [];
}

This example has changed the way the posts are fetched, but the performance improvement is noticeable, especially with lots of posts, or large databases.

Using Union Types to Validate User Input

// Before PHP 8.3
function validateEmail( $email ) {
    if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
        return true;
    } else {
        return false;
    }
}

echo validateEmail( "johndoe@example.com" ); // Outputs: true
echo validateEmail( "johndoe@@example.com" ); // Outputs: false

Becomes:

// With PHP 8.3
function validateEmail( string|null $email ) {
    if ( is_string( $email ) && filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
        return true;
    } else {
        return false;
    }
}

echo validateEmail( "johndoe@example.com" ); // Outputs: true
echo validateEmail( "johndoe@@example.com" ); // Outputs: false
echo validateEmail( null ); // Outputs: false

Using Improved Error Handling to Catch and Handle Exceptions

// Before PHP 8.3
function getPostById( $id ) {
    global $wpdb;
    $post = $wpdb->get_row( "SELECT * FROM wp_posts WHERE ID = $id" );
    if ( ! $post ) {
        trigger_error( "Post with ID $id not found", E_USER_ERROR );
    }
    return $post;
}

echo getPostById( 123 ); // Outputs: Post object with ID 123
echo getPostById( 456 ); // Triggers error and stops execution

Becomes:

// With PHP 8.3
function getPostById( $id ) {
    global $wpdb;
    try {
        $post = $wpdb->get_row( "SELECT * FROM wp_posts WHERE ID = $id" );
        if ( ! $post ) {
            throw new Exception( "Post with ID $id not found" );
        }
        return $post;
    } catch ( Exception $e ) {
        echo $e->getMessage(); // Handle the exception gracefully
    }
}

echo getPostById( 123 ); // Outputs: Post object with ID 123
echo getPostById( 456 ); // Outputs: Post with ID 456 not found

Using Fibers to Handle Asynchronous WordPress Tasks

// Before PHP 8.3
// Make an API call to a third-party service
$response = wp_remote_get( "https://example.com/api/data" );
// Process the API response
$data = json_decode( $response["body"] );

Becomes:

// With PHP 8.3
// Spawn a new fiber to make the API call asynchronously
new Fiber( function () {
    // Make the API call in the background
    $response = wp_remote_get( "https://example.com/api/data" );
    // Process the API response in the callback
    $data = json_decode( $response["body"] );
    // Perform other tasks while waiting for the API call to finish
    // ...
});

Using Union Types to Handle User-Generated Data

// Before PHP 8.3
function get_user_age( $user_id ) {
    $user_data = get_userdata( $user_id );
    if ( isset( $user_data['user_meta']['age'] ) ) {
        return $user_data['user_meta']['age'];
    } else {
        return null;
    }
}

$user_age = get_user_age( 123 );
if ( $user_age !== null ) {
    echo "User is " . $user_age . " years old.";
} else {
    echo "User age is not available.";
}

Becomes:

// With PHP 8.3
function get_user_age( $user_id ): int|null {
    $user_data = get_userdata( $user_id );
    return $user_data['user_meta']['age'] ?? null;
}

$user_age = get_user_age( 123 );
echo "User is " . $user_age . " years old."; // No need for additional null check

Using Improved String Handling to Prevent SQL Injection

// Before PHP 8.3
function get_post_by_slug( $slug ) {
    global $wpdb;

    $post_id = $wpdb->get_var( "SELECT ID FROM {$wpdb->prefix}posts WHERE post_name = '$slug'" );
    if ( $post_id ) {
        return get_post( $post_id );
    } else {
        return null;
    }
}

echo get_post_by_slug( 'my-post-slug' );

Becomes:

// With PHP 8.3
function get_post_by_slug( $slug ): WP_Post|null {
    global $wpdb;

    $post_id = $wpdb->get_var( "SELECT ID FROM {$wpdb->prefix}posts WHERE post_name = :slug", [ ':slug' => $slug ] );
    if ( $post_id ) {
        return get_post( $post_id );
    } else {
        return null;
    }
}

echo get_post_by_slug( 'my-post-slug' );

======

Using Typed Class Constants in WordPress Plugin or Theme Development

When you build a theme or a plugin, you might create classes for custom post types, shortcodes, or other functionalities. With PHP 8.3, you can use typed class constants to ensure consistency in your class definitions.

// Before PHP 8.3

class BookPostType {
    const POST_TYPE = 'book';
    // More code for registering post type
}

Becomes:

// With PHP 8.3

class BookPostType {
    const string POST_TYPE = 'book';
    // More code for registering post type
}

Here, the POST_TYPE constant is explicitly typed as a string, ensuring that it can only hold a string value.

Using json_validate() in AJAX Requests or API Integrations

When handling AJAX requests in WordPress or integrating with external APIs, you often work with JSON data. The json_validate() function can be used to validate JSON data before processing it.

// Before PHP 8.3

add_action( 'wp_ajax_my_action', function() {
    $json_data = $_POST['json_data'];
    $data      = json_decode( $json_data );

    if ( json_last_error() !== JSON_ERROR_NONE ) {
        wp_send_json_error( 'Invalid JSON' );
    }

    // Process $data
});

Becomes:

// With PHP 8.3

add_action( 'wp_ajax_my_action', function() {
    $json_data = $_POST['json_data'];

    if ( ! json_validate( $json_data ) ) {
        wp_send_json_error( 'Invalid JSON' );
    }

    $data = json_decode( $json_data );
    // Process $data
});

Deep Cloning of readonly Properties in Advanced Custom Solutions

In advanced custom solutions where you might be cloning objects, PHP 8.3’s deep cloning feature can be useful.

// Before PHP 8.3

class CustomSettings {
    public readonly array $settings;

    public function __construct( array $settings ) {
        $this->settings = $settings;
    }
    // Cloning this object wouldn't allow changing $settings
}

Becomes:

// With PHP 8.3

class CustomSettings {
    public readonly array $settings;

    public function __construct( array $settings ) {
        $this->settings = $settings;
    }

    public function __clone() {
        $this->settings = [ 'new' => 'settings' ]; // Allowed in PHP 8.3
    }
}

In PHP 8.3, you can reinitialize read-only properties in the __clone method, which can be useful in scenarios like duplicating settings objects with modifications.

Conclusion

I won’t use any of these really soon in my WordPress projects. There are 2 exceptions:

WP Whiskey Air – my current WordPress theme – not for public use – which is a bespoke theme, crafted with speed in mind. As I have full control over the server, I will be able to use PHP 8.3-only features without any issue.

Hound CMS – This is a personal project, a small CMS engine, currently using a flat-file database, but soon moving to SQLite 3+. As I know no one is using it (sigh…) I can do whatever I want.

Say hi to Ellie, below :)

Ellie

Related posts