Tag Archives: WordPress

10 reasons why WordPress is a better starting point for a project than you thought, even though you’re an advanced PHP developer

I didn’t quite mean to make this a “top 10” list, but it did work out to 10 items, and it’s indeed a list.

UPDATE: I didn’t make a few points clear originally, so let me add some context here.

The impetus for writing this post was of 3 weekend projects that never got off the ground because I got bogged down with the setup. I was familiar with the framework I was using, it gave me all the tools I needed to get the project going, and as I started building my functionality I started realizing I needed X, Y and Z to keep going. So I grabbed packages (some I was familiar with, others less so, but all were modular and would fit right in with the framework I was using) and spent a bunch more time getting them connected and playing nice with each other, realizing I needed to modify my database schema to support certain features from those packages, etc.

By the middle of weekend #2 I still hadn’t really started coding what I wanted to work on. During the following week I had a bit of an epiphany that everything I’d spent a bunch of time doing to get my project started using simple, existing modular components that were easy to plug in and use already existed within WordPress. At the start of weekend #3, I started fresh with a WordPress install and just started coding the thing I wanted. I just never realized how much time I spent wrangling modules and components before.

Google App Engine provided a similar feeling. At the time I started playing with App Engine it didn’t support PHP, so I used Python, and I was able to just start coding. Users, permissions, access, etc, were all taken care of. The only thing I was mildly annoyed with was that I couldn’t find any existing themes, but it wasn’t too time consuming to grab a WordPress theme and make it work with Jinja2. Having to define my routes was a little annoying / time consuming too. I realized that WordPress would have made even those two things inconsequential, but they weren’t burdensome. But I digress.

Any CMS or Framework can do most of this stuff. The point isn’t that only WordPress does it, nor is WordPress necessarily the best at it. The point is just that WordPress already has these building blocks in place so when you just want to get your idea out there, there’s a bunch of stuff you just don’t have to deal with.

  1. User management and roles.  Has a fully functional user system available, and easy to hook into or customize.  So much better than rolling your own.
  2. Database structure and abstraction/access.  Fully functional database abstraction layer, and supports custom data types (just imagine custom post types are your default storage engine, not “posts”; and custom taxonomies are your relationships).
  3. Control/Admin panel.  WordPress has an admin panel “for free”. Easy to add pages, customize who can see what, etc.  This includes a fully-customizable user experience and WYSIWYG editor already present, which can be easily attached to anything.
  4. HTTP client.  WordPress has a pretty smart HTTP client, and can handle a lot of situations (mostly encountered by shared hosts).  It’s no Guzzle but it’s very reliable, and functions as expected under most normal circumstances.  It even allows non-blocking requests (these are asynchronous but may still trigger handlers on failure/success, just not in the same process or request).
  5. Scheduled tasks.  WP Cron leaves a lot to be desired, but it’s there, and it (mostly) works, and having even a half-assed solution pre-built beats building your own half-assed solution that you’ll never get around to finishing.
  6. Cache client.  With automated fallback if no persistent cache is available.
  7. Route management / pretty URLs / SEO URLs are already present, and works by default when implementing custom pages, post types, taxonomies, and kind of archive or detail page..
  8. Pretty much everything has an observer pattern built-in.
  9. Search.  Like scheduled tasks / cron, WordPress search kind of sucks out of the box, but it’s there, and plugins (+ the observer pattern for searches) are available to allow you to search using any kind of actual search engine you want. But again, it’s at least got a “good enough” search already available until you implement an improvement.
  10. It’s “legacy” code, and ugly, but that doesn’t mean your code has to be.  There’s nothing to stop you from writing modern PHP code for your classes and themes.  (Except that WordPress can be a little weird about namespaces, due to the way the observer pattern is implemented.)   Incidentally, it’s improving.  Thing is, these are things you just have to deal with when your software has millions of users and requires backwards compatibility, and also so that your software can reach hundreds of millions of users in the first place.
Advertisements

Debugging WordPress: Find Slow Filters and Actions

English: WordPress Logo

This is one of the ways I diagnose performance issues within WordPress, specifically “hey this site is slow, what can we do to speed it up?”  Whereas a callgrind file gives you a look into the whole callstack of your script, the point of this is to profile actions and filters in WordPress.

It does require that you make some changes to core files.  This is not meant to be something that you use in production, nor is it meant to be something you run all the time.  This is for “spring cleaning” your site, and/or tracking down general slowness.

The code between “// @custom start” and “// @custom end” is what you will be adding.  I am not including line numbers because they change frequently with the version of WordPress.

