In: , , , , ,
On: 2009 / 09 / 11
Shorter URL for this post: http://ozh.in/ok

Short intro for readers who don't follow me or this blog's feed: I've been a judge in the annual WordPress Plugin Competition, and as such I have reviewed a number of plugins. Read more about this.

As promised, I'm going to share a list of the most common mistakes, errors, misunderstandings, bad habits or wrong design decisions I've encountered while reviewing all these 43 plugins. Some are highly critical stuff (I've contacted 3 plugins authors after finding serious security holes in their plugin), some are more potential annoyances than real bugs, or are just causing a waste of server resources that could be avoided, but all have something in common: they're trivial to fix.

wtf-code
(Image stolen from Thom Holwerda without permission)

I've classified them in two parts: 10 bad code signs, plus a bonus with design decisions that suck. If you consider yourself a semi experienced coder or better, be sure to skip this article, you're not going to learn a thing :)

10 most frequent bad code bits

What I call bad code can be: code that might work but is ugly, code that works but will fail one day, and obviously code that doesn't work at all.

1. It's not a plugin, it's a mess

I've been truly shocked by the number of coders who deliver stuff with no comment, no or random indentation in code (honestly!! just *no* indentation!! can you believe this??). As a judge I've poured my wrath on their final grade, just like as a user I would simply never install a plugin that looks like a mess, because if it looks like a mess it is obviously one.

A plugin with no comment where needed and no indentation to make code readable tells one thing: the plugin won't ever be updated or maintained, because in a couple of months the author will be simply lost and won't remember what, how and why they've done this or that.

I won't elaborate too much on this because it's just plain common sense, but if you're one of those messy guys or gals who don't really see the point here, please read the following article: Make clean and readable sources: why and how. Hopefully you'll change your mind and adhere to WordPress Coding Standards.

2. Way too generic function names

Again basics here, but about 40% of the plugins I've reviewed use a waaaaay too generic function naming scheme. The point here is to make sure your plugin will never trigger a "Fatal error: Cannot redeclare your poorly named function" error.

Function names must be two things: descriptive and unique. I've found plugins with function named as simply as pretty_title(), pages() or update_options(). Ironically enough, a coder submitted several plugins that won't be able to run on the same blog because they all use the same function declarations.

Better function names would have been for instance joeplugin_post_pretty_title(), joeplugin_list_pages() or joeplugin_update_options(). An alternative to keep function names short is to wrap them into a class (that also must have a unique and descriptive name).

3. What? 87 new rows in the option table?

If you've been reading me for a while, you know it's my favorite pet peeve: don't save each setting into a separate DB entry. Store them in an array, and save it in one DB row. One of the plugins in the competition for instance creates 87 entries in the option table.

What I don't like about adding a bunch of entries in the option table is that it makes a unnecessarilly cluttered database once you deactivate the plugin, and that the plugin performs a bunch of SQL write queries instead of just one.

There's an exception to this rule: if your plugin has a gazillion options and only a few of them are going to be used on every instance while the rest will only be used by an admin page, then it makes sense to store them into 2 or more entries and set the rather unknown autoload parameter to 'no'.

WordPress works like this: when initialized, it requires all its needed files, then does a "SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'". This loads in memory *all* the options which have autoload set to (default value) 'yes'.

To specify that an item from the DB option must not be loaded on start, simply use:

  1. add_option('option_name', 'option_value', '', 'no'); // 'no' = not autoload

4. You create new tables for what?

WordPress comes with a several tables for all the various tasks you might think of: users, meta informations about users, posts and their meta informations, etc..

Now, of course, it's possible that your plugin needs to create one or more extra tables, but before doing so, think and be sure the existing tables just won't fit. Typically, consider using wp_options and the wp_*meta tables.

If you *really* have to create tables, give them a name that makes sense. Always begin with $wpdb->prefix, use your plugin shortname, and append a word that will describe the table usage. Something like $wpdb->prefix.'myplugintitle_custom_logs'.

5. No uninstall function

