[wp-trac] [WordPress Trac] #20103: Rewrite get_themes() and other enemies of sanity
WordPress Trac
wp-trac at lists.automattic.com
Mon Feb 27 22:09:44 UTC 2012
#20103: Rewrite get_themes() and other enemies of sanity
--------------------------+------------------
Reporter: nacin | Owner:
Type: defect (bug) | Status: new
Priority: normal | Milestone: 3.4
Component: Themes | Version: 1.5
Severity: normal | Resolution:
Keywords: |
--------------------------+------------------
Changes (by nacin):
* milestone: Awaiting Review => 3.4
Comment:
[attachment:wp_theme.diff]
= Introducing the WP_Theme class =
As the ticket promises, WP_Theme replaces get_themes() and other enemies
of sanity. Each theme is its own object, decorated on construction and
packed with 21 main public methods, four public static methods, and a few
internal helpers. Full support for multiple theme roots and directories of
themes within a theme root. The handling for broken-ness and odd
situations is smoother than get_themes().
{{{
#!php
array wp_get_themes( $args = array() );
}}}
To fetch a array of WP_Theme objects, call wp_get_themes(). wp_get_themes(
$args ) takes three arguments that filter the results: errors, allowed,
and blog_id.
* '''errors''' (Default: FALSE) - Filters themes based on whether they
have errors. True returns only themes with errors, False only themes
without errors, Null if all themes should be returned.
* '''allowed''' (Default: NULL) - Filters themes based on whether they
are allowed on the site or network. (Multisite only.) Valid options are
'network' (allowed on the network), 'site' (allowed on the site specified
by the blog_id argument), true (allowed at either level), false (not
allowed).
* '''blog_id''' (Default: current) - Controls which site should be
checked for the allowed filter. Only works, of course, in multisite.
{{{
#!php
WP_Theme wp_get_theme( $stylesheet = null, $theme_root = null );
}}}
To fetch a single theme, call wp_get_theme(). This will return a single
WP_Theme object for the requested theme.
It takes two argumements: $stylesheet and $theme_root. If $stylesheet is
omitted, the current stylesheet (via get_stylesheet()) will be used. If
the $theme_root is omitted, get_raw_theme_root($stylesheet) will be used.
Thus, to get the current theme, you may call wp_get_theme() with no
arguments.
wp_get_theme() replaces get_theme(). get_theme() previously used
get_current_theme() to get the name-keyed theme array from get_themes().
Not only are we no longer juggling arrays keyed by the theme name, we're
not even processing every theme into memory to get a single WP_Theme
object. Nice.
== Handling Errors ==
Currently, search_theme_directories() and get_themes() will build a
separate array of broken themes, $wp_broken_themes. This array is then
returned by get_broken_themes().
With WP_Theme, errors like no template, invalid parent themes, or
unreadable stylesheets can be nicely caught. Errors are populated into an
errors property, accessible via WP_Theme->errors(). It is either null or
no errors, or a WP_Error object. wp_get_themes( array( 'errors' => true )
) enables one to loop through and pull out error messages and codes from a
WP_Error object. In some cases, the current theme could 'error' but it
still works, and we have access to what went wrong.
== Network Support ==
WP_Theme encapsulates the logic for allowed themes on a network, with
static methods to fetch all allowed themes on a network, themes allowed at
the site level, and a merge of both lists (all themes allowed for a single
site).
Each theme object then has a simple is_allowed( $check = 'both', $blog_id
= 0 ) method to allow for checking whether it is allowed.
The okay-named function get_site_allowed_themes() and the terribly named
function wpmu_get_blog_allowedthemes() are both deprecated in favor of
static class methods.
== Outputting Headers ==
What started me on WP_Theme was that get_themes() could not be
internationalized in any sane way (#15858). The data usually came out of
get_theme_data() already cleaned and even marked up (such as with
wptexturize), making translation difficult or impossible. Since the data
was keyed by name, changing that value would break quite a bit. As
get_themes() already stored a lot of data in memory (and blows up
memcached on WP.com, as they try to store it persistently), adding more
keys didn't make sense.
{{{
#!php
mixed WP_Theme->get( $header )
mixed WP_Theme->display( $header, $markup = true, $translate = true )
}}}
WP_Theme has two main method for gathering header data: get() and
display(). Once you call get(), the data has already been sanitized,
either by strip_tags(), kses, or esc_url, depending on the header. (Tags
are returned as an array.) By calling display(), the data is translated,
and then marked up. (Even Tags — the array is translated for known tags.)
The textdomain is automatically loaded if necessary.
display() returns data. It does not echo it. It is formatting the data for
display, and would roughly be analogous to get_bloginfo( 'name', 'display'
).
get('Author') returns Andrew Nacin. display('Author') marks that up with
<a> tags and a link to http://nacin.com (being AuthorURI).
display() also allows you to decline markup and translation with
additional arguments, but these are mainly used for backwards
compatibility or to allow fields to be searched through without Author
HTML and the like.
Private methods sanitize_header(), translate_header(), and markup_header()
support get() and display() in their endeavors.
== Helpers ==
WP_Theme has a number of public methods that corresponds to the global
function acting on the active theme. These include:
{{{
#!php
bool WP_Theme->is_child_theme()
string WP_Theme->get_stylesheet()
string WP_Theme->get_template()
string WP_Theme->get_stylesheet_directory()
string WP_Theme->get_template_directory()
string WP_Theme->get_stylesheet_directory_uri()
string WP_Theme->get_template_directory_uri()
string WP_Theme->get_theme_root()
string WP_Theme->get_theme_root_uri()
bool WP_Theme->load_textdomain()
array WP_Theme->get_page_templates()
}}}
In some cases (notably, get_page_templates()), the global function now
wraps the method.
== Screenshots ==
{{{
#!php
string function get_screenshot( $uri = 'relative' )
int function get_screenshot_count()
array function get_screenshots()
}}}
3.4 means multiple screenshots. WP_Theme has you covered. Three public
methods provide the screenshot URL (absolute URL, or relative to the
stylesheet directory), the total count of screenshots, and an array of
each screenshot filename.
== Template and Stylesheet Files ==
{{{
#!php
array WP_Theme->get_files( $type = null )
}}}
get_themes() stored the template and stylesheet files in the array, which
resulted in longer processing times and, especially since absolute paths
were added in 2.9, an explosion in memory usage.
WP_Theme handles traversing the PHP and CSS files theme directories ''on
demand''. As these are only used in the theme editor (and only for the
current theme), this is a pretty huge performance and memory boost.
== Full Backwards Compatibility ==
WP_Theme is 100% backwards compatible. Because it implements ArrayAccess,
you can take a theme object and look for keys like 'Template Files',
'Stylesheet', and 'Version' — just as they were returned by get_themes().
WP_Theme also includes a magic getter, handling all properties that were
returned by current_theme_info() (which returned stdClass in 3.3). This
enables us to pass a fully decorated WP_Theme object as an existing action
argument, without plugins choking because they expected an array or an
older object.
Indeed, get_themes() now returns an array of WP_Theme objects, because it
can. get_theme() returns a WP_Theme object, as does current_theme_info().
(All are deprecated, as is get_broken_themes().)
If you don't believe me on backwards compatibility, head over to the Theme
Editor, which passes around theme names like they're crack. This file is
the only area that hasn't been updated to rely on WP_Theme; it still uses
get_themes() and the array it returns for each theme. Everything works.
== The Parent Theme ==
A reference to a WP_Theme object for a parent theme is stored in the
parent property, and accessible with the parent() method. Want a theme's
parent name? $theme->parent() && $theme->parent()->display('Name').
== Caching and Performance ==
Okay, so, this is sweet. After profiling get_themes() and WP_Theme with
Xdebug and KCacheGrind, a few functions have been sped up, in some cases
pretty significantly. This includes search_theme_directories() and theme
searching, which was a drag for no real reason. Searching is now improved
significantly, and is implemented as a class method to allow for better
speeds. (Now that things are "ready," I hope to go back and look for
further improvements.)
Once sorting was fixed, two things stood out as weighing down processing
the most: Filesystem reads, and sanitization of headers. With WP_Theme,
both are non-persistently cached, internally, to ensure serious speed. No
style.css is ever read twice, and no header is ever sanitized twice. Same
goes for identifying screenshots and PHP/CSS files — everything gets
cached.
Non-persistence is a must for themes because someone might make an FTP
change then refresh the page. BUT, that doesn't mean a particular
installation shouldn't be able to opt into persistence.
When the themes cache bucket is persistent (there's a filter), WP_Theme
gets smart about it. To prevent thousands of cache sets to update the
headers cache, it sanitizes every header once the first one is requested
and adds it to cache. Additionally, search_theme_directories() is cached.
With persistent caching, it is possible to hit themes.php, and even do a
search, and avoid a single filesystem or kses operation. The 150-something
themes in the WP.com themes repository sings in this situation.
== What's Next? ==
1. Commit this bad boy.
2. If object cache drop-ins had a wp_cache_get_non_persistent_groups()
function, we could scrap that filter and make it much cleaner. (It's a
rough implementation and could use some tweaks anyway.)
3. Some of the filesystem functions — specifically, the underlying
scandir() static method and get_files() — don't quite have their arguments
or loops ironed out yet. Once the theme editor is converted over, those
things should come into place nicely. Code review appreciated.
--
Ticket URL: <http://core.trac.wordpress.org/ticket/20103#comment:9>
WordPress Trac <http://core.trac.wordpress.org/>
WordPress blogging software
More information about the wp-trac
mailing list