“But wait”, you say, “Just add the profiling to the ‘all’ filter and you don’t have to do all this.”  That’s probably fine for most cases.  This method measures actions and filters that execute before plugins are loaded.  Additionally, there’s always the chance of a priority conflict even if you put priority 0, because if there are multiple items at the same priority level they just get appended to the list, so your profiling hook may execute after another action/filter.

So without further ado:

index.php

Put this at the top of index.php, before any other code.

class Pmc_Debug
{
    protected static $_threshold_to_hide = 0.1;

    public static function get_threshold_to_hide() {
        return static::$_threshold_to_hide;
    }

    public static function set_threshold_to_hide( $time ) {
        static::$_threshold_to_hide = (float) $time;
    }

    public static function record_action( $name ) {
        $backtrace = debug_backtrace(false);
        if ( ! isset($backtrace[2]['args']) ) {
             return;
        }

        foreach ( $backtrace[2]['args'] as &$arg ) {
            $arg = ( is_string($arg) ) ? stripslashes($arg) : $arg;
        }
        $GLOBALS['pmc_action_timer'][microtime()][] = array(
            'name' => $name,
            'backtrace' => var_export($backtrace[2], true),
        );
    }

    public static function print_recorded_actions() {
        ?>
        <hr />
        <div style="padding: 10px; background-color: white; color: black; white-space: pre; text-align: left;">
        <pre>
        <?php
        $timetotals = array();
        $break = '<hr />';
        $eol = '<br />';
        $detail_list = $eol . $eol . $break . $eol . $eol;
        $detail_list .= 'Detail action list.' . $eol;
        if ( static::get_threshold_to_hide() > 0.0 ) {
            $detail_list .= 'Hiding items faster than ' . static::get_threshold_to_hide() . ' seconds.' . $eol;
        }
        $detail_list .= $eol . $break . $eol;

        reset($GLOBALS['pmc_action_timer']);
        $previous_item = array('timestamp' => array('sec' => 0.0, 'usec' => 0.0), 'items' => array());
        do {
            if ( ! isset( $timestamp ) ) {
                $timestamp = microtime();
            }
            if ( ! isset( $items ) ) {
                $items = array();
            }

            list($usec, $sec) = explode(" ", $timestamp);
            $sec = (float) $sec;
            $usec = (float) $usec;
            $timestamp_float = $sec + $usec;

            if ( $timestamp && ! isset($start_time) ) {
                $start_time = $timestamp_float;
            } else {
                $end_time = $timestamp_float;
            }

            if ( empty($previous_items['items']) ) {
                $diff = 0.0;
                $previous_items['items'][] = array('name' => 'nothing', 'backtrace' => '');
            } else {
                $diff = $timestamp_float - ( $previous_items['timestamp']['sec'] + $previous_items['timestamp']['usec'] );
            }

            if ( $diff > static::get_threshold_to_hide() ) {
                $previous = array();
                if ( ! empty($previous_items['items']) ) {
                    foreach ( $previous_items['items'] as $previous_item ) {
                        $previous[] = $previous_item['name'];
                    }
                }
                foreach ( $items as $item ) {
                    $detail_list .= $eol . round($diff, 3) . ' seconds between starting ' . implode(', ', $previous) . ' and starting ' . $item['name'] . $eol;
                    if ( ! empty($previous_items['items']) ) {
                        foreach ( $previous_items['items'] as $previous_item ) {
                            $detail_list .= $previous_item['backtrace'] . $eol;
                        }
                    }
                    $detail_list .= $item['backtrace'] . $eol;
                }
            }
            foreach ( $items as $key => $item ) {
                $timetotals[$item['name']][] = $diff;
            }

            $previous_items['timestamp']['sec'] = $sec;
            $previous_items['timestamp']['usec'] = $usec;
            $previous_items['items'] = $items;
        } while ( list($timestamp, $items) = each($GLOBALS['pmc_action_timer']) );
        $detail_list .= $eol . $break . $eol;

        $summary_list = $eol . $eol . $break . $eol . $eol;
        $summary_list .= 'Action overview: aggregate totals.' . $eol;
        if ( static::get_threshold_to_hide() > 0.0 ) {
            $summary_list .= 'Hiding items faster than ' . static::get_threshold_to_hide() . ' seconds.' . $eol;
        }
        $summary_list .= $eol . $break . $eol;

        do {
            $total_time = 0.0;
            if ( isset($times) ) {
                foreach ( $times as $time ) {
                    $total_time += $time;
                }
            }
            if ( $total_time > static::get_threshold_to_hide() ) {
                $summary_list .= $tag . ' took approximately ' . round($total_time, 3) . ' seconds' . $eol;
            }
        } while ( list($tag, $times) = each($timetotals) );
        $summary_list .= $eol . $break . $eol;
        if ( $end_time < $start_time ) {
            $end_time = $start_time;
        }
        echo 'Request took ' . ( $end_time - $start_time ) . ' seconds from start to finish.' . $eol;
        echo $summary_list;
        echo $detail_list;
        ?>
        </pre>
        </div>
        <?php
    }
}