WordPress since version 2.7 has implemented functions to uninstall plugins. There are two ways of doing so: using an uninstall hook, which some might find a bit complicated (well, it's not, really), and using an uninstall file, which is a no brainer dead easy solution, as Jacob explains.

Now, having uninstall functions is not really something I consider a prerequisite or an absolute must have feature. It's just a nice touch to know that the plugin will leave no trails in the database once the user will want to get rid of it.

This said, I find it totally unacceptable when a plugin creates a collection of option entries (see point #3) or extra tables (see point #4) and provide no automatic way of cleaning things up upon removal.

6. Custom javascript or CSS added on each and every admin pages

This one is an all time classic. When you create option pages for your plugin and will need custom javascript for them, please, please, pretty please, don't just hook your jQuery bits to admin_head. Inserting your javascript to all other pages *will* eventually break another plugin interface that also uses javascript, not to mention that it's useless.

Inserting your script or CSS to your page only is very easy.

7. Plugin forms with no security, or nonces misunderstood

When an option form is submitted, a plugin should verify if the submitter has authority and intention. There's a very well explained article from super star Mark Jaquith on this subject.

The problem I've seen too many times in the plugins I've reviewed is that form data are handled and processed without even checking that is_admin() or that current_user_can(). Basically, this means that anyone (users with no authority) can POST data to your blog and play with the plugin.

But even checking for authority can be insufficient. Imagine a plugin that would, say, delete posts. It's trivial to make a webpage that sends POST data to someone else's blog plugin option page, and share with them a bitlyfied short URL of that webpage via Twitter. One click on it and you would be redirected to your own blog admin area where you do have authority, and delete your posts. You have authority, but no intention.

This is the issue nonces address. Nonce functions generate unique and temporary timestamps that are impossible to guess, and check that data submitted come from a particular page from *your* admin area, not just from anywhere on the interwebs.

A very common mistake I've seen in a lot of plugin is simply adding a nonce field to the form. This won't work, it is not enough. You need nonce fields on the form, and nonce checks where the form is processed, otherwise it's just like telling "everybody needs an ID card to get into my bar" but never check them.

Read more about nonces and how to implement them. Or even better: read this article on how to deal with options in WP 2.8, and learn how to both correctly store all your options in a single DB entry and implement security in your forms, using just one function call.

8. Actions triggered from unchecked GET data

I've seen similar constructs to the following block more than once, and that's a bit scary:

  1. add_action('init', 'myplugin_init');
  2. function myplugin_init() {
  3.     global $wpdb;
  4.     call_user_func($_GET['action']);
  5.     $wpdb->query('DELETE from sometable WHERE somefield = '. $_GET['value']);
  6. }

This case is similar to the previous one: use an unchecked request (yoursite.com?action=bleh&value=wot) but this time to execute code or even perform SQL queries. Omit to implement security in this case is highly critical since you don't even have to trick a blog owner into clicking on a URL that sends them to their own admin: just anyone can do it.

Once again, nonce functions are what you need. If you want to use $_GET['action'] as the switch for your action, please do this:

  • Instead of sending user to site.com/?action=something, use the following code:
    1. $url = "site.com/?action=something";
    2. $action = "myplugin-update";
    3. $link = wp_nonce_url( $url, $action );
    4. echo "<a href='$link'>click here</a>"; // whatever you're doing to echo the nonced link
  • In the function triggered by the $_GET parameter, do:
    1. if ( isset( $_GET['action'] ) && $_GET['action'] == 'something' ) {
    2.     check_admin_referer( 'myplugin-update' ); // die if invalid or missing nonce
    3.    
    4.     // rest of the code ...
    5. }

See? Nonces are simple. One function call in the form or the link, one function call in the processing part.

There are also other functions that might be required or just sensible to use here: is_admin() to make sure we're in the admin area, current_user_can() to make sureprivileges are sufficient.

9. Trust user input and pass it to SQL

This one is the most critical security hole you can craft, especially when used in conjunction with point #8, as I've seen in two plugins. Whenever you're performing SQL queries containing user input data, validate them. The risk here is SQL injection.

If you're passing a parameter that's supposed to be an integer, use intval() before storing it in the DB. If you're allowing HTML, esc_attr() it. If you're expecting a string, preg_replace('/[^a-zA-Z]/', '') it. And so on.

Once the data for your SQL query is validated, send the query string through $wpdb->prepare() before running it. Method prepare() is similar in use to sprintf(), and handles for you all the escaping, quoting and integer casting you'll need. It's as simple as:

  1. $calvin = "6 years";
  2. $hobbes = "stuffed";
  3.  
  4. // "Prepare" the query
  5. $sql = $wpdb->prepare( "INSERT INTO $wpdb->joeplugin_table( id, field1, field2 ) VALUES ( %d, %s, %s )", $_POST['id'], $calvin, $hobbes );
  6.  
  7. // Run it
  8. $wpdb->query( $sql );

If your plugin is going to play with MySQL, make sure you grok the whole $wpdb class.

10. Localization done wrong

This last one has to be the most frequent code error I've encountered: thinking __('some string') will be enough to make a plugin translatable.

The correct syntax to use for a plugin to support localization is __('some string', 'myplugin') where 'myplugin' is a unique identifier to the text domain, which has to be initialized with load_plugin_textdomain()

Complete example with a subdirectory 'translations/' in your plugin directory:

  1. add_action('init', 'myplugin_load_translation_file');
  2.  
  3. function myplugin_load_translation_file() {
  4.     // relative path to WP_PLUGIN_DIR where the translation files will sit:
  5.     $plugin_path = plugin_basename( dirname( __FILE__ ) .'/translations' );
  6.     load_plugin_textdomain( 'myplugin', '', $plugin_path );
  7. }

For a comprehensive tutorial on plugin translation, I suggest your read this one. There's also a page on the Codex about I18n for WordPress Developers that you should read.

Design decisions that suck

During my reviewing of all the plugins, I've found numerous bits of code that are not exactly bad (and can even be smart, actually) but are simply a bad idea because one day or another it will fail on someone's setup, or just because there are easier ways to go.

Unadvertised or unchecked PHP5+ functions

As of writing, WordPress has pretty loose requirements, namely PHP 4.3. I'm not sure how many people are still running PHP 4.x, but there are. It's tempting to use PHP5 functions such as json_encode(), and that's OK, but in such a case, you need to either warn the user on the plugin's page ("This plugin requires PHP5"), or make your plugin check the environment and tell the user it's not going to run as expected.

Extension required. Is it available?

A lot of plugins I've looked at require PHP's CURL extension, I've also seen one needing mbstring functions, and yet they don't check if it's present. This is similar to the previous point: you have to tell the user, or make your code check that everything is OK prior to do anything.

Wheel reinvented

Speaking of cURL, why are you still using it? It's awesome and everything, but it totally might be unavailable. What's the point in writing a 10 line code block to fetch remote content while a single call to wp_remote_get() is enough, and will work even if there's no cURL?

WordPress has a number of internal functions to make your code faster to write and more compatible with every setup. I've written for instance about making HTTP requests the easy way, managing options without mucking with $_POST, but there's also built-in Ajax functions and hooks, and more.

Whenever you're going to write a code block that seems to be quite generic and common, check first if there's a WP function that can do it for you.

Compatibility maintained with deprecated versions of WordPress

I've seen code comments mentioning stuff like "// we're doing this for people using WP 2.5". This one is more a personal choice, but I think maintaining compatibility with older versions is a terrible idea.

Terrible for you: fixing bugs and implementing new features is quite a task already, don't add to the burden with more deprecated code.

Terrible for the users: it's nice that your plugin is going to run fine on their obsolete, insecure and already hacked blog, but it really does not motivate them to upgrade, which is vital.

I know I've dropped backward compat with my plugins a long time ago, and always code for the latest release available. It makes life so much easier :)

Hardcoded paths

Since several point releases, things might not be where you think they are: wp-config.php gone up a directory, or the whole wp-content directory moved somewhere else. I've also seen users rename a plugin's directory.

In any of these edge cases, your plugin will break if you're relying on hardcoded path. Use WP_PLUGIN_DIR, WP_PLUGIN_URL, plugin_basename( __FILE__ )

All files included, even if not needed

Make your life easier: split your plugin in several smaller chunks, put files in various directories instead of dropping everything in the same folder (includes/, css/, translations/, etc…) and include only what you need. Maybe !is_admin()? Then don't require_once() all the stuffs that create the option forms.

User left alone in the dark if something goes wrong

Once you're done with your neat functions that send stuff to Twitter, ask yourself: what's going to happen if Twitter is down?

Nothing is more frustrating than users coming to your site and asking for support because the plugin did not work and they just cannot tell what went wrong. Anywhere possible, try to add diagnosis functions and messages, check results of operations and warn the user if something looks like things failed.

No download link on the plugin's page

Yeah, this last one sounds like a joke, but it's not. I've seen it last year, I've seen it this year again: people make a plugin, write a nice page about it, and don't tell how to download it. Please make sure the download link is unmissable :)

And that's it

That was a way too long article for such basic tips :)

