DEV Community

Tyler Smith
Tyler Smith

Posted on • Updated on

Simplifying WordPress's functions.php with OOP: Part 2

After a year to reflect on object-oriented programming practices, I no longer endorse the approach I took in this post. The inheritance used in the code below adds unnecessary obfuscation and can lead to code that is less maintainable in the long run. I will leave this post up for historical purposes.

If you haven't read the original post (Simplifying WordPress's functions.php with OOP), I still highly endorse those practices and use them regularly.


The other month, I wrote an article titled Simplifying WordPress's functions.php with OOP with the hope of simplifying WordPress's often-bloated catch-all for adding functionality to themes. I became interested in the topic when I was adding functionality to a WordPress site I had built when I had only been a developer for less than a year. Eminem might liken the site's functions file to "mom's spaghetti."

If you haven't read the previous post, I'm going to recommend you read it here as this post builds on top of its code and concepts.

I approached simplifying the functions file with a few goals:

  1. Clean up the site's functions file by creating a simpler, less verbose API for interacting with the theme.
  2. Prevent hook failure for the most common hooks.
  3. Abstract the boiler plate API to somewhere that I never have to look at it again.

When I looked at solving these problems in a basic theme setup, I decided that it made sense to treat the theme like an object and build a simple API of methods to perform actions on this object. In the last post, we were able to implement this in a way that left our functions file looking like this:

<?php // functions.php

require get_template_directory() . '/classes/theme-class.php';

$theme = new MyTheme;

$theme->addNavMenus([
    'menu-1' => 'Primary',
]);

$theme->addImageSize('full-width', 1600);

$theme->addStyle('theme-styles',  get_stylesheet_uri())
      ->addScript('theme-script', get_template_directory_uri() . '/js/custom.js');
Enter fullscreen mode Exit fullscreen mode

I'm pretty happy where I landed with this, but most of my themes have lots of action and filter hooks.

Back when I started building WordPress themes, I'd have lots of hooks at the bottom of the functions file, taking up space like this:


<?php // functions.php

/** Below theme setup */

function mytheme_excerpt_length ( $length ){
    return 20
}
add_filter( 'excerpt_length', 'mytheme_excerpt_length' );


function mytheme_excerpt_more ( $more ) {
    return '&hellip;';
}
add_filter('excerpt_more', 'mytheme_excerpt_more' );


function mytheme_add_excerpt_class ( $excerpt ){
    return '<div class="mytheme-excerpt">' . $excerpt . '</div>';
}
add_filter('the_excerpt', 'mytheme_add_excerpt_class' );

/** More filters and actions forever */

Enter fullscreen mode Exit fullscreen mode

Code like this could easily go on for a hundred lines or more.

I don't like code like this for two reasons:

  1. It clutters up the functions file.
  2. You need to prefix every function with something unique or risk a name collision.

So this is no good.

To improve this, I started putting my hooks into another file that I included inside functions.php using PHP's include command, and I would put all my hooks in closures (PHP's version of an anonymous function) to avoid compulsory prefixing.

The included file typically ended up looking something like this:

<?php // inc/hooks.php


add_filter( 'excerpt_length', function ( $length ) {
    return 20;
});

add_filter('excerpt_more', function ( $more ) {
    return '&hellip;';
});

add_filter('the_excerpt', function ( $excerpt ){
    return '<div class="wp-excerpt">' . $excerpt . '</div>';
});
Enter fullscreen mode Exit fullscreen mode

I think this is an improvement, but it isn't without its issues.

These are simple self-descriptive hooks, but what if I had a hook that added social share buttons to the bottom of my content? Without a descriptive name, I'd need to dig into the code to understand what it was doing. I could comment the code, but it's probably best to use patterns that don't absolutely rely on comments.

Additionally, there's no way to unhook a closure. While this is less of a concern for a custom theme than a plugin, it's something worth keeping in mind.

Side note about the last post: Technically in our base theme class, there's no way to unhook the closures either. However, if you look at my gist of the completed class, we have an API for removing anything that we've added through our 'remove' methods.

So what should we do?

Thinking in Object-Oriented Programming

When you think about it, these filters are performing actions on the theme itself. We've already created a theme class with an API for the things we need to set up on almost every WordPress theme. However, outside of the methods already on that class, I tend to need very different hooks on every site I build.

So, why not extend the theme class? OOP for the win!

<?php // classes/my-theme-class.php
require get_template_directory() . '/classes/theme-class.php';