Pmc_Debug::record_action('bootup');

wp-includes/plugin.php

In this file, look for the functions below and add the lines between “@custom start” and “@custom end”.  Basically, they go at the top of all the functions where filters are applied and actions are called.

function do_action($tag, $arg = '') {
   global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
   // @custom start
   Pmc_Debug::record_action($tag);
   // @custom end 

function do_action_ref_array($tag, $args) {
   global $wp_filter, $wp_actions, $merged_filters, $wp_current_filter;
   // @custom start
   Pmc_Debug::record_action($tag);
   // @custom end 

function apply_filters($tag, $value) {
   global $wp_filter, $merged_filters, $wp_current_filter;
   // @custom start
   Pmc_Debug::record_action($tag);
   // @custom end 

function apply_filters_ref_array($tag, $args) {
   global $wp_filter, $merged_filters, $wp_current_filter;
   // @custom start
   Pmc_Debug::record_action($tag);
   // @custom end

wp-content/themes/my-theme/footer.php

Here is the code that outputs the results.  This goes anywhere after the wp_footer() function, but it’s easiest just to put it before the closing </html> tag.

Pmc_Debug::record_action('done');
Pmc_Debug::print_recorded_actions();

Find yourself constantly battling browser cache when developing a script in WordPress?

This snippet will always give you a fresh copy of all the scripts and styles loaded via wp_enqueue_script() and wp_enqueue_style():

function pmc_dev_cachebuster( $src ) {
	return add_query_arg( 'ver', time(), $src );
}
add_filter( 'style_loader_src', 'pmc_dev_cachebuster' );
add_filter( 'script_loader_src', 'pmc_dev_cachebuster' );

Here’s a list of all the default / built-in querystring paramaters (or query variables) used by WordPress:

  • attachment
  • attachment_id
  • author
  • author_name
  • calendar
  • cat
  • category_name
  • comments_popup
  • cpage
  • day
  • debug
  • error
  • exact
  • feed
  • hour
  • m
  • minute
  • monthnum
  • more
  • name
  • order
  • orderby
  • p
  • page
  • page_id
  • paged
  • pagename
  • pb
  • post_type
  • posts
  • preview
  • robots
  • s
  • search
  • second
  • sentence
  • static
  • subpost
  • subpost_id
  • tag
  • taxonomy
  • tb
  • term
  • w
  • withcomments
  • withoutcomments
  • year

Setting custom values for _trackPageviews in Google Analytics may have unexpected consequences

I recently deployed Joost de Valk‘s awesome Google Analytics for WordPress plugin on a few sites. (I mean, he even SEO’d the plugin name, how brilliant is that?)  One of the features of this plugin will add custom parameters to the URL in your _trackPageviews call so that you can gain insight into what kind of results you return.

For example, if you search for “K-Stew” and get no results, that will get tracked in GA as [“_trackPageview”,”http://www.example.com/?s=no-results:k-stew&cat=no-results”%5D.  Great for being able to view GA reports and realize, hey, people want to see K-Stew and I’m not giving them any!

So what’s the problem? Continue reading Setting custom values for _trackPageviews in Google Analytics may have unexpected consequences

WordPress Must-Use Plugins

"My words fly up, my thoughts remain belo...
Image by ZedZap via Flickr

WordPress has quite a few hidden features.  My favourite so far: MU Plugins, or “must-use plugins”.  Introduced with WP 3.0, the code says:

/**
 * Check the mu-plugins directory and retrieve all mu-plugin files with any plugin data.
 *
 * WordPress only includes mu-plugin files in the base mu-plugins directory (wp-content/mu-plugins).
 *
 * @since 3.0.0
 * @return array Key is the mu-plugin file path and the value is an array of the mu-plugin data.
 */

Continue reading WordPress Must-Use Plugins

Performance overhead of using plugins and includes in WordPress

Modularity requires additional overhead.  That’s just the way it is.  If you want to semantically separate different components of your web app or theme, you have to include files, run safety checks (e.g., include_once), extend components that you only use 20% of, etc.

WordPress encourages this behaviour, if you have plugins that only effect your admin panel, the files still get included and the actions and filters still get included, the functions are still defined, for every frontend visitor page render — but they are only called into action when you’re on the backend of your site.

And lately when coding for WordPress, I’ve come to embrace and accept that it wants me to be modular and extend.   Continue reading Performance overhead of using plugins and includes in WordPress