Shorter URL

Want to share or tweet this post? Please use this short URL: http://ozh.in/ok

Metastuff

This entry "Top 10 Most Common Coding Mistakes in WordPress Plugins" was posted on 11/09/2009 at 7:31 pm and is tagged with , , , , ,
Watch this discussion : Comments RSS 2.0.

110 Blablas

  1. Nabeel says:

    i am new to plugin development for wordpress but not new to programming though.. . but honestly speaking i might have done some of the similar mistakes too :) will keep them in mind now . bookmarked ur page

  2. if you would like your classes to be generic and be use used in other coding environments with only the admin class specific to WP then wp_remote_get() is not handy.

  3. I made my second plugin last week. I was wondering why when I updated to the newest tag that wordpress doesn't show that my plugin needs updated from the plugins page? Do I have to do something special?

    You can get to my plugin and development log from the wordpress website here:

    http://wordpress.org/extend/plugins/sm-sticky-featured-widget/

  4. Don Elliott says:

    Thank you for writing this article, it would be great if WordPress would endorse an evolving best-practices document. A lot of developers with great ideas out there and poor delivery!

    Cheers!

  5. Joe says:

    Referring to best code formatting practices: some people have trouble seeing the trees through the forest, others can't see the forest when the trees are in the way. The 'best coding practices' promoted here and elsewhere seem to prefer the former people, not the latter. I grew up writing Assembly Language for the 6809 on a system with 24K of user memory. Commenting -every- line as to not only why, but as to what it is doing was absolutely imperative. My biggest project was 10 files long and compiled down to 8K of machine language. Debugging required hard-copy printing of the listing to see the forest through the trees. Modern languages like PHP don't require the massive comments, but larger files can still pose a problem seeing the forest. Wide screens and such make printing obsolete, and the world's real wood forests can no longer be abused to make scrap paper, which is what a debug listing is within 24 hours. As such, I find a true 'code' editor and a slightly modified version of the 'standard practice' the best option for me. You don't need extra spaces within parenthesis and before/after = == != === !== in MOST places – but SOMETIMES this is wise. ALWAYS indent, use tabs at the beginning of lines, spaces only in the middle, BUT don't waste a line for a single closing bracket, and don't waste a line to separate SIMPLE conditionals and loops, but do double space after the condition, and between statements and before the closing bracket. Brackets that open and close on the same line do not get extra spaces of padding, but may then be followed by padding and a closing bracket from a previous line. Keep open brackets with their conditions, not on the next line:
    if ('hi'==$message) echo "hello";
    if ('bye'==$message) {echo "good-bye"; exit;}
    if ('hold on'==$message) {
    echo "I'll wait";
    for ($i=0; $i<100000; $i++) {echo "still waiting";} }
    This format allows more of the program to be seen on the screen at once, allowing you to follow the overall flow without having to scroll up and down and up and down to see where variables' data is sourced from, etc.
    While it's important to follow the same code formating practice throughout your program, a small but thoughtfully configured deviation can make a huge difference in readability at key places, but human judgment, not machine-generatable strictly defined rules must make this decision.
    Using advanced tools in most modern 'code' editors will identify missing or misplaced brackets, no need for the code to be spread out with a line reserved just for a single bracket indented with tabs. I want to see more than 10 lines of code at once! If you're still using Notepad to edit your files, give up and download a free editor (I like tsWebEditor). ALOHA!

  6. Joe says:

    Sorry, my previous entry did not format the example code correctly:
    echo "I'll wait";
    for ($i=0; $i<100000; $i++) {echo "still waiting";} }
    should be indented.
    Sorry, I don't understand your raw code posting rules.

  7. Joe says:

    If this is a WordPress site, it demonstrates the inferior handling of user-input to (X)HTML. A blog site should TRANSLATE the inputted text into (X)HTML formatted the same way. If I double space, convert to the equivalent HTML entity. If I indent, do the same. If the user INCLUDES validated (X)THML, allow it (limit the tags as needed), but convert all non-validated to HTML entities. Try a Google search for "XHTMLencode.php" and you should find my solution, but you will need to modify the list of tags is allows, and should add a line to filter out JavaScript-powered tags, for it to be safe for all world-wide-web data.

  8. James Tang says:

    This article is useful for primary PHP programmers, it's basic and important!

  9. Jan says:

    Just writing my frist plugin, i wonder why WP itself does not store options natively in an array when they come from one form. And how can i use an array in a wordpress options page?

    As i try my first plugin, there are about 50 db table entries of options from the woothemes theme that i installed :)

  10. Phoenixheart says:

    @Jan, WP itself supports saving option array. Just do add_option($key, $your_array) or update_option($key, $your_array). No need of (de)serialization, as WP handles it automatically.

  11. codecowboy says:

    How about people using CURL directly in plugins so that it can't use the proxy settings which you can define in wp-config? As a PHP Dev, I increasingly find myself having to work on a machine which sits behind a proxy server. I can't test your plugin becuase you haven;t used the WP_Http class. Grrr.

    Rant ends.

  12. Marc says:

    Wow this one is really nice. I like your humor, it's so funny to read your articles and, whats even more important, they're informative as hell.

  13. Rene says:

    This one is really huge.

    I am new to the real l33t WP development and your posting prevents me from doing some of the n00b mistakes.

    One thing: I really appreciate a print CSS for your postings.

  14. great post! i agree entirely, and especially with the js/css point #6. i really like the Contact Form 7 plugin for how easy it is to generate forms, and the ShareThis plugin as well, but the fact that the js/css loads on every front-end page, rather than just where necessary (only where a form or SHARETHIS button appears) just seems sloppy.

    what we really need is a plugin on the WordPress.org plugin repository that checks submitted code against all of your points!

  15. Balthazar says:

    I agree about much of this. I came to this site trying to find out how NOT to hardcode file paths. I agree with what you say about avoiding it, but why must a new WP developer have to be Sherlock Holmes to figure out how to do that?? Shouldn't it be in the "writing a plugin" introduction pages already? I searched for it in the Codex, and the results were ambiguous. The documentation I've found on these basic functions seem vague to me! Is WP_PLUGIN_DIR supposed to contain a trailing slash or not? Whatever the answer is, can I rely on it, or should I check each time?

    What would be the canonical way of finding the following: A filesystem path to the directory containing YOUR plugin, without a filename, WITH a trailing slash, so it can be used to open and read a file inside it, with the use of PHP functions? Should one use wp special functions for reading the file, or common PHP functions such as file_get_contents?

  16. Rem says:

    Thanks for this great post. This, along with WordPress Codex has become my plugin development bible :)

  17. Ozh says:

    Rem » Wait for my book then, you'll like it :)

  18. Dave Doolin says:

    Book!?

    When?

    I talked to Beau Lebens a couple of months ago about just this: best practices for WordPress plugins. He suggested I start with a Codex page, then solicit the community. And probably put on my Nomex flame retardant firefighting suit…

    Anyways, it's a lot of work, and you're way more qualified than I am to tackle such a project.

    So I'm looking forward to purchasing as soon as you release it.

  19. Ozh says:

    Dave Doolin » In March! :)

  20. Curtiss Grymala says:

    Thanks for this awesome write-up. It's great to see so much advice for plug-in developers in one post.

    Regarding the hard-coded file paths; you also have to be careful using the WP_PLUGIN_URL constant. I don't know if this is a bug in WordPress or a feature, but the WP_PLUGIN_URL does not include "https://" when you have FORCE_SSL_ADMIN enabled; meaning that it will serve your JavaScript, CSS, etc. files from the non-secure location (throwing annoying, though generally harmless, errors in most browsers). The plugins_url() function does include the correct protocol prefix, though, so I think it is generally a better option than the WP_PLUGIN_URL constant.

    Also, one of my recent pet peeves in WordPress plug-ins (and so many of them seem to do this) is when a function is called/executed automatically in the same file that contains the class definition. In a lot of cases, this is simply a matter of instantiating a new instance of the class you just created, but it's still frustrating.

    This makes it extremely difficult for people to extend the class defined in your plug-in. If that function is executed every single time I try to include the class definition, that can cause some serious issues within WordPress. Please, for the love of all that is holy, define your new class in its own file and do not call any functions directly from that class file. That way, other developers can utilize your class without having to try to fix all the things that get messed up when the function is called.

  21. Ozh says:

    Curtiss Grymala » you're right, actually now I would revise this advice and state that instead of using constants, you should always use the associated function, which will comply to SSL preference.

  22. Ray Morris says:

    Regarding #3, any SQL class or book will teach
    you that there's always ONE value in a certain
    column and field. If you're going to start making
    up your own data structures and sticking "arrays"
    where values go, you may as well ditch SQL and
    go back to text files, because you just threw
    away all of the befits of SQL. It might bug you,
    but you're absolutely dead wrong.
    Read ANY book on SQL to find out why.

  23. Will says:

    This was great, thanks. You can never explain things too simply, as looking at the API for the first time doesn't explain how to code a plugin. And looking at bad plugin code doesn't help either :)

    I've just ordered your book as well due to this blog.

  24. Quentin says:

    You can share this on ma.gnolia, netscape, newsvine, tailrank and technocrati but not on Facebook or Twitter ? WTF ?

  25. Ozh says:

    Quentin » hehe. Yeah, that was kind of an oooold share plugin. Removed and updated :)

  26. Quentin says:

    Thanks Ozh, way better :)

  27. Ryan says:

    Wow dude. great article. I am glad there are some people out there that care about neat code that's logical and simple

  28. Oliver says:

    Hello. Great article. But unfortunately my problem is not that kind mentioned in the text above. I am testing a plugin right now called Wikibox, it is able to display a summary of an wikipedia article with this code:

    But as you see it is always the same – Pink Floyd. I now like to add a new functionality but don't know how. I added the_tags instead of Pink Floyd and it looks like this:

    But – nothing happens. What I want is, the code should get the tags for the post and should display a summary of every tag (wikipedia article) one after another.

    Maybe you can help me out with this. Sorry if it doesn't match with the content but I desperately need help with this.

  29. Cody says:

    "Compatibility maintained with deprecated versions of WordPress"

    Excellent, excellent points. Thank you so much for mentioning this! (See below about responsibility). It's something I think of all the time with any kind of program. Yes, backward compatibility is important in some things, but with something like WordPress – absolutely not. In general, an out of date webserver (and whatever runs on it) is a terrible mistake.

    I must disagree though that it might encourage others to upgrade. Some, it might. But sadly not everyone. Sad especially because security is the responsibility of everyone. Think of Blaster or other worms, for example. And to that end, though, the chance for more to upgrade is excellent and as you also point out it's a benefit for the developer: easier to maintain. Also, the code will be cleaner by the very fact you don't have a bunch of hacks/workarounds/etc.

    As for comments, well I must admit I'm fairly guilty of that and have been for years. I think the only code I really commented a lot, was assembly. I do comment I just know I should more. Some times I will go back to code and wonder what in the hell I was thinking, or what the code did or what the idea was behind the code. As for indentation – true. But it should be pointed out, of course, that style is also preference. So yes indentation is important, but styles is another matter entirely. I mean, look at the C language: you have K&R style to start with, and then others too. Does a brace to open a block start on the same line or the next? What about a closing brace? For me I even vary it depending on the language. In C++ I tend to do the more traditional on the same line. In java likewise. But in C I usually did the next line, just because I 'inherited' that with other projects.

    Already way too long so I'll just end it with this – and it's in defence of WordPress's api documentation.

    Although I agree that the codex is a bit of a mess or not organized or thorough you have to remember something: WordPress is open source, more or less. This is on people's free time. So, sure you can complain about the api documentation being out of date or not good enough, but well, why not fix it or offer to help? Don't like it? Well that's just how it goes at times, unfortunately. I've worked on projects with over 130k lines of C code and most (ok, all ?) not commented. I worked it out. I added a lot of that code too (figure 20-30k lines in the time I was active). And while this project was not open to all (source wise) it's still a point: if someone is doing something on their own free time, and if it makes sense to them, that's what matters – so they can work and continue to work on it. And since it's not a corporate project, they really don't have to try to remember to do this or that to save the company money and time.

    Some times you do have to get your hands dirty and look at code if you're a developer. I know, it's so very shocking and terrifying that a developer would ever have to look at someone else's code (with or without comments at a level of your liking.. oh the horror of it all!), but… that's part of being a developer! And you can actually learn a great deal by looking at others code: you can find new ideas, get inspired, you can learn new techniques, and you can learn more about the internals of the project you're working on. As a friend of mine has said before, programming involves risks and sometimes you will mess up, make a mess or struggle. That's how it is and all the more power to those who can manage.

    Regardless, thanks for this post. I think it could come in handy at times. I don't do much php (in truth I don't like it that much compared to others; but I'm more into system programming anyway) but I have written a plugin and a theme (though not public for either). I've done most of what you suggested, but there may be something to look at, so thanks.

    Cheers,
    Cody

  30. Alex Wheeler says:

    Hi Ozh,
    i am an experienced web developer just getting into WordPress development. I've been reading up on plugin development learning the essentials when i stumbled across this article – started reading – and couldn't stop until i finished every line.

    This is a great comprehensive and informative article i found to be very useful. I've actually been wondering about a few issues you've mentioned, namely the lack of comments in wordpress plugins, throughout my quest to learn WP plugin development. Thanks for the great information, i have a feeling it will save me a lot of time & effort down the road.

    – Alex

  31. Ozh says:

    Alex Wheeler » Glad you like it. Now go a step further and have a look at http://planetozh.com/blog/books/professional-wordpress-plugin-development/ :)

  32. adrian says:

    I love the WTF/minute unit of measurement. both funny and perfectly correct.

    computers dont care about WTF/minute, but humans do. code for a human.

    a high WTF/min is a sign of a low ego. instead think, of course others will want to maintain my code, its does great things. i have to make it awesome and low WTF/min

  33. sixeightzero says:

    Looking forward to using your tips above to make my first plugin for WordPress a success

  34. abdussamad says:

    Regarding 3: According to core developer otto its better to store options as an array. The reason he gives is that it saves on queries to the database:

    http://lists.automattic.com/pipermail/wp-testers/2009-January/011116.html

    But here you've shown the exact opposite. WordPress simply loads all autoload options with one DB query! So why store data as in serialized form? The memory usage is the same and it still takes just one query. Frankly I think its better to store options in separate rows. Easier to code as well.

    Regarding number 7: It's now redundant with the new settings api. It does all the checking for you.

  35. Ozh says:

    abdussamad » Otto is saying exactly the same as me. Store 10 options in one array instead of saving one in 10 different entries. Regarding serializing, it's just how WP stores arrays.

    Number 7 is still valid. You need to either implement nonces or, preferably, use the API settings, but anyway not use raw forms with no checks.

  36. ray says:

    thanks Ozh,
    Don't always think of all these things when coding so it's great to get a reminder…

  37. Amna says:

    Wonderful List, it will be a great help while writing plugins.

  38. tdwtf says:

    mistake 11) displaying errors to users on a production site. From your own site:

    Warning: require_once(/home/ozh/lib/simplepie/simplepie.inc) [function.require-once]: failed to open stream: Permission denied in /home/planetozh/planetozh.com/blog/wp-content/themes/planetozh/functions-feed.php on line 56

    Fatal error: require_once() [function.require]: Failed opening required '/home/ozh/lib/simplepie/simplepie.inc' (include_path='.:/usr/local/lib/php:/usr/local/php5/lib/pear:/home/planetozh/planetozh.com/blog/wp-content/themes/planetozh') in /home/planetozh/planetozh.com/blog/wp-content/themes/planetozh/functions-feed.php on line 56

  39. ray says:

    @tdwtf
    update to mistake 11
    finding an error and posting it publicly instead of contacting the author privately…
    doesn't make you look like some really incredible coder.
    it makes you look like a disgruntled hacker who tried to hack his site and failed and now you're mad…
    just sayin… think before you post
    Ray

  40. PJH says:

    Ray, how does pointing out a clearly visible PHP error make someone (like) a 'disgruntled hacker?'

    Looks more like it hit a nerve and you're peeved.

    Well at least you made it to TDWTF message boards.

  41. Ryan says:

    He pointed out an error that everybody could see. Although contacting him privately would' ve been a decent way to go about it but I see nothing wrong with pointing it out. Who knows maybe there was something wrong with his production server and it wasn't the same as his development server. Maybe he just needed to debug something quick.

    @Ray you cannot tell us that you never debugged on a live server before because of some strange reason that you had no choice to. If you haven't then you are not a proper programmer and just some dude that learned html and php basics

  42. ray says:

    you're right, my choice of words could have been better but the point is still valid. Now that clearly visible error is also clearly visible in the inbox of everybody that subscribes to this post. Just sayin, you don't post somebodys filepaths on a public list.

  43. Ryan says:

    @Ray Apologies i got the names mixed up. But you have a good point. My previous comment was directed @tdwtf.

    But still if no one is going to make an effort ill contact the author and ask him to remove the comment with his file paths. Our company got hacked a few months ago because of a developer leaving vulnerabilities open like listing the file directories. Not a nice feeling if you need to rewrite every single index.php file on our web server

  44. Al says:

    Your number 3 suggestion is encouraging people to violate first normal form:

    http://en.wikipedia.org/wiki/First_normal_form

    I know it's a pretty common practice, but this is like one of the first things you learn in a college database class.

  45. Ozh says:

    Al » This has absolutely *nothing* to do with first normal form.

  46. PJH says:

    It has everything to do with 1NF Ozh, in that you're deliberately subverting it.

    I suggest you read the section titled 'Atomicity' in the Wiki article.

    You're effectively storing a database in a single value in another database. This is what 1NF attempts to get rid of.

  47. Ozh says:

    PJH » Example: Plugin needs to know the values of var1, var2, var3.
    Case 1: 3 rows in wp_options, each row holding one value.
    Case 2: 1 row in wp_options, containing array( var1, var2, var3 ).
    In case 1, to update var1, var2 and var3, we need 3 queries. To uninstall the plugin, we need 3 queries.
    In case 2, to update the 3 vars or uninstall, 1 query is enough.
    It's a simple performance point of view. I don't see any reason to prefer case 1 over case 2.

  48. PJH says:

    Surely, from a performance point of view, most of the options are read-only most of the time, and not "all of them every time," especially when the number of options increase?

    Writing/updating and/or deleting (most/all) of them is surely less common.

    Case 1: 87 rows in wp_options each holding one value.
    Case 2: 1 row in wp_options containing array(var1, var2,….var87).

    In case 1, to read var35 you need one query.
    In case 2, to read var35 you need to pull all 87 variables and parse out var35.

    (Denormalising would /possibly/ help here in the case where most of the options are actually needed every time.)

    But to address your update scenario

    In case 1, to update var35 you need one (write) query
    In case 2, to update var35, you need one query to read the current state, update the variable, and another query to write the new state.

    To uninstall (a one off thing, not something done every time a page is created) yes, you need 87 queries. Uninstallation is not a performance affecting operation, so the number of queries is irrelevant in this regard.

    But of course, all this smells of premature optimisation. Are there any benchmarks regarding the difference between using 1NF and not using 1NF?

    If there is, indeed, little actual difference between the two, then the extra processing required to process subverted 1NF wouldn't appear to make it worth the bother.

  49. Ozh says:

    PJH » The read process doesn't matter: reading one value containing an array of 200 values or reading 200 individual values takes just one query, since, as I explain, WP reads every option when it starts. If a plugin doesn't need all the vars all the time, I suggest here in my tip to make several entries and selectively leverage the 'autoload' parameter.

  50. AK Ted says:

    Very interesting! Some very good advice for all plugin devs and especially useful for n00b devs like myself. I've just created my first WP plugin, MU Plugins Tool. In a nutshell, it allows you to selectively (or all at once!) enable/disable any must-use plugins you've installed. If you'd be interested in critiquing it I'd be honored. If not, no harm.

    Something that should be stressed is to only have your plugin do what it needs, when needed. While researching plugin writing, I didn't see this expounded upon. If you're hooking admin_init, go through what you're executing there with a fine-toothed comb and make sure it needs to be run every time *any* page in admin is loaded. I had to refactor a bunch of my code because I didn't really grasp how a plugin is run every page load, regardless of which section of the admin you're in.

    This page is now in my bookmarks, and it's something I'll refer to for any WP code I create. Thanks for the great write-up!

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Gravatars: Curious about the little images next to each commenter's name ? Go to Gravatar and sign for a free account
Spam: Various spam plugins may be activated. I'll put pins in a Voodoo doll if you spam me.

Read more ?