wordpress development paradigms, idiosyncrasies and other big words
DESCRIPTION
For seasoned developers approaching WordPress customization or development for the first time the biggest challenge is often not learning the API and method calls: it's grasping the idiosyncrasies of the WordPress framework. In this 45-minute presentation aimed at web coders who are interested in diving into WordPress customization and development, you will learn the key idioms that will accelerate your learning curve and help you approach the framework from a best practices perspective: template hierarchies, themes and child themes, taxonomies, filters and action hooks, execution order and other need-to-know concepts will be presented as well as tips on what the most active online developer communities are and the best places to go for quick (free) help and advice.TRANSCRIPT
WordPress Development Paradigms, Idiosyncrasies
and Other Big Words
Tom Auger, Zeitguys inc.
WordCamp Montreal, 2011
Introduction
• About me:– Developer of standalone RIAs and large-scale
server-side systems– Siebel integrator and database admin– Perl, PHP, JavaScript, ActionScript 3, SQL,
Python, Java, etc.
– Very much a “DIY Guy”
Surprising Learning Curve
• Lots of files!
• Confusing themes!
• Arcane symbols!– sprintf(__('Something %s', 'domain'), 'weird');
• Hooks everywhere!– add_action('init', 'obscure_func');– $var = apply_filters('obscure_filter');
• Oh my!
The biggest challenge...
• was learning what the topics were that I needed to learn about:– themes, templates, template parts and child themes– database access and the WP_Query in all its
instances– action and filter hooks– WordPress object types: posts, taxonomies and meta
data– i18n (l10n) – where to get reliable, high-level information and help
Meh.
• Let's just go skunkworks.
• I'm a seasoned dev after all...
DIY Approach = Low Level
• Direct database access
• In-place editing of plugin files
• Forking theme files
• In-place (gasp) editing of core files
• Brute force JavaScript injection
• Rolling your own sanitization and security measures
The Bad News
• Unsustainable, fragile to updates
• Non-scalable and highly one-off
• Big security gaps
• Missing out on built-in value-add intelligence
• Re-inventing the wheel
Example: Adding <p> to content
• The starting code:
$posts = get_posts(array(
'post_type' => 'my-custom-type'
));
foreach($posts as $post){
echo '<div>';
echo $post->post_content;
echo '<div>';
}
Option 1: Manually Wrap
• Wait... what about multiple paragraphs? or enclosing block tags?
$posts = get_posts(array(
'post_type' => 'my-custom-type'
));
foreach($posts as $post){
echo '<div>';
echo '<p>'.$post->post_content.'</p>;
echo '<div>';
}
Option 1b: RegEx
• Wheel, meet Tom. Tom, meet Wheel.
$blockElements = implode("|", array('ADDRESS','BLOCKQUOTE','CENTER','DIR','DIV','DL', etc...);
$added_p = preg_replace(
'/(?:\s*<('.$blockElements.')(?:\s+[^>]+\s*)*>\s*(.+?)\s*<\/\1>(?:\r|\n|$)*)|(?:\s*(.+?)\s*(?:\r|\n|$|(?=<(?:'.$blockElements.')>))+)/is','<p$1>$2$3<\/p\1>\n', $post->post_content);
But wait... doesn't WP already do this?
• wpautop(), defined in formatting.php handles all the logic and edge cases
foreach($posts as $post){
echo '<div>';
echo wpautop($post->post_content);
echo '<div>';
}
So what about the_content()?
• Let's exploit those globals and template tags
foreach($posts as $post){
setup_postdata($post);
echo '<div>';
the_content();
echo '<div>';
}
wp_reset_postdata();
So how does the_content() do it?
• IDE – site-wide search (or use phpxref or WPSeek)
• In post-template.php:
function the_content($more_link_text = null, $stripteaser = 0) {$content = get_the_content($more_link_text, $stripteaser);$content = apply_filters('the_content', $content);$content = str_replace(']]>', ']]>', $content);echo $content;
}
Keep following the White Rabbit
• Search for "the_content" within default-filters.php• It runs the following functions:
– capital_P_dangit– wptexturize– convert_smilies– convert_chars– wpautop– shortcode_unautop– prepend_attachment
• That's a lot of value add!
I want all that other good stuff, too!
• Leverage all those the_content methods
foreach($posts as $post){
echo '<div>';
echo apply_filters(
'the_content',
$page->post_content);
echo '<div>';
}
The lesson thus far
• TMTOWTDI, but not all ways are equal
• Use the method that:– is the most abstracted (high level)– requires the least amount of additional code– encapsulates the most value-added
intelligence
“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.” – Ozh Richard, de facto WP Plugin expert
Big Words
• Paradigm: a model or pattern that defines a way of thinking or working with a tool
• Idiosyncrasy: an unusual, distinctive and recognizable trait or pattern
• Idiom: a form of expression, natural to a language or context
WordPress Paradigms and Patterns
• Plugins
• Templates
• Hooks
• Post Types and Taxonomies
WordPress Idioms
• The Loop and the Query
• Template tags (and get_)
WordPress Idiosyncrasies
• $wpdb
• wp_enqueue_script()
• No TIME!! (part deux?)– wp_kses(), esc_attr()– body_class(), post_class(), comment_class()
Paradigm: Plugins
• Often confused with Widgets• Nothing more than a distributableway to
inject code at the start of the loading / execution cycle
• Not to be confused with pluggable functions
<?php /*Plugin Name: Name Of The Plugin*/?>
Paradigm:Themes and Templates
Themes
• Control visual look-and-feel of a site
• Require at minimum only index.php and style.css
• But wait, there's more, with functions.php:– Widgets– Admin options– Changes to core behaviour
Child Themes
• Best way to customize a theme for a specific client application
• Clever overloading scheme
• Leverage existing intelligence
%> wp-content/themes/my-child-theme/style.css/*Theme Name: Twenty Ten Child ThemeTemplate: twentyten*/
@import url('../twentyten/style.css');
Template Files
• PHP files, loaded contextually based on Query string
• Overloaded using a strict Template Hierarchy
• Follows conditional hierarchy
Template Parts
• Breaks down template hierarchy to custom level of granularity
• Code reuse design pattern• Allows multiple templates to share
sections
• Eg: events-by-date.php, events-by-category.php, all-events.php
• Note: local vars lose scope (uses "get")include(locate_template('template-part'));
get_template_part('template', 'part');
Page Templates
• Templates that can be admin-applied to Pages (not posts)
<?php/*Template Name: 3 Column*/?>
Post Formats
• Standardized contributor-assignable taxonomy for posts
• Meant to be consistent across all blogs
• aside, chat, gallery, link, image, quote, status, video, audio
<?php get_template_part( 'format', get_post_format());?>
Paradigm:Hooks, Actions and Filters
“WordPress action hooks are a means of providing a way for other developers to insert their own code in specific locations within your code, in order to change or expand the functionality of your code.” – Nathan Rice
http://www.nathanrice.net/blog/an-introduction-to-wordpress-action-hooks/
Hooks
• Spiritually related to GoF's Template Method Design Pattern
• Alternative extension method to subclassing
• Developers create hooks wherever they think other devs may wish to extend their code
• Documented under PluginAPI, but you will encounter them sooner than that
Actions vs. Filters
• Actions inject code into a workflow
• Filters take a variable, modify it and return the same type
// set up action hookdo_action('hook-name');
// set up filter hook$variable = apply_filters('filter-name',
$variable);
Leveraging Action Hooks
• Use an action hook to do something at a specific point in the execution cycle
• Multiple sources can register for a single action hook
add_action('init', 'stuff_to_do_at_init');
General Execution Order
• muplugins_loaded• plugins_loaded• sanitize_comment_cookies• setup_theme• load_textdomain• after_setup_theme• auth_cookie_malformed• set_current_user• init• widgets_init• register_sidebar• wp_register_sidebar_widget• wp_loaded• parse_request*• send_headers*• parse_query*• pre_get_posts*• posts_selection• wp*
• template_redirect• get_header• wp_head• wp_enqueue_scripts• wp_print_styles• wp_print_scripts• get_template_part_loop• loop_start*• the_post*• loop_end*• get_sidebar• dynamic_sidebar• get_search_form• wp_meta• get_footer• twentyten_credits• wp_footer• wp_print_footer_scripts• shutdown
http://codex.wordpress.org/Plugin_API/Action_Reference#Actions_Run_During_a_Typical_Request
Action examples
add_action('init', 'register_my_custom_post');
add_action('init', 'register_my_custom_taxonomy');
add_action('widgets_init', 'register_my_widget');
add_action('wp_enqueue_scripts', 'add_my_js_libs');
add_action('save_post', 'save_my_custom_meta');
Leveraging Filter Hooks
• Use a filter hook to modify output or an internal variable
add_filter('the_content', 'change_content');
function change_content($content){$content = 'All your base are belong to us!!!';return $content;
}
Priority and Parameters
• add_filter can take two additional arguments: $priority and $num_args
add_filter('contextual_help', 'my_help', 10, 3);
function my_help($text, $screen_id, $screen){if ($screen_id == 'edit-my_custom_post_type'){
$text = '<p>You are on your own pal</p>';}return $text;
}
Filter Examples
add_filter( 'excerpt_more', 'custom_more' );
add_filter( 'excerpt_length', 'my_length', 999 );function my_length($length){
return 6; // six is average, right?}
Existing Filters and Actions
• WordPress already registers filters and actions
• Find them in wp-includes/default-filters.php
• remove_filter($tag, $function, $priority)
• remove_action() is an alias
So How Do I Find Them?
• Partial lists in the codex:– codex.wordpress.org/Plugin_API/Filter_Reference
– codex.wordpress.org/Plugin_API/Action_Reference • Use a good IDE like Eclipse/PHP, Aptana,
NetBeans, PHPStorm etc... and search for:– do_action(), and do_action_ref_array()– apply_filters(), and apply_filters_ref_array()
• http://adambrown.info/p/wp_hooks
Good Citizenship
• Add your own action and filter hooks wherever it may be appropriate:– things a dev may wish to override but you
don't want to expose to an Admin user– default values (max/min limits etc)– logical spots for extension
• Example: zg-event-query (filter)
• For the love of Pete, document it!
Idiom:The Loop and The Query
Getting Your Content: "The Loop"
• One "The Loop" per page
• Sets globals (eg: $post)
• Drives all template tags (eg: the_content())
• Driven by "the query": the request string
• Affects conditional tags
• Can be supplanted by query_posts()
Secondary Loops
• Used in widgets and sidebars, sub-content and related content
• Must be careful not to clobber globals and affect the main Loop
• Leverage get_posts() or get_pages() for inobtrusive secondary loops
• Or get dirty with WP_Query class• Either way, be sure to read Codex WP_Query
Suspicious Stuff
• Leverage template tags in secondary loops by:– using the_post() method on WP_Query
instance– or setup_postdata() on a $post object
$the_query = new WP_Query( $args );while ( $the_query->have_posts() ){ $the_query->the_post();
the_title();}wp_reset_postdata();
Template Tags and get_
• Template tags– Used exclusively within The Loop– Always echo their results– Rarely (?) take arguments (always working on
the global $post object)
• get_– Most template tags have a get_ equivalent– Does not echo result– Usually takes the ID of a post as argument
Template Tag vs. get_
• Use Template Tag when:– you're in The Loop!
• Use get_ when:– you want the value, not the echo– you're not in The Loop– you are in the loop but want a related item
(eg: attachment)
Idiosyncrasy:$wpdb
wpdb Class
• Instantiated once and globalized in $wpdb
• Provides API to low-level DB calls– $wpdb->query()– $wpdb->get_results()– $wpdb->get_var()
Example: Events
• Custom post type "event"
• Categorized by taxonomy "event-type"
• Event start_date stored in meta
• Let's select: all 'speaking' events with a start date of '2011-07-09'
Pure SQLselect * from wp_postsjoin wp_term_relationships
on ID = object_idjoin wp_term_taxonomy
on wp_term_taxonomy.term_taxonomy_id =wp_term_relationships.term_taxonomy_idand taxonomy = 'event-type'
join wp_termson wp_term_taxonomy.term_id =wp_terms.term_idand wp_terms.slug = 'speaking'
join wp_postmetaon wp_postmeta.post_id = IDand meta_key = 'event-date'and meta_value = '2011-07-09'
wherepost_type = 'event'and post_status = 'published'
More Properer SQLselect * from wp_posts as Pjoin wp_term_relationships TR_EVENT
on P.ID = TR_EVENT.object_idjoin wp_term_taxonomy TT_EVENT
on TT_EVENT.term_taxonomy_id =TR_EVENT.term_taxonomy_id
TT_EVENT.taxonomy = 'event-type'join wp_terms T_EVENT
on TT_EVENT.term_id = T_EVENT.term_idand T_EVENT.slug = 'speaking'
join wp_postmeta M_DATEon M_DATE.post_id = P.IDand M_DATE.meta_key = 'event-date'and M_DATE.meta_value = '2011-07-09'
whereP.post_type = 'event'and P.post_status = 'published'
Don't Use Table Names!select * from {$wpdb->posts} Pjoin {$wpdb->term_relationships} TR_EVENT
on P.ID = TR_EVENT.object_idjoin {$wpdb->term_taxonomy} TT_EVENT
on TT_EVENT.term_taxonomy_id =TR_EVENT.term_taxonomy_id
TT_EVENT.taxonomy = 'event-type'join {$wpdb->terms} T_EVENT
on TT_EVENT.term_id =T_EVENT.term_idand T_EVENT.slug = 'speaking'
join {$wpdb->postmeta} M_DATEon M_DATE.post_id = P.IDand M_DATE.meta_key = 'event-date'and M_DATE.meta_value = '2011-07-09'
whereP.post_type = 'event'and P.post_status = 'published'
More Saferer Query$wpdb->prepare("select * from {$wpdb->posts} Pjoin {$wpdb->term_relationships} TR_EVENT
on P.ID = TR_EVENT.object_idjoin {$wpdb->term_taxonomy} TT_EVENT
on TT_EVENT.term_taxonomy_id =TR_EVENT.term_taxonomy_id
TT_EVENT.taxonomy = 'event-type'join {$wpdb->terms} T_EVENT
on TT_EVENT.term_id =T_EVENT.term_idand T_EVENT.slug = %s
join {$wpdb->postmeta} M_DATEon M_DATE.post_id = P.IDand M_DATE.meta_key = 'event-date'and M_DATE.meta_value = %s
whereP.post_type = 'event'and P.post_status = 'published'",
'speaking', '2011-07-09');
But, Whenever We Can...
get_posts(array('post_type' => 'event','tax_query' => array(array(
'taxonomy' => 'event-type','field' => 'slug''terms' => 'speaking'
)),'meta_query' => array(array(
'key' => 'event-date','value' => $todays_date
))));
Use Built-Ins Whenever You Can
• Take advantage of cacheing and optimization
• Future-proof code
• Chances are, there's a function or method to do what you want– Look in Codex under WP_Query– Look in source under query.php, post.php,
category.php, taxonomy.php
Idiosyncrasy:wp_enqueue_script
We Love jQuery
• (Don't)– Jump into header.php– Add <script src="my-jquery-min.js"></script>
• (Don't)– add_action('wp_head', 'add_my_jquery');
Enqueue = The Canadian Way, eh?
• DO
add_action('wp_enqueue_scripts', 'add_my_scripts');function add_my_scripts(){
wp_enqueue_scripts('jquery-ui-core',get_stylesheet_directory_uri() .
'/script',array('jquery'),'1.8.14',false // in-header
);}
add_action('admin_enqueue_scripts', 'add_admin_scripts'); // careful here...
No One Likes Conflict
• jQuery runs in "no-conflict" mode...
jQuery(document).ready(function($) { $('#mydiv').css({color:'red', border:'2px solid red'});});
(function($) {var test = "hello, world!";function testMe(){
alert(test);}$(document).ready(testMe);
})(jQuery);
Resources and Forums
• Documentation:– Codex (of course)– Google: "wordpress add_action"– Adam Brown– WPSeek, phpxref– Your own IDE
• Help:– wordpress.org/support (yeah, not really...)– www.experts-exchange.com
• http://www.experts-exchange.com/Web_Development/Blogs/WordPress/
– wordpress.stackexchange.com
Thanks!
www.tomauger.com
www.zeitguys.com
@TomAuger
Other Stuff for more time
Paradigm:Post Types and Taxonomies
Posts
• Posts are the atomic element for all WordPress content – "actual" posts– pages– comments– attachments– drafts and revisions– custom post types
Taxonomies
• Taxonomies are the relational device that group posts together.– categories, – tags, – nav menus– post formats?– custom taxonomies
• Groupings that are shared by different items
What About Meta?
• Use meta for discrete, non-grouping data, unique to the content item (eg: price)
• Sortables (eg: year)
• Groups that are not meaningfully shared (eg: Otto's TV show seasons across different shows)
Custom Post vs. Custom Tax
• Use Custom Post for content that:– should not appear in your feed– represents a distinct type of "content"– is always associated with a set of metadata– may require a customized input form– needs a separate capability / access– needs its own private taxonomy
• Examples: products, events, albums
Custom Post vs. Custom Tax
• Use a custom taxonomy when:– You want additional ways to group items
together– You want a way to filter out a set of items– You want a new metabox
• Eg: colour, software posts, manufacturers