MyThemeClass extends MyTheme
{

    protected $excerptLength = 20;

    public function __construct()
    {
        add_filter( 'body_class', [ $this, 'filterExcerptLength' ] );
        add_filter( 'the_content', [ $this, 'filterExcerptMore' ] );
        add_filter( 'nav_menu_css_class' , [ $this, 'filterAddExcerptClass' ]);

        parent::__construct();
    }

    public function filterExcerptLength($length){
        return $this->excerptLength;
    }

    public function filterExcerptMore($more) {
        return '&hellip;';
    }

    public function filterAddExcerptClass($excerpt){
        return '<div class="mytheme-excerpt">' . $excerpt . '</div>';
    }
}
Enter fullscreen mode Exit fullscreen mode

I've created methods to correspond to each hook and prefixed 'filter' in front of each (I don't like prefixing theme names (mytheme_excerpt_length) because they feel hacky, but 'filter' or 'action' prefixes are descriptive of what the method is doing). I add the filters for each method in the class constructor, meaning they will be hooked as soon as the class is initialized. Filters methods on a class are added as an array, with the class as the first array item and method as the second. Finally, the parent class constructor is called.

This feels a lot better to me for a few reasons:

  1. We're performing actions directly on the theme object once it's initialized. Because the filters modify the theme, this feels logical.
  2. We have descriptive names of what each method is doing.
  3. Because these are within a theme class, each method is encapsulated and does not need a theme name prefix.

There are still things I'm not thrilled with in this implementation. Hooks can still fail silently. Yet with so many hooks in WordPress core and in plugins, this may be unavoidable.

I think this a more elegant implementation. The global namespace is uncluttered with prefixed functions, and everything lives within the theme class, which feels like the right place for it.

With this, your functions file can look like this:

<?php // functions.php

require get_template_directory() . '/classes/my-theme-class.php';

$theme = new MyThemeClass;

$theme->addNavMenus([
    'menu-1' => 'Primary',
]);

$theme->addImageSize('full-width', 1600);

$theme->addStyle('theme-styles',  get_stylesheet_uri())
      ->addScript('theme-script', get_template_directory_uri() . '/js/custom.js');
Enter fullscreen mode Exit fullscreen mode

All the hooks from MyThemeClass are added when the object is initialized, and you still have the parent class API available to add menus, stylesheets and more.

We can take this one step further. As these are a part of the MyThemeClass, we could add all of this to the constructor of MyThemeClass:

<?php // classes/my-theme-class.php
require get_template_directory() . '/classes/theme-class.php';

MyThemeClass extends MyTheme
{

    protected $excerptLength = 20;

    public function __construct()
    {
        $this->addNavMenus([
            'menu-1' => 'Primary',
        ]);

        $this->addImageSize('full-width', 1600);

        $this->addStyle('theme-styles',  get_stylesheet_uri())
              ->addScript('theme-script', get_template_directory_uri() . '/js/custom.js');


        add_filter( 'body_class', [ $this, 'filterExcerptLength' ] );
        add_filter( 'the_content', [ $this, 'filterExcerptMore' ] );
        add_filter( 'nav_menu_css_class' , [ $this, 'filterAddExcerptClass' ]);

        parent::__construct();
    }

    public function filterExcerptLength($length){
        return $this->excerptLength;
    }

    public function filterExcerptMore($more) {
        return '&hellip;';
    }

    public function filterAddExcerptClass($excerpt){
        return '<div class="mytheme-excerpt">' . $excerpt . '</div>';
    }
}
Enter fullscreen mode Exit fullscreen mode

In this implementation, the constructor offers a birds eye view of all of the actions and filters affecting the theme.

And with all this moved to the child theme class constructor, your functions.php can look like this:

<?php // functions.php
require get_template_directory() . '/classes/my-theme-class.php';
$theme = new MyThemeClass;

Enter fullscreen mode Exit fullscreen mode

I've started refactoring the code in one of my projects to function this way, and I'm porting that code to my personal starter theme that I maintain in hopes that I'll be able to code projects both faster and cleaner in the future.

If you're interested in learning more about object-oriented programming for WordPress, I strongly recommend checking out the Object-Oriented Theme Development WordCamp Talk by Kevin Fodness. I grabbed a lot of his ideas when starting to move towards Object-Oriented WordPress. For those who are already using OOP in WordPress, I've love to hear your opinions about camelCase vs snake_case for method names, PHP namespaces and more. Cheers!

Top comments (3)

Collapse
 
james0r profile image
James Auble • Edited

I really like what you're doing here. Is there a repo for this?

Also, in the constructor for the child class, could the invocation of the parent classes constructor come at the beginning of its constructor?

Question though, what are you doing there with the add_filter('body_class', [$this, 'filterExcerptLength']); ?

This throws an error for me about arguments to array_unique().

Collapse
 
tylerlwsmith profile image
Tyler Smith

I'm glad you like it, James! I don't do a lot of WordPress development anymore so I never ended up building a repo for this. Feel free to take the ideas and build something yourself though!

Collapse
 
andrade1379 profile image
Boba

This is great! Thanks for the write-up. Cheers!